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 areUseLasts
Exploring on this siteYour explorer token: ready when you sign in1 hour, renewed while you use the site
Running a script, a notebook or Claude CodeA 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 credentialsEach 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:

  • iss and sub: your client id
  • aud: 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

ScopeAllows
catalog.readListed products, brands, categories, the change feed and export
catalog.read.extendedPackage details and other certifications
verification.readA TA's clients, products, packages, verifications, facilities and certificates
verification.writeCreating and updating them, and the verification actions
client.writeApplications, client details, brands and owners
prospect.read, prospect.writeOpportunities shared with a TA; claiming and updating them
certificate.issueCertificate PDFs
webhook.manageWebhook subscriptions