Developer Docs

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
Note: There is currently no /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"
Security: Never expose your API key in client-side code, version control, or public repositories. Store keys in environment variables or a secrets manager.

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-IDUnique request identifier for tracing
X-RateLimit-LimitMonthly API call limit for the organization
X-RateLimit-UsedAPI calls used this month
X-RateLimit-RemainingCalls remaining in the current period
X-Usage-WarningPresent when usage ≥ 80%, value: approaching_limit

Error Codes & HTTP Status

HTTP Status Code Meaning
400VALIDATION_ERRORRequest body or parameters are invalid
400INVALID_JSONRequest body is not valid JSON
400INVALID_BARCODEBarcode format is invalid
400INVALID_IMAGEImage is not a valid JPEG (magic bytes are checked)
401UNAUTHORIZEDMissing or invalid API key
403FORBIDDENValid auth but insufficient permission
404NOT_FOUNDResource does not exist
409IDEMPOTENCY_CONFLICTA request with this Idempotency-Key is already in progress
413BODY_TOO_LARGERequest body exceeds the 10 MB limit (a decoded image is capped at 3 MB)
429RATE_LIMITEDMonthly API call limit exceeded
500SERVER_ERRORUnexpected server error
502VISION_UNAVAILABLEThe vision model failed or timed out
502AI_UNAVAILABLEThe chat model is unavailable; retry shortly
502UPSTREAM_ERRORA downstream service (mesh generation, AI) is unreachable
503NOT_CONFIGUREDAn 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
Free100 callsFree
Starter1,000 calls$29/month
Pro10,000 calls$99/month
Enterprise100,000 callsCustom 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 /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
barcodestringYes8–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_idstringYesYour own unique user identifier
food_namestringYesName of the food
sourcestringYesphoto, barcode, search, or manual
serving_qtynumberYesNumber of servings (e.g. 1.5)
meal_typestringYesbreakfast, lunch, dinner, or snack
caloriesnumberNoTotal calories for the entry
protein_gnumberNoTotal protein in grams
carbs_gnumberNoTotal carbohydrates in grams
fat_gnumberNoTotal fat in grams
serving_unitstringNoUnit (default: "serving")
brandstringNoBrand name (optional)
barcodestringNoAssociated barcode
logged_atstringNoISO 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
datestringNoFilter 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 limits: The 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_base64stringYesBase64-encoded JPEG image only (max 3 MB decoded)
meal_typestringNobreakfast, 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 is required: the 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

FieldTypeRequiredDescription
height_cmnumberYesStanding height, 100–250. This is the scale anchor for the whole mesh, so measure it rather than estimate it.
weight_kgnumberYesBody weight in kilograms, 20–300
genderintegerYes0 = male, 1 = female. Used only to fit the mesh proportions, never returned as a derived attribute.
chest_cmnumberYesChest circumference, 50–200
waist_cmnumberYesWaist circumference, 40–200
hips_cmnumberYesHip circumference, 50–200
fit_iterationsintegerNo0–20, default 0. Higher values refine the fit at the cost of latency.
consentbooleanYesMust be exactly true, otherwise the request is rejected with VALIDATION_ERROR
capture_modestringNoOptional label describing how the measurements were taken (for example manual or photo). Stored, never echoed back.

Headers

HeaderRequiredDescription
Idempotency-KeyNoAny 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 */ }
}
Quota: a 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

ParameterTypeRequiredDescription
limitnumberNoScans per page, 1–50 (default 20)
offsetnumberNoNumber 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.

This is an organization-wide wipe. There is currently no endpoint for deleting a single scan or a single person's scans. Mesh-file removal is best effort: if a file cannot be removed at that moment the database record is still deleted, and the response's 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

FieldTypeRequiredDescription
messagesarrayYes1–20 items of { "role": "user" | "assistant", "content": "..." }, each message 1–2000 characters. Send the full history you want considered; the API is stateless.
meal_typestringNobreakfast, lunch, dinner or snack
calorie_goalintegerNoDaily target, 500–10000
user_profileobjectNoOptional 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"
  }
}
Acting on suggestions: 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.

Email Support

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