bodify.fit API
Build nutrition-aware applications with food recognition, barcode scanning, intelligent search, AI chat, and 3D body scanning. One REST API — no SDK required.
Introduction
The bodify.fit API provides programmatic access to food recognition via AI-powered photo analysis, global barcode-based product lookup, nutrition database search, and food logging. All API responses follow a consistent JSON envelope format with built-in rate limiting metadata.
This documentation covers the REST API.
No version prefix (/v1/) is currently used; the API is accessed directly at the root.
Base URL
| Environment | Base URL |
|---|---|
| Production | https://api.bodify.fit |
/v1/ prefix on any route. All endpoints are accessed directly at the root path.
Authentication
Most API endpoints require authentication via an API key. Keys are issued per organization and are passed
in the Authorization header using the Bearer scheme.
API Key Format
Keys follow the format sk_bodify_<32 characters> and are
scoped to a single organization. You can manage your API keys from the
Dashboard.
httpAuthorization: Bearer sk_bodify_AbCdEf0123456789AbCdEf0123456789
Example: Authenticated Request
curlcurl https://api.bodify.fit/food/search?query=chicken \ -H "Authorization: Bearer sk_your_api_key_here"
Response Format
All API responses follow a consistent JSON envelope:
✅ Success
json{ "success": true, "data": { /* domain object */ }, "meta": { "request_id": "uuid", "rate_limit_limit": 1000, "rate_limit_used": 42, "rate_limit_remaining": 958, "usage_warning": null } }
❌ Error
json{ "success": false, "error": "Human-readable message", "code": "ERROR_CODE", "details": null }
Response Headers
| Header | Description |
|---|---|
X-Request-ID | Unique request identifier for tracing |
X-RateLimit-Limit | Monthly API call limit for the organization |
X-RateLimit-Used | API calls used this month |
X-RateLimit-Remaining | Calls remaining in the current period |
X-Usage-Warning | Present when usage ≥ 80%, value: approaching_limit |
Error Codes & HTTP Status
| HTTP Status | Code | Meaning |
|---|---|---|
400 | VALIDATION_ERROR | Request body or parameters are invalid |
400 | INVALID_JSON | Request body is not valid JSON |
400 | INVALID_BARCODE | Barcode format is invalid |
400 | INVALID_IMAGE | Image is not a valid JPEG (magic bytes are checked) |
401 | UNAUTHORIZED | Missing or invalid API key |
403 | FORBIDDEN | Valid auth but insufficient permission |
404 | NOT_FOUND | Resource does not exist |
409 | IDEMPOTENCY_CONFLICT | A request with this Idempotency-Key is already in progress |
413 | BODY_TOO_LARGE | Request body exceeds the 10 MB limit (a decoded image is capped at 3 MB) |
429 | RATE_LIMITED | Monthly API call limit exceeded |
500 | SERVER_ERROR | Unexpected server error |
502 | VISION_UNAVAILABLE | The vision model failed or timed out |
502 | AI_UNAVAILABLE | The chat model is unavailable; retry shortly |
502 | UPSTREAM_ERROR | A downstream service (mesh generation, AI) is unreachable |
503 | NOT_CONFIGURED | An optional integration is not enabled for this deployment |
Rate Limits
Rate limits are enforced on a monthly basis per organization. The limits depend on your subscription tier:
| Tier | Monthly Limit | Price |
|---|---|---|
| Free | 100 calls | Free |
| Starter | 1,000 calls | $29/month |
| Pro | 10,000 calls | $99/month |
| Enterprise | 100,000 calls | Custom pricing |
When you exceed 80% of your monthly limit, the response includes a
X-Usage-Warning: approaching_limit header. Once the limit is exceeded,
subsequent requests return 429 RATE_LIMITED until the next billing cycle.
Need more than 100,000 calls per month? Contact sales for custom volume limits tailored to your deployment.
Endpoints
GET /health No Auth
Returns server health and status information. Useful for monitoring and connectivity checks. The database pool is probed on every call; if the database or the vision provider is unavailable the endpoint returns 503 with "status": "degraded". This endpoint is not metered and returns no meta object.
cURL Example
curlcurl https://api.bodify.fit/health
Response
json{ "success": true, "data": { "status": "ok", "version": "0.1.0", "uptime": 1234.5, "environment": "production", "database": "connected", "vision_ready": true, "db_pool": { "active_connections": 3, "latency_ms": 12 } } }
GET /food/search?query=<query> Auth Required
Search the nutrition database for food items.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Search text (e.g. "chicken breast") |
limit | number | No | Results per page (default: 20, max: 100) |
source | string | No | Data source: local, openfoodfacts, usda, or all (default) |
page | number | No | Page number (default: 1) |
cURL Example
curlcurl "https://api.bodify.fit/food/search?query=chicken+breast&limit=5&source=openfoodfacts" \ -H "Authorization: Bearer sk_your_api_key"
Response
json{ "success": true, "data": { "data": [ { "id": "uuid", "name": "Chicken Breast, Boneless Skinless", "brand": "Perdue", "calories_100g": 165, "protein_g_100g": 31, "carbs_g_100g": 0, "fat_g_100g": 3.6, "fiber_g_100g": 0, "sugar_g_100g": 0, "sodium_mg_100g": 74, "_source": "usda" } ], "total": 42, "page": 1, "limit": 5, "has_more": true }, "meta": { /* rate limit info */ } }
data field in the response contains a nested data array with the search results. The total, page, limit, and has_more fields provide pagination metadata.
GET /barcode/<barcode> Auth Required
Look up a product by its barcode (EAN-13 or UPC). Returns detailed nutrition information per 100g.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
barcode | string | Yes | 8–13 digit numeric barcode (EAN-13 or UPC) |
cURL Example
curlcurl https://api.bodify.fit/barcode/5901234123457 \ -H "Authorization: Bearer sk_your_api_key"
Response
json{ "success": true, "data": { "id": "uuid", "name": "Whole Milk", "brand": "Organic Valley", "barcode": "5901234123457", "calories_100g": 61, "protein_g_100g": 3.2, "carbs_g_100g": 4.8, "fat_g_100g": 3.3, "fiber_g_100g": 0, "sugar_g_100g": 4.8, "sodium_mg_100g": 50, "serving_size": "1 cup (240ml)", "image_url": "https://...", "source": "open_food_facts" }, "meta": { /* rate limit info */ } }
POST /log Auth Required
Create a food log entry for a user. If the user_external_id does not exist in your organization, it will be auto-created.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
user_external_id | string | Yes | Your own unique user identifier |
food_name | string | Yes | Name of the food |
source | string | Yes | photo, barcode, search, or manual |
serving_qty | number | Yes | Number of servings (e.g. 1.5) |
meal_type | string | Yes | breakfast, lunch, dinner, or snack |
calories | number | No | Total calories for the entry |
protein_g | number | No | Total protein in grams |
carbs_g | number | No | Total carbohydrates in grams |
fat_g | number | No | Total fat in grams |
serving_unit | string | No | Unit (default: "serving") |
brand | string | No | Brand name (optional) |
barcode | string | No | Associated barcode |
logged_at | string | No | ISO 8601 timestamp (default: now) |
cURL Example
curlcurl https://api.bodify.fit/log \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "user_external_id": "user_abc123", "food_name": "Grilled Chicken Salad", "source": "manual", "serving_qty": 1, "meal_type": "lunch", "calories": 420, "protein_g": 35, "carbs_g": 12, "fat_g": 24 }'
Response
json{ "success": true, "data": { "id": "log_entry_uuid", "user_id": "internal_user_uuid", "org_id": "org_uuid", "food_name": "Grilled Chicken Salad", "meal_type": "lunch", "source": "manual", "serving_qty": 1, "serving_unit": "serving", "calories": 420, "protein_g": 35, "carbs_g": 12, "fat_g": 24, "logged_at": "2026-05-07T22:00:00Z", "created_at": "2026-05-07T22:00:00Z" }, "meta": { /* rate limit info */ } }
GET /log/<user_external_id> Auth Required
Retrieve all food log entries for a given user. Only returns entries that have not been deleted (soft delete).
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
date | string | No | Filter by date in YYYY-MM-DD format |
cURL Example
curlcurl "https://api.bodify.fit/log/user_abc123?date=2026-05-07" \ -H "Authorization: Bearer sk_your_api_key"
Response
json{ "success": true, "data": [ { "id": "log_entry_uuid", "food_name": "Grilled Chicken Salad", "meal_type": "lunch", "calories": 420, "logged_at": "2026-05-07T22:00:00Z" // ... full FoodLogEntry object } ], "meta": { /* rate limit info */ } }
DELETE /log/<id> Auth Required
Delete a food log entry. This is a soft delete: the entry is hidden from GET /log but retained in the database for recovery and auditing.
cURL Example
curlcurl -X DELETE https://api.bodify.fit/log/log_entry_uuid \ -H "Authorization: Bearer sk_your_api_key"
Response
json{ "success": true, "data": { "deleted": true }, "meta": { /* rate limit info */ } }
POST /analyze/photo Auth Required
Analyze a food photo using a vision AI model. The model identifies foods, estimates portion sizes, and calculates nutritional breakdown.
image_base64 string must be a valid JPEG image encoded as base64. PNG and other formats are not accepted. The decoded image size must not exceed 3 MB.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
image_base64 | string | Yes | Base64-encoded JPEG image only (max 3 MB decoded) |
meal_type | string | No | breakfast, lunch, dinner, or snack |
cURL Example
curlcurl https://api.bodify.fit/analyze/photo \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "image_base64": "/9j/4AAQSkZJRg...base64_encoded_image_data...", "meal_type": "dinner" }'
Response
json{ "success": true, "data": { "foods": [ { "name": "Grilled Chicken Breast", "estimated_portion": "150g", "calories": 248, "protein_g": 46.5, "carbs_g": 0, "fat_g": 5.2, "fiber_g": 0, "confidence": "high" }, { "name": "Roasted Vegetables", "estimated_portion": "100g", "calories": 85, "protein_g": 2.1, "carbs_g": 15, "fat_g": 2.5, "fiber_g": 4, "confidence": "medium" } ], "meal_description": "A grilled chicken breast with roasted vegetables" }, "meta": { "cost": 0.005, "tokens": { "prompt": 381, "completion": 69 }, "latency_ms": 2340, "request_id": "req_abc123" } }
POST /body/scan Auth Required
Generate a fitted 3D body mesh from anthropometric measurements. Send the
measurements you already hold and the API returns the scan, its measurement set, and
a summary of the generated mesh. Download the mesh itself with
GET /body/mesh/<id>. Body scans are metered in their own monthly
bucket, separate from your general call quota.
consent field must be exactly
true on every call. It is our record that the person whose body was
measured agreed to those measurements being used to generate a mesh.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
height_cm | number | Yes | Standing height, 100–250. This is the scale anchor for the whole mesh, so measure it rather than estimate it. |
weight_kg | number | Yes | Body weight in kilograms, 20–300 |
gender | integer | Yes | 0 = male, 1 = female. Used only to fit the mesh proportions, never returned as a derived attribute. |
chest_cm | number | Yes | Chest circumference, 50–200 |
waist_cm | number | Yes | Waist circumference, 40–200 |
hips_cm | number | Yes | Hip circumference, 50–200 |
fit_iterations | integer | No | 0–20, default 0. Higher values refine the fit at the cost of latency. |
consent | boolean | Yes | Must be exactly true, otherwise the request is rejected with VALIDATION_ERROR |
capture_mode | string | No | Optional label describing how the measurements were taken (for example manual or photo). Stored, never echoed back. |
Headers
| Header | Required | Description |
|---|---|---|
Idempotency-Key | No | Any unique string. Re-sending the same key after success replays the stored result without charging your quota again; re-sending it while a scan is still queued or running returns 409 IDEMPOTENCY_CONFLICT. |
cURL Example
curlcurl -X POST https://api.bodify.fit/body/scan \ -H "Authorization: Bearer sk_bodify_your_key" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3f8a1c2e-9b4d-4f10-8a77-1c2d3e4f5a6b" \ -d '{ "height_cm": 175, "weight_kg": 70.5, "gender": 1, "chest_cm": 92, "waist_cm": 74, "hips_cm": 96, "consent": true }'
Response
json{ "success": true, "data": { "id": "9c1f4a7e-2b85-4d63-9a10-3e7b5c8d4021", "status": "succeeded", "model_version": "body-1", "measurements": { "height_cm": 175, "weight_kg": 70.5, "gender": 1, "chest_cm": 92, "waist_cm": 74, "hips_cm": 96 }, "fit_iterations": 0, "mesh": { "format": "obj", "byte_size": 183442, "checksum": "sha256:6f1c8a...", "vertex_count": 13718, "face_count": 27420 }, "usage": { "body_scans_used": 1, "body_scans_limit": 5, "body_scans_remaining": 4 } }, "meta": { /* request_id, rate-limit headers */ } }
429 RATE_LIMITED from this endpoint means the
monthly body-scan limit is exhausted, not your general call quota. The response
carries a Retry-After header in seconds.
GET /body/mesh/<id> Auth Required
Fetch one scan and, when it has succeeded, the generated mesh. The mesh is returned as
mesh.content_base64 — a base64-encoded .obj file inside
the JSON envelope. There is no separate download URL: decode the string and write it to
a .obj file. Meshes are scoped to your organization, and an id belonging
to another organization is reported as 404 NOT_FOUND, exactly like an id
that does not exist.
cURL Example
curlcurl https://api.bodify.fit/body/mesh/9c1f4a7e-2b85-4d63-9a10-3e7b5c8d4021 \ -H "Authorization: Bearer sk_bodify_your_key"
Response — scan succeeded
json{ "success": true, "data": { "id": "9c1f4a7e-2b85-4d63-9a10-3e7b5c8d4021", "status": "succeeded", "created_at": "2026-09-19T18:04:11.512Z", "completed_at": "2026-09-19T18:04:18.077Z", "model_version": "body-1", "fit_iterations": 0, "measurements": { /* height_cm, weight_kg, gender, chest_cm, waist_cm, hips_cm */ }, "mesh": { "format": "obj", "byte_size": 183442, "checksum": "sha256:6f1c8a...", "vertex_count": 13718, "face_count": 27420, "content_base64": "IyBPYmpmaWxlIGdlbmVyYXRlZCBieSBib2RpZnkuZml0 ..." } } }
Response — still queued, running, or failed
json{ "success": true, "data": { "id": "9c1f4a7e-2b85-4d63-9a10-3e7b5c8d4021", "status": "failed", "error_code": "UPSTREAM_TIMEOUT", "measurements": { /* echo of what you submitted */ }, "created_at": "2026-09-19T18:04:11.512Z", "completed_at": null } }
GET /body/progress Auth Required
Paginated history of your organization's body scans, newest first — the measurement series to plot body-composition progress over time.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | No | Scans per page, 1–50 (default 20) |
offset | number | No | Number of scans to skip (default 0) |
cURL Example
curlcurl "https://api.bodify.fit/body/progress?limit=20&offset=0" \ -H "Authorization: Bearer sk_bodify_your_key"
Response
json{ "success": true, "data": { "scans": [ { "id": "9c1f4a7e-2b85-4d63-9a10-3e7b5c8d4021", "status": "succeeded", "model_version": "body-1", "vertex_count": 13718, "face_count": 27420, "error_code": null, "measurements": { /* the six measurements */ }, "fit_iterations": 0, "created_at": "2026-09-19T18:04:11.512Z", "completed_at": "2026-09-19T18:04:18.077Z" } ], "total": 7, "limit": 20, "offset": 0 } }
DELETE /body/scans Auth Required
Permanently delete every body scan and stored mesh belonging to your organization, and return how many records and mesh files were removed. This is the self-service deletion path for body data; use it when a person withdraws consent or when you no longer need the history.
meshes_deleted count will be lower than
scans_deleted.
cURL Example
curlcurl -X DELETE https://api.bodify.fit/body/scans \ -H "Authorization: Bearer sk_bodify_your_key"
Response
json{ "success": true, "data": { "scans_deleted": 7, "meshes_deleted": 7 } }
Existing usage records are not refunded: the scan was already computed. Deleting a scan does not free a slot in the monthly body-scan quota.
POST /chat Auth Required
A nutrition-focused chat endpoint. Send the conversation so far and receive a reply in the same register, optionally with concrete food suggestions the client can act on. Every call counts toward your monthly call quota.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
messages | array | Yes | 1–20 items of { "role": "user" | "assistant", "content": "..." }, each message 1–2000 characters. Send the full history you want considered; the API is stateless. |
meal_type | string | No | breakfast, lunch, dinner or snack |
calorie_goal | integer | No | Daily target, 500–10000 |
user_profile | object | No | Optional context for better answers: age, sex, weight_kg, height_cm, activity (sedentary, light, moderate, active, very_active), dietary_preferences, allergies. Include only what the user has agreed to share. |
cURL Example
curlcurl -X POST https://api.bodify.fit/chat \ -H "Authorization: Bearer sk_bodify_your_key" \ -H "Content-Type: application/json" \ -d '{ "messages": [{ "role": "user", "content": "Suggest a 500-calorie high-protein lunch" }], "meal_type": "lunch", "calorie_goal": 1800 }'
Response
json{ "success": true, "data": { "reply": "A grilled chicken breast with quinoa and roasted vegetables gets you there...", "suggested_foods": [ { "name": "Grilled Chicken Breast", "serving": "150g", "serving_qty": 1, "serving_unit": "breast", "calories": 248, "protein_g": 46.5, "carbs_g": 0, "fat_g": 5.2, "fiber_g": 0, "sugar_g": 0, "sodium_mg": 74, "meal_type": "lunch" } ], "actions": [ { "type": "add_food", "food_index": 0, "meal_type": "lunch" } ] }, "meta": { "cost": 0.0021, "tokens": { "prompt": 412, "completion": 96 }, "model": "bodify-text", "latency_ms": 1180, "request_id": "req_7f3c91" } }
actions tells you which suggested
food the reply was recommending and for which meal, so a client can offer a one-tap
"add to log" that then calls POST /log. add_food is the only
action type today.
GET /usage Auth Required
Retrieve current usage statistics for your organization, including call counts, cost estimates, and daily breakdown.
cURL Example
curlcurl https://api.bodify.fit/usage \ -H "Authorization: Bearer sk_your_api_key"
Response
json{ "success": true, "data": { "org_id": "9c1f4a7e-2b85-4d63-9a10-3e7b5c8d4021", "period": "current_month", "monthly_limit": 1000, "usage": { "total_calls": 342, "total_cost": 1.28, "photo_analyses": 120, "barcode_scans": 64, "text_searches": 158 }, "daily_breakdown": [ { "date": "2026-09-18", "total_api_calls": 88, "photo_analyses": 31, "barcode_scans": 12, "text_searches": 45, "estimated_cost": 0.31 } ] } }
Contact & Support
Have questions, need help integrating, or want to report an issue? Our team is here to help.
Send us a message at support@bodify.fit and we'll get back to you within 24 hours.
bodify.fit API v0.1.0
↑ Back to Top