openapi: 3.2.0 info: title: Noms Nutrients API summary: 'A REST API for nutritional data: foods, brands, nutrients and food groups.' description: '**Noms** is a REST API for nutritional data: **foods**, **brands**, **nutrients** and **food groups**.' contact: name: Noms url: https://noms.sh/ email: support@noms.sh license: name: Proprietary — © Noms, all rights reserved version: v1 x-logo: url: /wordmark.svg backgroundColor: '#ffffff' altText: Noms href: https://noms.sh servers: - url: https://api.noms.sh description: Production tags: - name: nutrients description: The canonical nutrient catalog (codes, units, hierarchy). x-displayName: Nutrients paths: /v1/nutrients: get: tags: - nutrients summary: List nutrients description: 'Return a keyset-paginated page of the canonical nutrient catalog. Supports filtering (`id`, `name`, `unit`), sorting, sparse fieldsets (`fields[nutrients]`), and typo-tolerant search (`?q=`).' operationId: nutrientsListNutrients security: - ApiKey: [] parameters: - name: page_size in: query required: false schema: anyOf: - type: integer - type: 'null' description: Number of items to return per page (keyset pagination). Allowed range is 1–20; a value outside it is rejected with a 400. examples: - 20 title: Page Size description: Number of items to return per page (keyset pagination). Allowed range is 1–20; a value outside it is rejected with a 400. - name: cursor in: query required: false schema: anyOf: - type: string - type: 'null' description: Opaque keyset-pagination cursor. Don't build it by hand; follow `links.next` from a previous response to fetch the next page. title: Cursor description: Opaque keyset-pagination cursor. Don't build it by hand; follow `links.next` from a previous response to fetch the next page. - name: sort in: query required: false schema: anyOf: - type: string - type: 'null' description: Sort key; prefix with `-` for descending (`-name`). Comma-join keys for tie-breakers. Defaults to the resource's natural order. examples: - -name title: Sort description: Sort key; prefix with `-` for descending (`-name`). Comma-join keys for tie-breakers. Defaults to the resource's natural order. - name: q in: query required: false schema: anyOf: - type: string - type: 'null' description: Typo-tolerant text search. Filters to matching rows and, when no `sort` is given, orders them by relevance. Composes with filters and pagination. examples: - chocolat title: Q description: Typo-tolerant text search. Filters to matching rows and, when no `sort` is given, orders them by relevance. Composes with filters and pagination. - name: fields[nutrients] in: query required: false description: Sparse fieldset. A comma-separated subset of `nutrients` fields to return (e.g. `id,name`). Omit to return all fields. schema: type: string example: id,name - name: id in: query required: false description: Filter on `id`. Bare `id=value` means `eq`; the bracket form names the operator — `id[eq]`, `id[in]`. `id[in]` takes a comma-separated list. Rows with no value for the field never match. schema: type: string - name: name in: query required: false description: Filter on `name`. Bare `name=value` means `eq`; the bracket form names the operator — `name[eq]`, `name[in]`. `name[in]` takes a comma-separated list. Rows with no value for the field never match. schema: type: string - name: unit in: query required: false description: Filter on `unit`. Bare `unit=value` means `eq`; the bracket form names the operator — `unit[eq]`, `unit[in]`. `unit[in]` takes a comma-separated list. Rows with no value for the field never match. schema: type: string - name: If-None-Match in: header required: false schema: type: string description: An `ETag` from a previous response for this URL. If it still matches, the API answers `304 Not Modified` with no body; otherwise you get the full `200`. Either way the request is metered. examples: revalidate: value: '"9f2c1b7e4a0d6538bb1c0e2f7a4d9c30"' responses: '200': description: A page of nutrients. `fields[nutrients]` may trim the keys present in each `data` item. content: application/json: schema: $ref: '#/components/schemas/Page_NutrientSchema_' headers: RateLimit-Limit: schema: type: integer description: Your account's request quota for the current window. RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window (floored at 0). RateLimit-Reset: schema: type: integer description: Seconds until the current window resets. X-RateLimit-Limit: schema: type: integer description: Legacy alias for `RateLimit-Limit`. X-RateLimit-Remaining: schema: type: integer description: Legacy alias for `RateLimit-Remaining`. X-RateLimit-Reset: schema: type: integer description: Legacy alias for the reset time, as a Unix epoch timestamp (seconds). ETag: schema: type: string description: 'A strong entity-tag for this exact response. Send it back as `If-None-Match` on your next request for the same URL: if nothing has changed you get a `304 Not Modified` with no body instead of the full page. The tag covers everything the response depends on — your tier and market, `include`, `fields`, `sort`, and the page cursor — so it is only ever valid for the request that produced it.' '422': description: 'The request was well-formed but could not be processed: a parameter failed validation, or a value is not one this resource accepts.' content: application/problem+json: schema: $ref: '#/components/schemas/Problem' example: type: /problems/validation-error title: Request validation error status: 422 errors: - type: int_parsing loc: - query - page_size msg: Input should be a valid integer detail: Request validation failed. doc_url: https://api.noms.sh/docs#tag/validation-error '400': description: 'Malformed request: an invalid query parameter, cursor, filter, sort, page size, include, or sparse-fieldset selection.' content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: Invalid pagination cursor: value: type: /problems/invalid-cursor title: Invalid pagination cursor status: 400 cursor: bm90LWEtY3Vyc29y reason: invalid base64 payload detail: 'Cursor could not be decoded: invalid base64 payload. Follow links.next from a fresh first page rather than constructing a cursor by hand.' doc_url: https://api.noms.sh/docs#tag/invalid-cursor Invalid filter: value: type: /problems/invalid-filter title: Invalid filter status: 400 field: name op: contains supported_operators: - eq - in supported_fields: - id - name - barcode detail: 'Filter ''name'' is invalid: operator ''contains'' is not supported for ''name''. Supported operators: eq, in.' doc_url: https://api.noms.sh/docs#tag/invalid-filter Invalid sort: value: type: /problems/invalid-sort title: Invalid sort status: 400 column: created_at supported_columns: - id - name detail: 'Sort column ''created_at'' is not sortable on this endpoint. Sortable columns: id, name.' doc_url: https://api.noms.sh/docs#tag/invalid-sort Invalid page size: value: type: /problems/invalid-page-size title: Invalid page size status: 400 requested: 0 minimum: 1 maximum: 20 detail: page_size=0 is below the minimum of 1 doc_url: https://api.noms.sh/docs#tag/invalid-page-size Unknown query parameter: value: type: /problems/unknown-query-param title: Unknown query parameter status: 400 param: page[size] supported_params: - cursor - page_size - sort suggestion: page_size detail: '''page[size]'' is not a supported query parameter. Did you mean ''page_size''? Supported parameters: cursor, page_size, sort; plus LHS-bracket filters (e.g. name[eq]).' doc_url: https://api.noms.sh/docs#tag/unknown-query-param Invalid sparse-fieldset selection: value: type: /problems/invalid-fields title: Invalid sparse-fieldset selection status: 400 resource: foods field: colour supported_fields: - id - name - barcode detail: 'Field ''colour'' is not exposed on resource ''foods''. Exposed fields: id, name, barcode.' doc_url: https://api.noms.sh/docs#tag/invalid-fields '401': description: Authentication is missing or invalid. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' example: type: /problems/unauthorized title: Unauthorized status: 401 detail: Valid authentication is required to access this resource. doc_url: https://api.noms.sh/docs#tag/unauthorized '403': description: The request is authenticated but not permitted. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: Bulk enumeration not available on this tier: value: type: /problems/enumeration-forbidden title: Bulk enumeration not available on this tier status: 403 detail: This tier does not allow bulk listing. Add a `q` search query to this request, fetch a single item by id, or upgrade for full enumeration. doc_url: https://api.noms.sh/docs#tag/enumeration-forbidden Result depth limit exceeded: value: type: /problems/result-depth-exceeded title: Result depth limit exceeded status: 403 cap: 1000 detail: This tier returns roughly the first 1000 results of a query. Narrow your search with `q` or filters, or upgrade for unlimited pagination depth. doc_url: https://api.noms.sh/docs#tag/result-depth-exceeded '429': description: 'The key exceeded a rate limit: either its per-window request quota or its short-window burst rate; retry after the `Retry-After` interval (quota responses also carry `RateLimit` headers).' content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: Rate limit exceeded: value: type: /problems/rate-limited title: Rate limit exceeded status: 429 limit: 1 window: second retry_after: 1 detail: Burst rate limit of 1 requests per second exceeded. Slow down and retry after the Retry-After interval. doc_url: https://api.noms.sh/docs#tag/rate-limited Quota exceeded: value: type: /problems/quota-exceeded title: Quota exceeded status: 429 limit: 250 window: daily retry_after: 3600 detail: Request quota of 250 per day exceeded. Retry once the current window resets. doc_url: https://api.noms.sh/docs#tag/quota-exceeded headers: RateLimit-Limit: schema: type: integer description: Your account's request quota for the current window. RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window (floored at 0). RateLimit-Reset: schema: type: integer description: Seconds until the current window resets. X-RateLimit-Limit: schema: type: integer description: Legacy alias for `RateLimit-Limit`. X-RateLimit-Remaining: schema: type: integer description: Legacy alias for `RateLimit-Remaining`. X-RateLimit-Reset: schema: type: integer description: Legacy alias for the reset time, as a Unix epoch timestamp (seconds). Retry-After: schema: type: integer description: Seconds until the quota window resets. '503': description: The service is temporarily unable to handle the request; retry shortly. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: Service unavailable: value: type: /problems/service-unavailable title: Service unavailable status: 503 detail: The service is briefly unavailable; retry shortly. doc_url: https://api.noms.sh/docs#tag/service-unavailable Search unavailable: value: type: /problems/search-unavailable title: Search unavailable status: 503 detail: The search service is temporarily unavailable, so this request could not be answered. Retry shortly. Requests that do not use `q` or a nutrient sort are unaffected. doc_url: https://api.noms.sh/docs#tag/search-unavailable '504': description: The `?q=` search outran its time budget and was cancelled; narrow the query and retry. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' example: type: /problems/search-timeout title: Search timed out status: 504 timeout_ms: 800 detail: The search exceeded its 800ms time limit and was abandoned. Narrow the query — a more specific `q`, or additional filters — and retry. doc_url: https://api.noms.sh/docs#tag/search-timeout '500': description: An unexpected server error occurred. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' example: type: /problems/internal-server-error title: Internal server error status: 500 detail: An unexpected error occurred while processing the request. doc_url: https://api.noms.sh/docs#tag/internal-server-error '304': description: 'Your copy is still current — the `If-None-Match` you sent matches the current entity-tag, so no body is returned. **This still counts against your quota**: the request reached the API, so it is metered and rate-limited exactly like a `200`.' headers: RateLimit-Limit: schema: type: integer description: Your account's request quota for the current window. RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window (floored at 0). RateLimit-Reset: schema: type: integer description: Seconds until the current window resets. X-RateLimit-Limit: schema: type: integer description: Legacy alias for `RateLimit-Limit`. X-RateLimit-Remaining: schema: type: integer description: Legacy alias for `RateLimit-Remaining`. X-RateLimit-Reset: schema: type: integer description: Legacy alias for the reset time, as a Unix epoch timestamp (seconds). ETag: schema: type: string description: 'A strong entity-tag for this exact response. Send it back as `If-None-Match` on your next request for the same URL: if nothing has changed you get a `304 Not Modified` with no body instead of the full page. The tag covers everything the response depends on — your tier and market, `include`, `fields`, `sort`, and the page cursor — so it is only ever valid for the request that produced it.' /v1/nutrients/{id}: get: tags: - nutrients summary: Get a nutrient description: 'Return a single nutrient by its canonical code, or a 404 if unknown. Supports sparse fieldsets via `fields[nutrients]`. A non-code id (lowercase slug, legacy integer) resolves to a clean 404, not a validation error.' operationId: nutrientsGetNutrient security: - ApiKey: [] parameters: - name: id in: path required: true schema: type: string description: Canonical nutrient code (UPPER_SNAKE, e.g. `TOTAL_FAT`). examples: - TOTAL_FAT title: Id description: Canonical nutrient code (UPPER_SNAKE, e.g. `TOTAL_FAT`). - name: fields[nutrients] in: query required: false description: Sparse fieldset. A comma-separated subset of `nutrients` fields to return (e.g. `id,name`). Omit to return all fields. schema: type: string example: id,name - name: If-None-Match in: header required: false schema: type: string description: An `ETag` from a previous response for this URL. If it still matches, the API answers `304 Not Modified` with no body; otherwise you get the full `200`. Either way the request is metered. examples: revalidate: value: '"9f2c1b7e4a0d6538bb1c0e2f7a4d9c30"' responses: '200': description: A single nutrient. `fields[nutrients]` may trim the keys present in `data`. content: application/json: schema: $ref: '#/components/schemas/Item_NutrientSchema_' headers: RateLimit-Limit: schema: type: integer description: Your account's request quota for the current window. RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window (floored at 0). RateLimit-Reset: schema: type: integer description: Seconds until the current window resets. X-RateLimit-Limit: schema: type: integer description: Legacy alias for `RateLimit-Limit`. X-RateLimit-Remaining: schema: type: integer description: Legacy alias for `RateLimit-Remaining`. X-RateLimit-Reset: schema: type: integer description: Legacy alias for the reset time, as a Unix epoch timestamp (seconds). ETag: schema: type: string description: 'A strong entity-tag for this exact response. Send it back as `If-None-Match` on your next request for the same URL: if nothing has changed you get a `304 Not Modified` with no body instead of the full page. The tag covers everything the response depends on — your tier and market, `include`, `fields`, `sort`, and the page cursor — so it is only ever valid for the request that produced it.' '422': description: 'The request was well-formed but could not be processed: a parameter failed validation, or a value is not one this resource accepts.' content: application/problem+json: schema: $ref: '#/components/schemas/Problem' example: type: /problems/validation-error title: Request validation error status: 422 errors: - type: int_parsing loc: - query - page_size msg: Input should be a valid integer detail: Request validation failed. doc_url: https://api.noms.sh/docs#tag/validation-error '404': description: No resource matches the requested id. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' example: type: /problems/not-found title: Not found status: 404 resource: foods id: abc123def456 detail: Food with id 'abc123def456' was not found. doc_url: https://api.noms.sh/docs#tag/not-found '400': description: 'Malformed request: an invalid query parameter, cursor, filter, sort, page size, include, or sparse-fieldset selection.' content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: Unknown query parameter: value: type: /problems/unknown-query-param title: Unknown query parameter status: 400 param: page[size] supported_params: - cursor - page_size - sort suggestion: page_size detail: '''page[size]'' is not a supported query parameter. Did you mean ''page_size''? Supported parameters: cursor, page_size, sort; plus LHS-bracket filters (e.g. name[eq]).' doc_url: https://api.noms.sh/docs#tag/unknown-query-param Invalid sparse-fieldset selection: value: type: /problems/invalid-fields title: Invalid sparse-fieldset selection status: 400 resource: foods field: colour supported_fields: - id - name - barcode detail: 'Field ''colour'' is not exposed on resource ''foods''. Exposed fields: id, name, barcode.' doc_url: https://api.noms.sh/docs#tag/invalid-fields '401': description: Authentication is missing or invalid. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' example: type: /problems/unauthorized title: Unauthorized status: 401 detail: Valid authentication is required to access this resource. doc_url: https://api.noms.sh/docs#tag/unauthorized '429': description: 'The key exceeded a rate limit: either its per-window request quota or its short-window burst rate; retry after the `Retry-After` interval (quota responses also carry `RateLimit` headers).' content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: Rate limit exceeded: value: type: /problems/rate-limited title: Rate limit exceeded status: 429 limit: 1 window: second retry_after: 1 detail: Burst rate limit of 1 requests per second exceeded. Slow down and retry after the Retry-After interval. doc_url: https://api.noms.sh/docs#tag/rate-limited Quota exceeded: value: type: /problems/quota-exceeded title: Quota exceeded status: 429 limit: 250 window: daily retry_after: 3600 detail: Request quota of 250 per day exceeded. Retry once the current window resets. doc_url: https://api.noms.sh/docs#tag/quota-exceeded headers: RateLimit-Limit: schema: type: integer description: Your account's request quota for the current window. RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window (floored at 0). RateLimit-Reset: schema: type: integer description: Seconds until the current window resets. X-RateLimit-Limit: schema: type: integer description: Legacy alias for `RateLimit-Limit`. X-RateLimit-Remaining: schema: type: integer description: Legacy alias for `RateLimit-Remaining`. X-RateLimit-Reset: schema: type: integer description: Legacy alias for the reset time, as a Unix epoch timestamp (seconds). Retry-After: schema: type: integer description: Seconds until the quota window resets. '503': description: The service is temporarily unable to handle the request; retry shortly. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' example: type: /problems/service-unavailable title: Service unavailable status: 503 detail: The service is briefly unavailable; retry shortly. doc_url: https://api.noms.sh/docs#tag/service-unavailable '500': description: An unexpected server error occurred. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' example: type: /problems/internal-server-error title: Internal server error status: 500 detail: An unexpected error occurred while processing the request. doc_url: https://api.noms.sh/docs#tag/internal-server-error '304': description: 'Your copy is still current — the `If-None-Match` you sent matches the current entity-tag, so no body is returned. **This still counts against your quota**: the request reached the API, so it is metered and rate-limited exactly like a `200`.' headers: RateLimit-Limit: schema: type: integer description: Your account's request quota for the current window. RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window (floored at 0). RateLimit-Reset: schema: type: integer description: Seconds until the current window resets. X-RateLimit-Limit: schema: type: integer description: Legacy alias for `RateLimit-Limit`. X-RateLimit-Remaining: schema: type: integer description: Legacy alias for `RateLimit-Remaining`. X-RateLimit-Reset: schema: type: integer description: Legacy alias for the reset time, as a Unix epoch timestamp (seconds). ETag: schema: type: string description: 'A strong entity-tag for this exact response. Send it back as `If-None-Match` on your next request for the same URL: if nothing has changed you get a `304 Not Modified` with no body instead of the full page. The tag covers everything the response depends on — your tier and market, `include`, `fields`, `sort`, and the page cursor — so it is only ever valid for the request that produced it.' components: schemas: NutrientSchema: properties: id: type: string title: Id description: Stable UPPER_SNAKE nutrient code (e.g. `ENERGY`, `PROTEIN`); the canonical identity and the `/v1/nutrients/{id}` handle. name: type: string title: Name description: Human-readable nutrient name (e.g. `Protein`). unit: anyOf: - type: string enum: - g - mg - mcg - kcal - type: 'null' title: Unit description: Measurement unit — one of the closed canonical set `{g, mg, mcg, kcal}`, or `null` for a declared-dimensionless nutrient (e.g. Glycemic Load, Specific Gravity). parent_id: anyOf: - type: string - type: 'null' title: Parent Id description: Code of this nutrient's parent in the hierarchy (e.g. Saturated fat → `TOTAL_FAT`), or `null` for a top-level node. Exposed for hierarchy walking; not filterable or sortable. type: object required: - id - name - unit - parent_id title: NutrientSchema description: 'A canonical nutrient in the catalog, with its measurement unit and optional position in the nutrient hierarchy.' examples: - id: SATURATED_FAT name: Saturated fat parent_id: TOTAL_FAT unit: g - id: GLYCEMIC_LOAD name: Glycemic load PageMeta: properties: has_more: type: boolean title: Has More description: Whether more pages follow this one (equivalently, whether ``links.next`` is non-null). page_size: type: integer minimum: 1.0 title: Page Size description: 'Number of items requested per page: the effective ``page_size`` (the requested value, or the default when ``page_size`` is omitted).' type: object required: - has_more - page_size title: PageMeta examples: - has_more: true page_size: 10 Page_NutrientSchema_: properties: data: items: $ref: '#/components/schemas/NutrientSchema' type: array title: Data links: $ref: '#/components/schemas/PageLinks' meta: $ref: '#/components/schemas/PageMeta' type: object required: - data - links - meta title: Page[NutrientSchema] PageLinks: properties: self: type: string maxLength: 2083 minLength: 1 format: uri title: Self description: Canonical URL of the current page, echoing the request's query parameters. next: anyOf: - type: string maxLength: 2083 minLength: 1 format: uri - type: 'null' title: Next description: URL of the next page, or ``null`` when this is the last page. The ``cursor`` query parameter is advanced to the next keyset position; all other query parameters are preserved verbatim. type: object required: - self title: PageLinks examples: - next: https://api.noms.sh/v1/foods?page_size=2&cursor=eyJ2IjpbeyJ0IjoicmF3IiwidiI6Ik1pbGsifV0sInMiOiIrbmFtZSJ9 self: https://api.noms.sh/v1/foods?page_size=2 Item_NutrientSchema_: properties: data: $ref: '#/components/schemas/NutrientSchema' type: object required: - data title: Item[NutrientSchema] Problem: properties: title: type: string title: Problem title description: Short, human-readable summary of the problem type. type: type: string title: Problem type description: Stable problem-type URI under `/problems/`. Relative, so it resolves against the host that served the response. The slug is never renamed — branch on this, not on `title` or `detail`. status: type: integer title: Status code description: The HTTP status code, repeated in the body. detail: anyOf: - type: string - type: 'null' title: Problem detail description: 'Human-readable explanation of *this* occurrence. Free text: it may name concrete values and may change without notice.' doc_url: type: string title: Documentation URL description: Deep link to this problem's entry in these docs, stamped per request against the host that served it. Present on every problem body. type: object required: - type - title - status title: Problem description: 'An **RFC 9457** problem document, served as `application/problem+json`. `type` is a stable, never-renamed URI you can branch on; the other core members are for humans. Individual problems add **extension members** on top of these — `retry_after` on a `429`, `supported_columns` on an invalid sort, `errors` on a validation failure. They are documented on the problem itself, under Problems, and a client should ignore any member it does not recognise rather than reject the body.' securitySchemes: ApiKey: type: apiKey description: Programmatic access key for the `/v1` resource endpoints. Send it as the `X-API-Key` header (`noms_sk__...`). Create one in your dashboard under **API Keys → Create key**; the raw key is shown once and never stored. Your account's tier governs which data you can read, and every key you own draws on the same request allowance. in: header name: X-API-Key x-tagGroups: - name: Resources tags: - foods - brands - nutrients - foodGroups - name: Usage tags: - usage - market - name: Problems tags: - invalid-cursor - invalid-filter - invalid-sort - invalid-include - invalid-fields - invalid-page-size - unknown-query-param - not-found - unauthorized - maintenance - quota-exceeded - rate-limited - enumeration-forbidden - result-depth-exceeded - market-not-selected - unknown-market - market-not-applicable - market-account-required - key-revoked - key-limit-reached - forbidden - service-unavailable - search-timeout - search-unavailable - api-version-removed - validation-error - internal-server-error x-provenance: method: harvested publisher: Noms source: https://api.noms.sh/openapi.json note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json); stamped by stamp-openapi-provenance.py (roadmap#793)