Authentication
Every API request is authenticated with an API key sent as a Bearer token:
Authorization: Bearer dk_live_your_api_keyRequests without a valid key are rejected with 401 Unauthorized — see the
error catalog. The only unauthenticated endpoint is
GET /v1/openapi.json — the machine-readable catalog of the whole API,
readable before you have a key.
The venue's subscription must also be active: while an account is
suspended or canceled, every API request is rejected with
403 Forbidden — the same lockout that applies in the DuckHub app.
Rate limits are tied to the key's venue (not your IP) and depend on the venue's plan — see Rate limits. Traffic from several hosts using the same key shares one budget.
API keys#
- Keys have the format
dk_live_followed by 32 URL-safe base64 characters, e.g.dk_live_a1B2c3D4.... - One key per venue. Generating a new key replaces the previous one — the old key stops working immediately.
- Only the first 16 characters (the prefix) are stored in readable form and shown in the app for identification. The full key is stored as a hash and cannot be recovered after generation.
The full key is shown only once
Copy the key right after generating it and store it in a secrets manager. Never commit API keys to source control or embed them in client-side code. If a key leaks, revoke it or generate a new one — the old key is invalidated instantly.
Generating and revoking keys#
In the DuckHub app, open Integrations (available on every plan, including the free Egg plan):
- Generate API key — creates the key and shows it once.
- Regenerate — replaces the existing key (old one stops working).
- Revoke — deletes the key; all API access stops until a new key is generated.
Read-only key#
A venue can hold a second, read-only key alongside the full one —
generated from the same Integrations page. It has the same dk_live_
format, authenticates identically, but can only perform GET requests
(menu, settings, reference, orders, audit log…). Any write —
POST/PATCH/PUT/DELETE — is rejected with 403 and the code
READ_ONLY_KEY.
Use it for anything that only needs to read: a menu widget on your website, a reporting dashboard, a monitoring script. Keep the full key for the integration that actually builds the menu. The two keys are independent — regenerating or revoking one never affects the other.
About dk_test_ keys
The API accepts the dk_test_ prefix as a valid key format, but
DuckHub currently issues only dk_live_ keys — there is no separate test
mode. Use a non-production venue for testing.
Request headers#
| Header | Value | Required |
|---|---|---|
Authorization | Bearer dk_live_... | Yes |
Content-Type | application/json | For requests with a body |
User-Agent | e.g. my-pos/1.2 (+https://example.com) | Recommended — helps diagnose integration issues |
Example authenticated request#
curl -X POST https://api.duck-hub.com/v1/sync \
-H "Authorization: Bearer dk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{"categories":[{"externalId":"cat-mains","name":"Mains"}]}'const response = await fetch('https://api.duck-hub.com/v1/sync', {
method: 'POST',
headers: {
Authorization: 'Bearer dk_live_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
categories: [{ externalId: 'cat-mains', name: 'Mains' }],
}),
})
const result = await response.json()Authentication errors#
| Response | Cause |
|---|---|
401 Missing API key | No Authorization header, or it does not start with Bearer |
401 Invalid API key format | The token does not start with dk_live_ or dk_test_ |
401 Invalid API key | Unknown, revoked or regenerated key |
403 READ_ONLY_KEY | A read-only key attempted a write — use the full key |
403 This account's subscription is not active | Subscription suspended or canceled — applies to every endpoint |