API conventions
Draft v0.1. This page is the vocabulary every API on this platform speaks: the shared domain types, the unit conventions, the confidence and status values, the auth header and the versioning policy. It is one URL, always current, so a link to it stays correct as the draft moves. What changed, and when, is in the changelog at the foot of the page.
Everything here is a draft contract: a shape published as a proposal. No endpoint serves it today, and nothing on this page should be read as documentation for one. Individual request and response signatures belong to each API’s own page, written in the vocabulary below rather than restating it.
Shared types
Four types carry across every API in the set. An identifier is opaque and stable: it is a key, never a name to show a user and never something to parse.
/** Opaque, stable, safe to store. Not a slug, and not parseable. */
type FoodId = string;
/** Anything the platform states about a food, with its provenance attached. */
interface Measured<T> {
value: T;
confidence: Confidence;
/** Where the number came from: a dataset id, never a free-text note. */
source: string;
}
interface Food {
id: FoodId;
/** Display name in the requested locale, defaulting to English. */
name: string;
/** ISO 8601 instant. The record changed, not the food. */
updatedAt: string;
status: Status;
}
interface NutrientProfile {
/** Per 100 g of the food as described, unless a portion is given. */
basis: 'per100g' | 'perPortion';
energyKcal: Measured<number>;
proteinG: Measured<number>;
fatG: Measured<number>;
carbohydrateG: Measured<number>;
}
Measured<T> is the reason the platform can be asked where a number came from.
A bare number on a nutrition field is a number nobody can audit, so it is not
a shape any API here returns.
Units
The unit lives in the field name, never in a sibling field and never in the
value. proteinG is grams, always, in every API. Nothing returns a string like
"12 g" for a quantity, and nothing returns a unit field beside a value
field: two fields that have to agree are two fields that eventually will not.
| Suffix | Unit | Example |
|---|---|---|
G | grams | proteinG |
Mg | milligrams | calciumMg |
Ug | micrograms | vitaminB12Ug |
Kcal | kilocalories | energyKcal |
Kj | kilojoules | energyKj |
Ml | millilitres | volumeMl |
KgCo2e | kilograms CO2 equivalent | footprintKgCo2e |
Pct | percentage, 0 to 100 | bioavailabilityPct |
Ratio | unitless, 0 to 1 | digestibilityRatio |
Numbers are JSON numbers, not strings. Rounding is the caller’s: the platform returns the precision it holds, which is frequently more than a label needs.
Data confidence
Every measured value carries one of four levels. The enum is ordered, and the order is meaningful: a consumer can refuse anything below a threshold.
type Confidence =
/** Measured in a laboratory, attributable to a named dataset. */
| 'measured'
/** Taken from a reference table for the same food, not this sample. */
| 'reference'
/** Calculated from measured components of the same food. */
| 'derived'
/** Inferred from a similar food. Directionally useful, not a claim. */
| 'estimated';
There is deliberately no unknown level. A value the platform cannot place at
one of these four is a value it does not return, because an unlabelled number
is the one a consumer trusts by accident.
Status
Status describes the record, not the request. It is how a record is retired without breaking a caller that stored its identifier.
type Status =
/** Current. The default, and the only status most records ever hold. */
| 'active'
/** Superseded but still served. `replacedBy` names the successor. */
| 'deprecated'
/** Withdrawn: served for identifier resolution, with no values attached. */
| 'retired';
A record never disappears. An identifier that resolved once resolves forever, which is what makes it safe to store one in your own database.
Authentication
One scheme, one header, every API:
Authorization: Bearer sq_live_<key>
Keys are per-environment and carry their environment in the prefix: sq_live_
and sq_test_. There is no API key in a query string, because a query string
is written to logs, referrer headers and browser history. There is no cookie
session and no OAuth flow: these are machine-to-machine APIs, and a browser
that holds one of these keys has already leaked it.
Rate limits and quotas are per key, and every response states the remaining
allowance in RateLimit headers.
Versioning
The major version is a prefix on every API path (v1). It changes only for a
break, and a break is narrowly defined: removing a field, renaming one,
changing a field’s type, or narrowing an enum. Adding a field, adding an enum
member the ordered set already accommodates, or widening an optional are all
done in place, without a version bump, so an integration that ignores unknown
fields keeps working.
A superseded major version is served for twelve months after its successor
ships, and Sunset headers state the date from the day the successor exists.
Feedback
This draft is meant to be argued with while it is still cheap to change. Push back in GitHub Discussions, where the conversation is public and the decisions that come out of it end up in the changelog below.
Changelog
v0.1, 16 September 2026. First draft: shared types, unit suffixes, the confidence and status enums, the auth header and the versioning policy.