Python quickstart: nutrition data in 10 lines
You can search Dietly's public nutrition catalog with one GET request to https://api.getdietly.com/search. The response is a JSON array of foods, not an object with a results property. Install requests, run the example below, and you have a working nutrition lookup. The rest of this page turns that single call into something dependable: a client class, correct field handling, and retries that respect the rate limit.
Make your first request
The only required parameter is q (minimum two characters). Use limit to cap results between 1 and 50. Ten is plenty while you explore.
import requests
r = requests.get("https://api.getdietly.com/search",
params={"q": "greek yogurt", "limit": 5}, timeout=10)
r.raise_for_status()
foods = r.json()
for food in foods:
print(food["name"], food.get("protein_g"))Understand the fields you get back
Each result is a flat object. Nutrient values are per 100 g of the product unless noted, and any nutrient can be null when the source label did not list it. Treat null as unknown, never as zero.
| Field | Meaning |
|---|---|
id | Stable Dietly food ID, used for direct lookups |
name, brand | Product name and brand when known |
calories_kcal | Energy per 100 g |
protein_g, fat_g, carbs_g | Macronutrients per 100 g |
fiber_g, sugar_g, saturated_fat_g, sodium_mg | Detail nutrients per 100 g, often null |
serving_size_g, serving_desc | One serving in grams and its label wording, when provided |
source, confidence | Where the record came from and how sure Dietly is |
static_url | Path to the food's page on getdietly.com, when one exists |
To convert a per-100 g value to a portion, multiply by grams and divide by 100. For a 150 g serving of a food with 10 g protein per 100 g, that is 10 * 150 / 100 = 15 g.
Read the result defensively
An empty search is [], which is a normal outcome rather than an error. Guard for it, and keep null nutrients as null in your own model.
food = foods[0] if foods else None
if food is None:
print("No close match")
else:
protein = food.get("protein_g")
print(f"{food['name']}: {protein if protein is not None else 'unknown'} g/100g")Look up a known food or barcode
Once you have an id from search, fetch that record directly with GET /food/{id}. To resolve a scanned product, call GET /barcode/{code} with an EAN or UPC. Both return a single food object, or a 404 when there is no match, which you should treat as an ordinary empty state.
detail = requests.get("https://api.getdietly.com/food/86", timeout=10)
if detail.status_code == 404:
print("Unknown food ID")
else:
detail.raise_for_status()
print(detail.json()["name"])
scan = requests.get("https://api.getdietly.com/barcode/737628064502", timeout=10)
print("match" if scan.ok else "no product for that barcode")Wrap it in a small client class
A thin client keeps the base URL, timeout and optional key in one place, and gives you a single spot to add retries. This is enough structure for a real feature without a heavy SDK.
import requests
class Dietly:
def __init__(self, api_key=None, timeout=10):
self.base = "https://api.getdietly.com"
self.timeout = timeout
self.session = requests.Session()
if api_key:
self.session.headers["Authorization"] = f"Bearer {api_key}"
def search(self, query, limit=10):
r = self.session.get(f"{self.base}/search",
params={"q": query, "limit": limit}, timeout=self.timeout)
r.raise_for_status()
return r.json()
def food(self, food_id):
r = self.session.get(f"{self.base}/food/{food_id}", timeout=self.timeout)
if r.status_code == 404:
return None
r.raise_for_status()
return r.json()
client = Dietly()
print(len(client.search("oat milk")))Add an API key when your app needs one
Read endpoints work anonymously at a shared per-IP limit, which is fine for prototypes. For account-level limits, usage reporting and higher throughput, create a free key in the dashboard and send it as a bearer token, exactly as the client class does above. Keep server keys out of browser bundles, public repositories and logs. If a key is ever exposed, rotate it from the dashboard rather than editing it in place.
Handle rate limits and errors with backoff
When you hit the limit the API returns 429 with a Retry-After header. Wait that long instead of retrying immediately. Retry only a small number of transient 5xx failures with growing delays, and let 404 become an empty state rather than a crash.
import time, requests
def get_with_retry(session, url, params=None, tries=3):
for attempt in range(tries):
r = session.get(url, params=params, timeout=10)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "2")))
continue
if 500 <= r.status_code < 600 and attempt < tries - 1:
time.sleep(2 ** attempt)
continue
r.raise_for_status()
return r.json()
raise RuntimeError("DietlyAPI unavailable after retries")Cache lookups where you can. Nutrition records change rarely, so a short local cache on id or query text cuts both latency and your request count. That is the same fast and cheap principle Dietly runs on: do the work once and reuse it.
Attribution
Records derived from Open Food Facts carry an open licence. When you display that data publicly, include the attribution described in the API terms. It is a short line, and it keeps the open catalog healthy for everyone building on it.