Automation
Your workflows and what they act on. Reading requires workflows:read; creating, editing, cloning, deleting and running them require workflows:write.
/v1/workflows/{id} returns the resolved configuration — the node graph plus every variable with its effective value and whether it was overridden. /campaigns is the one worth reading first: membership is not simply "campaigns linked to the workflow", so a workflow can be scheduled every night and still process nothing. When the set comes back empty, empty_reason names the cause.
Three routes are worth knowing before you write anything:
/v1/workflow-node-typesis the vocabulary of the engine — which node types exist, what each one requires and what the server fills in for you. Read it before building a graph rather than guessing and letting validation reject you./v1/workflows/{id}/clonecopies an existing workflow server-side. Duplicating one by reading it and re-posting its graph costs orders of magnitude more for the same result./v1/workflows/{id}/cost-estimateforecasts what a run would cost without running it. The cost grows with the number of items eachforEachwalks, so check it before running anything over a large catalogue.
Running is asynchronous and explicit. /v1/workflows/{id}/run queues the execution and returns its id — it does not wait for the run to finish, so read the outcome afterwards from /executions and /executions/{id}/payloads. dry_run is required and has no default: a real run moves bids and budgets on Amazon, a simulation walks the whole graph and writes nothing. A paused workflow can still be simulated; running it for real requires it to be active.
What a workflow actually changed on a campaign lives under the campaign instead, at /v1/campaigns/{id}/optimization-history — see Advertising.
/v1/workflow-templates is the catalogue Epinium publishes, and instantiating one creates this account's own copy. Which templates an account sees depends on its feature flags, so the list is not the same for everyone. Reading requires workflows:read; instantiating also requires workflows:write.
Servers
workflows
The authenticated account's automation workflows, the campaigns they process and those campaigns' performance. Requires workflows:read.
Operations
List the authenticated account's workflows
Filter by description_key to tell workflow types apart: name and name_key are NOT reliable discriminators because several templates share a display name. template filters by the global template a workflow was cloned from. Global templates themselves are never listed here — only the account's own workflows.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Query Parameters
Page size. The default is enough to explore; raise it only when you need to walk a whole collection.
20201100Cursor for the next page: the id of the last record you received. Returns the records after it.
Cursor for the previous page: the id of the first record you received. Returns the records before it.
Trim the response to the fields you need, comma-separated and keyed by ENTITY TYPE rather than by resource name: fields[campaign]=id,name. A key that matches no entity is ignored silently and the full object comes back.
Case-insensitive substring match over the workflow name and description. Use it to find a record by name when you do not have its id, instead of paging through the whole list. Regex metacharacters are matched literally.
1200Set to true to also receive total_count: how many records match every filter of this request, ignoring starting_after/ending_before/offset - the size of the whole result set, not of what is left. has_more is always returned regardless. It costs an extra count query, so ask for it once, on the first page, to decide whether the set is worth walking. On metrics resources it counts entities (or groups with group_by), and, with any granularity other than total, each of their periods that has data, not entities times periods: entity-days with granularity=daily. total_count comes back null when the count could not finish in time; the list is still returned, so treat it as unknown, never as zero.
"true""false""true""false"24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Responses
200
Create a workflow, paused
The workflow is created PAUSED, so it does nothing until a human turns it on in the app — this endpoint cannot activate it, with or without a schedule. Sending schedule only says WHEN it would run once somebody enables it; it never makes it run. The graph must pass validation: on failure you get 422 with the same error list validate returns, and nothing is written. Requires the workflows:write scope.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Request Body
Responses
201
Retrieve one workflow with its resolved configuration
Returns the full definition: the node graph, and every configuration variable with its effective value. overridden on a variable tells you whether the customer set it or it still carries the template default — which is usually the question worth asking when auditing a setup.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Query Parameters
Trim the response to the fields you need, comma-separated and keyed by ENTITY TYPE rather than by resource name: fields[campaign]=id,name. A key that matches no entity is ignored silently and the full object comes back.
Responses
200
Delete a workflow and everything under it
Deletes the workflow, its execution history, the payloads of those executions and its version snapshots. There is no undo and no bin: once gone, the record of what it did is gone with it. Requires the workflows:write scope.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Request Body
Responses
204
Edit a paused workflow, or pause one
Editing the graph only works while the workflow is paused: doing it on one that is scheduled returns 409, and so does sending schedule for a workflow that is active at all. The one exception is active: false on its own, which is ALWAYS allowed — pausing can only make less happen on Amazon, and refusing it would leave a scheduled workflow impossible to edit through this API at all. Sending active: true turns the workflow on: from then on its schedule fires for real — because its next run moves real bids and a half-reviewed graph should not be what executes. Pause it in the app first. nodes and edges are replaced wholesale and must be sent together — a partial patch would leave edges pointing at nodes that no longer exist, so sending one without the other is a 400, as is an empty body. The graph must pass validation: on failure you get 422 and nothing is written. Requires the workflows:write scope.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Request Body
Responses
200
Campaigns this workflow actually processes
NOT simply the campaigns that link to the workflow. Membership is decided by three controls stored per campaign (force-include, force-exclude, global opt-out) combined with the workflow's own filter and its auto-selection setting. Each row carries a gate object saying why it is in, plus the campaign's effective AI configuration. When the set is empty, empty_reason names the cause: manual-sin-enlaces means the workflow is on manual selection with nothing linked — the state a workflow is BORN in, and the usual reason one runs nightly for weeks doing nothing. filtro-vacio means no campaign passes its own filter. no-itera-campanas means this workflow does not act on campaigns at all.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Query Parameters
Page size. The default is enough to explore; raise it only when you need to walk a whole collection.
20201100Cursor for the next page: the id of the last record you received. Returns the records after it.
Cursor for the previous page: the id of the first record you received. Returns the records before it.
Responses
200
Advertising performance of the campaigns this workflow optimizes
Aggregated over a required start_date/end_date window, restricted to the campaigns the workflow actually processes. Each row puts the configured objective_type/objective_value next to the realized metrics, which is how you judge whether the workflow is hitting its target. optimizing_since is when the workflow first touched that campaign for real — results before that date are NOT its doing, so a window straddling it will mislead you. Monetary amounts are decimal strings; ratios are fractions (0.1642 = 16.42%) and are null, never 0, when their denominator is zero.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Query Parameters
Both ends are included in the range. The window is capped, and the cap depends on the resource AND on granularity — daily allows a much shorter span than total, and target-metrics and search-term-metrics are stricter than the rest because limit/offset bound the response but not what ClickHouse scans. Going over returns a 400 naming the exact limit for your request.
"2026-07-01""^\\d{4}-\\d{2}-\\d{2}$"Both ends are included in the range. The window is capped, and the cap depends on the resource AND on granularity — daily allows a much shorter span than total, and target-metrics and search-term-metrics are stricter than the rest because limit/offset bound the response but not what ClickHouse scans. Going over returns a 400 naming the exact limit for your request.
"2026-07-31""^\\d{4}-\\d{2}-\\d{2}$"total (default) aggregates the whole window into one row per campaign, which is the row to compare against its objective. daily splits each campaign into one row per day with activity, but these rows carry no date field, so they cannot be told apart: for a dated daily series use campaign-metrics with campaign and granularity=daily. Calendar periods (weekly, monthly, quarterly, yearly) are not available on this route. daily has a shorter window cap than total.
"total""daily""total""total"Page size. For a top-N, combine order_by with a small limit instead of raising it: limit/offset bound the response, not what ClickHouse scans.
100100110000009007199254740991"asc""desc""desc""desc"Responses
200
Runs of a workflow, newest first
What the workflow has actually done, run by run. status tells them apart, and partial is its own outcome, not a shade of the other two: the graph finished but some items inside a forEach failed and the rest carried on, so neither "it all worked" nor "nothing happened" is true. dry_run says whether the run wrote anything at all — filter it out before drawing conclusions from a series. The per-node detail of a run is in its payloads.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Query Parameters
Page size. The default is enough to explore; raise it only when you need to walk a whole collection.
20201100Cursor for the next page: the id of the last record you received. Returns the records after it.
Cursor for the previous page: the id of the first record you received. Returns the records before it.
"true""false"Responses
200
What each node produced in a run
The output every node left behind in that run — this is where a workflow's decisions actually are, one row per node and per forEach item. Read this, not the run itself, to answer what a workflow decided and why: the run document only carries the top-level nodes, and in this product the reasoning lives inside the forEach.
It takes two calls, on purpose. The first, with the default data=none, is the INDEX: every step with its label, and nothing else. A label starts with the CONTAINER — the forEach the work happened in — then the item, then the node that did it: wf-foreach-targets-item-2-p9747-wf-calc-bid. So label_prefix takes the container, not the node at the end. Read the index first and copy a prefix from it rather than composing one; a prefix that matches nothing is rejected and the error lists the ones that exist. Asking for data=full across a whole run of a catalogue-sized workflow returns millions of characters and overflows what a host will accept, which is why it is not the default. type separates a node's own decision (nodeResult) from the engine's scaffolding.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Query Parameters
Page size. The default is enough to explore; raise it only when you need to walk a whole collection.
20201100Cursor for the next page: the id of the last record you received. Returns the records after it.
Cursor for the previous page: the id of the first record you received. Returns the records before it.
"subGraph""context""nodeResult""none""full"Responses
200
What a workflow graph can be made of
Everything the workflow engine understands: the node types and what each is for, which fields their data needs and which the server fills in for you, the actions a node can run, the operators a condition accepts, and what a forEach can iterate over. Read it BEFORE building a graph - without it the only way to learn the shape is to read an existing workflow and infer, and an account with no workflow of the kind you need leaves you guessing. Identical for every account, so it is not charged.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Responses
200
Saved versions of a workflow, newest first
Every create, clone, instantiation and edit leaves a version behind, and so does a restore — restoring is never a one-way trip. Versions EXPIRE, so an old workflow can show fewer than were actually made: a short list does not mean nobody touched it. author says where the change came from, which is what separates an edit made by an integration from one made by hand in Epinium. The graph is not here; read one version to see it.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Query Parameters
Page size. The default is enough to explore; raise it only when you need to walk a whole collection.
20201100Cursor for the next page: the id of the last record you received. Returns the records after it.
Cursor for the previous page: the id of the first record you received. Returns the records before it.
Responses
200
One saved version, with its graph
The full graph as it was when that version was saved. Read it before restoring: restoring replaces the live graph wholesale, and this is the only way to see what it would become.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Responses
200
Put a saved version back
Replaces the live graph with the one from that version. It is an edit, so the same rule applies: a workflow that can fire on its own is refused with 409 — pause it first with active: false, restore, and turn it back on when the result is what you wanted. The state being replaced is snapshotted first, so a restore can itself be undone. Requires the workflows:write scope.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Request Body
Responses
201
What running this workflow would cost, without running it
The same forecast the app shows in the workflow detail, so read it BEFORE run with dry_run: false on anything that touches a large catalogue. A row closes exactly: estimated_items * tokens_per_item + loop_tokens = estimated_tokens. Do NOT multiply by billable_nodes_per_item — it counts the nodes rather than pricing them, and a create-task does not cost the same as a plain action. What the number is worth depends on one flag. For the rows with forecastable: true it is an UPPER bound: it counts every item that matches today, and a run can process fewer. But a row with forecastable: false is a forEach whose size cannot be known until the run happens, and its cost is left OUT of the total — so if any row carries that flag the total is not an upper bound at all, it is incomplete, and the real cost can exceed it. A zero there means 'not forecast', never 'free'.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Responses
200
Run a workflow now, for real or as a simulation
Queues an execution and returns its id — it does NOT wait for it to finish, because a run over dozens of campaigns takes far longer than any tool call. Read the outcome afterwards with the executions and payloads of this same workflow. dry_run is required and has no default on purpose: a real run moves bids and budgets on Amazon, and a simulation walks the whole graph writing nothing, so which one you meant should never depend on a field you forgot. A paused workflow can still be simulated — that is the point, checking it before turning it on — but running it for real needs it active. Requires the workflows:write scope.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Request Body
Responses
201
Check a workflow graph without saving it
Runs the same structural, connectivity and semantic rules that create and update enforce, and persists nothing. name is optional here, unlike in create: nothing is saved, so there is nothing to name. Each error carries a hint saying what to change. Use it to iterate on a graph before writing it: a rejected write costs you the same round-trip but leaves you guessing less if you validated first.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Request Body
Responses
200
Copy an existing workflow, paused
Copies the graph, the declared variables and the schedule of an existing workflow server-side, so the caller never resends them. Like create, the copy is PAUSED and this endpoint cannot turn it on. Use it to run the same logic with different values: variables takes only the keys you want to change, and every other one keeps the source value. Requires the workflows:write scope.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Request Body
Responses
201
workflow-templates
The catalogue of automations Epinium publishes, and the route to create this account's own copy of one. Which templates an account sees depends on its feature flags. Requires workflows:read; instantiating also requires workflows:write.
List the workflow templates this account can instantiate
The catalogue of automations Epinium publishes, filtered to the ones this account is allowed to use. These are NOT workflows of the account: they do nothing until you instantiate one, which gives the account its own paused copy. Requires the workflows:read scope.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Responses
200
Create this account copy of a template
Creates the account's own copy of a template, PAUSED, with the template's graph, variables and schedule already in place — turning it on is a human step in Epinium. Instantiating the same template twice is allowed and gives two independent copies, so check what the account already has first: epinium_list_workflows with template filters by the template a workflow came from. A template this account is not entitled to returns 403. Requires the workflows:write scope.
Authorizations
Client API key (POST /api-keys). Send as Authorization: Bearer epk_live_....
Parameters
Path Parameters
24-character hexadecimal Mongo ObjectId.
"^[a-f0-9]{24}$"Request Body
Responses
201