# ID formats (/docs/api/concepts/id-formats)



Four ID shapes cover every resource on the API integration:

1. **Prefixed UUID v4** — `<prefix>_<8-4-4-4-12>`. The default for top-level resources backed by a Postgres `gen_random_uuid()` column (projects, content containers, scheduled posts, social accounts, …).
2. **Prefixed ULID** — `<prefix>_<26 Crockford base32>`. Used for IDs we mint in app code (jobs, media, uploads, events, request IDs, lease requests).
3. **Bare UUID v4** — a small set of resources where the catalog still emits the UUID without a prefix (webhook endpoint, webhook delivery, audit-log `eventId`).
4. **Custom shapes** — `app_<24 hex>` for SDK apps. That's the only one.

You don't need to memorize the prefixes — every path param accepts the bare UUID too. See the [tolerance rule](#the-tolerance-rule) below.

## Catalog [#catalog]

Every resource you'll see at the boundary. This table is the source of truth; if you find an endpoint emitting something different, file a bug.

| Resource              | Shape          | Example                                    |
| --------------------- | -------------- | ------------------------------------------ |
| Organization          | `org_<UUID>`   | `org_2481fa5c-a404-44ed-a561-565392499abc` |
| Project               | `prj_<UUID>`   | `prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39` |
| Layer (project layer) | `lyr_<UUID>`   | `lyr_8b3c1d2e-4f5a-46b7-9c8d-0e1f2a3b4c5d` |
| Content container     | `cnt_<UUID>`   | `cnt_7d18b9a1-8b2c-4f3e-a4d5-6e7f8a9b0c1d` |
| Scheduled post        | `sp_<UUID>`    | `sp_b9b66cde-7c8e-43dc-a9d2-3f4e5a6b7c8d`  |
| Social account        | `sa_<UUID>`    | `sa_9c1e42a0-b7f3-4e5d-a2c1-8b4f5e6c7d8e`  |
| Influencer            | `inf_<UUID>`   | `inf_4a8e1bc2-3d4f-46a8-9b0c-1d2e3f4a5b6c` |
| Ads content           | `adc_<UUID>`   | `adc_6f5d4c3b-2a1e-49d8-87a6-5b4c3d2e1f0a` |
| Recommendation        | `rec_<UUID>`   | `rec_5e4d3c2b-1a09-48f7-8e6d-5c4b3a2e1f0d` |
| API key               | `key_<UUID>`   | `key_c2037bb9-354d-4662-96b7-97a28ad6b6e1` |
| Job                   | `job_<ULID>`   | `job_01HXA1NHKJZXPV8R7Q6WSM5BCD`           |
| Media asset           | `med_<ULID>`   | `med_01HXA4MNP5RSTUVWXYZABCDEFG`           |
| Upload (presigned)    | `upl_<ULID>`   | `upl_01HXA4PQR7TUVWXYZABCDEFGHJ`           |
| Lease request         | `lreq_<ULID>`  | `lreq_01HXB2J9FGHZMNOPQRSTUVWXY`           |
| Webhook event         | `evt_<ULID>`   | `evt_01KPM7QZEC6NJF4XJTCZRR6S3N`           |
| Request ID            | `req_<ULID>`   | `req_RKT95R73PHHF5N1AMH9H2Q58MC`           |
| Audit-log event       | bare UUID      | `b9c1d2e3-4f5a-46b7-8c9d-0e1f2a3b4c5d`     |
| Webhook endpoint      | bare UUID      | `3f71a8b2-4c58-4d2e-b1e3-8e0a2ae5c0c1`     |
| Webhook delivery      | bare UUID      | `5a2b3c4d-6e7f-4a8b-9c0d-1e2f3a4b5c6d`     |
| GitHub installation   | integer        | `56781234`                                 |
| SDK app               | `app_<24 hex>` | `app_8ffb9410eb0eb848264f8a17`             |

<Callout>
  **Ads sub-resources** (ad account, campaign, ad-set, ad) are not in this catalog. Their wire format varies by endpoint and is documented inline on each ads reference page. Treat those IDs as opaque strings; don't try to parse them.
</Callout>

<Callout>
  Two different "event" namespaces exist: webhook events carry `evt_<ULID>` on `X-Layers-Event-Id`; audit-log events carry a bare UUID in the `eventId` field. They're independent — don't try to correlate them.
</Callout>

## Prefixed UUID format [#prefixed-uuid-format]

`<prefix>_<8-4-4-4-12>`. The body is a standard UUID v4 (RFC 4122) emitted lowercase with hyphens preserved. Most top-level partner resources (`prj_`, `cnt_`, `sp_`, `inf_`, `sa_`, `lyr_`, `org_`, `key_`, `adc_`, `rec_`) take this shape.

The prefix is metadata for humans and grep — the underlying value is the UUID. Two safe operations: pass the whole string back verbatim, or strip the prefix yourself to recover the raw UUID for storage in a UUID-typed column.

## Prefixed ULID format [#prefixed-ulid-format]

`<prefix>_<26 characters of Crockford base32>`. Crockford base32 is the 32-character alphabet `0123456789ABCDEFGHJKMNPQRSTVWXYZ` — no `I`, `L`, `O`, or `U` to avoid visual confusion.

* 26 chars carry 130 bits, slightly more than a UUID's 128. Collision risk is negligible.
* **Not monotonic.** We don't rely on the ULID time prefix for ordering; the leading chars are time-based but the trailing chars are random entropy. Sort on `createdAt`, not the id string.
* Case-insensitive in spirit (Crockford), but we always emit uppercase body after the lowercase prefix. Don't re-case them before sending back.

This shape is used for IDs we mint in application code rather than reading off a Postgres UUID column: jobs (`job_`), media (`med_`), uploads (`upl_`), lease requests (`lreq_`), webhook events (`evt_`), and request IDs (`req_`).

## Request ID format [#request-id-format]

Every response carries `X-Request-Id: req_<26 Crockford base32>`. The same value appears as `error.requestId` in every 4xx/5xx body — copy that one line into a support ticket and we can pull the full trace.

```http
HTTP/1.1 200 OK
X-Request-Id: req_RKT95R73PHHF5N1AMH9H2Q58MC
```

If you set your own `X-Request-Id` request header, we use it as-is (up to 128 chars) so your traces can span the proxy. Otherwise we mint one.

## Webhook event ID format [#webhook-event-id-format]

Webhook deliveries carry `X-Layers-Event-Id: evt_<26 Crockford base32>`. The id is **stable across retries** of the same event — dedupe on it in your handler. Replays (via `POST /v1/webhook-deliveries/:id/replay`) mint a fresh event id and set a top-level `replayOf` (a sibling of `id`/`type`/`data`) to the original delivery id.

## SDK app format [#sdk-app-format]

The odd one out: SDK apps use `app_<24 hex chars>`, not a UUID or ULID. This pre-dates the partner ID conventions — SDK app IDs ship into customer binaries and the shorter hex form was baked into the client SDKs before the prefix catalog existed. They're still opaque; treat them as strings.

## The tolerance rule [#the-tolerance-rule]

Every path param that documents a prefixed id also accepts the bare body (UUID or ULID).

```http
GET /v1/content/cnt_7d18b9a1-8b2c-4f3e-a4d5-6e7f8a9b0c1d   ← prefixed
GET /v1/content/7d18b9a1-8b2c-4f3e-a4d5-6e7f8a9b0c1d       ← bare
```

Both forms resolve to the same resource. A malformed id returns `NOT_FOUND`. Every content, scheduled-post, social-account, and leased-account path param behaves this way.

<Callout type="warn">
  Tolerance runs one way. **Responses always carry the canonical shape** — we don't strip prefixes on the way out. Store whatever we hand you.
</Callout>

## Which to use when [#which-to-use-when]

* **Storing a reference in your system** — save the exact string we returned. Don't normalize, don't re-prefix, don't parse it.
* **Calling back with it** — pass it through verbatim.
* **Displaying to an end-user** — the prefixed form (`cnt_<uuid>`) is the intended UX. It's self-describing at a glance and pastes cleanly into logs.
* **Grepping logs for a specific id** — the prefixed form is unambiguous; the bare body can collide with unrelated UUIDs in a large log corpus.

## See also [#see-also]

* [Common patterns - error envelope, request ID](/docs/api/getting-started/common-patterns#error-shape)
* [Jobs - job IDs and the polling pattern](/docs/api/concepts/jobs)
* [Webhooks - event IDs and delivery IDs](/docs/api/operational/webhooks)
* [Idempotency - server-generated IDs and Idempotency-Key semantics](/docs/api/operational/idempotency)
