Skip to main content
A subject is your id for one of your end users. You already have one — a row id, a UUID, a user id from your auth provider — so we never generate a second. Rows carry it, keys can be bound to it, and a bound key can only see and create rows for that one person.
The rule, in one line. A key with a subject sees only that subject’s rows. A key without one sees the whole tenant, exactly as it always has.

Why this exists

Scopes narrow what a key may do. subject narrows whose data it may do it to. You need the second axis for the same reason you need the first, only more so. The holder of a Farthing key is frequently an autonomous agent, and that agent spends its run reading merchant pages — untrusted text, written by someone else, quoted back to the model in option names, form labels and progress messages. “List the buyer profiles” is a completely plausible call for a confused or prompt-injected agent to make. It is also a call that, on a tenant-wide key, returns every name, email and shipping address you have ever stored. The blast radius of that call should be one person. That is the whole feature. Concretely, on a tenant serving thousands of end users through one agent, a leaked or manipulated tenant-wide key exposes all of them at once. A bound key exposes the one buyer it was minted for, and cannot mint itself anything wider.

Nothing changes until you opt in

Every key issued before this existed carries no subject, which means tenant-wide, which is exactly what those keys have always been. There is no migration, no behaviour change, and no new failure mode on an integration that ignores this page. Isolation is something you turn on per key, at creation.

Bind a key to an end user

subject on POST /v1/api-keys is the only place a binding is created. It is fixed for the life of the key, like mode and scopes. The call at the top of this page answers with:
Response (201)
subject is present on the creation response only when the key is bound. On GET /v1/api-keys it is always present and null for a tenant-wide key — which is how you audit, from your admin key, which of your outstanding keys act for whom.
string
1 to 200 characters, trimmed. Opaque to us: never parsed, never validated against anything, never sent to a merchant, and never typed into a checkout form. There is no registry of subjects and no endpoint that lists them — a subject exists because a row or a key mentions it.Prefer your own internal id over an email address or a name. It is stored with us and echoed back on reads, and an opaque id is the version of that data you would rather have in both places.
subject: "" is refused. An empty or whitespace-only string is a 400 on every body that takes the field, not a subject that quietly means nothing.subject: user.id ?? "" is the shape this protects you from. An empty string is falsy, so it used to mint a key that looked bound and acted tenant-wide, and tag rows that no bound key could ever see — an opt-in that silently no-ops is worse than no opt-in. It now fails at the call site, where you can still fix it.
400
message is Invalid profile on POST /v1/buyer-profiles and Invalid request on the other two; the issues are the same. Surrounding whitespace is stripped before the value is stored, so " user_a91f " and "user_a91f" are the same subject rather than two.

What a bound key can reach

Every lookup a bound key makes is filtered on (tenant, subject), and the endpoints that answer account-level questions refuse it outright. The four account-level 403s — the key list, revocation, usage, webhooks — are all the same judgement: those calls answer questions about the account, not about a buyer. An end user’s key enumerating its siblings, revoking the account’s own key, reading the account’s total spend, or subscribing to everyone’s checkout activity would each be a leak between your own customers.
403 — a bound key reading account usage

Authority passes down, never sideways

A bound key can mint keys, which is useful — a per-session credential for a single buyer, narrower in scope than the one that issued it. What it cannot do is widen:
  • Omitting subject inherits the binding. It does not mean tenant-wide. There is no body that mints a tenant-wide key from a bound one.
  • Naming a different subject is a 403.
403
This is the same rule that already governs scopes and mode: a key can only ever hand out authority it already holds. Without it, one POST turns a single buyer’s credential into the account’s.

Which buyer profile a checkout may use

buyerProfileId on POST /v1/checkouts is validated against the checkout’s subject, not the authenticating key’s. That distinction is the whole point: a tenant-wide admin key passes any key-level fence trivially, so a check written against the key would have let one POST create a checkout stamped subject: "user_bob" while spending Alice’s stored address — handing her address to a merchant, her order to a stranger, and writing any merchant login that run captured back onto her profile.
400 — a bound key reaching for another end user's profile
403 — a tenant-wide key mixing two end users
The first is a 400 rather than a 403 because from a bound key’s position the profile genuinely does not exist — the ownership check and the existence check are the same query, and a 403 would confirm that some other end user has a profile by that id.
An untagged profile with a tagged checkout stays legal, and so does the reverse. Only a genuine mismatch between two different end users is refused.That is deliberate: adoption is incremental. Profiles you created before you started tagging have no subject, a shared operations address is a reasonable thing to keep untagged, and refusing those combinations would mean no tenant could turn subject on without a backfill it has no endpoint for.

Tagging rows from a tenant-wide key

subject is also accepted on the create bodies, which is how rows get tagged in the first place if you would rather keep one admin key than mint one per user:
On a bound key the field is redundant but permitted: sending the key’s own subject is fine, sending any other value is a 403.
403
Tagging from an admin key gets you the audit trail — you can answer “what has this user bought” — without getting you the containment. The agent still holds a key that can read everyone. If the point is to survive a prompt injection, the agent needs a bound key.

A typo cannot produce an untagged row

All three bodies that accept subjectPOST /v1/api-keys, POST /v1/checkouts and POST /v1/buyer-profiles — reject unknown top-level fields. A misspelled subjectt is a 400:
400
This is the difference between a typo that fails loudly and a 201 carrying an untagged row that every key on the account can read. A permissive schema drops what it does not recognise, and the field most worth misspelling here is the one that decides who can see the row. Nested objects stay permissive on purpose — target, constraints and metadata are extension points, and unknown keys inside them are ignored rather than refused. The strictness is at the top level, where subject lives.

How subject comes back

Three shapes, and they differ. Worth reading once if you are writing a client: Read it with a null-and-absent-tolerant accessor rather than a presence check, and none of this matters.

Listing one end user’s runs

GET /v1/checkouts?subject=user_a91f narrows the list to one end user, so a tenant that tags from an admin key can answer “what is this person’s agent doing” without fetching every checkout:
The filter composes with status, limit and cursor, and takes the same shape as every other subject — 1 to 200 characters, or a 400. A well-formed subject that no checkout carries is an empty page, not an error. Rows carry subject when the checkout has one, so you can also read it off an unfiltered page. A bound key does not need the parameter — its page is already narrowed to one person — and asking for somebody else’s is refused rather than ignored:
403
Passing its own subject is fine, which means the same client code can run against either kind of key. There is still no way to list the subjects you have used: that list is your user table.

A profile cannot be reassigned

PATCH /v1/buyer-profiles/{id} does not accept subject, and does not ignore it either:
400
The schema is strict on purpose. A caller trying to move a stored identity from one person to another should be told it did not happen, rather than getting a 200 and a profile that is still where it was. There is no move endpoint: create a profile under the right subject and delete the wrong one.

What isolation does not cover

Honest boundaries. These are tenant-level facts, and a subject does not partition them.
The default 5 concurrent checkouts and 200 per rolling 24 hours are counted across the whole account. One end user’s five parked runs will 429 everybody else’s next create. If you are fanning a real user base through this, the concurrency cap is the number to talk to us about before the subject model is. See Quotas.
One endpoint receives the status transitions of every checkout on the account, for every end user. There is no per-subject endpoint, no per-subject delivery filter, and a bound key cannot register or read an endpoint at all (403).The payload does name the end user — subject is on it whenever the checkout has one — so routing a delivery to the right person needs neither a callback nor a map of your own. See the event.
GET /v1/usage reports completed checkouts and gross merchandise volume for the tenant. There is no per-subject breakdown, and a bound key cannot read it at all. Attribute spend yourself from the subject on each checkout — ?subject= on the checkout list is the cheapest way to gather them.
A bound key is still a ck_test_ or ck_live_ key, and mode is enforced independently — a bound test key is confined to sandbox merchants exactly like any other test key. Binding a key does not make it safe to spend, and making it live does not widen whose data it sees.
Subjects are not objects. Nothing creates, lists, renames or deletes one; a subject exists because a key or a row mentions the string. You cannot ask the API which subjects you have used, so keep that list on your side — it is your user table.
The rule is applied to authenticated requests. Our own orchestrator drives runs as the system rather than on behalf of a caller, because it has to be able to complete any checkout it is handed — so subject is a fence around your keys, not an encryption boundary. If your threat model needs the latter, talk to us rather than inferring it from this page.

Adopting it on an existing account

1

Use the id you already have

Your user table’s primary key. Anything stable and opaque, up to 200 characters.
2

Create the buyer profile under that subject

Either with a bound key (which stamps it automatically) or with your admin key and an explicit "subject". Do this before the first checkout for that user, because a profile cannot be moved afterwards.
3

Mint one bound key per end user

Keys are cheap and revocation is a timestamp, so a key per user — or per session — is a reasonable shape. Narrow the scopes at the same time: ["checkouts.write"] plus the buyerProfileId in your own config is the tightest useful pair.
4

Keep one tenant-wide key for the account

Wherever you keep your other deployment secrets. It is the only credential that can list keys, read usage, manage webhooks, and see across your users — which is the point of it being the one you do not hand to an agent.
Rows created before you adopted this cannot be stamped. Existing checkouts and buyer profiles have no subject, PATCH will not set one, and there is no backfill endpoint. A tenant-wide key still sees them all; a bound key sees only what was created for that subject after you started tagging. Plan for a cut-over date rather than a migration.

Idempotency keys are namespaced by subject

An Idempotency-Key is unique per (tenant, subject), not per tenant. Two of your end users independently choosing "order-1" — a timestamp, a retry counter, a cart id — is ordinary, and they have no way to coordinate because they cannot see each other. Each gets their own checkout; neither can replay the other’s. The window is 24 hours, and it is per key rather than per subject: after that the same string is free again for whoever sends it next.

Errors

Next

Authentication

Scopes, key mode, rotation — the other two dimensions of what a key may do.

Buyer profiles

The identity a checkout ships to, and the row subject most matters on.

Buyer sessions

Stored merchant logins. They inherit isolation through the profile they belong to.

Idempotency

Safe retries on POST /v1/checkouts, keyed per end user.