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
└──► cancelledcompleted, 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
type | Source | Required field |
|---|---|---|
web | An https URL — recipe sites, Instagram, YouTube, TikTok | url |
media | Uploaded images, e.g. photographed cookbook pages | file_ids |
text | Pasted or uploaded plain text | text or text_file_id |
image_batch | Many images, fanned out into one child job per image | file_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 https — http 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'{
"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
| Symptom | Likely cause |
|---|---|
failed with a fetch or 403 error | The site blocked the fetch. Retrying rarely helps; re-import as text by pasting the recipe. |
failed mentioning an invalid image | Usually the image could not be fetched, not that it was malformed. Check the file uploaded cleanly, then retry. |
Stuck in processing past a few minutes | The attempt has likely exhausted its budget and will land on failed. Wait for terminal state rather than creating a duplicate. |
| Batch children with mixed results | Expected. Handle per child. |
Never create a second import for the same source while the first is non-terminal — you get two recipes.