Quickstart
Authenticate, import a recipe from a URL, and read the result back.
This walks through the whole loop: get a token, start an import, wait for it, and read the recipe it produced.
1. Get a token
Every request needs a Clerk session JWT. In a browser app with a Clerk SDK already wired up:
import { useAuth } from '@clerk/nextjs';
const { getToken } = useAuth();
const token = await getToken();For a script or server-side integration, mint a token from your Clerk instance rather than hard-coding one — session tokens are short-lived by design. Authentication covers both paths in detail.
Check it works. This is the cheapest possible authenticated call:
curl https://api.flambe.dev/api/users/me \
-H "Authorization: Bearer $FLAMBE_TOKEN"401 has two meanings
Access token required means the Authorization header never arrived — a
client bug. Invalid or expired token means it arrived but did not verify.
Only the second one is worth refreshing a session over.
2. Start an import
curl -X POST https://api.flambe.dev/api/imports \
-H "Authorization: Bearer $FLAMBE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"type": "web",
"url": "https://www.seriouseats.com/best-chocolate-chip-cookies"
}'{
"id": "4d1f8c2e-9a3b-4f1e-8c7d-1b2a3c4d5e6f",
"type": "web",
"status": "pending",
"url": "https://www.seriouseats.com/best-chocolate-chip-cookies",
"created_at": "2026-09-10T18:22:04.512Z"
}Note the status. Nothing has been extracted yet.
3. Wait for it
Poll the import until status is terminal — completed, failed or
cancelled:
curl "https://api.flambe.dev/api/imports/4d1f8c2e-9a3b-4f1e-8c7d-1b2a3c4d5e6f" \
-H "Authorization: Bearer $FLAMBE_TOKEN"{
"id": "4d1f8c2e-9a3b-4f1e-8c7d-1b2a3c4d5e6f",
"status": "completed",
"recipe_id": "9b8a7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
}A one- to two-second interval is plenty. For anything long-lived, subscribe to the event stream instead and skip polling entirely.
4. Read the recipe
curl "https://api.flambe.dev/api/recipes/9b8a7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d" \
-H "Authorization: Bearer $FLAMBE_TOKEN"{
"id": "9b8a7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"title": "The Best Chocolate Chip Cookies",
"servings": 24,
"prep_time": 20,
"cook_time": 12,
"ingredients": [
{ "name": "all-purpose flour", "quantity": 2.25, "unit": "cups" },
{ "name": "unsalted butter", "quantity": 1, "unit": "cup", "preparation": "browned" },
{ "name": "flaky salt", "isOptional": true }
],
"instructions": [
"Brown the butter and let it cool to room temperature.",
"Cream with both sugars, then beat in the eggs."
]
}The whole thing, in one script
const API = 'https://api.flambe.dev';
const token = process.env.FLAMBE_TOKEN!;
const headers = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
};
async function importRecipe(url: string) {
const started = await fetch(`${API}/api/imports`, {
method: 'POST',
headers,
body: JSON.stringify({ type: 'web', url }),
});
if (!started.ok) throw new Error(`create failed: ${started.status}`);
const { id } = await started.json();
// Terminal states only. Anything else means the worker still has it.
const terminal = new Set(['completed', 'failed', 'cancelled']);
const deadline = Date.now() + 3 * 60_000;
while (Date.now() < deadline) {
const res = await fetch(`${API}/api/imports/${id}`, { headers });
const job = await res.json();
if (terminal.has(job.status)) {
if (job.status !== 'completed') {
throw new Error(`import ${job.status}: ${job.error ?? 'no reason given'}`);
}
const recipe = await fetch(`${API}/api/recipes/${job.recipe_id}`, { headers });
return recipe.json();
}
await new Promise((r) => setTimeout(r, 1500));
}
throw new Error('import did not finish within three minutes');
}
const recipe = await importRecipe(process.argv[2]);
console.log(recipe.title, `— ${recipe.ingredients.length} ingredients`);