---
title: "Configure a recipe"
method: PUT
path: "/api/v3.1/automated-recipes/{recipe_id}/"
tags: ["Automated Recipes"]
---

# Configure a recipe

`PUT /api/v3.1/automated-recipes/{recipe_id}/`

## Request

- Base URL: `https://embedded.improvado.io`
- URL: `https://embedded.improvado.io/api/v3.1/automated-recipes/{recipe_id}/`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `dts_session_id`
- TypeScript: <https://skmtc.dev/improvado/apis/improvado-embedded-api-v3/docs/api/v3.1/automated-recipes/:recipe_id/put.ts> (a function with typed inputs; its response is checked against the schema)

Gives a recipe the data sources, connections and accounts it builds from. **This step is mandatory**: a recipe created through the API holds no configuration, and a build started before it answers 409 `recipe_not_configured`.

The body is the **full set**, not a delta: send every data source, connection and account the recipe should have, including the ones it already has. Take `connection_id` from `GET /api/v3/datasources/{datasource_name}/connections` and every account `id` from `GET /api/v3.1/connections/{connection_id}/accounts/` of this same workspace, and use only accounts with `is_active: true` - anything else is rejected with 400 `account_unavailable`.

**Configuration only grows.** Data sources, connections and accounts can be added, and none of them can be taken away: a body that leaves one out is rejected with 409 and the matching `*_removal_not_allowed` code. **An account cannot be removed from a recipe at all**, by this operation or by any other, and deleting the recipe does not remove it either. If the data of an account has to be taken out of a result, contact support.

A successful call answers 200 with the recipe as it now stands, so the response can be compared with what was sent. It changes neither `state` nor `error` and starts no build; `POST .../build/` does that.

## Path parameters

- `recipe_id` string, required

## Request body

- object
  - `data_sources` string[], required: Every data source the recipe reads, by name. Cannot be empty, and every name has to be one of the recipe's `allowed_data_sources`. A data source already configured has to stay in the list.
  - `connections` object[], required: Every connection the recipe reads, with its accounts. Cannot be empty. Each `connection_data_source` has to appear in `data_sources`, the pair of `connection_id` and `connection_data_source` has to be unique in the list, and every data source added by this call needs at least one connection here.
    - `connection_id` integer, required: Connection identifier in this workspace.
    - `connection_data_source` string, required: Data source name of that connection, for example `facebook`.
    - `accounts` object[], required: The accounts of that connection the recipe reads. Cannot be empty. Every account already configured has to stay in the list.
      - `id` integer, required: Account identifier from `GET /api/v3.1/connections/{connection_id}/accounts/` of this workspace, with `is_active: true`.
  - `sync_from_date` string, date, nullable: The first date the recipe collects data from. Omit it or send `null` to leave the date the recipe already has unchanged.

## Response `200`

The recipe as it now stands, with the configuration this call saved

- RecipeV31: A single recipe with its configuration and the outcome of its last build. A strict superset of the list item.
  - `recipe_id` string: Recipe identifier. A string, even though it is made of digits.
  - `template_id` integer: The template this recipe was created from.
  - `template_title` string: Current name of that template.
  - `title` string: Recipe name. When you create a recipe without a title, the template supplies the default.
  - `workspace_id` integer: The workspace this recipe lives in.
  - `state` 'extracting_data' | 'building_dependencies' | 'ready_to_work' | 'failed': Where the recipe stands. `extracting_data` - data is being collected; `building_dependencies` - the tables are being built; `ready_to_work` - the last build finished and the output table can be queried; `failed` - the last build failed, see `error`. `ready_to_work` means built, not built completely: it is built from the extractions that delivered, and how complete that is shown by `degraded` and `sources`. New values can be added, so tolerate unknown ones.
  - `degraded` boolean, nullable: Whether the last build was incomplete. Only meaningful while `state` is `ready_to_work`: in every other state, a failed recipe and a running build included, it is `false`. `true` - the build left something out, either a whole data source or some extractions; see `not_delivered_reasons` and `sources`. `false` - everything in the build delivered. `null` - the completeness of the last build is unknown because that build ran before version 3.1.
  - `not_delivered_reasons` string[]: The distinct reasons behind an incomplete last build, sorted and deduplicated across all data sources. Empty when `degraded` is `false` or `null`.
  - `error` RecipeBuildErrorV31, nullable: Why a build failed. On a recipe this is not `null` if and only if `state` is `failed`; on an entry of `sources` it is not `null` if and only if that entry's `status` is `failed`. Build error codes are a namespace of their own, separate from the codes of HTTP error responses - a few names appear in both. The catalogue is on the [Errors](/errors) page.
    - `code` string: Machine-readable build error code, for example `no_data_delivered` or `sql_object_not_found`. Branch on this, never on the message. New codes can be added, so tolerate unknown ones.
    - `message` string: Public human-readable text: a fixed sentence for our own codes, and the cleaned-up message of the database for SQL failures. It never carries raw internal output, and its wording is not part of the contract.
    - `stage` 'extraction' | 'transformation_build' | 'final_build' | 'null', nullable: Which phase of the build failed: `extraction` while data was being collected, `transformation_build` while an intermediate step was built, `final_build` while the output table was built. `null` when the failure was recorded without a phase.
    - `data_source` string, nullable: The data source this failure belongs to, or `null` when it belongs to no single one. Inside `sources` it always matches that entry's `data_source`.
    - `occurred_at` string, date-time, nullable: When the failure was recorded, or `null` when no time was stored with it.
    - `details` object: Extra data for this code, always an object; `{}` for codes that carry none. See [Errors](/errors) for the shape per code.
  - `datatable_id` integer, nullable: The recipe's output data table, or `null` while none is registered. Resolve the physical table name with GET /api/v3/data-tables/{data_table_id} instead of deriving it from a title.
  - `last_build_at` string, date-time, nullable: When the last build of this recipe was started, no matter what started it. `null` means the recipe has never been built. For recipes last built before version 3.1 this is an approximation: the time the recipe record last changed, which is often the end of that build rather than its start.
  - `created_at` string, date-time: When the recipe was created.
  - `updated_at` string, date-time: When the recipe itself last changed. It says nothing about builds - for those use `last_build_at` and `state`.
  - `data_sources` string[]: Data source names the recipe is configured for, sorted. Empty on a recipe that has been created but not configured yet.
  - `allowed_data_sources` string[]: Data source names the template allows, sorted. Everything in `data_sources` has to come from this list.
  - `connections` object[]: The connections and accounts the recipe reads. This is the full set the recipe holds; configuring a recipe always sends the full set, and nothing can be taken away from it.
    - `connection_id` integer: The connection, as numbered in this workspace. The same connector has a different `connection_id` in every workspace.
    - `connection_data_source` string: Data source this connection belongs to, for example `facebook`.
    - `accounts` object[]: The accounts of this connection that the recipe reads.
      - `id` integer: Account identifier, the one the v3.1 connection accounts operation returns.
  - `sync_from_date` string, date: The date the recipe collects data from. Always set in a response, even though it may be omitted from a configuration request.
  - `sources` RecipeSourceV31[]: What the last build did with each configured data source. One entry per name in `data_sources`.
    - `data_source` string: Data source name, for example `facebook`.
    - `status` 'queued' | 'extracting' | 'building' | 'ready' | 'failed' | 'not_built': How this data source ended up in the last build. `queued` - waiting to start; `extracting` - collecting data; `building` - building its tables; `ready` - built; `failed` - it did not build, see `error`; `not_built` - it was not part of the last build, because it was added to the configuration afterwards or because the recipe has never been built. Run a build to include it. New values can be added, so tolerate unknown ones.
    - `error` RecipeBuildErrorV31, nullable: Why a build failed. On a recipe this is not `null` if and only if `state` is `failed`; on an entry of `sources` it is not `null` if and only if that entry's `status` is `failed`. Build error codes are a namespace of their own, separate from the codes of HTTP error responses - a few names appear in both. The catalogue is on the [Errors](/errors) page.
      - `code` string: Machine-readable build error code, for example `no_data_delivered` or `sql_object_not_found`. Branch on this, never on the message. New codes can be added, so tolerate unknown ones.
      - `message` string: Public human-readable text: a fixed sentence for our own codes, and the cleaned-up message of the database for SQL failures. It never carries raw internal output, and its wording is not part of the contract.
      - `stage` 'extraction' | 'transformation_build' | 'final_build' | 'null', nullable: Which phase of the build failed: `extraction` while data was being collected, `transformation_build` while an intermediate step was built, `final_build` while the output table was built. `null` when the failure was recorded without a phase.
      - `data_source` string, nullable: The data source this failure belongs to, or `null` when it belongs to no single one. Inside `sources` it always matches that entry's `data_source`.
      - `occurred_at` string, date-time, nullable: When the failure was recorded, or `null` when no time was stored with it.
      - `details` object: Extra data for this code, always an object; `{}` for codes that carry none. See [Errors](/errors) for the shape per code.
    - `not_delivered_count` integer: How many extractions of this data source did not make it into the last build. This is the full number; `not_delivered` lists at most 100 of them.
    - `not_delivered` object[]: The extractions that did not make it into the last build, ordered by `account_id` and capped at 100 entries per data source. An empty list means everything this data source was waiting for delivered.
      - `extraction_id` integer: The extraction that did not deliver. Look it up in the extractions API of the same workspace.
      - `account_id` integer: The account the extraction belongs to. This is the same `id` you sent in `connections[].accounts[]` when you configured the recipe.
      - `reason` 'connection_unavailable' | 'account_disabled' | 'permission_denied' | 'extraction_failing' | 'report_type_deprecated' | 'paused' | 'not_running' | 'timed_out' | 'unknown': Why it did not deliver. `connection_unavailable` - the connection has to be reconnected; `account_disabled` - the account is switched off; `permission_denied` - access to the account was withdrawn on the platform; `extraction_failing` - the extraction keeps failing on the platform; `report_type_deprecated` - the platform retired this report; `paused` - the extraction is paused; `not_running` - a reused extraction is not running; `timed_out` - it did not deliver within the window the build waits; `unknown` - none of the above. What to do about each is in [Deploying recipes](/recipes). New values can be added, so tolerate unknown ones.

## Other responses

- `400`: The body is not valid JSON, a field of it is invalid, or it names something this workspace cannot give the recipe. Nothing was saved. `bad_request` is the catch-all code for a 4xx that has no code of its own, so the same envelope can arrive with another 4xx status, for example 415.
- `401`: The bearer token is missing, invalid or expired
- `403`: The caller is authenticated but this operation is not available to them
- `404`: No such recipe in this workspace. The same answer covers a recipe of another workspace or agency, a recipe that was deleted and a recipe that is not managed through this API.
- `405`: The HTTP method is not supported on this path
- `409`: The body leaves out something the recipe already has. Configuration only grows, so nothing was saved: send the full set, including what is already configured. Accounts in particular cannot be removed from a recipe at all - contact support if the data of an account has to be taken out of a result.
- `500`: Something failed on our side. The request was not served; retry later.
- `503`: Temporarily unavailable. The response carries the header `Retry-After: 30`, so wait 30 seconds, re-read the recipe to see what it holds now, and only then repeat the request.

---

[API](https://skmtc.dev/improvado/apis/improvado-embedded-api-v3.md) - [All operations](https://skmtc.dev/improvado/apis/improvado-embedded-api-v3/llms.txt) - [OpenAPI document](https://skmtc.dev/improvado/apis/improvado-embedded-api-v3/revisions/a881099bd6c4?raw)
