Authentication
One header, two kinds of key, and the rules the browser one plays by.
Base URL:
https://client.usequeek.com/v1Every request carries the store's API key in X-Client-Key, and should ask for JSON:
curl https://client.usequeek.com/v1/store/info \
-H "X-Client-Key: pk_live_your_key_here" \
-H "Accept: application/json"There is no OAuth flow and no token exchange. The key is the credential, and it belongs to one store: a key never reaches another merchant's data.
Two kinds of key
The merchant mints both in the dashboard under Settings → API keys.
pk_live_… — public
Made to be shipped in browser code, where anyone can read it. Because the key itself cannot be
a secret there, the browser's Origin is what is actually checked: a public key only works
from an origin on its own allowlist, which the merchant controls. Typically that is
http://localhost:3000 while building and the real domain in production.
A public key whose allowlist is * works from any origin and from none — which is what
makes it the practical choice for an AI or a script building a site before that site has a
domain. Ask the merchant to narrow it to the real domain at launch.
Calling an origin-restricted public key from a terminal or a test runner? Send an allowlisted origin yourself:
curl https://client.usequeek.com/v1/store/products \
-H "X-Client-Key: pk_live_your_key_here" \
-H "Origin: https://the-store.example"sk_live_… — private
For servers, SSR and build-time fetches, and native apps — anywhere the key is not handed to a
visitor. A private key needs no Origin, and it must never appear in a browser bundle, a
public repository, or a client-side environment variable.
Rule of thumb
If a visitor's device can read the string, it has to be a pk_live_ key with a real origin
allowlist.
What the browser is allowed to send
Cross-origin requests may carry X-Client-Key, X-Cart-Session, If-None-Match,
Content-Type and Accept. Anything else is not part of the CORS contract.
Rate limits
180 requests per minute, per API key and IP. A 429 carries Retry-After and the
X-RateLimit-* headers, which are on successful responses too — read
x-ratelimit-remaining rather than guessing.
Reads answer with an ETag and Cache-Control: private, no-cache. Send the value back as
If-None-Match and an unchanged resource costs you a 304 instead of a payload.
When authentication fails
The reply carries error_code — it names exactly what to fix.
error_code | Status | What it means |
|---|---|---|
client_key_required | 401 | No X-Client-Key header was sent. |
invalid_client_key | 401 | The key is unknown or was revoked. |
origin_required | 403 | A public key was used with no Origin and its allowlist is not *. |
origin_not_allowed | 403 | The Origin is not on this key's allowlist. The message lists the ones that are. |
plan_inactive | 403 | The store's plan does not include API access. |
route_not_available | 404 | Not part of this API — the endpoints are exactly those in the reference. |
An origin_* failure is the merchant's to fix, not yours: they add your origin to the key, or
set it to *. See versioning & errors for the full envelope.