v1.0

NUTBOM External API

Integrate NUTBOM recipe data, nutritional values, BOM trees, and compliance labels into your ERP, POS, or e-commerce platform.

Introduction

The NUTBOM API allows Enterprise and Pro customers to access their organisation's data programmatically via REST. It is designed for Machine-to-Machine communication — ERP sync, POS label printing, e-commerce nutrition display, and compliance auditing.

Available to Pro and Enterprise plan organisations only. Manage your credentials and scopes from the Developer Portal inside NUTBOM.
Base URLFormatAuth
https://dxespeybjglydorwxros.supabase.co/functions/v1/nutbom-apiJSONOAuth 2.0 Bearer

Authentication

The API uses OAuth 2.0 Client Credentials flow. Each request requires a short-lived Access Token (15 minutes).

Step 1 — Request an Access Token

Send your client_id and client_secret (from the NUTBOM Developer Portal) to the token endpoint:

POST /oauth/token
ParameterTypeRequiredDescription
grant_typestringrequiredMust be client_credentials
client_idstringrequiredYour Client ID from the Developer Portal
client_secretstringrequiredYour Client Secret — store securely, never in frontend code
cURL
Python
Node.js
curl -X POST https://dxespeybjglydorwxros.supabase.co/functions/v1/nutbom-api/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=nutbom_abc123&client_secret=YOUR_SECRET"
import requests r = requests.post( "https://dxespeybjglydorwxros.supabase.co/functions/v1/nutbom-api/oauth/token", data={"grant_type":"client_credentials","client_id":"nutbom_abc123","client_secret":"YOUR_SECRET"} ) token = r.json()["access_token"]
const res = await fetch("https://dxespeybjglydorwxros.supabase.co/functions/v1/nutbom-api/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "grant_type=client_credentials&client_id=nutbom_abc123&client_secret=YOUR_SECRET" }); const { access_token } = await res.json();

Token Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "recipes.read bom.read materials.read labels.read"
}

Step 2 — Use the Token

Include the token in every API request as a Authorization: Bearer header:

curl https://dxespeybjglydorwxros.supabase.co/functions/v1/nutbom-api/v1/materials \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Tokens expire after 15 minutes. Your integration must request a new token before each session or when a 401 is received.

Scopes

Scopes define what data the API key is allowed to access. They are set by your organisation admin in the Developer Portal and are embedded in the issued JWT.

ScopeDescriptionRisk
recipes.readView recipe names, versions, and statusLow
bom.readFull ingredient tree including waste and evaporationMedium
materials.readRaw materials, allergens, and nutritional valuesLow
labels.readEU-compliant label data: ingredient statement, nutrition table, red labelsLow
nutrition.readCalculated per-100g and per-serving nutritional valuesLow
materials.prices.readPrice per kg for raw materialsMedium

GET /v1/materials

GET /v1/materials Scope: materials.read
Returns all raw materials for your organisation with their nutritional values and allergen data.

Query Parameters

ParameterTypeDescription
updated_sincestring (ISO-8601)optionalReturn only materials updated after this timestamp. Useful for delta sync with ERP.

Response

{
  "count": 2,
  "data": [
    {
      "material_id": "d3f1...",
      "name": "White Wheat Flour",
      "name_he": "קמח חיטה לבן",
      "allergens": { "gluten": "Contains" },
      "price_per_kg": 2.40,
      "status": "Approved",
      "updated_at": "2026-03-15T10:22:00Z",
      "nutritional_values": {
        "energy_kcal": 364,
        "proteins_g": 10.3,
        "total_fats_g": 1.0,
        "saturated_fats_g": 0.2,
        "total_carbs_g": 76.3,
        "sugars_g": 0.5,
        "fibers_g": 2.7,
        "sodium_mg": 2,
        "moisture_percent": 14
      }
    }
  ]
}

GET /v1/recipes/{id}/bom

GET /v1/recipes/{recipe_id}/bom Scope: bom.read
Returns the full Bill of Materials (BOM) for a recipe — ingredient tree including waste factors and evaporation rates for production planning.

Path Parameters

ParameterTypeDescription
recipe_iduuidrequiredRecipe ID from NUTBOM

Response

{
  "recipe_id": "a1b2...",
  "recipe_name": "Whole Wheat Sourdough",
  "version": 3,
  "status": "Approved",
  "yield_factor": 0.95,
  "total_input_weight_g": 1650,
  "barcode": "7290001234567",
  "ingredients": [
    {
      "type": "material",
      "material_id": "d3f1...",
      "sub_recipe_id": null,
      "name": "White Wheat Flour",
      "net_weight_g": 1000,
      "waste_factor": 0.02,
      "evaporation_rate": null
    },
    {
      "type": "material",
      "material_id": "e8a9...",
      "sub_recipe_id": null,
      "name": "Water",
      "net_weight_g": 650,
      "waste_factor": null,
      "evaporation_rate": 0.15
    }
  ]
}

waste_factor — proportion lost during prep (e.g. 0.02 = 2% trim loss). evaporation_rate — proportion of weight lost via evaporation during baking (e.g. 0.15 = 15%).

GET /v1/labels/{id}

GET /v1/labels/{recipe_id} Scope: labels.read
Returns EU Regulation 1169/2011 compliant label data for a recipe: ingredient declaration, calculated nutrition per 100g, Israeli red labels, and allergen declaration.

Response

{
  "recipe_id": "a1b2...",
  "recipe_name": "Whole Wheat Sourdough",
  "barcode": "7290001234567",
  "ingredients_statement": "White Wheat Flour, Water, Salt, Sourdough Starter",
  "nutrition_per_100g": {
    "energy_kcal": 248,
    "proteins": 8.2,
    "total_fats": 0.9,
    "saturated_fats": 0.1,
    "total_carbs": 48.5,
    "sugars": 1.2,
    "fibers": 3.1,
    "sodium": 380
  },
  "red_labels": {
    "high_sodium": false,
    "high_sugar": false,
    "high_sat_fat": false
  },
  "allergens": {
    "contains": ["gluten"],
    "may_contain": []
  }
}

Red label thresholds follow Israeli Ministry of Health Phase A (solid food): sodium > 600mg, sugar > 13.5g, saturated fat > 4g per 100g.

Webhooks

Instead of polling, NUTBOM can push real-time notifications to your server when data changes.

Supported Events

EventTriggered when
recipe.updatedA recipe is saved, approved, or its ingredients are changed
material.price_changedThe price per kg of a raw material is updated
recipe.compliance_changedA recipe's red label status changes (e.g. ingredient swap changes sodium level)

Payload format

{
  "event": "recipe.updated",
  "org_id": "org-uuid",
  "recipe_id": "recipe-uuid",
  "recipe_name": "Whole Wheat Sourdough",
  "timestamp": "2026-04-12T08:30:00Z"
}

Security — HMAC Verification

Every webhook payload is signed with your Webhook Secret (found in the Developer Portal). Verify the X-Nutbom-Signature header to confirm the request came from NUTBOM:

Python
Node.js
import hmac, hashlib def verify(secret: str, payload: bytes, signature: str) -> bool: expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature)
const crypto = require("crypto"); function verify(secret, payload, signature) { const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex"); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); }

Test your endpoint

Use this cURL command to simulate a webhook delivery before going live:

curl -X POST https://your-server.com/webhook \
  -H "Content-Type: application/json" \
  -H "X-Nutbom-Signature: test_sig" \
  -d '{"event":"recipe.updated","test":true}'

Error Codes

All errors return a consistent JSON body:

{ "error": { "status": 401, "code": "UNAUTHORIZED", "message": "Invalid or expired token" } }
HTTPCodeMeaning
400INVALID_GRANT_TYPEgrant_type must be client_credentials
400MISSING_CREDENTIALSclient_id or client_secret missing
401UNAUTHORIZEDToken missing, invalid, or expired — request a new one
401INVALID_CREDENTIALSWrong client_id or client_secret
403FORBIDDENToken valid but missing required scope for this endpoint
404NOT_FOUNDRecipe/resource not found, or no route matched
422NO_INGREDIENTSRecipe has no ingredients — cannot calculate nutrition
429RATE_LIMIT_EXCEEDEDToo many requests — slow down
500DB_ERRORInternal error — contact support

Rate Limits

PlanLimitWindow
Pro100 requestsper minute per organisation
Enterprise100 requestsper minute per organisation

When the limit is exceeded the API returns HTTP 429. Implement exponential backoff — wait at least 60 seconds before retrying.

NUTBOM API v1.0 · Questions? support@nutbom.com