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

# Identifiers

> Unif IDs, resolution, and why you should store the ID rather than the URL.

Every entity has a Unif ID: a prefix, then a sortable 26-character identifier.

```
shp_01k3m9x7v2q8r4t6y0b1n5d7fa
└┬┘ └────────────┬────────────┘
 │               └─ ULID: lexicographically sortable by creation time
 └───────────────── entity type
```

| Prefix | Entity     | Prefix | Object    |
| ------ | ---------- | ------ | --------- |
| `shp_` | Shop       | `job_` | Job       |
| `prd_` | Product    | `lst_` | List      |
| `crt_` | Creator    | `trk_` | Tracker   |
| `vid_` | Video      | `whk_` | Webhook   |
| `liv_` | Livestream | `wsp_` | Workspace |
| `cat_` | Category   | `req_` | Request   |

The prefix means an ID is self-describing in a log line, and passing a `crt_` ID to a shop
endpoint fails immediately with `parameter_invalid` rather than returning a confusing `404`.

## Why not just store the URL

URLs are not identity. Over a few months a storefront can change its handle, a product can be
delisted and re-listed under a new native ID, and a channel can migrate its URL structure —
all without the underlying entity changing.

<CardGroup cols={2}>
  <Card title="A Unif ID survives" icon="anchor">
    Renames, handle changes, re-listings and URL migrations. It is assigned once and never
    recycled.
  </Card>

  <Card title="A URL does not" icon="link-slash">
    It is a lookup key that happens to work today. Storing it means re-resolving forever, and
    paying for it every time.
  </Card>
</CardGroup>

Resolve once, store the ID on your row, and every later call is a direct lookup.

## Resolving what you already have

[`POST /v1/resolve`](/api-reference/resolution/resolve-identifiers-to-entities) accepts up to 100
inputs and takes any of three identifier forms.

```json theme={null}
{
  "inputs": [
    { "type": "shop",    "url": "https://www.tiktok.com/shop/glowlab", "ref": "row-1" },
    { "type": "creator", "handle": "@glowbyjune", "market": "US",      "ref": "row-2" },
    { "type": "product", "channel": "tiktok_shop", "external_id": "1729384756102938", "ref": "row-3" }
  ]
}
```

* **`url`** — the most reliable form. Channel and market are inferred from it.
* **`handle`** — needs `market`, because the same handle can exist in several markets.
* **`external_id`** — the channel's native ID. Needs `channel` to be unambiguous.

`ref` is yours. Unif echoes it back untouched, which turns joining results onto your own rows
into a dictionary lookup instead of positional bookkeeping.

## Four possible outcomes

Results come back in the order you sent them, and one input failing never fails the batch.

```json theme={null}
{
  "data": [
    { "ref": "row-1", "status": "resolved",  "id": "shp_01k3m9x7v2q8r4t6y0b1n5d7fa", "type": "shop" },
    { "ref": "row-2", "status": "not_found", "id": null, "type": "creator" },
    { "ref": "row-3", "status": "ambiguous", "id": null, "type": "product",
      "candidates": [
        { "id": "prd_01k3m9x7v2q8r4t6y0b1n5d7fa", "name": "Serum 30ml", "url": "https://…" },
        { "id": "prd_01k3m9x7v2q8r4t6y0b1n5d7fb", "name": "Serum 30ml", "url": "https://…" }
      ]
    }
  ]
}
```

<AccordionGroup>
  <Accordion title="resolved" icon="circle-check">
    Exactly one match. `id` is set and safe to store.
  </Accordion>

  <Accordion title="not_found" icon="circle-question">
    No match. Usually a dead listing, a typo, or an entity in a market your workspace has not
    enabled. Retrying will not help; the input needs fixing.
  </Accordion>

  <Accordion title="ambiguous" icon="code-branch">
    More than one match — most often the same product under multiple shops. `candidates` lists
    them so you can disambiguate with a rule of your own, or surface the choice to a person.
  </Accordion>

  <Accordion title="unsupported" icon="ban">
    The channel or entity type is not enabled for your workspace. Check
    [`GET /channels`](/api-reference/meta/list-channels-and-markets); this is an entitlement
    problem, not a data problem.
  </Accordion>
</AccordionGroup>

## Resolve, or enrich

[`POST /v1/enrich`](/api-reference/resolution/enrich-known-entities) takes exactly the same input
shape and returns the full record instead of just the ID.

| Use        | When                                                                  |
| ---------- | --------------------------------------------------------------------- |
| `/resolve` | You are backfilling IDs onto existing rows and will fetch data later. |
| `/enrich`  | You want IDs and data in one round trip.                              |

Enriching costs more than resolving, so for a one-time backfill of 50,000 rows, resolve first and
then pull metrics only for the rows you keep.

## Idempotent by design

Resolving the same identifier twice returns the same ID. There is no create-or-get race, and no
deduplication step needed on your side. The one thing to handle is `ambiguous` — the same input
stays ambiguous until you pick a candidate, so record the decision on your row rather than
re-resolving and hoping.
