Ice Cream Calc

REST API Reference

Complete technical reference for all Ice Cream Calculator REST API endpoints: authentication, recipes (list, get, create, update, delete, calculate, balance), ingredients, request/response formats, error handling, and rate limits.

Tools
Updated July 22, 2026

Ice Cream Calculator REST API Reference

Introduction

This document provides complete technical reference for all Ice Cream Calculator REST API endpoints. Each endpoint is documented with request format, response structure, and example code.

📘 New to the API? Start with the Getting Started Guide to learn how to generate your API key and make your first request.


Base URL

https://icecreamcalc.app/api

All endpoints are relative to this base URL.


Authentication

All endpoints (except /recipes/test) require authentication using an API key.

Include API Key in Header

X-API-Key: icc_your_api_key_here

Authentication Errors

Error HTTP Code Response
Missing API Key 401 {"error": "API key is required", "details": "Include X-API-Key header with your API key"}
Invalid API Key 401 {"error": "Invalid API key", "details": "The provided API key is not valid or the user account is inactive"}

Endpoints Overview

Endpoint Method Description
/recipes GET List all your recipes (compact summaries)
/recipes/{id} GET Get a single recipe with ingredients
/recipes/{id}/calculated GET Get calculated data for a recipe
/recipes POST Create a new recipe
/recipes/{id} PUT Update a recipe (full replacement)
/recipes/{id} DELETE Delete a recipe permanently
/recipes/{id}/balance POST Run Smart Balance and get a proposal (nothing is saved)
/ingredients GET List all accessible ingredients
/ingredients/{id} GET Get a single ingredient with full details

Recipe Endpoints

GET /api/recipes

Retrieve a list of all your recipes with basic information. This endpoint returns compact summaries – use GET /api/recipes/{id} for the full recipe.

Request

GET /api/recipes
Headers:
  X-API-Key: icc_your_api_key_here

Response (200 OK)

{
  "success": true,
  "count": 2,
  "recipes": [
    {
      "id": 1,
      "name": "Classic Vanilla Ice Cream",
      "description": "Traditional vanilla base",
      "info": "Traditional vanilla base",
      "externalId": "ERP-REC-0042",
      "totalWeight": 1000.0,
      "createdDate": "2025-01-15T10:30:00Z",
      "ingredientCount": 6
    }
  ]
}

Recipe Summary Fields

Field Type Description
id integer Unique recipe identifier
name string Recipe name
description string Recipe description – legacy alias of info, kept for compatibility
info string Recipe description (same value as description; matches the field name on the single-recipe endpoint)
externalId string or null Your external-system ID for this recipe – lets you match recipes from one list call
totalWeight number Total mix weight in grams
createdDate string (ISO 8601) When the recipe was created
ingredientCount integer Number of ingredients in the recipe

Note: The list endpoint does not include ingredients, notes, category, tags, or metadata – those are on GET /api/recipes/{id}.

Example (curl)

curl -H "X-API-Key: icc_your_key" \
     https://icecreamcalc.app/api/recipes

Example (Python)

import requests

response = requests.get(
    "https://icecreamcalc.app/api/recipes",
    headers={"X-API-Key": "icc_your_key"}
)

recipes = response.json()
for recipe in recipes['recipes']:
    print(f"{recipe['name']} - {recipe['totalWeight']}g")

GET /api/recipes/{id}

Retrieve detailed information for a single recipe, including all ingredients.

Request

GET /api/recipes/1
Headers:
  X-API-Key: icc_your_api_key_here

Response (200 OK)

{
  "success": true,
  "recipe": {
    "id": 1,
    "name": "Classic Vanilla Ice Cream",
    "info": "Traditional vanilla base",
    "notes": "Heat base to 85C, cool overnight before churning.",
    "externalId": "ERP-REC-0042",
    "metadata": { "externalId": "repo-123", "version": 3 },
    "category": "IceCream",
    "tags": ["Home", "LowSugar"],
    "bestBeforeDays": 180,
    "overrun": -1,
    "evaporation": 0.05,
    "createdDate": "2025-01-15T10:30:00Z",
    "changedDate": "2025-11-08T14:20:00Z",
    "hasChart": true,
    "chartName": "Ice Cream Base",
    "mixWeight": 1000.0,
    "finalWeight": 950.0,
    "ingredients": [
      {
        "id": 1,
        "ingredientId": 42,
        "ingredientName": "Heavy Cream",
        "ingredientSource": "Default",
        "isOverride": false,
        "weight": 500.0,
        "inclusion": false,
        "infusion": false,
        "excludeFromEvaporation": false,
        "position": 0,
        "amountMin": 0.0,
        "amountMax": 1.0
      }
    ]
  }
}

Recipe Object Fields

Field Type Description
id integer Unique recipe identifier
name string Recipe name
info string Recipe description
notes string Free-text notes (up to 2000 characters)
externalId string or null Your external-system ID (up to 50 characters); null when unset
metadata object or null Your own JSON object, stored and returned verbatim (see “The metadata field” below); null when unset
category string or null Recipe category (see accepted values below); null when uncategorized
tags array of strings Recipe tags (see accepted values below); empty when none
bestBeforeDays integer or null Shelf life in days; null when unspecified
overrun number Per-recipe overrun percentage. -1 means “inherit the account’s global overrun default”
evaporation number Evaporation percentage (0.05 = 5%)
hasChart / chartName boolean / string Whether a chart is linked, and its name
mixWeight number Total mix weight in grams (before freezing)
finalWeight number Final weight in grams (after evaporation)
ingredients array List of ingredients with weights, positions, and amount bounds

Error Responses

HTTP Code Response Meaning
404 {"error": "Recipe not found"} Recipe with this ID doesn’t exist (temporary recipes also return 404)
403 {"error": "Access denied", "details": "You don't have permission to access this recipe"} Recipe belongs to another user

Example (curl)

curl -H "X-API-Key: icc_your_key" \
     https://icecreamcalc.app/api/recipes/1

GET /api/recipes/{id}/calculated

Retrieve calculated data for a recipe including PAC, POD, serving temperatures, and all other ice cream science properties.

Note: This endpoint performs full recipe calculations, so it may take slightly longer than other endpoints.

Request

GET /api/recipes/1/calculated
Headers:
  X-API-Key: icc_your_api_key_here

Response (200 OK)

{
  "success": true,
  "data": {
    "recipeId": 1,
    "recipeName": "Classic Vanilla Ice Cream",
    "values": {
      "PAC": {
        "name": "PAC",
        "displayValue": "285.43",
        "numericValue": 285.43,
        "info": "Freezing point depression",
        "isTemperature": false
      },
      "POD": {
        "name": "POD",
        "displayValue": "32.15",
        "numericValue": 32.15,
        "info": "Relative sweetness",
        "isTemperature": false
      },
      "Serving temp ice cream": {
        "name": "Serving temp ice cream",
        "displayValue": "-14.2°C",
        "numericValue": -14.2,
        "info": "Recommended serving temperature for ice cream",
        "isTemperature": true
      }
      // ... many more calculated values
    }
  }
}

Calculated Values Included

  • PAC – Freezing point depression
  • POD – Relative sweetness
  • Serving temperatures – For ice cream and gelato
  • Nutritional data – Fat, protein, sugars, etc.
  • Milk solids – MSNF, milk fat, milk protein
  • Percentages – Water, solids, overrun, etc.
  • Weights – Mix weight, final weight, additions
  • And more – All calculated recipe properties

Example (Python)

import requests

response = requests.get(
    "https://icecreamcalc.app/api/recipes/1/calculated",
    headers={"X-API-Key": "icc_your_key"}
)

data = response.json()['data']
pac = data['values']['PAC']['numericValue']
pod = data['values']['POD']['numericValue']

print(f"PAC: {pac}, POD: {pod}")

POST /api/recipes

Create a new recipe with ingredients.

Request

POST /api/recipes
Headers:
  X-API-Key: icc_your_api_key_here
  Content-Type: application/json

{
  "name": "Mango Sorbet",
  "info": "Fruity sorbet base",
  "notes": "Blend fruit before adding syrup.",
  "externalId": "ERP-REC-0043",
  "category": "Sorbet",
  "tags": ["Vegan", "DairyFree"],
  "bestBeforeDays": 180,
  "overrun": -1,
  "metadata": { "externalId": "repo-123", "version": 1 },
  "evaporation": 0,
  "chartName": "Sorbet Base",
  "ingredients": [
    {
      "ingredientId": 42,
      "weight": 500,
      "inclusion": false,
      "infusion": false,
      "amountMin": 0,
      "amountMax": 0
    }
  ]
}

Request Fields

Field Type Required Constraints and omitted behaviour
name string Yes Must be non-empty; max 200 characters (longer → 400)
info string No Description; empty when omitted
notes string No Max 2000 characters (longer → 400); empty when omitted
externalId string No Max 50 characters (longer → 400); empty when omitted
category string No Must match an accepted category value, case-insensitively (unknown → 400); uncategorized when omitted
tags array of strings No Each tag must match an accepted tag value, case-insensitively (unknown → 400); duplicates are collapsed; no tags when omitted
bestBeforeDays integer No Must not be negative (→ 400); unspecified when omitted
overrun number No -1 (inherit the global default) or any value ≥ 0; other negatives → 400. Omitted = -1
metadata object No Top level must be a JSON object; compact serialization max 4096 characters (violations → 400); unset when omitted. See “The metadata field” below
evaporation number No Default 0
chartName string No Looked up by name, case-insensitively. If not found, the recipe is still created without a chart
ingredients array Yes At least one. Each: ingredientId (required – must exist and be accessible to you), weight (grams), inclusion, infusion, excludeFromEvaporation, amountMin, amountMax (all booleans/numbers optional, defaulting to false/0)

Accepted category values

IceCream, Gelato, Sorbet, Sherbet, FrozenYogurt, Granita, SoftServe, Special, Confectionery, Other, Ingredient, Topping

Accepted tags values

Vegan, EggFree, DairyFree, LowSugar, Home, BatchFreezer, NinjaCreami, PacoJet, Alcohol, Ganache, Commercial

💡 Tip: Matching is case-insensitive on write ("vegan" is accepted), but responses always return the canonical names listed above – so what you read back can be sent again unchanged.

Response (201 Created)

{
  "success": true,
  "recipeId": 15,
  "message": "Recipe created successfully",
  "recipe": { /* the full recipe object, as returned by GET /api/recipes/{id} */ }
}

Error Responses

HTTP Code Example Meaning
400 {"error": "Validation failed", "details": "Recipe name is required"} Missing name, no ingredients, bad field value (unknown category/tag, negative bestBeforeDays, invalid overrun, metadata not an object or over the cap)
400 {"error": "Invalid ingredient", "details": "Ingredient with ID 999 does not exist or you don't have access to it"} An ingredient ID doesn’t exist or isn’t accessible to your account
403 {"error": "Recipe limit reached", "details": "..."} Your plan’s recipe limit is reached

The metadata field

metadata is an opaque JSON key-value store for your own use – for example syncing your system’s IDs, versions, or timestamps. Ice Cream Calculator stores and returns it but never reads, interprets, or indexes its contents.

  • The top level must be a JSON object ({...}). Arrays, strings, numbers, and other bare values are rejected with 400. Nested values may be anything.
  • The compact (no-whitespace) serialization must be at most 4096 characters; larger → 400.
  • Returned as a nested JSON object on GET /api/recipes/{id}, the create response, and the update response – never as a quoted string. Not included in the list endpoint.
  • Full replacement on update: omitting metadata on a PUT clears it.

Example (curl)

curl -X POST \
     -H "X-API-Key: icc_your_key" \
     -H "Content-Type: application/json" \
     -d '{"name":"Mango Sorbet","category":"Sorbet","tags":["Vegan"],"ingredients":[{"ingredientId":42,"weight":500}]}' \
     https://icecreamcalc.app/api/recipes

PUT /api/recipes/{id}

Update an existing recipe. This is a full replacement: the ingredient list is completely replaced, and every optional field resets to its default when omitted – the API does not merge with the previous values.

Request

Same body format and validation as POST /api/recipes (see the request-field table above). Key differences:

Field When omitted on PUT
info, notes, externalId Cleared (set to empty)
category, tags, bestBeforeDays, metadata Cleared (unset / no tags / null)
overrun Reset to -1 (inherit the global default)
chartName Chart link cleared (also cleared if the name isn’t found)
ingredients Not allowed to omit – at least one is required; the previous list is always fully replaced

⚠️ Warning: Because updates are full replacements, always send every field you want to keep. A safe pattern is: GET /api/recipes/{id}, modify the fields you need, then PUT the whole thing back.

Response (200 OK)

{
  "success": true,
  "recipe": { /* the full updated recipe object */ }
}

Error Responses

Same validation errors as create (400), plus: 404 when the recipe doesn’t exist, 403 when it belongs to another user, and 400 when attempting to update a temporary recipe.


DELETE /api/recipes/{id}

Permanently delete a recipe and all its ingredients. This cannot be undone.

Request

DELETE /api/recipes/1
Headers:
  X-API-Key: icc_your_api_key_here

Response (200 OK)

{
  "success": true,
  "message": "Recipe deleted successfully",
  "recipeId": 1
}

Error Responses

HTTP Code Meaning
404 Recipe doesn’t exist
403 Recipe belongs to another user
400 Temporary recipes cannot be deleted via the API

Example (curl)

curl -X DELETE \
     -H "X-API-Key: icc_your_key" \
     https://icecreamcalc.app/api/recipes/1

POST /api/recipes/{id}/balance

Run Smart Balance on a recipe against its linked chart and get back a full diagnostic proposal.

💡 Non-mutating: This endpoint never saves anything. It balances a copy of the stored recipe and returns the proposed weights with diagnostics. If you want to keep the proposal, send a PUT /api/recipes/{id} with the new ingredient weights.

Request

No request body. The balance run is reconstructed entirely from stored recipe state:

  • Targets come from the recipe’s linked chart (the chart items marked for balancing).
  • Locked ingredients (set in the recipe editor) keep their weights.
  • Classification chips saved on the recipe’s ingredients (Flavor, Stabilizer, Emulsifier, etc.) drive the per-ingredient bounds; ingredients without a saved chip use their own min/max amount settings.
POST /api/recipes/1/balance
Headers:
  X-API-Key: icc_your_api_key_here

Response (200 OK)

{
  "success": true,
  "result": {
    "status": "Converged",
    "message": null,
    "mixWeightBefore": 1000.0,
    "mixWeightAfter": 1000.0,
    "wasReverted": false,
    "bestIteration": 4,
    "initialOutOfRangeCount": 3,
    "proposedOutOfRangeCount": 0,
    "elapsedMs": 412,
    "iterationCount": 4,
    "targets": [
      {
        "propertyKey": "Total fat",
        "target": 10.0,
        "initialValue": 8.2,
        "finalValue": 10.0,
        "isLinear": true,
        "priority": 1.0,
        "achievableMin": 6.5,
        "achievableMax": 14.8,
        "isUnmovable": false,
        "unmovableReason": null,
        "rangeAchievable": true,
        "bindingIngredients": []
      }
    ],
    "iterations": [
      { "iteration": 1, "objective": 2.31, "maxWeightChange": 42.5, "elapsedMs": 118 }
    ],
    "ingredients": [
      {
        "ingredientId": 42,
        "name": "Heavy Cream",
        "initialWeight": 250.0,
        "finalWeight": 291.3,
        "class": "Unknown",
        "lowerBoundGrams": 0.0,
        "upperBoundGrams": 1000.0,
        "locked": false,
        "excluded": false
      }
    ],
    "classification": {
      "success": true,
      "errorMessage": null,
      "classifications": [
        { "name": "Vanilla Extract", "class": "Flavor", "recMinPct": null, "recMaxPct": null, "reason": null }
      ]
    }
  }
}

Result Fields

Field Type Description
status string One of Converged, MaxIterationsReached, Infeasible, SolverUnavailable, Failed
message string or null Human-readable explanation (e.g. why the run stopped early or reverted)
mixWeightBefore / mixWeightAfter number Total mix weight before and after (mass is held fixed, so these should match)
wasReverted boolean True when the safety net kept the original weights because no proposal beat the input recipe
bestIteration integer Which state the returned weights come from: 0 = the input recipe, N = after N accepted solver steps
initialOutOfRangeCount / proposedOutOfRangeCount integer How many targets were out of range before, and in the proposal
elapsedMs integer Total run time in milliseconds
iterationCount integer Number of solver iterations (the iterations array has one entry per iteration)

Per-Target Fields (targets)

Field Type Description
propertyKey string The chart property (e.g. “Total fat”)
target / initialValue / finalValue number What was asked for, where the property started, and where the proposal lands
isLinear boolean Whether the property responds linearly to weight changes
priority number Weighting of this target in the optimization
achievableMin / achievableMax number or null The locally achievable interval under the ingredient bounds; null when not computed. Treat “out of reach” as a strong warning rather than a proof
isUnmovable / unmovableReason boolean / string or null True when no redistribution of the adjustable ingredients can move this property at all, with the reason
rangeAchievable boolean or null false when the requested range lies outside the achievable interval; null when not computed
bindingIngredients array of strings When the range is unachievable: the ingredient bounds standing in the way (up to three, ranked by influence)

Per-Ingredient Fields (ingredients)

Field Type Description
ingredientId / name integer / string Which ingredient
initialWeight / finalWeight number Weight in grams before, and in the proposal – use these for the follow-up PUT
class string The functional class used for bound assignment (Flavor, Stabilizer, Emulsifier, Fiber, HighIntensitySweetener, Structural, Other, or Unknown when unclassified)
lowerBoundGrams / upperBoundGrams number The weight bounds the solver used for this ingredient
locked boolean Weight was held fixed
excluded boolean Excluded from balancing (inclusions and infusions)

The iterations array traces the solver (iteration, objective, maxWeightChange, elapsedMs per step), and classification echoes the classification set the run used (success, errorMessage, and per-ingredient name/class/recMinPct/recMaxPct/reason entries).

Error Responses

HTTP Code Example Meaning
400 {"error": "No chart linked", "details": "This recipe has no linked chart. Link a chart (its ranges define the balance targets) and try again."} The recipe has no linked chart (or the linked chart couldn’t be loaded)
404 / 403   As for GET /api/recipes/{id}
429 {"success": false, "error": "Rate limit exceeded", "details": "..."} The balance endpoint’s tighter rate limit was exceeded (see Rate Limiting)

Note: If the chart has no properties marked for balancing, or every ingredient is locked or an add-in, the endpoint still returns 200 – with status: "Converged", a message explaining the early exit (e.g. “Chart has no balancing targets”), unchanged weights, and empty targets/iterations/ingredients arrays. Check the message field, not just the status.

Typical agent workflow

  1. POST /api/recipes/1/balance – get the proposal
  2. Inspect status, wasReverted, the per-target achievability, and the per-ingredient finalWeight values
  3. If you like the proposal: GET /api/recipes/1, set each ingredient’s weight to the proposal’s finalWeight, and PUT the recipe back

Example (curl)

curl -X POST \
     -H "X-API-Key: icc_your_key" \
     https://icecreamcalc.app/api/recipes/1/balance

Ingredient Endpoints

GET /api/ingredients

Retrieve a list of all ingredients you have access to (personal ingredients + shared/default ingredients).

Note: This endpoint returns basic information only. For full nutritional data, use /api/ingredients/{id}

Request

GET /api/ingredients
Headers:
  X-API-Key: icc_your_api_key_here

Response (200 OK)

{
  "success": true,
  "count": 153,
  "ingredients": [
    {
      "id": 1,
      "name": "Whole Milk",
      "category": "Dairy",
      "source": "Default",
      "isOverride": false,
      "isRecipeDerived": false,
      "created": "2024-01-01T00:00:00Z",
      "changed": "2024-01-01T00:00:00Z"
    },
    {
      "id": 42,
      "name": "My Custom Sugar",
      "category": "Sugar",
      "source": "Personal",
      "isOverride": false,
      "isRecipeDerived": false,
      "created": "2025-11-01T10:30:00Z",
      "changed": "2025-11-08T14:20:00Z"
    }
  ]
}

Ingredient Summary Fields

Field Type Description
id integer Unique ingredient identifier
name string Ingredient name
category string Category (Dairy, Sugar, Fat, etc.)
source string “Default”, “Personal”, or “Modified Default”
isOverride boolean True if this is a modified default ingredient
isRecipeDerived boolean True if created from a recipe

Example (curl)

curl -H "X-API-Key: icc_your_key" \
     https://icecreamcalc.app/api/ingredients

GET /api/ingredients/{id}

Retrieve complete details for a single ingredient including all nutritional data and ice cream properties.

Request

GET /api/ingredients/1
Headers:
  X-API-Key: icc_your_api_key_here

Response (200 OK)

{
  "success": true,
  "ingredient": {
    "id": 1,
    "name": "Whole Milk",
    "category": "Dairy",
    "info": "Fresh whole milk",
    "source": "Default",
    "isOverride": false,

    // Nutritional data (per 100g)
    "water": 87.5,
    "totalFat": 3.5,
    "saturatedFat": 2.1,
    "protein": 3.3,
    "carbohydrates": 4.8,
    "totalSugars": 4.8,
    "lactose": 4.8,

    // Ice cream specific
    "msnf": 8.9,
    "milkSolids": 12.4,
    "milkProtein": 3.3,
    "milkFat": 3.5,
    "pac": 5.2,
    "pod": 0.15,

    // Allergens
    "contains": ["Milk"],
    "mayContain": [],

    "created": "2024-01-01T00:00:00Z",
    "changed": "2024-01-01T00:00:00Z"
  }
}

Complete Ingredient Fields

Basic Information:

  • id, name, category, info, source

Nutritional Data (per 100g):

  • water, totalFat, saturatedFat, transFat, cholesterol
  • sodium, salt, carbohydrates, fiber, totalSugars
  • protein, alcohol, lactose, polyols
  • vitaminD, calcium, iron, potassium

Ice Cream Properties:

  • msnf – Milk solids non-fat
  • milkSolids – Total milk solids
  • milkProtein – Milk protein content
  • milkFat, cocoaFat, cocoaSolids
  • pac – Freezing point depression
  • pod – Relative sweetness
  • hf – Hardening factor
  • stabilizers, emulsifiers

Allergen Information:

  • contains – Array of allergens this ingredient contains
  • mayContain – Array of potential cross-contamination allergens

Example (JavaScript)

fetch("https://icecreamcalc.app/api/ingredients/1", {
  headers: {"X-API-Key": "icc_your_key"}
})
  .then(r => r.json())
  .then(data => {
    const ing = data.ingredient;
    console.log(`${ing.name}: ${ing.totalFat}g fat, ${ing.protein}g protein`);
    console.log(`PAC: ${ing.pac}, POD: ${ing.pod}`);
  });

Response Format

Success Response

All successful responses include a success field set to true:

{
  "success": true,
  // ... response data
}

Error Response

Error responses include success: false and error details:

{
  "success": false,
  "error": "Brief error message",
  "details": "More detailed explanation"
}

HTTP Status Codes

Code Name Meaning
200 OK Request succeeded
201 Created Recipe created (POST /api/recipes)
400 Bad Request Invalid request data – see the error/details fields
401 Unauthorized Missing or invalid API key
403 Forbidden You don’t have access to this resource (or plan limit reached)
404 Not Found Resource doesn’t exist
429 Too Many Requests Rate limit exceeded – wait for the seconds in the Retry-After header
500 Server Error Server-side error occurred

Rate Limiting

The API enforces per-key rate limits to keep the service responsive for everyone. Limits use a fixed one-minute window:

Scope Limit Window
All API endpoints, per API key 100 requests 1 minute
POST /recipes/{id}/balance, per API key 20 requests 1 minute
Requests without an API key (shared) 10 requests 1 minute

The balance endpoint is computation-heavy, so it has its own tighter per-key limit (its calls are counted against the balance limit rather than the general one). Requests without an X-API-Key header share a single small bucket across all callers.

When you hit a limit

You’ll receive 429 Too Many Requests with a Retry-After header (seconds to wait) and an error body:

{
  "success": false,
  "error": "Rate limit exceeded",
  "details": "Too many API requests. Retry after 60 seconds (see the Retry-After header)."
}

Playing nicely

  • Cache responses when appropriate
  • Don’t make unnecessary repeated requests
  • On 429, wait for Retry-After before retrying – don’t hammer
  • Implement exponential backoff for retries

Note: The limits above are starting defaults and may be tuned. Read them from this page rather than hardcoding assumptions into your integration’s logic – and always honour Retry-After.


Data Access Rules

What You Can Access

  • ✅ Your own recipes (all types) – read and write
  • ✅ Your personal ingredients
  • ✅ Shared/default ingredients (public database)
  • ✅ Your modified default ingredients (overrides)

What You Cannot Access

  • ❌ Other users’ recipes
  • ❌ Other users’ personal ingredients
  • ❌ Temporary recipes (not accessible via API – reading, updating, deleting, and balancing them all return errors)

What You Can Do

  • ✅ Create recipes (POST /recipes) – counts against your plan’s recipe limit
  • ✅ Update recipes (PUT /recipes/{id}, full replacement)
  • ✅ Delete recipes (DELETE /recipes/{id})
  • ✅ Run Smart Balance (POST /recipes/{id}/balance) – returns a proposal, saves nothing
  • ✅ Store your own JSON in each recipe’s metadata field for syncing

Best Practices

Security

  • Always use HTTPS (required by the API)
  • Store API keys in environment variables, not in code
  • Never expose API keys in client-side JavaScript
  • Regenerate your key if compromised – remember the API can now modify and delete your recipes

Performance

  • Use /api/recipes/{id} when you need one recipe, not /api/recipes
  • Use the list endpoint’s externalId to match your system’s recipes in one call
  • Use /api/ingredients for browsing, /api/ingredients/{id} for details
  • Cache calculated data – it doesn’t change unless the recipe changes
  • Handle errors gracefully with retry logic, honouring Retry-After on 429

Updating safely

  • Because PUT is a full replacement, always GET first, modify, then PUT the whole recipe back
  • Use metadata (e.g. a version number) to detect concurrent changes from your own tooling
  • Preview balancing with POST /recipes/{id}/balance before committing weights with PUT

Error Handling

try {
    response = requests.get(url, headers=headers)
    response.raise_for_status()  # Raise exception for 4xx/5xx
    data = response.json()

    if not data.get('success'):
        print(f"API Error: {data.get('error')}")
        return None

    return data

except requests.exceptions.RequestException as e:
    print(f"Network error: {e}")
    return None

Changelog

Version 1.1 (July 2026)

  • Recipe write endpoints: create (POST), update (PUT, full replacement), delete (DELETE)
  • New recipe fields: notes, externalId, category, tags, bestBeforeDays, overrun
  • Opaque metadata JSON store per recipe for external-system syncing
  • List endpoint additions: info and externalId (description kept as a legacy alias)
  • Smart Balance endpoint: POST /recipes/{id}/balance (non-mutating proposal with full diagnostics)
  • Per-key rate limiting (100/min general, 20/min balance) with 429 + Retry-After

Version 1.0 (November 2025)

  • Initial API release
  • Recipe endpoints (list, single, calculated)
  • Ingredient endpoints (list, single)
  • API key authentication

Need Help?

  • 📘 Getting Started Guide – Learn how to generate an API key and make your first request
  • 🔬 API Test Page – Test endpoints interactively from your Account page
  • 📧 Support – Contact us if you encounter issues

💡 Pro Tip: Use the API Test Page (accessible from your Account page) to explore endpoints and see example responses before writing code!

 


REST API
API reference
endpoints
authentication
recipes
ingredients
calculated data
Smart Balance
HTTP methods
JSON
rate limiting
error handling
API key
integration
developer documentation
technical reference

Connection Lost

Attempting to reconnect to the server...

An error has occurred. This application may no longer respond until reloaded. Reload 🗙