Authentication
API keys for your own backend, OAuth for apps acting on a merchant's behalf, and the scope algebra both share.
Version v1 · updated 2026-02-01
There are exactly two ways to authenticate. Which one is correct is decided by a single question: does the credential belong to you, or to a merchant who has to consent to what you do with it?
| API key | OAuth app | |
|---|---|---|
| Use when | You own the store | You build for other merchants |
| Credential lives | Your server, in a secret manager | Issued per merchant, refreshable |
| Consent | Implicit (you are the owner) | Explicit consent screen, revocable |
| Scopes | Chosen at key creation | Requested ∩ allowed by registration |
| Rotation | Manual, zero-downtime overlap | Refresh token rotation on every use |
API keys#
Send the key as a bearer token. Framique stores only a hash, so a lost key cannot be recovered — it is rotated. Rotation issues a second live key so you can deploy before revoking the first.
GET /api/public/v1/products?limit=25 HTTP/1.1
Host: your-store.framique.com
Authorization: Bearer fq_live_9f2c…
Accept: application/jsonOAuth 2.0 with PKCE#
Public clients (CLI tools, single-page apps) must use PKCE; there is no implicit flow and no client secret in a browser. Authorization codes are single-use and expire in 60 seconds, and a reused code revokes the whole grant family — a replayed code is treated as theft, not as a retry.
curl -s -X POST "https://api.framique.com/api/public/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "'"$CODE"'",
"client_id": "'"$CLIENT_ID"'",
"code_verifier": "'"$VERIFIER"'",
"redirect_uri": "https://app.example.com/callback"
}'Scopes#
Scopes are the same catalogue everywhere: the consent screen, the key editor and the gateway authorizer all read one table, so a scope cannot mean one thing on screen and another at the door. A `.write` scope implies `.read` of the same resource and nothing else.
| Endpoint | Scope | What it does | Samples |
|---|---|---|---|
GET/me | products.read | Identity of the calling credential | |
GET/orders | orders.read | List orders (cursor paginated) | |
GET/orders/{id} | orders.read | Fetch one order | |
POST/orders/{id}/notes | orders.write | Append an order note | |
GET/products | products.read | List products (cursor paginated) | |
GET/products/{id} | products.read | Fetch one product | |
POST/products | products.write | Create a draft product | |
GET/customers | customers.read | List customers (cursor paginated) | |
GET/exports | exports.read | List export jobs | |
POST/exports | exports.write | Start an export job | |
GET/exports/{id} | exports.read | Export job detail + signed URL | |
GET/webhooks | webhooks.read | List webhook endpoints | |
POST/webhooks | webhooks.write | Register a webhook endpoint | |
DELETE/webhooks/{id} | webhooks.write | Delete a webhook endpoint | |
GET/themes | themes.read | List installed themes | |
GET/themes/{id} | themes.read | Fetch one installed theme | |
GET/themes/{id}/assets | themes.read | List a theme's CSS, font and image assets | |
POST/themes/{id}/activate | themes.write | Activate a theme on the storefront | |
GET/marketplace/themes | themes.read | List published marketplace themes |