Flambe docs
Guides

Imports

Turn a URL, a photograph of a cookbook page, or pasted text into a structured recipe.

An import is an asynchronous extraction job. You hand Flambe a source, it hands back a job id, and a worker turns the source into a structured recipe.

The lifecycle

pending ──► queued ──► processing ──┬──► completed
                                    ├──► failed
                                    └──► cancelled

completed, failed and cancelled are terminal; nothing leaves them except an explicit retry. Everything else means a worker still has the job.

POST /api/imports returns 201 with status: "pending" immediately. It does not wait for extraction. When the job reaches completed, recipe_id points at the recipe it created.

Picking a type

typeSourceRequired field
webAn https URL — recipe sites, Instagram, YouTube, TikTokurl
mediaUploaded images, e.g. photographed cookbook pagesfile_ids
textPasted or uploaded plain texttext or text_file_id
image_batchMany images, fanned out into one child job per imagefile_ids

From a URL

curl -X POST https://api.flambe.dev/api/imports \
  -H "Authorization: Bearer $FLAMBE_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{ "type": "web", "url": "https://example.com/cookies" }'

The URL must be httpshttp is rejected with 400 Only https URLs are allowed. A missing url gives 400 url is required for web imports.

For YouTube, pass youtube_extract_video: true to analyze the video frames as well as the description and transcript. It is slower, and worth it when the recipe is only spoken or shown.

From photographs

Two steps: upload the images, then reference them.

# 1. Upload. The multipart field name is `files`, and repeats.
curl -X POST https://api.flambe.dev/api/files \
  -H "Authorization: Bearer $FLAMBE_TOKEN" \
  -F 'files=@page-42.jpg' \
  -F 'files=@page-43.jpg'
201 Created
{
  "files": [
    { "id": "6f1c2e5a-6f0a-4c4e-9d2f-2c5a5b9e1a11", "url": "https://cdn.flambe.dev/..." },
    { "id": "7a2d3f6b-7b1b-4d5f-8e3a-3d6b6c0f2b22", "url": "https://cdn.flambe.dev/..." }
  ]
}
# 2. Import them as one recipe spanning both pages.
curl -X POST https://api.flambe.dev/api/imports \
  -H "Authorization: Bearer $FLAMBE_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "media",
    "file_ids": ["6f1c2e5a-6f0a-4c4e-9d2f-2c5a5b9e1a11", "7a2d3f6b-7b1b-4d5f-8e3a-3d6b6c0f2b22"],
    "source_section": "Chapter 4 — Breads"
  }'

media vs image_batch

media reads every image as one recipe — use it for a recipe that spans a two-page spread. image_batch treats each image as its own recipe and fans out into child jobs. Choosing wrong is the usual reason a batch of unrelated photos collapses into one garbled recipe.

From text

curl -X POST https://api.flambe.dev/api/imports \
  -H "Authorization: Bearer $FLAMBE_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "text",
    "text": "Brown Butter Cookies\n2 1/4 cups flour, 1 cup brown butter...",
    "source_url": "https://example.com/original-post"
  }'

text must be at least 3 characters. For anything large, upload it as a file and pass text_file_id instead. source_url, if given, must also be https.

Tracking a job

Poll it:

curl "https://api.flambe.dev/api/imports/$IMPORT_ID" \
  -H "Authorization: Bearer $FLAMBE_TOKEN"

A 1–2 second interval is appropriate; imports take seconds to a couple of minutes. Better, subscribe to the event stream and react to import.completed rather than polling at all.

Listing

Two endpoints, for two different jobs:

  • GET /api/imports/working-set — the newest imports, unpaginated. This is the bounded window an import screen renders.
  • GET /api/imports/v2 — the full archive, cursor paginated.

The working set orders by creation, not activity

working-set sorts by created_at. An old import that just failed or is being retried has a fresh updated_at but keeps its original created_at, so it is not pulled into the window. If you are showing "recent activity", you want /api/imports/v2.

Batches

An image_batch import is a parent that fans out into one child per image. Children succeed or fail independently, so a batch is routinely partially complete.

curl "https://api.flambe.dev/api/imports/$PARENT_ID/children" \
  -H "Authorization: Bearer $FLAMBE_TOKEN"

Treat the parent's status as a rollup, and the children as the real outcomes. working-set pulls in the parent of any child in the window even when the parent itself falls outside it, so a batch never renders orphaned.

Retrying

curl -X POST "https://api.flambe.dev/api/imports/$IMPORT_ID/retry" \
  -H "Authorization: Bearer $FLAMBE_TOKEN"

Retry re-enqueues a finished import against its original source. A job still in flight returns 409 — cancel it first if you want it to stop.

The retry budget is anchored to the attempt, not to the job: retrying re-stamps processing_started_at, so the new attempt gets a full budget rather than inheriting however long the previous one burned.

Cancelling

curl -X POST "https://api.flambe.dev/api/imports/$IMPORT_ID/cancel" \
  -H "Authorization: Bearer $FLAMBE_TOKEN"

A worker mid-extraction notices at its next checkpoint, so the job may run a little longer before it actually stops. Cancelling something already terminal returns 409.

When extraction is wrong

If a job completes but the recipe is wrong, report it. Reports feed triage of the extraction pipeline.

curl -X POST "https://api.flambe.dev/api/imports/$IMPORT_ID/report" \
  -H "Authorization: Bearer $FLAMBE_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{ "reason": "missing_ingredients", "details": "Dropped the frosting component." }'

One report per import per user; a second gives 409. Reporting an import that has not completed also gives 409.

Attribution

Pass source_section at creation for a chapter or page reference, or attach a full provenance record with POST /api/imports/{id}/source so the resulting recipe is credited to the right cookbook, site or creator. See Sources in the reference.

Failure modes worth handling

SymptomLikely cause
failed with a fetch or 403 errorThe site blocked the fetch. Retrying rarely helps; re-import as text by pasting the recipe.
failed mentioning an invalid imageUsually the image could not be fetched, not that it was malformed. Check the file uploaded cleanly, then retry.
Stuck in processing past a few minutesThe attempt has likely exhausted its budget and will land on failed. Wait for terminal state rather than creating a duplicate.
Batch children with mixed resultsExpected. Handle per child.

Never create a second import for the same source while the first is non-terminal — you get two recipes.

On this page