# Recipes

> Chains of tasks that turn one result into the input of the next, run one step at a time.

A recipe is not one job; you run each step. Every step is an ordinary `POST /v1/tasks/{name}` with
its own job, its own estimate and its own charge. A recipe tells you which tasks to call in which
order, and which output of one step goes into which input of the next.

The recipes are listed live by `GET /v1/recipes`: each entry has a `name`, a `title`, a `summary`
and its `steps` — the task names, in order ([List recipes](https://docs.refabric.com/api-reference/platform/reference-data/list-recipes)).

```json
{ "items": [ { "name": "<recipe name>", "title": "<what it makes>",
               "summary": "<one paragraph>", "steps": ["<first task>", "<next task>", "…"] } ] }
```

## How a step feeds the next

A step's output is an address the next step takes as it is — no download, no re-upload:

| The step delivers | You pass it as | Into, for example |
|---|---|---|
| a file (`art:…`) | that string | any media field: `image`, `references[].image` |
| a record (`moodboard:…`, `brand_kit:…`, …) | that handle | the field that takes the record: `moodboards`, `brand_kit` |
| a part of a record (`data.items[].url`) | that url | any media field |

Read each step's handle from its result's `files[].file` ([Asynchronous jobs](https://docs.refabric.com/task-apis/calling-tasks/asynchronous-jobs#result)).

## Example: from a moodboard to a refined design

Each step waits for the previous job to end. `run` submits a task and returns its result.

::::code-group
```python
import os, time, uuid, requests

API = "https://api.refabric.com/v1"
s = requests.Session()
s.headers["Authorization"] = f"Key {os.environ['REFABRIC_API_KEY']}"

def run(task, body):
    job = s.post(f"{API}/tasks/{task}", json=body, headers={"Idempotency-Key": str(uuid.uuid4())})
    job.raise_for_status()
    handle = job.json()
    while (status := s.get(handle["status_url"]).json())["lifecycle"] != "terminal":
        time.sleep(5)
    if status["outcome"] != "succeeded":
        raise RuntimeError(status["error"])
    return s.get(handle["result_url"]).json()

board = run("moodboard.create", {"kind": "brand_dna", "name": "Resort", "images": LOOK_URLS})
board_id = board["files"][0]["file"]                 # moodboard:…
designs = run("image.generate", {"prompt": "a relaxed linen shirt dress", "moodboards": [board_id]})
first = designs["files"][0]["file"]                  # art:…
refined = run("image.generate", {"prompt": "the same dress with short sleeves",
                                 "references": [{"image": first}]})
```

```javascript
const API = "https://api.refabric.com/v1";
const headers = { Authorization: `Key ${process.env.REFABRIC_API_KEY}`, "Content-Type": "application/json" };

async function run(task, body) {
  const r = await fetch(`${API}/tasks/${task}`, {
    method: "POST", headers: { ...headers, "Idempotency-Key": crypto.randomUUID() }, body: JSON.stringify(body),
  });
  if (!r.ok) throw new Error((await r.json()).error.code);
  const handle = await r.json();
  let status;
  do {
    await new Promise((done) => setTimeout(done, 5000));
    status = await (await fetch(handle.status_url, { headers })).json();
  } while (status.lifecycle !== "terminal");
  if (status.outcome !== "succeeded") throw new Error(status.error.code);
  return (await fetch(handle.result_url, { headers })).json();
}

const board = await run("moodboard.create", { kind: "brand_dna", name: "Resort", images: LOOK_URLS });
const designs = await run("image.generate", { prompt: "a relaxed linen shirt dress", moodboards: [board.files[0].file] });
const refined = await run("image.generate", {
  prompt: "the same dress with short sleeves",
  references: [{ image: designs.files[0].file }],
});
```

```bash
# 1. the moodboard — its result's files[0].file is moodboard:…
curl -s -X POST "https://api.refabric.com/v1/tasks/moodboard.create" \
  -H "Authorization: Key $REFABRIC_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "brand_dna", "name": "Resort", "images": ["https://example.com/look-01.jpg", "…"]}'
# 2. designs from it — its result's files[].file are art:…
curl -s -X POST "https://api.refabric.com/v1/tasks/image.generate" \
  -H "Authorization: Key $REFABRIC_API_KEY" -H "Content-Type: application/json" \
  -d '{"prompt": "a relaxed linen shirt dress", "moodboards": ["moodboard:<id>"]}'
# 3. refine the one you like
curl -s -X POST "https://api.refabric.com/v1/tasks/image.generate" \
  -H "Authorization: Key $REFABRIC_API_KEY" -H "Content-Type: application/json" \
  -d '{"prompt": "the same dress with short sleeves", "references": [{"image": "art:<id>"}]}'
```
::::

`LOOK_URLS` is your list of look images; `moodboard.create` states how many it takes. A moodboard
must have finished its analysis before `image.generate` can use it: a board that is not ready is
refused with `moodboard_not_ready`, so read it (`GET /v1/files/moodboard:<id>`) until its
`data.status` is `ready`.

## If a step fails

- Each step is charged on its own. The steps that succeeded are charged for what they delivered;
  the step that failed is charged only for what it delivered before failing
  ([Pricing](https://docs.refabric.com/task-apis/pricing#what-is-charged)).
- What the earlier steps made stays yours. Continue from the failed step with the same inputs — you
  do not run the earlier steps again.
- Before you retry a step, read its error's `retryable` ([Task errors](https://docs.refabric.com/task-apis/errors/task-errors#retrying)).
  Retry with the same `Idempotency-Key` only to repeat a submit whose answer you did not receive.

## Related

::::cards
:::card{title="Using records in tasks" href="/records-and-libraries/using-records-in-tasks"}
Which handles a task takes, and how.
:::
:::card{title="Asynchronous jobs" href="/task-apis/calling-tasks/asynchronous-jobs"}
Submit, poll and read the result.
:::
:::card{title="Webhooks" href="/task-apis/calling-tasks/webhooks"}
Start the next step when a job ends, without polling.
:::
::::
