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

# Resource IDs

> Every v3 resource has a readable, typed ID — a resource-type prefix plus a Base58 encoding of the underlying UUID. Foreign-key fields like withdrawalAccountId and quoteId reference these IDs across resources.

Every resource in v3 (`Quote`, `Trade`, `Deposit`, `Withdrawal`, `WithdrawalAccount`) carries an `id` that the server assigns at creation, plus an `object` field naming the resource type. IDs are **readable**: a lowercase 3-to-5-letter prefix identifies the resource type, followed by a Base58 encoding of the underlying 128-bit UUID. Two fields in v3 reference these IDs across resources: `quoteId` on a trade points at a `Quote.id`, and `withdrawalAccountId` on a withdrawal points at a `WithdrawalAccount.id`.

<Note>
  **v3 emits readable IDs on every response.** Bare UUIDs never appear in a v3
  response body — the UUID stays the internal primary key; the readable ID is
  the public identifier. For backward compatibility, v3 **accepts** either a
  readable ID or a bare UUID v4 wherever an ID is expected on input (path
  parameters, query parameters, and request-body foreign keys) — both decode to
  the same underlying record. New integrations should use the readable form
  returned by the API.
</Note>

## Format

`<prefix>_<base58(uuid)>` — a lowercase 3-to-5-letter type prefix, an underscore, and the Base58 encoding of the resource's 128-bit UUID.

| Resource           | `object`            | Prefix | Example                      |
| ------------------ | ------------------- | ------ | ---------------------------- |
| Quote              | `quote`             | `qte_` | `qte_3FfGK34vwMvVFDedyb2nkf` |
| Trade              | `trade`             | `tde_` | `tde_5W7guYdHT24JFnRQrZN9y8` |
| Deposit            | `deposit`           | `dpt_` | `dpt_EA9vbVngB76PmMibCiMNPb` |
| Withdrawal         | `withdrawal`        | `wtd_` | `wtd_6SATV6VSUdBTttHWBmCYjD` |
| Withdrawal account | `withdrawalAccount` | `wac_` | `wac_NDqQ9LmcUASpnHR6CTvdkk` |
| User / actor       | —                   | `usr_` | `usr_7m4VsfRw4pGrS76WYj5tnx` |

`Pair` and `Balance` have no resource UUID — a pair is identified by its `(buyCurrency, sellCurrency)` combination, and a balance by `currency`, not by an `id` field.

* The Base58 alphabet excludes `0`, `O`, `I`, and `l` to avoid visual ambiguity: `123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz`.
* The encoded body is at most 22 characters and is **not a fixed length** — 21–22 characters for a typical random UUID, shorter only for near-zero values (the nil UUID encodes to 16 characters).
* Regex pre-filter: `^[a-z]{3,5}_[1-9A-HJ-NP-Za-km-z]{1,22}$`. This is a loose shape check; the authoritative validation is a successful decode back to exactly 16 bytes. A value that matches the regex but doesn't decode to 16 bytes is rejected the same as a malformed ID.
* Base58 is case-sensitive. There is no case normalization — a mis-cased but otherwise well-formed ID decodes to a different value (or fails to decode) and is rejected.

Encoding is a pure, stateless, reversible function of the UUID: the same UUID always produces the same readable ID, and two different UUIDs never collide. There's no database column or backfill involved — the UUID remains the internal primary key, and the readable ID is derived from it on every response.

Any ID that fails to decode, or whose prefix names a different resource type than the field expects, is rejected at the validation layer with `400 VALIDATION_QUERY_FAILED` (query and body) or `400 VALIDATION_PATH_FAILED` (path parameters).

## Stability

* IDs are **permanent**. Once issued, the underlying UUID never changes and never gets reused, so its readable encoding doesn't either.
* Safe to store as foreign keys in your database. Index the readable ID, the underlying UUID, or both, depending on what your queries need.
* Safe to log in audit trails. IDs carry no PII — the prefix reveals only the resource type, and the body is an opaque, lossless encoding of the UUID.
* Safe to share in support tickets and idempotency-key collision reports.

The stability contract is the same contract that makes idempotency keys useful: if you persist a resource ID at write time and the network drops the response, that ID will still resolve to the same record on retry.

## Cross-resource references

Some fields are foreign keys pointing at IDs on other resources. The table below lists every such reference in v3:

| Field                 | On resource                             | Points to              | Notes                                                                                                                                                                 |
| --------------------- | --------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quoteId`             | `TradeCreateRequest`, `Trade`           | `Quote.id`             | Required when executing a trade. Echoed back on the resulting `Trade`.                                                                                                |
| `withdrawalAccountId` | `WithdrawalCreateRequest`, `Withdrawal` | `WithdrawalAccount.id` | The verified withdrawal account. Fiat or stablecoin is inferred from the account.                                                                                     |
| `actorId`             | `Withdrawal`                            | User (`usr_` prefix)   | The actor that initiated the withdrawal. Masked to a fixed placeholder for internal OpenFX operators. Not independently fetchable — v3 has no `GET` for user records. |
| `creatorId`           | `WithdrawalAccount`                     | User (`usr_` prefix)   | The user that created the withdrawal account. `null` only for legacy rows. Not independently fetchable.                                                               |

Anything else that looks like an ID (a transaction hash, a currency code) is not a foreign key into another v3 resource.

## ID vs Idempotency-Key

Two ID-shaped fields in v3 serve different purposes:

|                | Resource ID                                             | Idempotency-Key                              |
| -------------- | ------------------------------------------------------- | -------------------------------------------- |
| Generated by   | Server                                                  | Client                                       |
| Lifetime       | Permanent                                               | TTL (30 min / 24 h / 7 days, per endpoint)   |
| Format         | `<prefix>_<base58>` (e.g. `wac_NDqQ9LmcUASpnHR6CTvdkk`) | Any string matching `^[a-zA-Z0-9_-]{1,255}$` |
| Where it lives | Response body `id`, foreign-key fields                  | `Idempotency-Key` request header             |
| Purpose        | Identify a record forever                               | De-duplicate a single write attempt          |
| Safe to log    | Yes                                                     | Yes (but don't embed PII in them)            |

The two are complementary: you generate an idempotency key before the request, the server returns a resource ID in the response, and you persist both alongside each other. The idempotency key lets you safely retry the write; the resource ID lets you look up the result for the rest of time. See [Idempotency → Crash recovery](/v3/idempotency#crash-recovery) for the persist-both pattern.

## Common mistakes

* **Treating IDs as transient.** They aren't. Persist them.
* **Parsing or decoding the readable ID yourself.** The prefix tells you the resource type; don't try to derive anything else from the body. Treat the body as an opaque string, even though it happens to be reversible server-side.
* **Reading the `id` from your own DB row instead of the API response on a write.** If you assigned an identifier locally and sent it as the request body, that's not the resource ID. The server generates its own. Always trust the response, not your draft.
* **Confusing `quoteId` with `Idempotency-Key` on the trade execution call.** `quoteId` goes in the JSON body, `Idempotency-Key` goes in the header. They're unrelated.
* **Storing only the readable ID and losing the ability to index efficiently.** Both the readable ID and its underlying UUID identify the same record — store whichever shape fits your schema, or both.
* **Assuming a hardcoded body length.** The encoded body is at most 22 characters but isn't fixed-width. Validate by prefix and successful decode, not by string length.

## What's next

<CardGroup cols={2}>
  <Card title="Idempotency" icon="repeat" href="/v3/idempotency">
    Client-generated keys vs server-generated IDs.
  </Card>

  <Card title="Pagination" icon="layer-group" href="/v3/pagination">
    Opaque cursors — distinct from resource IDs. Pass them back verbatim.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/v3/errors">
    When you'll see `*_NOT_FOUND` and how to handle it.
  </Card>
</CardGroup>
