> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endstate.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Collections

> A collection groups related units under a shared registry, defines their serial-number namespace, and must be provisioned before units can be added.

## What is a collection?

A collection is a named group of related units — a product line, a drop, or a release. Every unit belongs to exactly one collection, and the collection defines the serial-number namespace shared across all units within it.

Create a collection once per product group. Then create units inside it for each individual physical item.

## Why it matters

Collections let you organize your products and enforce namespace boundaries:

* Units within a collection receive sequential serial numbers that are unique within that collection.
* Keeping separate product lines in separate collections avoids serial collisions and makes filtering your unit list predictable.
* A unit cannot exist without a collection. You must create and provision a collection before you can add units to it.

***

## Lifecycle

<Steps>
  <Step title="Create the collection">
    Call `POST /v1/collections`. The API returns `201` immediately. The
    collection's `contract.status` is `"deploying"`, meaning it is being
    provisioned. The registry is not yet ready for units.
  </Step>

  <Step title="Wait for provisioning">
    Provisioning is asynchronous. Poll `GET /v1/collections/{collection_id}`
    until `contract.status` is `"active"`.
  </Step>

  <Step title="Add units">
    Once the collection is active, create units inside it with `POST /v1/units`,
    passing the collection's `id` as `collection_id`.
  </Step>
</Steps>

### Provisioning status values

| `contract.status` | Meaning                                                    |
| ----------------- | ---------------------------------------------------------- |
| `deploying`       | The collection is being provisioned. Do not add units yet. |
| `active`          | The collection is ready. You can create units.             |
| `failed`          | Provisioning failed. Contact Endstate support.             |

<Note>
  If you attempt to create a unit in a collection that has not yet reached
  `active`, the API returns `collection.not_active` (409).
</Note>

***

## Collection object

<ResponseField name="id" type="string">
  Unique identifier for the collection (UUIDv4).
</ResponseField>

<ResponseField name="name" type="string | null">
  Human-readable name you provided at creation.
</ResponseField>

<ResponseField name="external_id" type="string | null">
  Your own identifier for this collection, if you provided one. Unique within
  your organization. Useful for correlating with your internal systems. Null if
  not set.
</ResponseField>

<ResponseField name="redirect_url" type="string | null">
  Where a user is sent after tapping the chip on any unit in this collection,
  unless that unit sets its own `redirect_url`. Null if not set — resolution
  then falls through to your organization default. See [Tap
  redirects](/concepts/tap-redirects).
</ResponseField>

<ResponseField name="contract" type="object">
  Registry details for the collection.

  <Expandable title="contract fields">
    <ResponseField name="contract.address" type="string">
      The collection's unique registry address. Assigned at creation and returned immediately, including while `contract.status` is still `deploying`.
    </ResponseField>

    <ResponseField name="contract.chain_id" type="integer">
      The network identifier for this collection's registry. Fixed at creation — your organization's default network, or the `chain_id` you supplied to override it.
    </ResponseField>

    <ResponseField name="contract.status" type="string">
      The collection's provisioning status. One of `deploying`, `active`, or `failed`. Poll this field after creation.
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Create a collection

### Request fields

<ParamField body="name" type="string" required>
  A human-readable name for the collection (1–256 characters). For example,
  `"Spring 2026 Hoodie"`.
</ParamField>

<ParamField body="symbol" type="string">
  A short identifier for the collection (1–4 characters). Derived from `name` if
  omitted. For example, `"S26H"`.
</ParamField>

<ParamField body="external_id" type="string">
  Your own identifier for this collection. Must be unique within your
  organization. Omit if you do not need to correlate with an external system.
</ParamField>

<ParamField body="chain_id" type="integer">
  The network to create the collection on. Optional — when omitted, the
  collection is created on your organization's **default network**, configured
  by Endstate at onboarding. An explicit value must be one of the networks
  enabled for your organization (`allowed_chain_ids` from [`GET
      /v1/settings`](/api-reference/introduction)); any other value returns
  `validation.failed`.
</ParamField>

<ParamField body="redirect_url" type="string">
  Where a user is sent after tapping a chip on any unit in this collection. Must
  be a valid absolute URL. Optional — you can also set or change it later with
  `PATCH /v1/collections/{collection_id}`. See [Tap
  redirects](/concepts/tap-redirects).
</ParamField>

### Example

```bash theme={null}
curl -X POST https://api2.endstate.io/v1/collections \
  -H "Authorization: Bearer end_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring 2026 Hoodie",
    "symbol": "S26H",
    "external_id": "hoodie-spring-2026"
  }'
```

```json theme={null}
{
  "id": "8e1a7f50-90ab-4cde-f012-3456789abcde",
  "name": "Spring 2026 Hoodie",
  "external_id": "hoodie-spring-2026",
  "redirect_url": null,
  "contract": {
    "address": "0x1111111111111111111111111111111111111111",
    "chain_id": 84532,
    "status": "deploying"
  }
}
```

The collection is returned at the top level, like every single-resource response (see [Response shape](/conventions/responses)). `contract.address` is assigned at creation and present immediately; `symbol` is accepted on create but not returned. Poll `GET /v1/collections/{collection_id}` until `contract.status` is `"active"`.

### Active collection (polled response)

```json theme={null}
{
  "id": "8e1a7f50-90ab-4cde-f012-3456789abcde",
  "name": "Spring 2026 Hoodie",
  "external_id": "hoodie-spring-2026",
  "redirect_url": null,
  "contract": {
    "address": "0x1111111111111111111111111111111111111111",
    "chain_id": 84532,
    "status": "active"
  }
}
```

***

## Endpoints

| Method  | Path                              | Description                                                  |
| ------- | --------------------------------- | ------------------------------------------------------------ |
| `POST`  | `/v1/collections`                 | Create a collection                                          |
| `GET`   | `/v1/collections`                 | List collections (cursor-paginated; filter by `external_id`) |
| `GET`   | `/v1/collections/{collection_id}` | Fetch a single collection by UUID                            |
| `PATCH` | `/v1/collections/{collection_id}` | Update a collection's `redirect_url`                         |

See the [API reference](/api-reference/introduction) for full request and response schemas.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Units" icon="box" href="/concepts/units">
    Create units inside a collection to represent individual physical items.
  </Card>

  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Walk through the full flow: collection, unit, chip, and first verification.
  </Card>

  <Card title="Tap redirects" icon="corner-up-right" href="/concepts/tap-redirects">
    Send a user to your own destination after they tap a chip in this
    collection.
  </Card>
</CardGroup>
