TempoLife

TempoLifeFeaturesDeveloper portal

Developer portal

Two free, read-only, CORS-open JSON endpoints over 5 038 canonical foods: a lookup and search endpoint, and a grounded answer endpoint limited to 60 requests an hour. No key required today.

Overview · Endpoint reference · Your API keys · Your usage

2public endpoints
5 038canonical foods
nokey required
freecost

Keys are not enforced yet. /food-api.php and /api/answer are open, unauthenticated and CORS-open right now, and no code in TempoLife reads an API key when answering them. A key you create here reserves your identity and the higher ceiling you will get when enforcement ships; sending it today changes nothing about the response you receive or the limit you are under.

What exists today

TempoLife publishes a small, deliberately boring read API: reference nutrition per 100 g, and a question-shaped wrapper over the same numbers. Both are GET-only, both send Access-Control-Allow-Origin: *, and both are safe to call straight from a browser. There is no authentication, no signup, no quota dashboard and no write path. That is the whole surface, and this page exists so you can tell what is real from what is planned.

Verified against food-api.php, api/answer.php and .well-known/openapi.yaml on 2026-09-02.
EndpointMethodPurposeAuthLimit
/food-api.phpGETOne food by canonical slug, or up to 20 search matchesnoneno limiter in code
/api/answerGETA grounded one-sentence answer, or a two-food comparisonnone60/h per IP
/datasets/foods.jsonGETThe whole table as one file — use this instead of looping the APInonestatic file
/.well-known/openapi.yamlGETOpenAPI 3.1 description of both endpointsnonestatic file

Two datasets, two row counts

This trips people up, so it is worth stating before you compare outputs. /food-api.php serves from datasets/foods.json, which the endpoint asserts holds exactly 5 038 rows and returns 503 dataset_unavailable if it does not. /api/answer resolves names against the baked index that also renders the /calories pages, which currently holds 5 237 rows. The overlap is nearly total, but a slug that answers on one endpoint can 404 on the other. Treat /food-api.php as the canonical list.

Look up one food

curl -s 'https://tempolife.app/food-api.php?food=banana'

The response envelope carries the licensing summary, then the food:

{
  "dataset_license": "mixed",
  "licensing": {
    "metadata_schema_and_tempolife_localization": "CC-BY-4.0",
    "usda_sr_legacy_values": "us_public_domain",
    "curated_unverified_values": null
  },
  "attribution": "https://tempolife.app/",
  "food": {
    "slug": "banana",
    "name": "Banana",
    "category": "…",
    "source": "usda_sr_legacy",
    "source_ref": null,
    "values_license": "us_public_domain",
    "per_100g": { "kcal": 89, "protein_g": 1.1, "carbs_g": 22.8, "fat_g": 0.3, "fiber_g": 2.6, "sugar_g": 12.2 },
    "url": "https://tempolife.app/calories/banana"
  }
}

Add &lang=et, &lang=fi or &lang=ru and the row gains a name_et / name_fi / name_ru field — but only where a distinct translation exists. If the localised name equals the English one, the field is omitted rather than duplicated. Do not treat its absence as an error.

Search for a slug

curl -s 'https://tempolife.app/food-api.php?search=chicken'

Search is a case-insensitive substring match over the English name and the slug, capped at 20 results. Each result is a thin row — slug, name, provenance and canonical URL — without the nutrient block. Fetch the slug you picked to get numbers. An empty search= is not an error: it returns the first 20 rows of the dataset.

{
  "dataset_license": "mixed",
  "query": "chicken",
  "count": 20,
  "results": [
    { "slug": "chicken-breast", "name": "…", "source": "usda_sr_legacy",
      "source_ref": null, "values_license": "us_public_domain",
      "url": "https://tempolife.app/calories/chicken-breast" }
  ]
}

Ask a question

curl -s 'https://tempolife.app/api/answer?q=calories+in+banana'
curl -s 'https://tempolife.app/api/answer?q=apple+vs+banana+protein'

The endpoint understands eight metrics — energy, protein, carbohydrate, fat, fibre, sugar, saturated fat and salt — and one comparison form (A vs B, A versus B, A and B). It answers from stored values only; it does not call a language model and it will 404 rather than guess. Every answer carries the canonical page it came from, so you can cite it.

{
  "query": "calories in banana",
  "answer": "Banana has 89 kcal per 100 g.",
  "value": 89,
  "unit": "kcal_per_100g",
  "food": { "slug": "banana", "name": "Banana" },
  "source_url": "https://tempolife.app/calories/banana",
  "source": {
    "name": "USDA SR Legacy-family food data",
    "source_ref": null,
    "values_license": "us_public_domain",
    "canonical_url": "https://tempolife.app/calories/banana"
  },
  "basis": "per 100 g edible portion; reference value, not a measurement of an individual meal",
  "license": { "response_text": "CC-BY-4.0", "nutrition_values": "mixed; inspect source metadata" },
  "attribution": "TempoLife — https://tempolife.app/"
}

Rate limits, precisely

/api/answer runs a fixed-window counter keyed on your IP: 60 requests per hour, resetting on the hour boundary rather than rolling. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; the 429 adds Retry-After in seconds until the window flips. The limiter is deliberately fail-open: if it cannot write its counter file it lets the request through rather than failing your integration.

/food-api.php has no limiter in its code path at all. That is not permission to loop it — it is a single 1.9 MB JSON file read per request, and the polite way to take a copy is the dataset download. Responses are sent with Cache-Control: public, max-age=86400, so an ordinary HTTP cache in front of your client will do most of the work for you.

Client snippets

JavaScript (browser or Node 18+)

async function tempolifeFood(slug, lang) {
  const url = new URL('https://tempolife.app/food-api.php');
  url.searchParams.set('food', slug);
  if (lang) url.searchParams.set('lang', lang);
  const res = await fetch(url, { headers: { Accept: 'application/json' } });
  if (res.status === 404) return null;                 // food_not_found
  if (!res.ok) throw new Error('tempolife: HTTP ' + res.status);
  const body = await res.json();
  const food = body.food;
  // Keep provenance with the value, always.
  return {
    name: food.name,
    kcal: food.per_100g.kcal,
    source: food.source,
    licence: food.values_license,     // null => no reuse right asserted
    canonical: food.url
  };
}

PHP 8

<?php
function tempolife_food(string $slug, ?string $lang = null): ?array {
    $qs = ['food' => $slug] + ($lang !== null ? ['lang' => $lang] : []);
    $url = 'https://tempolife.app/food-api.php?' . http_build_query($qs);
    $ctx = stream_context_create(['http' => [
        'timeout' => 8,
        'ignore_errors' => true,
        'header' => "Accept: application/json\r\nUser-Agent: my-app/1.0\r\n",
    ]]);
    $raw = @file_get_contents($url, false, $ctx);
    if ($raw === false) return null;
    $body = json_decode($raw, true);
    if (!is_array($body) || !isset($body['food'])) return null;   // 404/503
    return $body['food'];   // keep source / source_ref / values_license
}

Both snippets keep the provenance fields attached to the value. That is not decoration: a stored kcal figure without its values_license is a figure you cannot later prove you were allowed to republish.

Licensing, and why it is not one licence

The dataset behind /food-api.php has mixed provenance, and the API refuses to hide that. Every row carries its own source, a nullable source_ref and a nullable values_license, and the response envelope repeats the summary in licensing. You are expected to read those fields per row rather than assume a blanket grant.

Row counts are asserted by food-api.php itself: the endpoint returns 503 rather than serve a dataset whose counts do not match.
Row classRowsvalues_licenseWhat you may assume
usda_sr_legacy4 612us_public_domainNutrition values originate in the USDA SR Legacy source family. US Government works, public domain in the United States.
curated_unverified426nullNo retained source reference and no asserted value-reuse right. Their sugar_g is null, not zero. Filter these out when you need clean provenance.

Separately, TempoLife offers its metadata schema and its own localisation under CC BY 4.0 with attribution to https://tempolife.app/. That grant covers the shape of the data and the translated names TempoLife wrote. It does not relicense third-party nutrition values, and it cannot create a reuse right for the curated rows that never had one.

Practical rule: keep source, source_ref and values_license next to any value you store, and attribute TempoLife where you display it. If your product needs a single clean licence, request only USDA-family rows and drop the rest.

Source: TempoLife food nutrition API — provenance section · checked 2026-09-02

What crawlers are allowed to fetch

robots.txt disallows /api/ wholesale and then re-allows exactly four API-ish paths: /api/answer, /food-api.php, /datasets/ and /.well-known/openapi.yaml. If you are building an agent or a retrieval pipeline, those four are the paths you are welcome to touch.

Frequently asked questions

Do I need an API key?

No. Both endpoints are open and unauthenticated right now. The key pages on this portal mint keys for the day enforcement ships; nothing checks a key today.

What is the rate limit?

/api/answer allows 60 requests per IP per hour in a fixed window and answers 429 with a Retry-After header when you exceed it. /food-api.php has no limiter in the code at all — please cache instead of hammering it.

Can I use the data commercially?

It depends on the row. USDA-family rows carry us_public_domain values. Curated rows carry a null values_license and no retained source, so they grant you nothing. Read values_license per row.

Is there an OpenAPI file?

Yes, OpenAPI 3.1 at /.well-known/openapi.yaml. It describes both endpoints, their parameters and their status codes.

Is there a write API?

No. Nothing in TempoLife accepts writes from a third party today. There is no endpoint that logs a meal, a weight or a step count on a user's behalf.

Next

Endpoint reference

Every parameter, every status code, every response field, CORS and caching behaviour.

Your API keys

Mint a key for the day enforcement ships. Shown once, stored only as a hash.

Sign-in needed

Your usage

Calls per day per endpoint, once anything writes to the usage table.

Sign-in needed

Sign in with TempoLife

The OAuth provider story for partner apps: scopes, PKCE and what is not live yet.

Automations and webhooks

Event payloads, HMAC signing and the retry policy for outbound webhooks.

Embeddable widgets

Drop a calorie calculator or a nutrition card into your own site with one iframe.

Building on the food data?

An account is not needed to call the API. It is needed to mint a key, and it is the fastest way to see what the same numbers look like inside a real diary.

Create a free accountGet the app