Core API

Recipes

Generate, list, and manage structured recipes. Generation is the flagship endpoint: it turns a set of constraints into a complete recipe with ingredients, instructions, macros, and tags.

Endpoints

GET/v1/recipesList recipes under your account
GET/v1/recipes/{uuid}Retrieve a stored recipe
POST/v1/recipes/generateGenerate a new AI recipe
POST/v1/recipes/{uuid}/generate-variationBranch a stored recipe you own (healthy/budget)
POST/v1/recipes/callbackAsync callback receiver (signed)
DELETE/v1/recipes/{uuid}Hard-delete (Sanctum/app token only)

Identifying recipes

Stored recipes are identified by uuid, the stable public identifier returned by GET /v1/recipes and GET /v1/recipes/{uuid}. Use it for GET, DELETE, and POST /v1/recipes/{uuid}/generate-variation. The id returned in a POST /v1/recipes/generate response is a throwaway per-request client handle for the mobile client. Generation is stateless, so pass "save": true to persist the recipe and receive its real uuid.

List recipes under your account

GET /v1/recipes

Returns every recipe owned by the identity behind your key (the API key's owning user). Paginated with ?per_page= (1-100, default 20).

{
  "data": [
    {
      "uuid": "9b1c2d7e-...-4a3f",
      "title": "One-Pan Indian Chicken & Rice",
      "cuisine": "indian",
      "difficulty": "Medium",
      "servings": 2,
      "prep_time_minutes": 10,
      "cook_time_minutes": 20,
      "instructions": ["Brown the chicken...", "Add spices...", "Simmer 18 min."],
      "nutrition_info": { "calories": 520, "protein_g": 38, "carbs_g": 48, "fat_g": 14 },
      "tags": ["indian", "gluten-free", "one-pan"],
      "is_favorite": false,
      "created_at": "2026-07-03T18:02:11.000000Z",
      "ingredients": [
        { "name": "chicken thigh", "quantity": "400", "unit": "g", "is_optional": false, "was_substituted": false }
      ]
    }
  ],
  "meta": { "total": 42, "per_page": 20, "current_page": 1, "last_page": 3 }
}

Retrieve a single recipe

GET /v1/recipes/{uuid}

Returns one stored recipe by uuid, scoped to your account. A recipe you don't own returns 404.

Generate a recipe

POST /v1/recipes/generate

The request body mirrors the Dishora mobile wizard: ingredients, available pantry, cuisine, dietary restrictions, allergies, time, skill level, servings, instruction style, and budget. Generation is synchronous: the response returns the complete recipe in one call, typically within a few seconds.

{
  "ingredients": ["chicken", "rice", "tomato"],
  "available_ingredients": ["onion", "garlic", "cumin"],
  "cuisine": "indian",
  "dietary_restrictions": ["gluten-free"],
  "allergies": [],
  "time_available": 30,
  "skill_level": "comfortable",
  "servings": 2,
  "instruction_style": "normal",
  "health_mode": "regular",
  "budget": "standard",
  "meal_type": "dinner",
  "use_pantry_only": false,
  "save": false
}

Branch a recipe (healthy / budget / method variation)

POST /v1/recipes/{uuid}/generate-variation

Generates a variation (healthy or budget) of a recipe you own, scoped to the calling identity. The new recipe is stored as a child of the parent (linked via parent_uuid) and the response returns the new recipe plus variation_type and the new recipe_uuid.

{
  "variation_type": "healthy",
  "health_focus": "highProtein",
  "tone": "normal",
  "dietary_restrictions": ["dairy-free"],
  "allergies": ["peanuts"]
}

variation_type is required and must be healthy, budget, or method. health_focus applies to healthy variations only and accepts general, highProtein, gutHealth, heartHealthy, or diabeticFriendly. With method, pass target_method (stovetop, oven, pressure_cooker, slow_cooker, air_fryer, grill) to rewrite the recipe for a different appliance. tone, dietary_restrictions, and allergies are forwarded to the AI but not strictly validated.

Errors

  • 404: no recipe with that uuid, or it isn't owned by the calling identity.
  • 422: variation_type must be healthy, budget, or method (with a valid target_method).
  • 429: rate limited.
  • 500: { "error": "Failed to generate variation. Please try again." }.

Persist on generate (opt-in)

Pass "save": true in the request body (or ?save=true) to have the API store the generated recipe under your account. The response then additionally includes the real uuid, so you can immediately call POST /v1/recipes/{uuid}/generate-variation or GET /v1/recipes/{uuid} without a separate lookup. Without save, generation stays stateless (the id in the response is a throwaway client handle for the mobile app).

Where recipes actually live

The Dishora mobile app is offline-first: every recipe generated in-app is written to the device's local SQLite store and is not pushed to the remote database. A recipe only enters the server-side recipes table through one of two paths:
  • Collections: adding a recipe to a collection (POST /collections/{id}/recipes) upserts the full recipe payload server-side. This is how the app persists recipes it wants to keep across devices.
  • Developer API: calling POST /v1/recipes/generate with "save": true stores the recipe under the key's owning account.
The POST /sync endpoint only reconciles is_favorite and image URIs by uuid. It never creates recipes.

Key parameters

  • ingredients: must-have focus ingredients (max 30).
  • available_ingredients: pantry items that may also be used (max 200).
  • time_available: minutes (1-480), or a preset: superQuick, quick, standard, relaxed, leisurely, iHaveTime.
  • servings: 1-20.
  • skill_level: beginner | comfortable | advanced.
  • instruction_style: normal | funny | romantic | motivational | minimal | detailed | brief | step_by_step. (tone is accepted as an alias.)
  • health_mode: regular | healthy | light.
  • budget: standard | budget.
  • craving_mode: exact (make the requested dish) | creative (use it as inspiration).
  • use_pantry_only: restrict the recipe to the provided ingredients plus basic staples.
  • save: persist the recipe and return its real uuid.
All output is validated for allergens and dietary violations before being returned. Requests that would violate a stated restriction are rejected or auto-corrected, never silently ignored.

Caching

Requests for a specific named dish (e.g. “paneer tikka”) are cached on the tiered SQLite + Redis store and don’t count against your AI quota. Vague cravings and wizard flows always generate fresh.