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);
/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 path | Success response | Use |
|---|---|---|
GET /search?q=&limit=&source= | Bare food array | Ranked name and brand search; optional source is off or claude |
GET /barcode/{code} | One food object | EAN or UPC lookup |
GET /food/{food_id} | One food object | Fetch a known Dietly food ID |
GET /foods/popular | Bare food array | Popular foods for discovery |
GET /foods/categories | Category array | Category names and counts |
GET /health | Health object | Lightweight 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:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Your plan's requests-per-minute allowance (or the key's own cap, if lower). |
X-RateLimit-Remaining | Requests left in the current minute window. |
X-RateLimit-Reset | Seconds 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
| Fields | Unit | Basis |
|---|---|---|
calories_kcal | kcal | per 100 g |
protein_g, fat_g, carbs_g, fiber_g, sugar_g | grams | per 100 g |
sodium_mg, cholesterol_mg, potassium_mg | milligrams | per 100 g |
serving_size_g | grams | suggested 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
| Symptom | What to check |
|---|---|
results is undefined | Parse the response as a bare array: const results = await response.json(). |
401 | Confirm the bearer header format and that the key has not been rotated. |
403 on /food/lookup | This 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 barcode | The product is not in the current catalog. Treat this as a normal scan-miss state. |
422 | Check parameter names, minimum search length and request JSON types. |
429 | Honor Retry-After; do not retry immediately in a loop. |
5xx | Retry a small number of times and check the status page. |