Deploying recipes
An automated recipe turns a template into a table in your workspace. This page is the complete deployment path: what to call, in what order, and what each call needs.
Every request after the token exchange is workspace-scoped and needs a Bearer token. Get one from
POST /api/v3/token as described in
Authorization, and note that every path ends in a slash. Without it the API
answers 301, and most HTTP clients follow that redirect by turning your POST into a GET
and dropping the body — you end up parsing an HTML 404 as JSON.
Code
The four calls
Creating a recipe does not configure it. POST gives you an empty recipe; the PUT that
supplies data sources, connections and accounts is a required step, not an edit. Skipping it
leaves a recipe that builds and fails with a message naming neither cause.
1. Pick a template
Code
The detail call additionally returns output_columns — the schema of the table the template
produces — so you can validate a template against your warehouse before deploying anything:
Code
2. Create the recipe
Code
201 returns recipe_id and datatable_id.
One recipe per template per workspace. A second create from the same template returns
400 with existing_recipe_ids:
Code
Treat that as already deployed rather than as a failure: reuse one of the returned recipes.
The list can hold more than one id and its order is not defined — prefer the recipe whose
state is not Failed, and otherwise the most recently updated. To pull template changes into
one of them instead, see resetup below.
3. Configure it — mandatory
Resolve the connection and its accounts in this workspace first:
Code
Then send the configuration:
Code
accounts[] takes objects, not bare identifiers, and id is Improvado's internal account
id — passing the platform's account id as a string returns
400 "Invalid data. Expected a dictionary, but got str."
The call returns 200 with an empty body. Read the recipe back to confirm what was stored.
4. Build and poll
Code
state moves Extracting Data → Building Dependencies → Ready to work, or Failed.
The filter on the list endpoint matches state exactly and case-sensitively: an
unrecognised value returns 200 with an empty page rather than an error, so
?state=Ready To Work silently reports no recipes.
A 200 from /build/ means the build started, not that it will finish. When it fails, the
reason is in the error object of the recipe detail, not in any HTTP response.
What Ready to work means
Ready to work means the build waited for the extractions of the recipe and was built on the ones
that delivered. An extraction counts as delivered once one of its runs has completed: the first
data is in, while the historical backfill may still be loading.
The build does not wait for extractions that cannot deliver on their own - ones paused because a
connection was deleted or invalidated, an account was disabled, permissions were lost or someone
paused them, and existing extractions a rebuild reuses that have nothing running. It waits for the
others up to a limit, and any still missing then are left out. So when the recipe turns
Ready to work, rows of some accounts can be missing from the output, and no row counts are
checked. To confirm coverage, compare the accounts present in the output with the account list
you configured.
A build with nothing to build from fails: when no extraction delivered, or none did for one of the template's extract templates. Depending on the template, a data source whose extractions all failed to deliver either fails the recipe or is dropped while the other sources are built.
When a build fails for missing data
A recipe reads extraction tables, and a build that starts before they exist fails on missing data. Ask what it is waiting for:
Code
Code
ready is true once every listed table exists — that is the signal that repeating /build/
is worth it. The extraction_ids are the extractions the last build used - the ones it created
and the existing ones it reused - so they are empty before the first one, and a recipe that
reads no extraction tables answers ready: false with an empty sources. Each entry is one
table rather than one data source, so a source with several tables appears more than once —
key the list by table. Nothing is cached: every call queries the destination, and a
destination that cannot be queried answers 503 rather than reporting table_exists: false.
A table exists as soon as the first account's rows land in it, so ready: true does not mean that
every account has delivered, and it can be true while the recipe is still Extracting Data. The
recipe state is the readiness signal - see what Ready to work means above.
Rolling a template fix into deployed recipes
When the template changes, recipes already deployed from it keep the cells they were created
with. resetup recreates them from the current template:
Code
The configuration you supplied — data sources, connections, accounts, sync_from_date — is
preserved, and the build starts on its own, so no separate /build/ call is needed. As with
/build/, 200 means started, not finished.
Retiring a recipe
Code
204 and an empty body. This is a hard delete and cannot be undone: the SQL views are dropped,
the recipe's configuration and cells go with it, the data tables it created are cleaned up, and
the intermediate recipes it was built on are deleted with it. The cascade runs downwards, towards
what the recipe reads: a recipe built on top of this one is left in place and will break, and an
intermediate that another deployed recipe also reads is deleted all the same.
It frees the template slot — the one-recipe-per-template-per-workspace rule is checked on create,
so after a delete the same template can be deployed again. That is the way out of a failed
deployment, and the way to shrink an account set. 204 only means the deletion was attempted:
recipes are deleted one by one and a per-recipe failure is logged rather than returned, so confirm
with GET /api/v3/automated-recipes/?template_id= before deploying the template again.
PUT is append-only
The payload is the complete configuration, not a delta: send every data source, connection
and account the recipe should have, including the ones it already has. Removing any of them
returns 400 and saves nothing:
Code
There is no way to drop an account from a live recipe. If the set of accounts has to shrink, delete the recipe and deploy it again.
Deploying one template across many workspaces
A custom template is visible to every workspace in your agency, so the template id is the same everywhere. The identifiers inside a workspace are not. The same connector and the same ad account carry different internal ids in each workspace:
| workspace 23897 | workspace 23898 | |
|---|---|---|
Facebook connection_id | 69909 | 69933 |
TikTok Ads connection_id | 69910 | 69935 |
act_750501968745425 → account id | 3559308 | 3562350 |
7127282116047536129 → account id | 3559367 | 3562412 |
Only the external account_id is stable. For each workspace: get a token, list the
connections, list that connection's accounts, join your own account list on the external
account_id, and use the internal id you got back — never an id resolved in another
workspace.
The output table name
The name is derived from the template's title. The title you pass when creating a recipe
names the recipe in the UI and has no effect on the table: two deployments of the same template
with different titles produce the same table name.
Uniqueness is scoped to (agency, workspace), so the same table name repeating across your
workspaces is expected — do not try to make table names unique across your fleet. To learn the
physical name, take datatable_id from the recipe and read sql_name from
GET /api/v3/data-tables/{data_table_id}.

