Authentication
Every endpoint except GET /v1/openapi.json requires an API key. One key
carries both your identity and your permissions: what it may read is fixed when
it is issued and can never be widened by anything you put in a request.
The Authorization header
Section titled “The Authorization header”Send the key as a bearer token:
Authorization: Bearer sf_live_XXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXcurl -s https://api.scanfood.co/ext/v1/shops \ -H "Authorization: Bearer $SCANFOOD_API_KEY"const res = await fetch('https://api.scanfood.co/ext/v1/shops', { headers: { Authorization: `Bearer ${process.env.SCANFOOD_API_KEY}` },});headers = {"Authorization": f"Bearer {os.environ['SCANFOOD_API_KEY']}"}r = requests.get("https://api.scanfood.co/ext/v1/shops", headers=headers, timeout=30)The header is the only accepted channel. Keys in a query string or a request body are ignored and the call is answered as if no key had been sent — query strings end up in the access logs of every intermediary, and bodies end up in tracing systems.
A key has three parts:
| Part | Example | Notes |
|---|---|---|
| Prefix | sf_live_ |
Production keys only. |
| Key id | 12 hexadecimal characters | Identifies the key; safe to log. |
| Secret | 32 hexadecimal characters | Never log it. ScanFood keeps only a hash. |
Key scope
Section titled “Key scope”Scope is decided when the key is issued, and every request is checked against it. Nothing in a request can change it.
| Scope | Bound to | Which shops it reads | Which member brands it reads |
|---|---|---|---|
shop |
One shop | That shop only | The member brand attached to that shop |
franchise |
One franchise | Every branch of that franchise | Every member brand used by those branches |
The practical consequences:
GET /v1/shopstakes no parameters. It returns the branches the key may read, which makes it the fastest way to see what a key is allowed to do. A branch that has since been deleted drops out of this list while staying inside the key’s scope, so read the list as “what is there to read”, not as the literal definition of the scope.- Passing a
shopIdoutside the scope returns403 shop_not_in_scope, and aproviderIdoutside the scope returns403 provider_not_in_scope. The list endpoints answer with 403 rather than 404 because the id you sent is one you already knew. - Asking for a single transaction or member outside the scope returns
404 not_foundinstead — we do not confirm that an id exists in a shop you cannot read.
A second flag, set per key, decides whether member phone numbers and e-mail
addresses are returned. It is off by default; without it you still get
hasTel and hasEmail booleans, which are enough to know whether a member can
be contacted at all.
Rotating a key
Section titled “Rotating a key”Keys do not expire on their own, so rotation is something you schedule, not something the API forces on you. Because the key is only ever shown once, plan the change before you make it:
- Ask ScanFood for a second key with the same scope.
- Deploy it to your systems and confirm traffic is flowing on the new key.
- Ask for the old key to be revoked.
Store the key where your deployment already keeps secrets. Do not commit it, do not paste it into a ticket, and do not e-mail it. If it does leak, treat that as a rotation: issue a new key, deploy, revoke the old one.
Revoking a key
Section titled “Revoking a key”Revocation is permanent — a revoked key can never be re-enabled, and a new one must be issued instead. Revoking a key that is already revoked is harmless.
Requests made with a revoked key are answered 401 key_revoked. That is a
different code from invalid_key on purpose: only the holder of a correct
secret can ever see it, so it tells you “this key was retired” rather than
sending you off to hunt for a typo.
Common authentication errors
Section titled “Common authentication errors”| Status | error |
When it happens | What to do |
|---|---|---|---|
401 |
invalid_key |
No header, a malformed header, a key id that does not exist, or a wrong secret — all four give the same answer | Check that the header is exactly Authorization: Bearer sf_live_…, with no extra whitespace and no quotes |
401 |
key_revoked |
The key was retired | Move to the replacement key |
503 |
auth_unavailable |
ScanFood could not verify the key right now (Authentication service temporarily unavailable, retry later). This is our side, not yours |
Retry with backoff; do not touch your key |
401 responses carry no quota headers, because the key is checked before the
quota is. See Rate limits for what the headers mean
when they do appear, and Errors for every other code.