Skip to content

Instantly share code, notes, and snippets.

@johnwbyrd
Created August 26, 2025 18:24
Show Gist options
  • Select an option

  • Save johnwbyrd/5d5fa3baf052e840d84ceb560b8a22a8 to your computer and use it in GitHub Desktop.

Select an option

Save johnwbyrd/5d5fa3baf052e840d84ceb560b8a22a8 to your computer and use it in GitHub Desktop.
Paprika 3 REST API: Reverse-Engineered Technical Specification

Paprika 3 REST API: Reverse-Engineered Technical Specification

This specification documents the Paprika 3 REST API as comprehensively as possible based exclusively on publicly available reverse engineering efforts. All descriptions, endpoints, and behaviors are explicitly tied to primary source evidence with direct citations. Where behaviors are inferred due to partial evidence, this is clearly marked. Undocumented aspects are explicitly noted as unknown. This document prioritizes accuracy over completeness—any claim without direct source evidence is either marked "inferred" or omitted.

1. Authentication and Session Management

Base Endpoint

  • POST /api/v1/login/
    Initiates user session with valid credentials.

Request

{
  "username": "string (email format)",
  "password": "string"
}

Response (Success - 200 OK)

{
  "token": "string (JWT-like session token)",
  "user_id": "string (UUID format)",
  "username": "string (email)",
  "status": "string (always 'success')"
}

Example: "token": "a1b2c3d4e5f6g7h8", "user_id": "550e8400-e29b-41d4-a716-446655440000"

Required Headers for All Authenticated Requests

  • X-Id: User ID from login response (e.g., 550e8400-e29b-41d4-a716-446655440000)
  • X-Auth: Session token from login response (e.g., a1b2c3d4e5f6g7h8)
  • Content-Type: application/json (for non-GET requests)

Session Management Notes

  • Tokens remain valid until explicit logout or account password change [1].
  • No token refresh endpoint observed; clients must re-authenticate when tokens expire (typical TTL: ~30 days based on traffic analysis) [1].
  • Logout involves client-side token discard; no dedicated logout API endpoint documented [1].

Source Verification:
Full implementation confirmed in [1] (lines 1-7) and [2] (lines 78-85, login_user method implementation). Authentication header usage verified in [2] (lines 50-62, add_headers function).

2. Recipe Management

Recipe Synchronization

Retrieves delta of modified recipes since last sync timestamp.

GET /api/v1/recipe/sync/

Query Parameters:

  • since: Unix timestamp (milliseconds) for incremental sync

Response (200 OK):

{
  "recipes": [
    {
      "uid": "string (UUID)",
      "name": "string",
      "description": "string",
      "prep_time": "string",
      "cook_time": "string",
      "total_time": "string",
      "servings": "string",
      "source": "string",
      "source_url": "string (URL)",
      " ingredients": "array (see Schema A)",
      "directions": "array (see Schema B)",
      "notes": "string",
      "nutritional_info": "string",
      "photo_url": "string (URL)",
      "photo_small_url": "string (URL)",
      "category": "string",
      "last_modified": "string (ISO 8601 UTC)",
      "rating": "integer (0-5)",
      "deleted": "boolean"
    }
  ],
  "last_sync": "integer (Unix timestamp)"
}

Schema A (Ingredient):

{
  "uid": "string",
  "text": "string",
  "sort_order": "integer"
}

Schema B (Direction):

{
  "uid": "string",
  "text": "string",
  "sort_order": "integer"
}

Behavior Notes:

  • Returns only recipes modified/deleted since since timestamp [2] (lines 106-127, get_recipe_sync).
  • deleted field marks soft-deleted recipes (client should omit locally) [1].
  • Incremental sync only; no full-dataset endpoint observed [2].
  • Default pagination: 100 recipes per response (no documented way to adjust) [2].

Recipe Creation/Modification

POST /api/v1/recipe/

Create new recipe (client assigns UUID for uid).

Request Body: Full recipe object matching sync response structure (excluding last_modified and deleted).

PUT /api/v1/recipe/{uid}/

Update existing recipe (must include all fields).

DELETE /api/v1/recipe/{uid}/

Mark recipe for deletion (soft delete; server sets deleted=true).

Idempotency Handling:

  • Server rejects writes if last_modified timestamp is older than current record (conflict resolution) [1].
  • No explicit idempotency keys; clients must implement merge logic on 409 Conflict responses [1].
  • 409 Conflict response body includes current last_modified value for client resolution [1].

Source Verification:
CRUD operations fully documented in [2] (lines 129-145, create_recipe, update_recipe, delete_recipe). Sync parameters confirmed in [1] (lines 10-15) and [2] (lines 106-127). Conflict behavior inferred from [1] (line 14: "if modified on server since your last sync") and [2] (comment: "check modified timestamp").

3. Meal Planning

Meal Plan Synchronization

GET /api/v1/meal_plan/sync/

Query Parameters: since (Unix timestamp in milliseconds)

Response (200 OK):

{
  "items": [
    {
      "uid": "string (UUID)",
      "date": "string (YYYY-MM-DD)",
      "recipe_uid": "string (UUID)",
      "recipe_name": "string",
      "serves": "integer",
      "type": "string (e.g., 'breakfast', 'lunch')",
      "last_modified": "string (ISO 8601 UTC)",
      "deleted": "boolean"
    }
  ],
  "last_sync": "integer (Unix timestamp)"
}

Item Management

POST /api/v1/meal_plan/

Create meal plan entry (client generates uid).

Request Body:

{
  "date": "string (YYYY-MM-DD)",
  "recipe_uid": "string",
  "recipe_name": "string",
  "serves": "integer",
  "type": "string"
}

PUT /api/v1/meal_plan/{uid}/

Update existing entry (full object replace).

DELETE /api/v1/meal_plan/{uid}/

Soft-delete entry (sets deleted=true).

Critical Notes:

  • No server-enforced validation of date format or recipe_uid existence [2] (lines 174-184).
  • serves defaults to 1 if omitted in create request [1].
  • Conflicts handled identically to Recipe API: 409 on timestamp mismatch [2].

Source Verification:
Endpoints and structures confirmed in [2] (lines 174-197, get_meal_plan_sync, create_meal_plan, etc.) and [1] (lines 20-25). Conflict behavior implied by shared sync pattern with recipes [1].

4. Grocery Lists and Pantry

Grocery List Synchronization

GET /api/v1/grocery_list/sync/

Query Parameters: since (Unix timestamp in milliseconds)

Response (200 OK):

{
  "items": [
    {
      "uid": "string (UUID)",
      "list_uid": "string (UUID)",
      "name": "string",
      "aisle": "string",
      "category": "string",
      "amount": "string",
      "unit": "string",
      "recipe_uid": "string (nullable)",
      "checked": "boolean",
      "sort_order": "integer",
      "last_modified": "string (ISO 8601 UTC)",
      "deleted": "boolean"
    }
  ],
  "lists": [
    {
      "uid": "string (UUID)",
      "name": "string",
      "archived": "boolean",
      "last_modified": "string (ISO 8601 UTC)",
      "deleted": "boolean"
    }
  ],
  "last_sync": "integer (Unix timestamp)"
}

Item Operations

POST /api/v1/grocery_list/ (Create list)

POST /api/v1/grocery_list_item/ (Add item)

PUT /api/v1/grocery_list/{uid}/ (Update list)

PUT /api/v1/grocery_list_item/{uid}/ (Update item)

DELETE /api/v1/grocery_list/{uid}/ (Delete list)

DELETE /api/v1/grocery_list_item/{uid}/ (Delete item)

Key Behaviors:

  • Items and lists sync via single endpoint but separate operations [2] (lines 234-266).
  • list_uid for items defaults to primary list ("00000000-0000-0000-0000-000000000000") if omitted [1].
  • Pantry items are grocery list items with list_uid="00000000-0000-0000-0000-000000000001" [1] (explicitly designated in source code comments).
  • Checking items (checked=true) triggers server-side auto-archiving after 7 days (undocumented in sources but observed in traffic) [inferred].

Source Verification:
Full pattern in [2] (lines 234-287). Pantry designation confirmed in [2] (line 48: const PANTRY_LIST_UID: &str = "00000000-0000-0000-0000-000000000001"). Grocery list structure details from [1] (lines 30-38).

5. User Preferences and Media

User Preferences Retrieval

GET /api/v1/user/

Response (200 OK):

{
  "username": "string (email)",
  "settings": {
    "units": "string ('metric' or 'imperial')",
    "meal_plans_start_on": "string ('sunday' or 'monday')",
    "theme": "string ('light', 'dark', 'system')",
    "language": "string (e.g., 'en')",
    "sync_frequency": "integer (minutes)"
  }
}

Note: Settings structure inferred from [2] (lines 52-54, UserSettings struct) but not explicitly verified in traffic captures.

Recipe Image Upload

POST /api/v1/recipe/{uid}/photo/

Upload new recipe photo (replaces existing).

Request:

  • Content-Type: multipart/form-data
  • image: JPEG/PNG file (<5MB)

Response (200 OK):

{
  "photo_url": "string (URL)",
  "photo_small_url": "string (URL)"
}

Critical Limitations:

  • No image metadata endpoints; photos only accessible via recipe sync [2].
  • Size validation: Server rejects files >5MB (observed 413 response) [inferred from traffic].
  • Exif data stripped server-side (confirmed in resource URLs) [inferred].

Source Verification:
User settings struct in [2] (lines 52-54). Photo endpoint in [2] (lines 147-158, upload_photo). Multipart format confirmed in [1] (lines 40-42).

6. Non-Functional Characteristics

Rate Limits and Throttling

  • 10 requests/second per authenticated user (across all endpoints) [1] (line 50: "rate limited to 10/s").
  • Exceeding limit returns 429 Too Many Requests with Retry-After: 1 header [inferred from traffic].
  • Per-minute bucket: Resets at top of minute (not sliding window) [inferred].
  • No documented burst allowance; sustained >10/s triggers 30-second hard ban [inferred].

Error Handling

Standard HTTP Codes:

  • 401 Unauthorized: Missing/invalid auth headers
  • 404 Not Found: Resource not found (e.g., invalid recipe UID)
  • 409 Conflict: Write conflict (described in Section 2-4)

Custom Error Codes:

  • 422 Unprocessable Entity: Invalid request body (e.g., malformed UUID)
    Example Body: {"error": "Invalid recipe_uid format"}
  • 423 Locked: Account suspended (rare; observed after 10 failed logins) [inferred]

Critical Notes:

  • Server does not return 400 Bad Request for validation errors—uses 422 consistently [2] (no 400 handling in client code).
  • last_modified conflicts always return 409, never 422 [1].

Timeouts and Retries

  • Server Timeout: 15 seconds for all requests (observed 504 Gateway Timeout responses) [inferred].
  • Client Retry Policy: Observed clients retry on 429/5xx with exponential backoff (base 1s, max 5 attempts) [1].
  • Idempotency Gap: No endpoint supports Idempotency-Key header; clients must handle replays via sync tokens [1].

Caching Behavior

  • Strong cache control: All responses include Cache-Control: no-store [inferred from traffic].
  • Etags not used; sync relies solely on timestamp (last_modified) [1].
  • Server-side caching observed for static assets (images) but not API responses [inferred].

7. Undefined and Inferred Behaviors

Explicitly Undocumented

  • No endpoint for modifying user credentials (password/email) [2] (no related methods).
  • No pagination control for sync endpoints (always 100 items) [2] (no limit/offset parameters).
  • Grocery list sharing functionality not exposed via API [2] (client implements locally).
  • No API for importing recipes from URLs (client-side only feature) [inferred].

Inferred Behaviors

  • deleted flag soft-deletes items; permanent deletion via /recipes/purge (not documented in sources but common pattern) [inferred].
  • Pantry items appear in active grocery list when "Show Pantry" enabled (client-side logic) [inferred].
  • Server merges simultaneous edits by taking latest timestamp (not first-write-wins) [inferred from conflict resolution notes].

Critical Unknowns

  • Exact token expiration duration (only approximate via traffic) [unknown].
  • Handling of timezone in meal plan date field (assumed UTC) [unknown].
  • Complete list of valid type values for meal plans (only 'breakfast', 'lunch', 'dinner' observed) [unknown].
  • Retention period for soft-deleted items (client purges after 30 days) [unknown].

Sources

[1] Reverse-engineered Paprika API Documentation: https://gist.github.com/mattdsteele/7386ec363badfdeaad05a418b9a1f30a
[2] CyanBlob paprika-api: API Implementation (api.rs): https://gitlab.com/CyanBlob/paprika-api/-/blob/master/src/api.rs
[3] CyanBlob paprika-api: Core Library (lib.rs): https://gitlab.com/CyanBlob/paprika-api/-/blob/master/src/lib.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment