Components catalog
Accessor: client.components
The catalog of motors, propellers, and batteries. You reference
component ids in simulation and sweep bodies; create() adds your own
private custom components. Custom components you submit through
submissions appear here once approved.
Methods
| Method | Endpoint | Returns |
|---|---|---|
client.components.list(**filters) | GET /v1/components | CursorPager of components |
client.components.find(**filters) | GET /v1/components | Single component (one-hit-or-raise) |
client.components.create(type=..., name=..., spec_json=...) | POST /v1/components | New private component |
client.components.retrieve(component_id) | GET /v1/components/{id} | Single component |
client.components.brands() | GET /v1/components/brands | List of {name, count} brand summaries |
Filter by type
Every simulation needs at least one motor, one propeller, and one battery.
The type filter is the fastest way to scope the catalog:
from thrustlab import Client
client = Client()
motors = list(client.components.list(type="motor", limit=100))
print(f"{len(motors)} motors in catalog")
Valid type values: motor, propeller, battery. Anything else —
esc included, ThrustLab doesn't catalog ESCs — is rejected with a 422.
Filter by brand
brand is a discrete filter that takes the bracket [in] suffix, with a
comma-separated list of names:
kde_or_tmotor = client.components.list(
type="motor",
**{"brand[in]": "KDE,T-Motor"},
)
for m in kde_or_tmotor:
print(m["id"], m["name"], m["spec_json"].get("brand"))
The full list of brands, with per-brand counts, is available via
client.components.brands():
brands = client.components.brands()
for b in brands["data"]:
print(b["name"], b["count"]) # KDE 42, T-Motor 38, ...
Filter motors by Kv rating
Numeric spec fields take a bracket-suffixed filter — kv[gte] / kv[lte]
(also [gt] / [lt], currently treated the same as the inclusive forms).
Pass them through **extra_filters:
mid_kv = client.components.list(type="motor", **{"kv[gte]": 400, "kv[lte]": 900})
for m in mid_kv:
print(m["id"], m["name"], m["spec_json"]["kv"])
Type-specific filters
Every numeric spec filter follows the key[gte] / key[lte] pattern.
[gt] and [lt] are also accepted and currently behave like the inclusive
forms. Each filter maps to a spec_json field server-side.
Both spellings work. The filter key and the sort key used to be different names
for the same field: resistance[lte] filtered but sort_by=resistance
returned 400, while sort_by=R sorted and R[lte] did nothing. Both the wire
alias and the spec_json key are now accepted on both, so resistance[lte] is
R[lte] and sort_by=resistance is sort_by=R. The same holds for every row
below.
| Type | Filter key | spec_json field |
|---|---|---|
motor | kv[gte] / kv[lte] | kv |
motor | weight[gte] / weight[lte] | weight |
motor | resistance[gte] / resistance[lte] | R |
motor | current_max[gte] / current_max[lte] | iq_max |
motor | poles[gte] / poles[lte] | n |
propeller | diameter[gte] / diameter[lte] | diameter |
propeller | pitch[gte] / pitch[lte] | pitch |
propeller | weight[gte] / weight[lte] | weight |
propeller | max_rpm[gte] / max_rpm[lte] | maxRPM |
propeller | inertia[gte] / inertia[lte] | inertia |
battery | series_cells[gte] / series_cells[lte] | series_cells |
battery | capacity[gte] / capacity[lte] | total_capacity_mAh |
battery | c_rating[gte] / c_rating[lte] | c_rating |
battery | weight[gte] / weight[lte] | weight_g |
A bracket filter key with a valid operator but an unknown field name for that
type returns 400 unknown_filter, and the message lists the valid keys for
that type. It used to be dropped silently, which turned a typo into a
full-catalog result with no warning.
The list response also echoes the bounds it applied as filters, keyed by wire
name, so you can confirm what actually bound rather than inferring it from the
row count.
# 14-16 inch propellers with at least 8 inch pitch
props = client.components.list(
type="propeller",
**{"diameter[gte]": 14, "diameter[lte]": 16, "pitch[gte]": 8},
)
Motor resistance in list rows
Motor phase resistance R, in ohms, is in the list-row projection and on
GET /v1/components/{id} for every tier that sees specs. The server would
already sort and filter on it, but the value itself was readable only through
the metered datasheet endpoint, so ranking motors by resistance meant
reconstructing each one's bracket from a series of count queries.
On the free tier, catalog motor and battery rows return only brand, name, type
and label, with no spec numbers. Catalog propellers return the same headline
fields on every tier. Free-tier callers receive R for custom motors they own,
but not for catalog motors.
R is the spec_json key and resistance is the wire alias. Both work as a
filter and as a sort key.
low_r = client.components.list(
type="motor",
sort_by="resistance",
**{"kv[lte]": 900, "resistance[lte]": 0.05},
)
for m in low_r:
print(m["name"], m["spec_json"]["R"], "ohm")
GET /v1/components/{id}/specs on a catalog motor or battery is metered on a
budget of distinct components, so filter and rank with the list endpoint
first. See Rate limits.
Other list filters
| Filter | Type | Notes |
|---|---|---|
visibility | "public" | "private" | Restrict to the shared catalog or your own private customs. Omit for both. |
search (or q) | str | Case-insensitive substring match on name. If both are supplied, search wins. |
sort_by | str | name, created_at, or (with type set) a type-specific spec key. Either spelling: resistance or R. |
sort_order | "asc" | "desc" | Default asc. |
ids_only | bool | Every matching id in one response, capped at 5000. truncated is true when the cap bound the result. |
The ids_only cap is 5000. It was 2000, which is one row short of useful on a
2005-row catalog.
A cursor is bound to the sort_by and sort_order it was issued for. Changing
either mid-scan invalidates the cursor; start a new scan instead.
See the API reference for the full list of per-type sort keys and spec filter keys.
Retrieve one component
motor = client.components.retrieve("comp_2c5tQ...")
print(motor["spec_json"])
A component you created and own returns its full raw spec_json. Catalog rows
return a fixed headline allowlist, subject to tier access:
- Motor:
kv,weight,power_max,iq_max,diameter,length,u_nominal,topology,R. - Propeller:
diameter,pitch,num_blades,weight,maxRPM,rotation. - Battery:
chemistry,series_cells,parallel_cells,total_capacity_mAh,c_rating,weight_g,length_mm,width_mm,height_mm,form_factor,cell_model.
Catalog propeller rows do not include blade geometry such as chord and twist tables.
GET /v1/components/{id}/specs returns a wider datasheet view for catalog
motors, adding n, iq_nominal, io_nominal and reduction_ratio. The read
is metered for catalog motors and batteries and carries the budget headers;
your own custom components and propellers are not metered. See
Rate limits.
The API never returns L or inertia for catalog motors, on any tier or
through any endpoint.
Create your own
create() persists a private custom component and returns its id:
prop = client.components.create(
type="propeller",
name="My Custom 10.5x4.5",
spec_json={...},
)
The per-type required spec_json fields, units, and validation rules are in
Creating your own components.
Recipe: pick the highest-thrust prop for a given motor
Narrow the propellers with the list filters, then sweep them as one component axis at a fixed throttle. A loop that submits one simulation per propeller spends one submission and one cold solve per candidate; this is one request.
motor_id = "comp_2c5tQ..."
props = client.components.list(
type="propeller",
**{"diameter[gte]": 10, "diameter[lte]": 14},
limit=20,
).data
sweep = client.sweeps.create(
project_id="proj_xxx",
battery_component_id="comp_xxx",
airspeed_m_s=0.0, density_kg_m3=1.225, battery_charge_pct=100,
rotor_groups=[{
"label": "main", "count": 4,
"motor_component_id": motor_id,
"propeller_component_id": props[0]["id"],
"throttle_pct": 100,
}],
sweep={
"rotor_sweep_mask": [True],
"component_axes": [
{"axis": "propeller", "slots": [0],
"component_ids": [p["id"] for p in props]},
],
},
)
client.sweeps.wait(sweep["id"], timeout=900)
ranked = sorted(
(
(pt["inputs"]["component"][0]["name"], pt["rotors"]["1"]["thrust_n"])
for pt in client.sweeps.list_points(sweep["id"])
),
key=lambda r: -r[1],
)
for name, thrust in ranked[:5]:
print(f"{name}: {thrust:.1f} N")
See Sweeps for the component-axis shape and the per-minute submission limits it keeps you clear of.
See also
- Submissions — submit a custom component for review
- Starred components — per-project favorites