Flambe docs

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:

token.ts
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"
  }'
201 Created
{
  "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"
200 OK
{
  "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"
200 OK
{
  "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

import-recipe.ts
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`);

Next

On this page