Skip to content

Security best practices

An API key reads a shop’s sales history and its member list. It cannot change anything — every endpoint is read-only — so the risk it carries is disclosure, not damage. That still means the key deserves the same handling as a database password.

Two facts shape everything on this page:

  • The key is shown once. We store only a one-way hash of it, so no one at ScanFood can read it back to you. Lost means replaced, not recovered.
  • The key travels in one place only. We read it from Authorization: Bearer <key> and nowhere else — not from a query string, not from a body.

Keep the key in a secret manager, or in an environment variable injected at deploy time. Never in the repository, never in a file you commit, never pasted into a ticket or a chat thread.

Terminal window
# Read it into the shell without leaving it in your history…
read -rs SCANFOOD_API_KEY && export SCANFOOD_API_KEY
curl -s -H "Authorization: Bearer $SCANFOOD_API_KEY" \
"https://api.scanfood.co/ext/v1/shops"
# …and never like this: the key lands in shell history, in access logs,
# and in every proxy between you and us.
# curl "https://api.scanfood.co/ext/v1/shops?key=sf_live_…" ← the API ignores it anyway

Also worth knowing:

  • Traffic is HTTPS only. Do not disable certificate verification “just for testing” — that is exactly the request that ends up running in production.
  • We never log the key. Neither the full key nor its hash appears in our usage records. What we do keep is the keyId — the middle segment, safe to quote in a support conversation.

Ask for a separate key for every system that calls us: the nightly accounting sync, the dashboard, the analyst’s notebook, the vendor doing a proof of concept.

Why What it buys you
Revocation is surgical Turning off the vendor’s access does not stop your accounting sync at 2 a.m.
Usage is attributable “Who sent 40,000 requests yesterday?” has one answer instead of a shrug.
Limits are independent An experiment cannot spend the production key’s daily quota.
Blast radius is bounded One leaked key exposes one integration’s scope, not everything you have.

Every key carries a required label. Use it: accounting-nightly-sync, bi-dashboard, vendor-poc-2026-09 — a name a colleague can act on a year from now without asking anyone.

Two dials decide how much a key can see. Ask for the smallest setting that does the job; you can always be issued a wider key later.

Scope — which shops. A key is issued either for one shop or for a whole franchise. A franchise key sees every branch under it, now and in the future, including branches that open after the key was issued. If the integration only touches two branches, a franchise key is not the right tool.

Personal data — phone numbers and e-mail. Member phone numbers and e-mail addresses are not returned by default. An ordinary key sees hasTel and hasEmail — true or false — which is enough to answer “is this member reachable?” without moving personal data out of ScanFood. Keys that return the real values exist, but they are issued deliberately, for a stated purpose, with the data-protection obligations that come with it.

Also outside the API’s reach, by design: costs, recipes and ingredient data never leave through it, and no endpoint can write anything back into ScanFood.

  • Redact before you log. Log the path, the status, the row count and the timing — never the Authorization header. A helper like the one above, applied once at the boundary, is worth more than a rule people have to remember.
  • Log the keyId, not the key. In sf_live_<keyId>_<secret>, the keyId is a public handle. Recording it with each run means a support question can be answered in minutes.
  • Watch for the shape of abuse. Requests you did not schedule, at hours you do not run, are the signal that a key has escaped. Your own logs will see this before anyone else does.
  • Alert on 401. A working integration does not produce authentication errors. A burst of them means the key was revoked, rotated behind your back, or is being tried by someone who does not have the whole of it.
  • Watch the daily quota. An unexplained jump in usage is the cheapest leak detector you own — X-RateLimit-Remaining on every response is the number to track.

Keys do not expire on their own, so rotation is something you schedule rather than something you are forced into. Because a key is only shown once, rotation is “issue new, then retire old” — there is no way to re-read the current one.

The order matters, and done in this order there is no downtime:

  1. Ask for a new key for the same integration and scope.
  2. Deploy it to your secret store and restart or reload the caller.
  3. Confirm the new key is the one working — its usage appears, the old one goes quiet.
  4. Ask us to revoke the old key. Both keys are valid until you do; there is no window where neither works.

After revocation the key answers 401 with "error": "key_revoked", and it stays that way permanently. Revoking a key twice is harmless.

A key in a public repository, a screenshot, a pasted log or a former colleague’s laptop is a leaked key. Treat it as leaked even if you are not sure.

  1. Tell us immediately and ask for that key to be revoked. Quote the keyId — the middle segment of the key — not the whole key.
  2. Issue a replacement and deploy it. If the integration must not stop, do the replacement first (see the rotation order above), then the revocation.
  3. Assume the window was used. Within the five-minute propagation, the key still worked. Ask us what that key read and when; our usage records are kept per key, with path, status and timestamp.
  4. Close the hole that let it out. A leaked key that goes back into the same commit history, the same chat channel or the same shared drive will leak again.
  5. Check what the scope exposed. A single-shop, no-personal-data key that leaked for five minutes is a very different conversation from a franchise key that returns phone numbers.

Because the API is read-only, a leaked key cannot void a bill, change a price or delete a member. What it can do is read — which is exactly why the scope you asked for at the beginning is the thing that limits the damage at the end.