Gallery Platform API

Authentication

How API keys, scopes, and the two request principals work — and how the gallery is chosen for each request.

Every /api/v1 request runs as one of two principals against the same handlers. The database's row-level security (RLS) is the real access boundary in both cases — an application-layer bug can't cross tenants.

Principals

API key (gpk_ / opk_)

An external key acts as a gallery (or organization), not a person. The gateway verifies the key, then mints a short-lived (~60s) scoped token whose claims carry the key's gallery/org id and its granted scopes. Queries then run under claim-based RLS. This is the principal you use for integrations.

  • A gpk_ gallery key — the gallery is implicit in the key. Endpoints are under /api/v1/gallery/* (singular; there is no slug in the path).
  • An opk_ org key — the organization is implicit. Endpoints are under /api/v1/org/*.

User token (first-party)

The logged-in app forwards the signed-in user's session token; the gateway re-verifies it and runs under the user's existing membership RLS. Because a user can belong to many galleries, the gallery is named per request with a header (see below). You generally won't use this principal directly — it's how the app itself dogfoods the API.

Sending credentials

Authorization: Bearer gpk_…

The x-api-key: gpk_… header is accepted as an alternative. A missing or unrecognized credential returns 401; keys are matched by a non-reversible hash (HMAC-SHA256 with a server-side pepper), so the raw key is never stored.

PrincipalHow the gallery/org is chosen
gpk_ gallery keyImplicit in the key.
opk_ org keyImplicit in the key.
User token (gallery plane)X-Gallery-Id or X-Gallery-Slug header.
User token (org plane)X-Org-Id or X-Org-Slug header.

The target is never taken from the request body — always from the key or a header. Naming a gallery you can't see returns 404 (never 403), so the API doesn't leak the existence of other galleries' resources.

Scopes

Keys carry resource:action scopes. A write scope implies the matching read scope, so a key never needs both listed — grant artworks:write and reads are included.

Gallery plane — a resource:read / resource:write pair for each of: gallery, artworks, contacts, offers, members, invitations, sales, payments, payables, deals, locations, reports, publications, inbox. Note there is no separate invoices scope — invoices are part of the sales resource.

Org plane — the same pairs for: org, members, invitations, galleries, reports, billing.

When you create a key you pick per-resource access (None / Read / Read & write); that choice expands into the scope list at mint time. Each operation's required scope is shown on its page in the API reference and in the openapi.json security block.

A request that lacks the scope for an operation returns 403 with code forbidden. The in-code scope check is a fast 403; the database policy is the real ceiling, so a scope can only ever narrow what a principal could otherwise do — never widen it.

Key lifecycle & expiry

Keys may carry an expiry; an expired key returns 403. Rotate and revoke keys where they were created — the gallery's Developer → API keys section, or the organization's Settings page for org keys. Revocation takes effect immediately because every request re-checks the key against the store.

On this page