# Search records

Find, filter, and sort the records of an object definition with a query language.

The search API finds records of one object definition with a query language. Each search covers one object definition in one account and mode. Use search to filter or sort records, and use list requests (see [Create and manage records](https://docs.stripe.com/custom-objects/records.md)) to page through all of them. Search results are eventually consistent. A record that you create or update can take up to 10 seconds to appear in search results with its new values, and a record that you recently deleted can still appear. To read a record right after you write it, retrieve it by ID.

## Request

Send a POST request to `/v2/extend/objects/<plural_name>/search`, with the query in the JSON body and the page size in the `limit` URL parameter:

```bash
curl "https://api.stripe.com/v2/extend/objects/support_tickets/search?limit=20" \
  -H "Authorization: Bearer $STRIPE_SECRET_KEY" \
  -H "Stripe-Version: 2026-10-28.preview" \
  -H "Stripe-Custom-Object-Versions: support_tickets@0.0.1" \
  -H "Content-Type: application/json" \
  --data-binary @- <<'EOF'
{
  "query": "fields['status']:'open' fields['priority']>=3",
  "sort": ["-created"]
}
EOF
```

Use the REST request above to search records. Include the `Stripe-Custom-Object-Versions` header for the version you want to search.

## Response

Search returns a page of matching records:

```json
{
  "object": "v2.search_result",
  "data": [
    {
      "id": "objrec_test_61RfVn0yAJiQAQHk7O16UQupaQVSQsnLcmkMmyS57AbC",
      "object": "v2.extend.objects.support_ticket",
      "fields": {"subject": "Order arrived damaged", "status": "open", "priority": 3.0}
    }
  ],
  "next_page_url": null,
  "previous_page_url": null,
  "total_count": 1
}
```

`data` holds records in the same format as retrieve and list (shortened here). `total_count` is exact up to 10,000. If more than 10,000 records match, `total_count` is 10,000. A page has 10 results by default, and you can set `limit` to up to 100.

## Pagination

When more results match, the response includes a `next_page_url`. To get the next page, send a POST request to `next_page_url` with the same headers and the same body. If the query or sort differs from the first request, the request fails. On the last page, `next_page_url` is `null`.

## Query language

A query has one or more clauses. Each clause is a field, an operator, and a value, such as `fields['status']:'open'`. Refer to your own fields as `fields['<field_name>']`, and to the system fields `id`, `created`, and `updated` by name. Use these operators:

- `:` or `=`: Equals. For strings, the field must contain the words you search for, next to each other and in the same order. Words are separated by spaces, and case doesn’t matter. For example, `fields['subject']:'arrived damaged'` matches “Order arrived damaged”, but `fields['subject']:'damage'` doesn’t.
- `~`: Contains. The value needs at least 3 characters, and case doesn’t matter.
- `>`, `>=`, `<`, `<=`: Greater than or less than, for numbers and datetimes.
- `-` before a clause: Not. For example, `-fields['status']:'resolved'`.
- `:null`: The field has no value, as in `fields['<field_name>']:null`. Put `-` before the clause to find records where the field has a value.

To combine clauses, separate them with a space or `and` to require all of them, or with `or` to require any of them. A query can have up to 10 clauses, can’t mix `and` and `or`, and can’t use parentheses. For example:

```text
fields['status']:'open' fields['priority']>=3
fields['status']:'open' or fields['status']:'in_progress'
-fields['status']:'resolved'
fields['subject']~'damaged'
created>'2026-10-30T00:00:00.000Z'
fields['customer']:'cus_abc123'
```

## Search by field type

Each field type has its own search behavior:

- **Strings**: Search indexes only the first 1,024 characters of a value, so queries can’t match text after that point. Search results also return only the first 1,024 characters. To get the full value, retrieve the record by ID.
- **Datetimes**: Compare with an ISO 8601 timestamp, such as `'2026-10-30T00:00:00.000Z'`.
- **References**: Match on the referenced object’s ID, such as `fields['customer']:'cus_abc123'`.
- **Monetary amounts**: Query the amount (in the currency’s minor unit) or the currency. For example, on a shipment object with a `shipping_cost` field, use `fields['shipping_cost']['amount']>1000` or `fields['shipping_cost']['currency']:'usd'`.
- **Arrays**: A clause matches if any item matches. When you combine clauses on the same array field with `and`, the same item must match all of them.
- **Decimals**: Comparisons use floating-point precision, so a decimal with many digits might not compare exactly.

You can search only the fields that the requested version declares.

## Sort results

Set `sort` to a list of fields. Results sort in ascending order by default. Put `-` before a field to sort in descending order, such as `"-created"`. You can sort by `id`, `created`, `updated`, and your fields, except array fields. Monetary amounts sort by amount. Without `sort`, results sort by `created`, newest first.

## Errors

Search returns these errors:

- `search_invalid_query`: Stripe can’t parse the query, or the query or sort uses a field that the requested version doesn’t declare, or an operator or value that the field doesn’t support. A `~` value shorter than 3 characters and a `limit` outside 1 to 100 also return this error. The message describes the problem.
- `parameter_invalid_empty`: `query` is empty. The error type is `invalid_request_error`.
- `invalid_fields`: `query` is missing, the query has more than 10 clauses, `sort` isn’t a list, or a request to `next_page_url` has a different body than the first request.
- `version_required`, `invalid_version`, or `not_found`: The `Stripe-Custom-Object-Versions` header is missing or malformed, or names a version that doesn’t exist. See [Version your custom objects](https://docs.stripe.com/custom-objects/versioning.md).
