Integration guide

From first request to a reliable food experience.

A practical path through search, barcodes, nutrition fields, errors and production readiness—with examples you can paste into your project.

Make your first requestTry the playground
01ConnectSend a working request in minutes. 02Build safelyUnderstand shapes, nulls and errors. 03Ship confidentlyReview the production checklist.

Your first request

Public food reads work without a key. Start with search, then add a bearer key when you need your account's plan and usage reporting.

const response = await fetch(
  'https://api.getdietly.com/search?q=greek%20yogurt&limit=5'
);

if (!response.ok) throw new Error(`DietlyAPI ${response.status}`);
const results = await response.json(); // a bare array
console.log(results[0].name);
Important: /search returns [{...}], not {"results":[...]}.

Copy-and-run examples

These examples all call the existing GET /search endpoint directly and preserve its bare-array response.

cURL

curl --get 'https://api.getdietly.com/search' \
  --data-urlencode 'q=greek yogurt' \
  --data-urlencode 'limit=5' \
  -H 'Authorization: Bearer YOUR_API_KEY'

JavaScript

const url = new URL('https://api.getdietly.com/search');
url.search = new URLSearchParams({ q: 'greek yogurt', limit: '5' });

const response = await fetch(url, {
  headers: { Authorization: 'Bearer YOUR_API_KEY' }
});
if (!response.ok) throw new Error(`DietlyAPI ${response.status}`);
const foods = await response.json(); // FoodResult[]
console.log(foods[0]?.name);

Python

import requests

response = requests.get(
    'https://api.getdietly.com/search',
    params={'q': 'greek yogurt', 'limit': 5},
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    timeout=10,
)
response.raise_for_status()
foods = response.json()  # list[dict], not {'results': [...]}
print(foods[0]['name'] if foods else 'No match')

Public food endpoints

Method and pathSuccess responseUse
GET /search?q=&limit=&source=Bare food arrayRanked name and brand search; optional source is off or claude
GET /barcode/{code}One food objectEAN or UPC lookup
GET /food/{food_id}One food objectFetch a known Dietly food ID
GET /foods/popularBare food arrayPopular foods for discovery
GET /foods/categoriesCategory arrayCategory names and counts
GET /healthHealth objectLightweight availability check

Machine-readable version of these endpoints: OpenAPI 3.0 specification.

Food submission and AI routes used by Dietly applications have separate app authorization and are not included in public API plans. Their existing paths and wire formats remain unchanged.

Stable response behavior

Search

Always a JSON array. An empty match is [].

Barcode and food ID

One food object on success; 404 with detail when missing.

Missing nutrients

Unavailable values are null. Do not interpret missing values as zero.

Images

Use image_thumb_url in lists and image_url for larger views.

Authentication

curl 'https://api.getdietly.com/search?q=banana&limit=5' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Keep keys out of source control, logs and client-side web bundles. Mobile application keys should be treated as identifiers rather than secrets and protected with server-side limits.

Key scopes

Every key is created as full access or read-only in the dashboard. A read-only key can call every GET data endpoint but is rejected with 403 everywhere else — use one anywhere a key could leak (client-side code, CI logs, shared notebooks). You can also give each key its own expiry date and rate cap.

Limits and retries

Every authenticated response reports your remaining minute quota in three headers, so clients can slow down before hitting the limit:

HeaderMeaning
X-RateLimit-LimitYour plan's requests-per-minute allowance (or the key's own cap, if lower).
X-RateLimit-RemainingRequests left in the current minute window.
X-RateLimit-ResetSeconds until the window resets.

A rate-limited request returns 429, a JSON detail message and a Retry-After header (plus X-RateLimit-Remaining: 0). Wait for that interval and use capped exponential backoff for subsequent failures.

const remaining = Number(response.headers.get('X-RateLimit-Remaining'));
if (remaining === 0) {
  // proactively pause until the window resets
  const reset = Number(response.headers.get('X-RateLimit-Reset') || 60);
  await new Promise(resolve => setTimeout(resolve, reset * 1000));
}
if (response.status === 429) {
  const seconds = Number(response.headers.get('Retry-After') || 1);
  await new Promise(resolve => setTimeout(resolve, seconds * 1000));
}

Nutrition units

FieldsUnitBasis
calories_kcalkcalper 100 g
protein_g, fat_g, carbs_g, fiber_g, sugar_ggramsper 100 g
sodium_mg, cholesterol_mg, potassium_mgmilligramsper 100 g
serving_size_ggramssuggested serving when available

Data freshness and confidence

Open Food Facts changes are imported by a scheduled daily delta refresh; updates are not immediate and the importer does not remove upstream deletions. Search and listing caches may continue serving a previous result for their configured cache window. Use source and confidence to explain provenance; do not present community or AI estimates as laboratory measurements. Public search reads existing indexed entries and never creates a new AI estimate.

Attribution

Where Open Food Facts data is displayed, include a visible credit such as Food data from Open Food Facts (ODbL) via DietlyAPI and follow the requirements in the API Terms.

Before you launch

Parse the wire format

Treat search and list responses as arrays, preserve nullable nutrients, and handle an empty array as a normal result.

Handle expected failures

Give 404 scans a helpful empty state, show validation errors, honor Retry-After, and cap retries for 5xx responses.

Protect your key

Keep server keys out of repositories and logs. Never paste a production key into a support message or public issue.

Credit the source

Add visible Open Food Facts attribution wherever its food data is publicly displayed.

Troubleshooting

SymptomWhat to check
results is undefinedParse the response as a bare array: const results = await response.json().
401Confirm the bearer header format and that the key has not been rotated.
403 on /food/lookupThis app-only AI route is not part of public API plans; use GET /search.
403 "read-only"The key was created read-only; it only allows GET data endpoints. Use a full-access key.
404 barcodeThe product is not in the current catalog. Treat this as a normal scan-miss state.
422Check parameter names, minimum search length and request JSON types.
429Honor Retry-After; do not retry immediately in a loop.
5xxRetry a small number of times and check the status page.