Signing in
<!-- order: 1 | Tokens for you, for scripts and for integrations: which to use, and how to get each. -->Every call sends a token in the Authorization header:
Authorization: Bearer <token>
Never put a token in a URL. Tokens are only ever stored as hashes, so if you lose one, make a new one.
Which token
| You are | Use | Lasts |
|---|---|---|
| Exploring on this site | Your explorer token: ready when you sign in | 1 hour, renewed while you use the site |
| Running a script, a notebook or Claude Code | A personal token (Keys and tokens) | Up to 90 days |
| An integration (a TA's system, a retailer's feed, an app) | An organization's key: client credentials | Each token 1 hour; the key until revoked |
Explorer and personal tokens act as you, with your access. A key acts as its organization: a technical administrator's key sees only that TA's records.
An organization's key: client credentials
A key has a client id and either a secret or a public key. Ask for a token with it:
curl https://api.foodintegrityproject.org/v2/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=client_credentials
{ "access_token": "fipos_at_…", "token_type": "Bearer", "expires_in": 3600, "scope": "catalog.read verification.read" }
Keep the token for its hour and ask for a new one when it runs out (or when you get 401). Pass scope to ask for fewer scopes than the key has.
With a private key (private_key_jwt)
Technical administrators can sign in the way they sign in to Salesforce today: with their own key pair. Upload the public key to the key (Keys and tokens, "Add a public key"), then sign a short JWT with the private key for each token request:
issandsub: your client idaud:https://api.foodintegrityproject.org/v2/oauth/token(or…/sandbox/v2/oauth/token)exp: up to 10 minutes ahead;iat: now- algorithm RS256 (or ES256 for an EC key)
curl https://api.foodintegrityproject.org/v2/oauth/token \
-d grant_type=client_credentials \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
-d client_assertion="$SIGNED_JWT"
In TypeScript, with jose:
import { SignJWT, importPKCS8 } from "jose"
const key = await importPKCS8(process.env.PRIVATE_KEY_PEM!, "RS256")
const assertion = await new SignJWT({})
.setProtectedHeader({ alg: "RS256" })
.setIssuer(clientId).setSubject(clientId)
.setAudience("https://api.foodintegrityproject.org/v2/oauth/token")
.setIssuedAt().setExpirationTime("5m")
.sign(key)
Rotating and revoking
- Rotate secret (Keys and tokens) gives a new secret; the old one keeps working for 24 hours, so you can deploy the new one without breaking anything.
- Revoke stops a secret, a public key or the whole key at once, with every token it gave out.
Scopes
| Scope | Allows |
|---|---|
catalog.read | Listed products, brands, categories, the change feed and export |
catalog.read.extended | Package details and other certifications |
verification.read | A TA's clients, products, packages, verifications, facilities and certificates |
verification.write | Creating and updating them, and the verification actions |
client.write | Applications, client details, brands and owners |
prospect.read, prospect.write | Opportunities shared with a TA; claiming and updating them |
certificate.issue | Certificate PDFs |
webhook.manage | Webhook subscriptions |