Skip to content

Expanding & filtering

A few opt-in features keep responses tight by default and let you ask for more when you need it.

Expanding nested objects

By default, related objects are returned as IDs:

{
"id": "sub_…",
"customer_id": "cus_…",
"plan_code": "pro-monthly-usd"
}

Use the expand parameter to inline them:

GET /v1/subscriptions/{id}?expand=customer,plan,latest_invoice
{
"id": "sub_…",
"customer": { "id": "cus_…", "email": "", },
"plan": { "code": "pro-monthly-usd", "currency": "USD", },
"latest_invoice": { "id": "inv_…", "status": "paid", }
}

Multiple expansions are comma-separated. Nested expansions use dot notation: expand=customer.payment_methods returns the customer inlined, with their payment methods inlined inside that.

Each expansion costs a downstream join; don’t expand things you don’t need. Maximum depth is 3.

Field projection

Limit the response to specific fields with fields:

GET /v1/subscriptions/{id}?fields=id,status,current_period_end
{ "id": "sub_…", "status": "active", "current_period_end": "" }

Combine with expand:

GET /v1/subscriptions/{id}?expand=customer&fields=id,status,customer.email

Field projection is on the wire only — the server still computes the full object internally. You save bandwidth, not server time.

Filtering lists

List endpoints accept filters as query parameters. The filter name matches the field name on the resource:

GET /v1/subscriptions?status=active,past_due
GET /v1/invoices?customer_id=cus_…&status=open
GET /v1/payments?created_at[gte]=2026-05-01T00:00:00Z&amount[gte]=100
OperatorSyntaxNotes
equalsfield=vDefault.
infield=v1,v2,v3OR within a single field.
not equalsfield[ne]=v
not infield[nin]=v1,v2
greater thanfield[gt]=v
greater-or-equalfield[gte]=v
less thanfield[lt]=v
less-or-equalfield[lte]=v
starts withfield[starts]=vString fields only.
containsfield[contains]=vString fields only.
has keymetadata[has]=kMetadata only.
key equalsmetadata[k]=vMetadata only.

Filters compose with AND. There is no cross-field OR — issue multiple queries and union client-side if you need it.

Filtering on metadata

Every resource carries metadata (a flat map of strings up to 50 entries). Filter on it:

GET /v1/customers?metadata[your_user_id]=u_28471
GET /v1/subscriptions?metadata[has]=imported_from

Metadata fields are indexed lazily — the first query against a metadata key may be slow; subsequent queries are fast. If you filter on the same key constantly, it’s already hot.

Date ranges

ISO-8601 with timezone:

?created_at[gte]=2026-05-01T00:00:00Z&created_at[lt]=2026-06-01T00:00:00Z

Half-open ranges ([gte] / [lt]) are easier to compose than closed ranges; we recommend them.

Combining everything

GET /v1/invoices?
status=open,past_due&
customer_id=cus_…&
total[gte]=100&
created_at[gte]=2026-05-01T00:00:00Z&
expand=customer&
fields=id,status,total,due_at,customer.email&
sort=due_at:asc&
limit=100

The most expensive call you can write probably looks like that. The server handles it; you may want to break it up if your client times out at 30 seconds.

What can’t be expanded or filtered

A few fields aren’t first-class:

  • Computed fields (upcoming_invoice on a subscription, mrr on a customer) cannot be filtered or sorted on.
  • PDF download URLs cannot be expanded — request them directly from the resource’s pdf endpoint.
  • Audit log entries on a resource — query /v1/admin/audit-log separately.

The OpenAPI doc is the authoritative list of expandable / filterable fields per endpoint.