---
title: "List Components"
method: GET
path: "/v1/components"
tags: ["v1", "components"]
---

# List Components

`GET /v1/components`

## Request

- Base URL: `https://thrustlab.com`
- URL: `https://thrustlab.com/v1/components`
- Auth: HTTP bearer

Return a paginated list spanning the public catalog and caller's customs.

Query parameters: `type` (optional; motor|battery|propeller — when omitted
returns all three types ordered by name/created_at only),
`visibility` (optional; public/private; default: union),
`limit` (1-100, default 25), `cursor` (pagination token),
`search` (name substring match; `q` is an accepted alias — `search` wins
if both are supplied, and `q` is length-capped identically),
`sort_by` (name, brand, kv, etc.), `sort_order` (asc/desc).

The cursor is a sort-aware composite keyset: it carries the active
`sort_by`/`sort_order`, so a cursor issued for one sort cannot be reused
with a different sort (400 `invalid_cursor`).

Numeric spec_json fields are filterable via bracket syntax:
?kv[gte]=900&kv[lt]=1100. Discrete fields accept [in]:
?blades[in]=2,3. Per-type filters and per-type sort keys require ``type``
to be set; without ``type`` only the generic ``name`` and ``created_at``
sort keys are accepted.

Every range field answers to two names — the documented one and the raw
spec key — on BOTH surfaces: ``resistance[lte]`` and ``R[lte]`` filter
identically, and ``sort_by=resistance`` and ``sort_by=R`` sort identically.
A bracket filter naming a field this type does not have is rejected with
400 ``unknown_filter`` listing the fields it does have, so a typo can no
longer return the whole catalog in silence. The bounds the server applied
come back in ``filters``, keyed by the documented name.

Results are cursor-paginated in the requested sort order.

Readable WITHOUT a credential: an anonymous caller lists the curated public
catalog only — never a private row and never a user-submitted one — and is
metered per client IP on the same bucket as ``/v1/public/*``. A credential
adds the caller's own components and their raw specs. ``visibility=private``
is not an error without a credential, it simply matches nothing.

## Query parameters

- `type` string, nullable
- `visibility` string, nullable
- `cursor` string, nullable
- `limit` integer
- `search` string, nullable
- `q` string, nullable: Alias for `search` (name substring match). If both are supplied, `search` wins. Length-capped identically to `search`.
- `sort_by` string: Sort key. Without `type` only `name` and `created_at` are accepted. With `type` set, additionally — motor: R (or resistance), iq_max (or current_max), kv, n (or poles), power_max, weight, weight_g; battery: c_rating, energy_density, series_cells, total_capacity_mAh (or capacity), weight_g (or weight); propeller: diameter, inertia, maxRPM (or max_rpm), num_blades, pd_ratio, pitch, weight. Either spelling of a field works, here and in the bracket filters. The pagination cursor is bound to the sort: a cursor issued for one `sort_by`/`sort_order` cannot be replayed against another (400 `invalid_cursor`), so finish a scan before changing the ordering.
- `sort_order` string
- `ids_only` boolean: When true, return ALL matching component ids in the requested sort order (bounded, NOT cursor-paginated) for Select-all. Response is {object,data:[ids],total,truncated}; cursor/limit are ignored. At most 5000 ids are returned; beyond that the first ids in sort order come back with truncated=true.

## Response `200`

Successful Response

- union
  - AppV1SerializersComponentsComponentListResponse
    - `data` ComponentResource[], required
      - `base_component_id` string, uuid, nullable, required
      - `created_at` string, date-time, required
      - `id` string, required
      - `name` string, required
      - `object` string
      - `source` string, required
      - `spec_json` object, required
      - `type` string, required
      - `updated_at` string, date-time, required
      - `user_id` string, uuid, nullable, required
      - `visibility` string, required
    - `filters` object: Range filters the server applied, keyed by the documented field name. Empty when no range filter was supplied or when `type` was omitted (per-type filters need a type).
    - `gated_count` integer, nullable
    - `has_more` boolean, required
    - `next_cursor` string, nullable, required
    - `object` string
  - ComponentIdsResponse: The Select-all id list. Returns EVERY component id matching the current search + filters in the requested ``sort_by``/``sort_order`` (the SAME primary ordering the paginated list uses), bounded by a documented hard cap. This sidesteps the cursor-keyset bug — the cursor keyset is ``(created_at, public_id)`` and so cannot honor a non-default ``sort_by``; a single sorted ``.limit(cap)`` query can. ``total`` is the count returned (== matches when not truncated); ``truncated`` is true when the matching set exceeded the cap (the first ``cap`` ids, in sort order, are returned).
    - `data` string[], required
    - `filters` object: Range filters the server applied, keyed by the documented field name. Empty when no range filter was supplied or when `type` was omitted (per-type filters need a type).
    - `object` string
    - `total` integer, required: Number of ids returned — the number of matches unless truncated.
    - `truncated` boolean, required: True when more than 5000 components matched. The first 5000 ids in the requested sort order are returned; narrow the filters to get the rest.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/thrustlab/apis/thrustlab-api.md) · [All operations](https://skmtc.dev/thrustlab/apis/thrustlab-api/llms.txt) · [OpenAPI document](https://skmtc.dev/thrustlab/apis/thrustlab-api/revisions/c19717e52529?raw)
