Get startedAuthentication

Authentication

Flightdeck has two external credential types. Which one you hold determines both the header you send and what you are allowed to do. Neither credential ever belongs in browser code — both are server-side secrets.

The two credential types

Merchant credential (fdk_)

The tenant-scoped partner credential. It is an opaque bearer key prefixed fdk_, sent in the standard Authorization header:

curl https://api.ruo.pro/v1/my/store \
  -H "Authorization: Bearer fdk_your_key_here"

The tenant it acts on and the scopes it is granted are carried by the key, resolved server-side from the credential — never from a path or body parameter. This is the credential-carries-ownership model (see Tenancy).

fdk_ keys reach the self-serve /v1/my/* routes and the merchant API under /v1/merchant/*.

Storefront token

The narrow storefront BFF credential. It is an opaque secret (no fixed prefix), issued to you when your store is provisioned, sent on its own header, not as a bearer token:

curl -X POST https://api.ruo.pro/v1/tenants/nova-peptide/checkout \
  -H "x-storefront-token: your_storefront_token" \
  -H "Idempotency-Key: cart-8c1f2a:attempt-1" \
  -H "Content-Type: application/json" \
  -d '{ "customer_ref": "guest:jane@example.com", "items": [ ... ] }'

Consumers supply it as FLIGHTDECK_STOREFRONT_TOKEN. It places a checkout and reads a shopper’s own data for one deployment, and it is rejected on every operator route.

The public catalog reads (/v1/tenants/{tenant}/catalog/*) take no credential at all — they are the one deliberately public read surface.

Tenant resolution — from the credential, never the request

There is no default tenant anywhere in the system. Tenant-facing routes resolve their tenant from the credential:

  • fdk_ self routes live under /v1/my/* — the tenant is the credential’s own tenant. You never pass it.
  • Public per-tenant routes live under /v1/tenants/{tenant}/* — the tenant is the path segment, used by public catalog reads and storefront-token checkout.

A request whose tenant cannot be resolved fails closed. See Tenancy for the full fail-closed model and the one written host-resolution exception.

Scopes

Every fdk_ key is issued against a closed scope allowlist. A scope says “some tenant can do X”; the key row says which tenant. Merchant routes enforce the required scope per route. The allowlist, grouped by resource:

ResourceScopes
Ordersread:own_orders, manage:own_orders
Catalogread:own_catalog, manage:own_catalog
Storeread:own_store, write:own_store
Webhooksmanage:own_webhooks
Domainsmanage:own_domains
Credentialsmanage:own_credentials
Billingread:own_billing
Customersread:own_customers
Affiliatesread:own_affiliates, manage:own_affiliates
Mediamanage:own_media
Fraudmanage:own_fraud
Fulfillmentmanage:own_fulfillment
Inventorymanage:own_inventory
Marketingmanage:own_marketing
Returnsmanage:own_returns
Gift cardsmanage:own_giftcards
Store creditmanage:own_store_credit
Subscriptionsmanage:own_subscriptions
Analyticsmanage:own_analytics

Read and write are deliberately split where money or catalog state is at stake: a read:own_orders key can list orders but can never move money — that requires manage:own_orders.

A request whose credential lacks the route’s required scope is refused with 403 forbidden.

Credential lifecycle

Bootstrapping the first key

The first merchant key is self-serve. A store owner mints it with POST /v1/my/credentials/initial, authenticated by the owner identity session (not by an existing fdk_ key) — this route mints the pre-live scope set so a new store can start building before it goes live. From there, POST /v1/my/credentials (below) issues any further keys, subset-gated by the issuing key’s own grant. See Get API keys for the full bootstrap walkthrough.

Manage keys through the /v1/my/credentials routes. Every one of these requires the manage:own_credentials scope.

MethodPathPurpose
GET/v1/my/credentialsList the tenant’s keys — metadata only, revoked rows included. No key value or hash is ever returned.
POST/v1/my/credentialsIssue a new key. The plaintext key appears exactly once, in the 201 body.
POST/v1/my/credentials/{id}/revokeRevoke a key. Revocation is a recorded state, never a delete.

Issuance is subset-gated

A key can only mint a key whose scopes its own grant already includes. Requesting a scope the issuing key does not hold is refused with 403 scope_escalation (the message names the offending scope) — key management can never become privilege escalation.

curl -X POST https://api.ruo.pro/v1/my/credentials \
  -H "Authorization: Bearer fdk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "label": "ci-reader", "scopes": ["read:own_orders", "read:own_catalog"] }'

The 201 response carries the minted credential including its plaintext key — store it immediately; it is never retrievable again. Only the hash is kept server-side.

Revocation

curl -X POST https://api.ruo.pro/v1/my/credentials/{id}/revoke \
  -H "Authorization: Bearer fdk_your_key_here"

The response reports alreadyRevoked (the end state already held; nothing changed now) and selfRevoked (the key that made this call is now dead — its next request will 401). Self-revocation is allowed as a last action. There is no lastUsedAt — it is not tracked, so it is not reported.

Credential verification is rate-limited on the attempt — an fdk_ key is otherwise online-guessable. A spent limiter returns 429 with a retry-after header. See Pagination and rate limits.