Skip to content

API reference

Read and write your recipes, collections, pantry, meal plan, grocery list and nutrition over REST. Needs Pro or Family; on a free account every request returns 403 PREMIUM_REQUIRED. See plans.

Base URLhttps://recipeclip.app/api/v1

Quickstart

Create a read-only API key, then list your most recent recipes:

curl https://recipeclip.app/api/v1/recipes \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

The response is a JSON list of your own saved recipes. has_more tells you whether there's another page; see Pagination.

json
{  "recipes": [    {      "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",      "title": "Miso-Glazed Salmon",      "description": "Pan-seared salmon with a sticky miso-honey glaze.",      "image_url": null,      "servings": "4",      "prep_time": "10 min",      "cook_time": "15 min",      "visibility": "private",      "created_at": "2026-08-20T17:32:10.482000+00:00",

Authentication

Send the key as a bearer token on every request.

Authorization headerAuthorization: Bearer rcp_live_YOUR_KEY

Can't set a custom Authorization header? Send X-API-Key: rcp_live_YOUR_KEY instead. Both are accepted the same way.

What a key can access

A key acts as its account. Listing, searching, and What can I cook only ever return recipes the account owns, and writes (create, update, delete, import) only ever touch recipes the account owns. Getting a single recipe by id can also reach a recipe shared into the account's family, though there's no endpoint that lists those ids, so you'd need to already know one.

Key format

A key is 65 characters: the fixed prefix rcp_live_, a 12-character public key id, an underscore, and a 43-character secret. The key id is safe to log. It is how a key is identified in the developer console and in error messages. The secret is shown once, at creation, and cannot be recovered afterward.

Scopes

Every key has one scope, chosen when it's created.

read
GET /api/v1, GET /api/v1/recipes, GET /api/v1/recipes/{id}, GET /api/v1/collections, GET /api/v1/cook, GET /api/v1/pantry, GET /api/v1/meal-plan, GET /api/v1/grocery-list, GET /api/v1/nutrition.
read_write
Everything read can, plus POST /api/v1/recipes, POST /api/v1/recipes/import, PATCH /api/v1/recipes/{id}, DELETE /api/v1/recipes/{id}, POST /api/v1/pantry, DELETE /api/v1/pantry, POST /api/v1/meal-plan, PATCH /api/v1/meal-plan/{id}, DELETE /api/v1/meal-plan/{id}, POST /api/v1/grocery-items, PATCH /api/v1/grocery-items, DELETE /api/v1/grocery-items.

A read-only key calling a write-only route gets 403 INSUFFICIENT_SCOPE, never a 401. The key is valid, it just can't do this.

Keys are secrets

Use a key from a server, a script, or an environment variable, never from browser JavaScript. /api/v1 and /api/mcp send no CORS headers on purpose, so a browser cannot read their responses cross-origin even if a key leaked into client code.

Errors

Every error is JSON: { error, code, ...extra }. Branch on the status and code, not the message text. The message can change; the code won't.

400
Code: INVALID_REQUEST
Bad request shape. Missing or non-JSON body, or a missing required query param (e.g. /cook's ingredients).
400
Code: PANTRY_EMPTY
GET /cook?pantry=true on an empty pantry. Add items first, or pass ingredients.
400
Code: INVALID_CURSOR
Malformed cursor. Pass back the previous page's next_cursor unmodified.
400
Code: VALIDATION_FAILED
Body parsed as JSON but failed schema validation (e.g. an empty title).
401
Code: AUTH_MISSING
No Authorization or X-API-Key header at all.
401
Code: AUTH_MALFORMED
Credential present but not shaped like rcp_live_<id>_<secret>.
401
Code: AUTH_INVALID
Well-shaped key, but the id is unknown or the secret is wrong.
401
Code: AUTH_REVOKED
The key exists but was revoked.
403
Code: PREMIUM_REQUIRED
The account is on the free plan. The REST API and MCP server are part of Pro and Family. The body also has upgrade_required: true and an upgrade_url.
403
Code: INSUFFICIENT_SCOPE
A read-only key called a route that needs the read_write scope.
403
Code: FORBIDDEN
The recipe is reachable only via family sharing. You can read it, not write it.
404
Code: NOT_FOUND
Unknown id, or not yours. Always 404, never 403, so the two cases look the same from outside.
404
Code: NOT_IN_FAMILY
plan=family was requested but the account is not in a Family.
409
Code: DUPLICATE_RECIPE
This source_url was already saved. Retry with allowDuplicate: true to save a second copy.
429
Code: RATE_LIMITED
Per-key (120/min) or per-account (300/min) request ceiling tripped.
429
Code: AI_RATE_LIMITED
The account's AI import allowance is spent (POST /recipes/import only).
502
Code: FETCH_FAILED
Couldn't fetch the source page (POST /recipes/import, after every fetch path failed).
500 / 502 / 503 / 504
Code: AI_*
The model provider failed (POST /recipes/import only). Every AI_* body includes retryable: true or false. Retry only when it's true.
500
Code: INTERNAL
Something went wrong on our end. Logged server-side; safe to retry.
503
Code: INTERNAL
Couldn't verify the API key (a lookup failure, not a bad key). Safe to retry.

Two more 429s guard the key lookup itself, not any one route: 600/minute on every lookup attempt from one address, valid or invalid (a shared-egress integration with several keys behind one IP can hit this one too), and a tighter 30/minute on failed attempts specifically. That one only trips with a bad key. Both carry RATE_LIMITED.

Rate limits

Two ceilings apply to every request: one per key, one per account (so minting more keys doesn't buy more throughput). The API is for Pro and Family accounts only, so these limits apply to paid plans; a free account gets a 403 on every request. The AI import endpoint also draws from its own allowance.

Per key
Limit: 120 / minute
Sliding window. One bucket per API key.
Per account
Limit: 300 / minute
Sliding window, across every key and MCP connection combined.
AI imports, Pro/Family
Limit: 100 / day
Resets daily. This endpoint has its own 100, separate from imports in the app and over MCP.

A response from a valid key includes your remaining budget:

text
X-RateLimit-Limit: 120X-RateLimit-Remaining: 119X-RateLimit-Reset: 1791295785Cache-Control: no-store

A 429 adds a Retry-After header, in seconds.

Pagination

List endpoints are cursor-paginated, not offset-paginated. The page you're on can't shift under you, even if new recipes are being saved at the same time.

limit
Shape: 1–100, default 25
Out-of-range or non-numeric silently clamps to the default or max, never errors.
cursor
Shape: opaque string
Pass back the previous page's next_cursor unmodified. A malformed one 400s with INVALID_CURSOR.

Every page response has next_cursor (string or null) and has_more. Keep following next_cursor until it's null:

Fetch every recipe
let cursor;const recipes = []; do {  const url = new URL("https://recipeclip.app/api/v1/recipes");  url.searchParams.set("limit", "100");  if (cursor) url.searchParams.set("cursor", cursor);   const res = await fetch(url, { headers: { Authorization: "Bearer rcp_live_YOUR_KEY" } });  const page = await res.json();   recipes.push(...page.recipes);  cursor = page.next_cursor;} while (cursor);

Response objects

Three shapes recur across endpoints. Every endpoint below links back here instead of repeating the field list.

Recipe

List or search recipes and What can I cook return the summary fields only. Get, Create, Update and Import from a URL return every field, including the detail only rows. Delete doesn't return a recipe at all, only { deleted, id }. See Delete a recipe below.

id
Type: stringNullable: noIn: summary
UUID.
title
Type: stringNullable: noIn: summary
1–500 characters.
description
Type: stringNullable: yesIn: summary
image_url
Type: stringNullable: yesIn: summary
servings
Type: string | numberNullable: yesIn: summary
prep_time
Type: string | numberNullable: yesIn: summary
cook_time
Type: string | numberNullable: yesIn: summary
visibility
Type: stringNullable: noIn: summary
"private" or "public", set in the app. There's no way to change it through this API yet.
created_at
Type: stringNullable: noIn: summary
ISO 8601.
updated_at
Type: stringNullable: yesIn: summary
url
Type: stringNullable: noIn: summary
The recipe's page in the RecipeClip app.
notes
Type: stringNullable: yesIn: detail only
Not on List or search. Only Get, Create, Update, Import.
source_type
Type: stringNullable: yesIn: detail only
e.g. "manual", "url".
source_url
Type: stringNullable: yesIn: detail only
video_url
Type: stringNullable: yesIn: detail only
ingredients
Type: object[]Nullable: no (can be empty)In: detail only
Each item: name, quantity, unit, raw_text, all strings, never null.
steps
Type: string[]Nullable: no (can be empty)In: detail only
In order.

Collection

id
Type: stringNullable: no
UUID.
name
Type: stringNullable: no
description
Type: stringNullable: yes
visibility
Type: stringNullable: no
"private" or "public".
recipe_count
Type: numberNullable: no
Recipes in the collection that aren't trashed.
created_at
Type: stringNullable: no
ISO 8601.
url
Type: stringNullable: no
Always https://recipeclip.app/collections. There's no per-collection page or recipe filter in this API yet.

Ingredient match

What can I cook returns one of these per recipe: every Recipe (summary) field above, plus:

match_percent
Type: numberNullable: no
0–100: share of the recipe's own ingredients you have, rounded.
matched_ingredients
Type: string[]Nullable: no
Ingredient names from the recipe that you have.
missing_ingredients
Type: string[]Nullable: no
Ingredient names from the recipe that you don't.

Endpoints

Every path below is shown in full. Append it to https://recipeclip.app.

Check your key

GET/api/v1Read only

No parameters. The cheapest way to confirm a key works and see your account's limits and the full endpoint list.

Request

curl https://recipeclip.app/api/v1 \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

Trimmed to 1 of 20 endpoints
{  "version": "v1",  "account": {    "user_id": "3f1c9a7e-2b4d-4a6e-9c8f-1a2b3c4d5e6f"  },  "key": {    "id": "AbC123dEf456",    "scope": "read"  },  "limits": {    "requests_per_minute": 120,    "default_page_size": 25,
Errors5
401
Code: AUTH_MISSING
No credential. See Authentication.
401
Code: AUTH_MALFORMED
Credential present but unparseable.
401
Code: AUTH_INVALID
Unknown key id or wrong secret.
401
Code: AUTH_REVOKED
The key was revoked.
429
Code: RATE_LIMITED
Too many requests.

Recipes

List or search recipes

GET/api/v1/recipesRead only

Your own recipes, newest first, optionally filtered by a title search. Returns the Recipe (summary) fields; see Response objects.

Parameters

search
Type: string (query)Required: no
Case-insensitive substring match against the title only. Stripped of , ( ) * % \ " ' and capped at 200 characters.
limit
Type: integer (query)Required: no
1–100. Default 25.
cursor
Type: string (query)Required: no
Opaque. Pass back the previous page's next_cursor.

Request

curl "https://recipeclip.app/api/v1/recipes?search=salmon&limit=10" \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "recipes": [    {      "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",      "title": "Miso-Glazed Salmon",      "description": "Pan-seared salmon with a sticky miso-honey glaze.",      "image_url": null,      "servings": "4",      "prep_time": "10 min",      "cook_time": "15 min",      "visibility": "private",      "created_at": "2026-08-20T17:32:10.482000+00:00",
Errors2
400
Code: INVALID_CURSOR
Malformed cursor.
429
Code: RATE_LIMITED
Too many requests.

Create a recipe

POST/api/v1/recipesRead and write

Create a recipe from JSON, for a page you already parsed yourself. To import directly from a URL, see Import from a URL below.

Parameters

title
Type: stringRequired: yes
Trimmed. 1–500 characters.
description
Type: string | nullRequired: no
Up to 5000 characters.
servings
Type: string | number | nullRequired: no
Up to 50 characters if a string.
prep_time
Type: string | number | nullRequired: no
Up to 50 characters if a string.
cook_time
Type: string | number | nullRequired: no
Up to 50 characters if a string.
source_type
Type: string | nullRequired: no
Up to 50 characters.
source_url
Type: string (http/https URL) | nullRequired: no
Up to 2000 characters.
source_book_title
Type: string | nullRequired: no
Up to 500 characters.
source_page_number
Type: string | number | nullRequired: no
Up to 20 characters if a string.
image_url
Type: string (http/https URL, or a same-origin relative path) | nullRequired: no
Up to 2000 characters.
notes
Type: string | nullRequired: no
Up to 10,000 characters.
video_url
Type: string (http/https URL) | nullRequired: no
Up to 2000 characters.
step_photos
Type: string[] | nullRequired: no
Up to 50 items, 2000 characters each.
ingredients
Type: object[] | nullRequired: no
Up to 200 items. Each item: name (≤300), unit (≤50), quantity (string ≤50 or number), raw_text (≤500). raw is also accepted as an alias for raw_text.
steps
Type: string[] | nullRequired: no
Up to 100 items, 5000 characters each.
allowDuplicate
Type: booleanRequired: no
Save a second copy even if this source_url was already saved. Default false.

Request

curl -X POST https://recipeclip.app/api/v1/recipes \  -H "Authorization: Bearer rcp_live_YOUR_KEY" \  -H "Content-Type: application/json" \  -d '{  "title": "Miso-Glazed Salmon",  "description": "Pan-seared salmon with a sticky miso-honey glaze.",  "servings": "4",  "prep_time": "10 min",  "cook_time": "15 min",  "ingredients": [    {      "name": "salmon fillets",      "quantity": "4",      "raw_text": "4 salmon fillets"    },    {      "name": "white miso paste",      "quantity": "2",      "unit": "tbsp",      "raw_text": "2 tbsp white miso paste"    },    {      "name": "honey",      "quantity": "1",      "unit": "tbsp",      "raw_text": "1 tbsp honey"    },    {      "name": "soy sauce",      "quantity": "1",      "unit": "tbsp",      "raw_text": "1 tbsp soy sauce"    }  ],  "steps": [    "Whisk the miso, honey and soy sauce into a glaze.",    "Brush the glaze over the salmon fillets.",    "Broil for 8-10 minutes, until caramelized and cooked through."  ]}'

Response

201 Created
{  "recipe": {    "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",    "title": "Miso-Glazed Salmon",    "description": "Pan-seared salmon with a sticky miso-honey glaze.",    "image_url": null,    "servings": "4",    "prep_time": "10 min",    "cook_time": "15 min",    "visibility": "private",    "created_at": "2026-08-20T17:32:10.482000+00:00",    "updated_at": "2026-08-20T17:32:10.482000+00:00",
Errors3
400
Code: VALIDATION_FAILED
A field failed validation, e.g. an empty title: { "error": "title: Title is required", "code": "VALIDATION_FAILED" }. An entirely missing field gives Zod's own message instead.
403
Code: INSUFFICIENT_SCOPE
Key is read-only.
409
Code: DUPLICATE_RECIPE
source_url already saved. Response includes existing: { id, title }.

Get a recipe

GET/api/v1/recipes/{id}Read only

One recipe, with its full ingredients (quantity, unit, raw text) and ordered steps.

Parameters

id
Type: string (path)Required: yes
Recipe id.

Request

curl https://recipeclip.app/api/v1/recipes/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "recipe": {    "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",    "title": "Miso-Glazed Salmon",    "description": "Pan-seared salmon with a sticky miso-honey glaze, ready in under 30 minutes.",    "image_url": null,    "servings": "4",    "prep_time": "10 min",    "cook_time": "15 min",    "visibility": "private",    "created_at": "2026-08-20T17:32:10.482000+00:00",    "updated_at": "2026-08-22T09:15:44.118000+00:00",
Errors1
404
Code: NOT_FOUND
Unknown id, or not yours. A family-shared recipe is reachable here too.

Update a recipe

PATCH/api/v1/recipes/{id}Read and write

Updates title, description, servings, prep_time, cook_time, notes, image_url, ingredients and steps, all optional (field shapes: see Create a recipe). A field you omit is left untouched, so a title-only edit doesn't wipe ingredients or steps. When you DO pass ingredients or steps, they replace the whole list.

source_type, source_url, video_url, source_book_title, source_page_number and step_photos aren't updatable yet. They pass validation, so sending them doesn't error, but they're silently ignored, so don't rely on them changing.

Parameters

id
Type: string (path)Required: yes
Recipe id.

Request

curl -X PATCH https://recipeclip.app/api/v1/recipes/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d \  -H "Authorization: Bearer rcp_live_YOUR_KEY" \  -H "Content-Type: application/json" \  -d '{  "description": "Pan-seared salmon with a sticky miso-honey glaze, ready in under 30 minutes.",  "notes": "Even better the next day, cold over rice."}'

Response

200 OK, same shape as Get a recipe
{  "recipe": {    "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",    "title": "Miso-Glazed Salmon",    "description": "Pan-seared salmon with a sticky miso-honey glaze, ready in under 30 minutes.",    "image_url": null,    "servings": "4",    "prep_time": "10 min",    "cook_time": "15 min",    "visibility": "private",    "created_at": "2026-08-20T17:32:10.482000+00:00",    "updated_at": "2026-08-22T09:15:44.118000+00:00",
Errors3
403
Code: INSUFFICIENT_SCOPE
Key is read-only.
403
Code: FORBIDDEN
Reachable only via family sharing. You don't own it.
404
Code: NOT_FOUND
Unknown id, or not yours.

Delete a recipe

DELETE/api/v1/recipes/{id}Read and write

Moves the recipe to trash, the same soft delete the app itself uses, so nothing is permanently removed.

Deleting again returns the same 404 NOT_FOUND as an id that never existed. A second delete isn't a success.

Parameters

id
Type: string (path)Required: yes
Recipe id.

Request

curl -X DELETE https://recipeclip.app/api/v1/recipes/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "deleted": true,  "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"}
Errors1
404
Code: NOT_FOUND
Unknown id, not yours, or already deleted.

Import from a URL

POST/api/v1/recipes/importRead and write

Fetch a recipe page, extract it with AI, and save it: a food blog post, a recipe site, or a video link. Spends one of the account's AI imports from the daily Pro/Family allowance for this endpoint (see Rate limits).

Can take up to ~4 minutes, since video links take longest. Set your client timeout to at least 240 seconds, and call it once per URL rather than in parallel.

Parameters

url
Type: string (body)Required: yes
1–2000 characters, http(s) only. Localhost, private-network and cloud addresses are rejected.
allowDuplicate
Type: boolean (body)Required: no
Save a second copy even if this source_url was already saved.

Request

curl -X POST https://recipeclip.app/api/v1/recipes/import \  -H "Authorization: Bearer rcp_live_YOUR_KEY" \  -H "Content-Type: application/json" \  -d '{  "url": "https://www.bbcgoodfood.com/recipes/easy-pancakes"}'

Response

201 Created, ingredients trimmed to 2 of 8, steps to 1 of 5
{  "recipe": {    "id": "7e2d9f4a-1c6b-4e8a-9d3f-5a7b2c4e6f8d",    "title": "Easy pancakes",    "description": "Learn how to make the perfect pancakes every time with our foolproof easy crêpe recipe – elaborate flip optional",    "image_url": null,    "servings": "12",    "prep_time": "10 min",    "cook_time": "20 min",    "visibility": "private",    "created_at": "2026-09-13T11:42:05.902000+00:00",    "updated_at": "2026-09-13T11:42:05.902000+00:00",
A fetch-failure example (502 Bad Gateway)
json
{  "error": "Could not fetch that page after every attempt.",  "code": "FETCH_FAILED",  "retryable": false}
Errors5
400
Code: INVALID_REQUEST
url isn't a valid or reachable http(s) URL.
409
Code: DUPLICATE_RECIPE
This source_url was already saved. The AI extraction already ran by this point, so this still spends one AI import. Retry with allowDuplicate: true only if you actually want a second copy.
429
Code: AI_RATE_LIMITED
The account's AI import allowance is spent.
502
Code: FETCH_FAILED
Every fetch path (direct, proxy, archive) failed.
500 / 502 / 503 / 504
Code: AI_*
The model provider failed. retryable in the body says whether to retry.

Collections

List collections

GET/api/v1/collectionsRead only

Your collections; see Response objects. Read-only in this API: there's no create, update or delete for collections, and no way to filter recipes by collection yet.

Illustrative: this API has no endpoint to create a collection, so there's no example to capture from a fresh account. Field names and types are exactly as the code returns them; the id and recipe_count values are examples.

Parameters

limit
Type: integer (query)Required: no
1–100. Default 25.
cursor
Type: string (query)Required: no
Opaque. Pass back the previous page's next_cursor.

Request

curl https://recipeclip.app/api/v1/collections \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

Illustrative example, see note
{  "collections": [    {      "id": "3b1f9b4a-6e3b-4a0e-9b7a-2b7b6b9b2b11",      "name": "Weeknight dinners",      "description": null,      "visibility": "private",      "recipe_count": 12,      "created_at": "2026-08-14T09:00:00.000000+00:00",      "url": "https://recipeclip.app/collections"    }  ],
Errors1
429
Code: RATE_LIMITED
Too many requests.

Cook

What can I cook

GET/api/v1/cookRead only

Ranks your own recipes by how much of each one you could already make: match_percent is the share of the recipe's own ingredients that you have, ties broken by fewest missing. Matching is substring-based both ways, so "chicken" matches "chicken thighs".

Scans your newest 500 recipes, and uses at most the first 50 ingredients you pass.

Parameters

ingredients
Type: string (query)Required: one of
Comma-separated, e.g. salmon,miso,honey. Required unless pantry=true.
pantry
Type: boolean (query)Required: no
true ranks against your saved pantry (salt, pepper and water are assumed). Combine with ingredients to use both.
limit
Type: integer (query)Required: no
Default 10, clamped 1–50. An invalid value silently falls back to 10.

Request

curl "https://recipeclip.app/api/v1/cook?ingredients=salmon,miso,honey" \  -H "Authorization: Bearer rcp_live_YOUR_KEY" # or rank against your saved pantrycurl "https://recipeclip.app/api/v1/cook?pantry=true" \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "matches": [    {      "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",      "title": "Miso-Glazed Salmon",      "description": "Pan-seared salmon with a sticky miso-honey glaze.",      "image_url": null,      "servings": "4",      "prep_time": "10 min",      "cook_time": "15 min",      "visibility": "private",      "created_at": "2026-08-20T17:32:10.482000+00:00",
Errors2
400
Code: INVALID_REQUEST
Neither ingredients nor pantry=true was passed.
400
Code: PANTRY_EMPTY
pantry=true, but your pantry is empty and no ingredients were passed.

Pantry

List your pantry

GET/api/v1/pantryRead only

What you keep at home, alphabetically. Salt, pepper and water are always assumed, never stored, and listed in always_assumed.

Request

curl "https://recipeclip.app/api/v1/pantry" \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "items": [    {      "id": "7c1e9a52-3b4d-4f08-9a6e-2d5b8c0f1a34",      "name": "basmati rice",      "added_at": "2026-10-01T18:04:11.220000+00:00"    },    {      "id": "e04b77d1-92a3-4c6f-b1d8-5a3f0c9e6b72",      "name": "eggs",      "added_at": "2026-10-03T09:41:52.907000+00:00"    }

Add to your pantry

POST/api/v1/pantryRead and write

Adds ingredients. Names are matched the way the app matches them, so “Tomatoes” and “tomato” are the same item. Anything already there comes back in already_had, and salt, pepper and water in assumed.

Parameters

names
Type: string[] (body)Required: yes
1–50 ingredient names, e.g. chicken thighs.

Request

curl -X POST "https://recipeclip.app/api/v1/pantry" \  -H "Authorization: Bearer rcp_live_YOUR_KEY" \  -H "Content-Type: application/json" \  -d '{  "names": [    "chicken thighs",    "eggs",    "salt"  ]}'

Response

json
{  "added": [    {      "id": "3f8a2c6d-1e07-4b95-8d43-6c7e9b0a5f18",      "name": "chicken thighs",      "added_at": "2026-10-06T12:15:03.551000+00:00"    }  ],  "already_had": [    "eggs"  ],  "assumed": [
Errors2
400
Code: INVALID_REQUEST
names is missing, empty or over 50.
403
Code: INSUFFICIENT_SCOPE
A read-only key.

Remove from your pantry

DELETE/api/v1/pantryRead and write

Removes items you've used up. Pass names or ids; anything that isn't in the pantry comes back in not_found.

Parameters

names
Type: string (query)Required: one of
Comma-separated names, e.g. eggs,milk.
ids
Type: string (query)Required: one of
Comma-separated pantry item ids from the list call.

Request

curl -X DELETE "https://recipeclip.app/api/v1/pantry?names=eggs" \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "removed": [    {      "id": "e04b77d1-92a3-4c6f-b1d8-5a3f0c9e6b72",      "name": "eggs",      "added_at": "2026-10-03T09:41:52.907000+00:00"    }  ],  "not_found": []}
Errors2
400
Code: INVALID_REQUEST
Neither names nor ids was passed.
403
Code: INSUFFICIENT_SCOPE
A read-only key.

Meal plan

Read your meal plan

GET/api/v1/meal-planRead only

Planned meals for a date range, sorted by date and meal slot. A recipe is null when it's no longer available to this account.

Dates are YYYY-MM-DD. If you leave from and to out, you get 7 days starting today (UTC). A range can span at most 63 days (to can be up to 62 days after from).

Parameters

from
Type: date (query)Required: no
First day, inclusive. Default today (UTC).
to
Type: date (query)Required: no
Last day, inclusive. Default 6 days after from.
plan
Type: string (query)Required: no
all (default), mine or family.

Request

curl "https://recipeclip.app/api/v1/meal-plan?from=2026-10-06&to=2026-10-12" \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "from": "2026-10-06",  "to": "2026-10-12",  "plans": [    "mine",    "family"  ],  "slots": [    {      "key": "breakfast",      "name": "Breakfast"    },
Errors2
400
Code: INVALID_REQUEST
A bad date, a range over 63 days, or an unknown plan.
404
Code: NOT_IN_FAMILY
plan=family was requested but the account is not in a Family.

Plan a meal

POST/api/v1/meal-planRead and write

Puts one of your saved recipes on a date and meal slot. Returns 201 with the new entry.

Parameters

recipe_id
Type: string (body)Required: yes
The id of a recipe in this account.
date
Type: date (body)Required: yes
YYYY-MM-DD.
meal
Type: string (body)Required: no
A meal slot key such as breakfast, lunch or dinner. Default dinner.
plan
Type: string (body)Required: no
mine (default) or family.

Request

curl -X POST "https://recipeclip.app/api/v1/meal-plan" \  -H "Authorization: Bearer rcp_live_YOUR_KEY" \  -H "Content-Type: application/json" \  -d '{  "recipe_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",  "date": "2026-10-09",  "meal": "dinner",  "plan": "mine"}'

Response

201 Created
{  "meal": {    "id": "9d2f4b86-7a13-4e5c-a0b9-1c8d3e6f2a47",    "plan": "mine",    "date": "2026-10-09",    "meal": "dinner",    "meal_label": "Dinner",    "recipe": {      "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",      "title": "Miso-Glazed Salmon",      "url": "https://recipeclip.app/recipe/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"    }  }}
Errors4
400
Code: INVALID_REQUEST
A missing recipe_id, a bad date, or a meal that isn't one of your slots.
403
Code: INSUFFICIENT_SCOPE
A read-only key.
404
Code: NOT_FOUND
No such recipe in this account.
404
Code: NOT_IN_FAMILY
plan=family was requested but the account is not in a Family.

Move a planned meal

PATCH/api/v1/meal-plan/{id}Read and write

Changes a planned meal's date, meal slot, or both. It works on your own plan and, if you're in a Family, on the family plan.

Parameters

id
Type: string (path)Required: yes
The planned meal's id.
date
Type: date (body)Required: no
New date.
meal
Type: string (body)Required: no
New meal slot key. Send date, meal or both.

Request

curl -X PATCH "https://recipeclip.app/api/v1/meal-plan/9d2f4b86-7a13-4e5c-a0b9-1c8d3e6f2a47" \  -H "Authorization: Bearer rcp_live_YOUR_KEY" \  -H "Content-Type: application/json" \  -d '{  "date": "2026-10-10"}'

Response

json
{  "meal": {    "id": "9d2f4b86-7a13-4e5c-a0b9-1c8d3e6f2a47",    "plan": "mine",    "date": "2026-10-10",    "meal": "dinner",    "meal_label": "Dinner",    "recipe": {      "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",      "title": "Miso-Glazed Salmon",      "url": "https://recipeclip.app/recipe/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"    }  }}
Errors3
400
Code: INVALID_REQUEST
Neither date nor meal was sent, or a value is invalid.
403
Code: INSUFFICIENT_SCOPE
A read-only key.
404
Code: NOT_FOUND
No planned meal with that id.

Remove a planned meal

DELETE/api/v1/meal-plan/{id}Read and write

Takes one meal off the plan. The recipe itself is not touched.

Request

curl -X DELETE "https://recipeclip.app/api/v1/meal-plan/9d2f4b86-7a13-4e5c-a0b9-1c8d3e6f2a47" \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "removed": {    "id": "9d2f4b86-7a13-4e5c-a0b9-1c8d3e6f2a47",    "plan": "mine"  }}
Errors2
403
Code: INSUFFICIENT_SCOPE
A read-only key.
404
Code: NOT_FOUND
No planned meal with that id.

Grocery list

Read your grocery list

GET/api/v1/grocery-listRead only

Two lists in one: from_meal_plan is the ingredients for the meals planned in the range, combined and grouped by category; added_items are the things you added by hand, newest first.

Dates are YYYY-MM-DD. If you leave from and to out, you get 7 days starting today (UTC). A range can span at most 63 days (to can be up to 62 days after from).

Parameters

from
Type: date (query)Required: no
First day, inclusive. Default today (UTC).
to
Type: date (query)Required: no
Last day, inclusive. Default 6 days after from.
plan
Type: string (query)Required: no
mine (default) or family: which meal plan to build from.
include_checked
Type: boolean (query)Required: no
true also returns added items already checked off.

Request

curl "https://recipeclip.app/api/v1/grocery-list?from=2026-10-06&to=2026-10-12" \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "from": "2026-10-06",  "to": "2026-10-12",  "plan": "mine",  "from_meal_plan": [    {      "name": "salmon fillets",      "amount": "4",      "category": "Meat & Seafood",      "for_recipes": [        "Miso-Glazed Salmon"      ]
Errors2
400
Code: INVALID_REQUEST
A bad date, a range over 63 days, or an unknown plan.
404
Code: NOT_IN_FAMILY
plan=family was requested but the account is not in a Family.

Add grocery items

POST/api/v1/grocery-itemsRead and write

Adds items to the list. Returns 201 with the new items, including the ids you need to check them off or remove them.

Parameters

items
Type: object[] (body)Required: yes
1–50 items, each { name, quantity?, unit? }.

Request

curl -X POST "https://recipeclip.app/api/v1/grocery-items" \  -H "Authorization: Bearer rcp_live_YOUR_KEY" \  -H "Content-Type: application/json" \  -d '{  "items": [    {      "name": "lemons",      "quantity": "2"    }  ]}'

Response

201 Created
{  "added": [    {      "id": "b5e8d1a3-4c92-4f67-9e0a-7d2c6b3f8a15",      "name": "lemons",      "quantity": "2",      "unit": "",      "checked": false,      "added_at": "2026-10-06T12:20:44.318000+00:00"    }  ]}
Errors2
400
Code: INVALID_REQUEST
items is empty, over 50, or an item has no name.
403
Code: INSUFFICIENT_SCOPE
A read-only key.

Check off grocery items

PATCH/api/v1/grocery-itemsRead and write

Marks added items as bought, or puts them back on the list. Only items you added by hand can be checked; plan ingredients can't.

Parameters

ids
Type: string[] (body)Required: yes
Item ids from the list or add call.
checked
Type: boolean (body)Required: yes
true = bought, false = back on the list.

Request

curl -X PATCH "https://recipeclip.app/api/v1/grocery-items" \  -H "Authorization: Bearer rcp_live_YOUR_KEY" \  -H "Content-Type: application/json" \  -d '{  "ids": [    "b5e8d1a3-4c92-4f67-9e0a-7d2c6b3f8a15"  ],  "checked": true}'

Response

json
{  "updated": [    {      "id": "b5e8d1a3-4c92-4f67-9e0a-7d2c6b3f8a15",      "name": "lemons",      "quantity": "2",      "unit": "",      "checked": true,      "added_at": "2026-10-06T12:20:44.318000+00:00"    }  ],  "not_found": []}
Errors2
400
Code: INVALID_REQUEST
ids is empty or checked isn't a boolean.
403
Code: INSUFFICIENT_SCOPE
A read-only key.

Remove grocery items

DELETE/api/v1/grocery-itemsRead and write

Deletes added items. Ids that don't match one of your items come back in not_found.

Parameters

ids
Type: string (query)Required: yes
Comma-separated item ids.

Request

curl -X DELETE "https://recipeclip.app/api/v1/grocery-items?ids=b5e8d1a3-4c92-4f67-9e0a-7d2c6b3f8a15" \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "removed": [    "b5e8d1a3-4c92-4f67-9e0a-7d2c6b3f8a15"  ],  "not_found": []}
Errors2
400
Code: INVALID_REQUEST
ids is missing.
403
Code: INSUFFICIENT_SCOPE
A read-only key.

Nutrition

Read a day of nutrition

GET/api/v1/nutritionRead only

One day of your food diary: the entries, their totals, your daily goals and what's remaining. goals and remaining are null until you set goals. An entry's nutrition is null when it was logged without numbers.

Parameters

date
Type: date (query)Required: no
YYYY-MM-DD. Default today (UTC).
utc_offset_minutes
Type: integer (query)Required: no
Your UTC offset in minutes, ISO sign: -300 for UTC-05:00, 60 for UTC+01:00. -840 to 840. Default 0.

Request

curl "https://recipeclip.app/api/v1/nutrition?date=2026-10-06&utc_offset_minutes=0" \  -H "Authorization: Bearer rcp_live_YOUR_KEY"

Response

json
{  "date": "2026-10-06",  "entries": [    {      "id": "2a6c9e41-8d3b-4f70-b5a2-0e7d1c4f9b36",      "name": "Greek yogurt with berries",      "logged_at": "2026-10-06T13:05:00+00:00",      "meal": "breakfast",      "servings": 1,      "nutrition": {        "calories": 210,        "protein_g": 17.5,
Errors1
400
Code: INVALID_REQUEST
A bad date or utc_offset_minutes.

MCP

The same account is also reachable over MCP (Model Context Protocol, the way an AI assistant calls an app's tools directly) at the URL below, using the same API key or a signed-in connection. For setup steps per assistant, real screenshots, and the tool reference, see AI assistants.

MCP server URLhttps://recipeclip.app/api/mcp