Bitefull API
v1 · Live beta

Bitefull API

A REST API for recipes, AI recipe import, meal planning, shopping lists, and pantry management.

Discover and author recipes, import them from any link or photo, generate weekly meal plans around dietary goals, turn plans into de-duplicated shopping lists, and track pantry inventory. JSON over HTTPS.

762published recipes
42cuisines
100%with images & nutrition
The API is live in production and serving traffic. It is a beta: minor response-shape changes may still land before the stable v1 freeze — breaking changes will ship under /v2.

Base URL & versioning

Every endpoint lives under a versioned prefix:

https://api.kyox.io/bitefull/v1

The version is part of the path. Breaking changes ship under a new version (/v2) while older versions keep working. The interactive reference is at /bitefull/v1/docs.

Authentication

Bitefull uses two layers. An API key identifies the calling app or account and is required on every request. A user token (JWT) identifies an end-user and is required only on user-scoped routes.

API key

Send your key in the X-API-Key header on every request. Keys gate access to the API and are how usage is metered per account.

X-API-Key: bk_live_<your-key>

Manage keys at /api-keys (create, list, revoke). A key's secret is shown once at creation — store it securely; it can't be retrieved again. Requests without a valid key return 401.

User token

Register or log in to receive a short-lived access token and a rotating refresh token. Send the access token alongside your API key on authenticated routes:

X-API-Key: bk_live_<your-key>
Authorization: Bearer <accessToken>

Access tokens expire after 15 minutes. Exchange a refresh token at POST /auth/refresh for a new pair. Refresh tokens rotate on every use — reusing an old one revokes the whole session family.

Quick start

# Register a user (API key required on every call)
curl -X POST https://api.kyox.io/bitefull/v1/auth/register \
  -H "X-API-Key: bk_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","username":"chef","password":"Secret123"}'

# Call a user-scoped route with both credentials
curl https://api.kyox.io/bitefull/v1/auth/me \
  -H "X-API-Key: bk_live_<your-key>" \
  -H "Authorization: Bearer <accessToken>"

Roles: USER, PREMIUM, ADMIN — used for authorization and rate-limit tiers.

Errors

Errors use standard HTTP status codes and a consistent JSON envelope:

{
  "statusCode": 401,
  "error": "UnauthorizedException",
  "message": "Invalid credentials",
  "path": "/bitefull/v1/auth/login",
  "timestamp": "2026-06-01T12:00:00.000Z"
}
CodeMeaning
400Bad request
401Missing or invalid token
403Authenticated but not allowed
404Resource not found
409Conflict (e.g. email taken)
422Validation failed (message is a list)
429Rate limit exceeded
503Feature not configured on this server (e.g. AI image import)

Rate limits

Limits scale with your tier, counted per rolling 60-second window:

TierRequests / minute
Anonymous60
USER300
PREMIUM / ADMIN1200

Exceeding the limit returns 429 Too Many Requests.

API Keys

Issue and manage the keys that gate API access. These routes require a user token; the very first key is minted server-side.

POST/api-keysCreate a key (secret shown once) auth
GET/api-keysList your keys auth
DELETE/api-keys/:idRevoke a key auth

Create a key

POST /api-keys
{ "name": "Acme production", "expiresInDays": 365 }

The response includes key exactly once — store it now. Optionally set rateLimitPerMin to override the tier limit for a paid plan.

Auth

POST/auth/registerCreate account, return tokens
POST/auth/loginAuthenticate, return tokens
POST/auth/refreshRotate refresh → new tokens
POST/auth/logoutRevoke a refresh token auth
GET/auth/meCurrent user auth

Account recovery & verification

POST/auth/password/forgotSend a password-reset token
POST/auth/password/resetSet a new password with the token
POST/auth/verify-emailConfirm an email address
POST/auth/verify-email/resendResend the verification email

Example response

{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "k3jf9...",
  "tokenType": "Bearer",
  "expiresIn": 900
}

Recipes

The catalog ships with 762 published recipes across 42 cuisines — every one with a real image, instructions, an ingredient list, and estimated per-serving nutrition.

GET/recipesList published (paginated)
GET/recipes/searchFull-text + filters
GET/recipes/categoriesBrowse facets + counts
GET/recipes/trendingPopular recipes
GET/recipes/recommendedPersonalized auth
GET/recipes/:idSingle recipe
POST/recipesCreate auth
PUT/recipes/:idUpdate (author/admin) auth
DELETE/recipes/:idDelete (author/admin) auth

Search

Combine free-text with structured filters. All parameters are optional:

ParamExampleNotes
qsalmonFull-text over title, cuisine, tags, description
dietVEGANRepeatable; recipe must match all
tagsbreakfastRepeatable; recipe must carry all (meal types, descriptors)
cuisineMediterranean
maxTime30Total minutes
minCalories / maxCalories300 / 700Per serving
difficultyEASYEASY · MEDIUM · HARD
sortratingrelevance · rating · popular · newest · quickest
page / limit1 / 20Pagination
curl "https://api.kyox.io/bitefull/v1/recipes/search?q=salmon&diet=PESCATARIAN&maxTime=40"

Filtering lives on /recipes/search. The plain GET /recipes list only paginates — it ignores cuisine, diet, tags and the other filters.

Browse & categories

GET /recipes/categories returns the facets for building browse menus and home sections. Each entry is { value, count } (meal types also include a label), plus a filterParam map naming the /recipes/search parameter that applies each facet:

FacetApply on /recipes/search via
cuisines?cuisine=
mealTypes?tags=
diets?diet=
difficulties?difficulty=
popularTags?tags=
curl "https://api.kyox.io/bitefull/v1/recipes/categories"
curl "https://api.kyox.io/bitefull/v1/recipes/search?cuisine=Thai&tags=dinner&sort=rating"

Create a recipe

POST /recipes
{
  "title": "Grilled Chicken Bowl",
  "servings": 2,
  "prepTime": 10, "cookTime": 20,
  "difficulty": "EASY",
  "cuisine": "American",
  "diets": ["DAIRY_FREE"],
  "tags": ["high-protein"],
  "ingredients": [
    { "name": "chicken breast", "quantity": 300, "unit": "GRAM" },
    { "name": "white rice", "quantity": 200, "unit": "GRAM" }
  ],
  "instructions": [
    { "step": 1, "text": "Cook the rice." },
    { "step": 2, "text": "Grill the chicken." }
  ],
  "publish": true
}

Per-serving nutrition (calories, protein, carbs, fat, fiber, sodium) is computed automatically from ingredient data.

Engagement

POST/recipes/:id/saveSave a recipe auth
DELETE/recipes/:id/saveUnsave a recipe auth
GET/recipes/savedYour saved recipes auth
POST/recipes/:id/cookLog a cook auth
POST/recipes/:id/rateRate 1–5 auth

AI Import

Turn a link or a photo into a pre-filled, editable recipe draft.

These routes need only your X-API-Key — no user token — so an app can offer import before sign-up. The response is a RecipeDraft that mirrors the POST /recipes body field-for-field: drop it into a review form, let the user fix anything, then create the recipe normally.

GET/import/statusWhich import features are enabled
POST/import/urlWeb page → RecipeDraft
POST/import/imagePhoto → RecipeDraft (vision)

Import from a link

# Most recipe sites embed schema.org Recipe JSON-LD — parsed directly, no LLM needed.
# Pages without structured data fall back to an LLM. Results are cached per URL for 24h.
curl -X POST https://api.kyox.io/bitefull/v1/import/url \
  -H "X-API-Key: bk_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/best-lasagna-recipe"}'

Import from a photo

# JPEG / PNG / WebP / HEIC, up to 10 MB. Dish photos, handwritten cards, printed pages.
curl -X POST https://api.kyox.io/bitefull/v1/import/image \
  -H "X-API-Key: bk_live_<your-key>" \
  -F "image=@recipe-photo.jpg"

The RecipeDraft

{
  "draft": { "title": "Best Lasagna", "servings": 8, "ingredients": [ ... ], ... },
  "source": "jsonld",
  "warnings": ["2 ingredient quantities could not be parsed"]
}

Check GET /import/status at app start. url is always true; when AI is not configured on the server, image is false and POST /import/image returns 503 — hide photo import in that case. Some publishers block automated fetches; a failed URL import returns a clear error the user can work around by importing a photo instead.

Ingredients

A curated, USDA-backed ingredient catalog with per-100g nutrition. It powers the automatic per-serving nutrition on recipes and is handy for typeahead in recipe editors.

GET/ingredientsSearch the catalog (typeahead) auth
curl "https://api.kyox.io/bitefull/v1/ingredients?q=chick" \
  -H "X-API-Key: bk_live_<your-key>" \
  -H "Authorization: Bearer <accessToken>"

q is optional — omit it to list the catalog. Ingredient icons are served from the public media CDN at https://api.kyox.io/bitefull/media/.

Meal plans

GET/meal-plansYour plans auth
POST/meal-plansCreate manually auth
POST/meal-plans/generateAuto-generate auth
GET/meal-plans/:idSingle plan auth
PUT/meal-plans/:idReplace entries auth
DELETE/meal-plans/:idDelete auth

Generate a plan

The generator fills each day and slot with recipes that satisfy dietary restrictions, hit a daily calorie band, prefer your cuisines and pantry-covered recipes, and avoid repeats. Unspecified options fall back to your saved preferences.

POST /meal-plans/generate
{
  "startDate": "2026-06-08",
  "days": 7,
  "slots": ["BREAKFAST", "LUNCH", "DINNER"],
  "diets": ["VEGAN"],
  "dailyCalorieGoal": 1800,
  "cuisines": ["Mediterranean"],
  "usePantry": true
}

Shopping lists

GET/shopping-listsYour lists auth
POST/shopping-listsCreate empty auth
POST/shopping-lists/generateFrom recipes/plan auth
PATCH/shopping-lists/items/:idTick off / adjust auth

Generate from a meal plan

Collects every ingredient across the chosen recipes (or a whole meal plan), normalizes units, aggregates duplicates, and subtracts what your pantry already covers.

POST /shopping-lists/generate
{ "mealPlanId": "8bc883fc-...", "subtractPantry": true }

Pantry

GET/pantryList items auth
POST/pantryAdd item auth
PATCH/pantry/:idUpdate item auth
DELETE/pantry/:idRemove item auth
POST /pantry
{ "ingredientName": "olive oil", "quantity": 500, "unit": "GRAM", "expirationDate": "2026-12-01" }

Users

GET/users/meProfile + preferences auth
PATCH/users/meUpdate profile/preferences auth
DELETE/users/meDeactivate account auth

Dietary preferences set here (diets, allergens, preferred cuisines, daily calorie goal) become the defaults for search, recommendations, and meal-plan generation.

Uploads

Recipe images upload directly to object storage via a presigned URL — image bytes never pass through the API. A background worker then generates optimized WebP variants.

POST/uploads/recipe-imagePresigned upload URL auth
POST/uploads/completeFinish → queue optimization auth

Upload flow

# 1. Ask for a presigned URL
POST /uploads/recipe-image
{ "contentType": "image/jpeg" }
#    → { "uploadUrl": "...", "key": "...", "publicUrl": "..." }

# 2. PUT the file bytes straight to uploadUrl (no API key needed)

# 3. Confirm — queues WebP variant generation and links the image
POST /uploads/complete
{ "key": "<key from step 1>", "recipeId": "<recipe uuid>" }

Health

GET/healthAPI + database status
{ "status": "ok", "checks": { "api": "up", "database": "up" }, "timestamp": "..." }