> ## 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.

# Units

> A unit is the unique digital record of a single physical item — its digital twin. Units are the primary object you work with in the Endstate API.

## What is a unit?

A unit is the digital identity of a single physical item — its digital twin and its certificate of authenticity. Even if two items are identical SKUs (same product, same size, same color), each one is its own unit with its own identity, its own chip, and its own issuance history.

Units live inside a [collection](/concepts/collections). A collection defines the group (a product line, a drop, a release); a unit is one item within it.

## Why units matter

* **Register items.** Create a unit for each physical item when it enters your workflow — before it ships, before it's paired to a chip.
* **Map to your inventory.** Use `external_id` to tie each unit back to your own SKU, order line, or serial number. Filter lists by `external_id` to look up units without storing Endstate IDs.
* **Track issuance.** A unit's `collection.token.status` tells you whether the item has been issued — `pending` means the unit exists but has no chip yet; `active` means a chip is paired and the unit has a `collection.token.serial` within its collection.
* **Verify on tap.** When a user taps an NFC chip, the API returns the unit record attached to it. That record is what you display, validate, or act on.

***

## Unit object

<ResponseField name="id" type="string (UUID)">
  The unit's unique identifier. Stable; use this as your durable reference in
  Endstate.
</ResponseField>

<ResponseField name="external_id" type="string | null">
  Your own identifier for this item — SKU, order line ID, serial number, or any
  opaque string. Must be unique within your organization if provided. Null if
  not set.
</ResponseField>

<ResponseField name="name" type="string | null">
  A human-readable label for the item (e.g. `"Black Hoodie — Size M"`).
  Optional; null if not set.
</ResponseField>

<ResponseField name="attributes" type="object | null">
  Free-form JSON you attach to this unit. Use this for any item-level metadata
  that does not have a dedicated field — color, size, batch number, custom
  fields. No enforced schema; store whatever your integration needs.
</ResponseField>

<ResponseField name="redirect_url" type="string | null">
  Where a user is sent after tapping this unit's chip. Overrides the collection
  and organization-default redirects. Null if not set — resolution then falls
  through to the collection redirect, then your organization default. See [Tap
  redirects](/concepts/tap-redirects).
</ResponseField>

<ResponseField name="created_at" type="string (ISO-8601)">
  When the unit was created.
</ResponseField>

<ResponseField name="collection" type="object">
  The collection this unit belongs to.

  <Expandable title="collection fields">
    <ResponseField name="collection.id" type="string (UUID)">
      The collection's unique identifier.
    </ResponseField>

    <ResponseField name="collection.name" type="string | null">
      The collection's name.
    </ResponseField>

    <ResponseField name="collection.external_id" type="string | null">
      Your identifier for the collection, if set.
    </ResponseField>

    <ResponseField name="collection.contract" type="object">
      Registry details for the collection — `address` (string), `chain_id`
      (integer), and `status` (`deploying`, `active`, or `failed`). See
      [Collections](/concepts/collections) for what each field means.
    </ResponseField>

    <ResponseField name="collection.token" type="object">
      The unit's issuance status within this collection.

      <Expandable title="token fields">
        <ResponseField name="collection.token.status" type="string">
          The unit's issuance status. `pending` — the unit has no chip paired yet. `active` — a chip is paired and the unit has been issued a serial number within the collection.
        </ResponseField>

        <ResponseField name="collection.token.serial" type="integer | null">
          The unit's serial number within its collection. Assigned when `collection.token.status` becomes `active`. Null while still `pending`. Serial numbers are assigned in the order items are issued.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="chips" type="array">
  The NFC chips paired to this unit. Most units have exactly one chip; the array is empty until a chip is paired.

  <Expandable title="chip fields">
    <ResponseField name="chips[].chip_id" type="string">
      The chip's identifier (10 hex characters). This is the value that appears in the tap URL.
    </ResponseField>

    <ResponseField name="chips[].is_test" type="boolean">
      `true` for test chips used in integration testing without hardware. `false` for chips on real physical items.
    </ResponseField>

    <ResponseField name="chips[].scan_count" type="integer">
      The number of times this chip has been successfully verified.
    </ResponseField>

    <ResponseField name="chips[].created_at" type="string (ISO-8601)">
      When the chip was paired to this unit.
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Create a unit

`POST /v1/units`

<ParamField body="collection_id" type="string (UUID)" required>
  The ID of the collection this unit belongs to. The collection must already be
  provisioned (`contract.status: "active"`). Required.
</ParamField>

<ParamField body="external_id" type="string">
  Your own identifier for this item. Must be unique within your organization.
  Optional.
</ParamField>

<ParamField body="name" type="string">
  A human-readable label for the item. Optional.
</ParamField>

<ParamField body="attributes" type="object">
  Free-form JSON metadata to attach to the unit. Optional.
</ParamField>

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

### Example

```bash theme={null}
curl https://api2.endstate.io/v1/units \
  -X POST \
  -H "Authorization: Bearer end_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "collection_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "external_id": "sku-hoodie-black-m-001",
    "name": "Black Hoodie — Size M",
    "attributes": { "color": "black", "size": "M" }
  }'
```

```json theme={null}
{
  "id": "8e1a7f50-90ab-4cde-f012-3456789abcde",
  "external_id": "sku-hoodie-black-m-001",
  "name": "Black Hoodie — Size M",
  "attributes": { "color": "black", "size": "M" },
  "redirect_url": null,
  "created_at": "2026-05-14T10:30:00.000Z",
  "collection": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Summer Drop 2026",
    "external_id": null,
    "contract": {
      "address": "0x1111111111111111111111111111111111111111",
      "chain_id": 84532,
      "status": "active"
    },
    "token": {
      "status": "pending",
      "serial": null
    }
  },
  "chips": []
}
```

<Note>
  The unit is returned at the top level, like every single-resource response —
  see [Response shape](/conventions/responses). It is created with
  `collection.token.status: "pending"`; no serial is assigned yet — that happens
  when a chip is paired. See [Lifecycle](#lifecycle) below.
</Note>

***

## Lifecycle

Units move through a simple two-state lifecycle:

<Steps>
  <Step title="Create the unit">
    `POST /v1/units` — the unit is created with `collection.token.status:
            "pending"` and an empty `chips` array. No serial number yet.
  </Step>

  <Step title="Pair a chip">
    `POST /v1/chips` with `unit_id` and the chip's identifiers. Pairing links
    the physical item to its digital record and triggers issuance.
  </Step>

  <Step title="Unit is issued">
    Issuance is asynchronous. Poll `GET /v1/units/{unit_id}` until
    `collection.token.status` is `"active"`. When active,
    `collection.token.serial` holds the unit's serial number within the
    collection.
  </Step>
</Steps>

<Note>Poll `GET /v1/units/{unit_id}` to check issuance status.</Note>

***

## Endpoints

| Method  | Path                                                     | Description                                                                             |
| ------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `POST`  | `/v1/units`                                              | Create a unit                                                                           |
| `GET`   | `/v1/units`                                              | List units (cursor-paginated; filter by `external_id`)                                  |
| `GET`   | `/v1/units/{unit_id}`                                    | Fetch a single unit by ID                                                               |
| `PATCH` | `/v1/units/{unit_id}`                                    | Update a unit's `redirect_url`                                                          |
| `POST`  | `/v1/units/{unit_id}/chip-replacements`                  | [Replace the unit's chip](/concepts/chips#replacing-a-chip) (until the unit is claimed) |
| `GET`   | `/v1/units/{unit_id}/chip-replacements/{replacement_id}` | Poll a chip replacement's status                                                        |
| `POST`  | `/v1/units/{unit_id}/claims`                             | [Claim the unit](/concepts/claims) — session token required                             |
| `POST`  | `/v1/units/{unit_id}/transfers`                          | [Transfer the unit](/concepts/transfers) — session token required                       |

These endpoints take an API key, except claim and transfer creation, which require a session token (status polling takes either). See [Authentication](/authentication) for header format and credential types.

For full request/response schemas, see the [API reference](/api-reference/introduction).

***

## Next steps

<CardGroup cols={2}>
  <Card title="Chips" icon="scan-line" href="/concepts/chips">
    Pair an NFC chip to a unit to complete its digital record and trigger
    issuance.
  </Card>

  <Card title="Collections" icon="layers" href="/concepts/collections">
    Understand how collections group units and define the serial-number
    namespace.
  </Card>

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