Build, publish and bind a flow
Create a flow, save its draft, check and publish it, roll it back, give it an operation code, and read its executions and analytics.
A flow is the graph of nodes a request runs: a request names an operation code, and Slen walks the flow bound to that code. The operations of this page create a flow, edit its draft, check and publish it, roll it back, bind it to an operation code, and read its executions and analytics. The console shows the same flows on AI Flows; AI flows and Publish, versions and rollback describe them there.
Who may change a flow
Reading a flow, its draft, its versions and its executions takes management:read, and its analytics take read. Everything that changes a flow takes flows:write: creating and archiving it, saving the draft, publishing, rolling back and binding the operation code.
A flow belongs to one project. A key reads and changes the flows of its own project. A flow of another project, or one that does not exist, answers 404 with the code flow_not_found.
Create and archive
A flow is created in the project of the key, with a name of up to 120 characters. Its
slugis made from the name, and a slug the project already uses gets a number at its end.A new flow has no version that runs: its
statusisdraftuntil the first publish, andactiveafter it.statusset toarchivedin the update stops the flow. A request for its operation code is answered404with the codeflow_not_found, and its binding and its active version are kept.statusset toactivebrings it back as it was, and so does a publish.
The draft and its revision
A flow is edited as a draft and runs as a published version. GET /v1/flows/{id}/draft answers with the graph, its version_number and draft_rev, and the settings this installation checks graphs with — among them the node types a new version may not hold. A flow with no draft answers with the graph of the version that runs, from_published set to true, and base_version naming that version.
The save sends the whole graph back with the draft_rev it read.
A
draft_revthat is not the current one is refused with409and the codedraft_conflict, and the graph is not written: somebody saved in between. Read the draft again.A caller that holds the graph on screen also sends
version_number, since every draft counts its revisions from zero. The first save of a flow with no draft also sendsbase_version. Both are refused with the same409when the draft is no longer the one they name.A save checks only the shape of the graph. What the graph means is judged by the check and by a publish.
Check and publish
POST /v1/flows/{id}/validatechecks the draft, or the version that runs when there is no draft, and changes nothing.validsays whether a publish would pass,issueslists the errors and the warnings, andsourcesays which graph was checked.A publish turns the draft into the next numbered version and makes it the one requests run. A draft with an error is refused with
422and the codeflow_invalid, anderror.details.issueslists what was found. A warning does not stop it.A flow with no draft answers
404with the codeflow_draft_not_found: there is nothing new to publish.A save or another publish that lands while the publish is prepared refuses it with
409and the codedraft_conflict, and nothing is published. A publish may name its draft withversion_numberanddraft_rev, to be refused the same way when the draft has moved since it was read.A node of a frozen type stops a publish: the issue
node_type_frozennames the node, andparams.typeits type. Http Request is frozen unless the service is set up otherwise. A version published with such a node goes on running, and a rollback can return to it.
How a flow is built lists what every flow must have. Slen sends the webhook event flow.published after a publish.
Roll back
A rollback makes an earlier published version run again. With version_number it goes to that version. Without it the flow goes to the published version just below the active one, so repeated calls walk back through the history.
A version the flow never had answers
404with the codeflow_version_not_found, and a draft409with the codeflow_version_not_published.With no published version below the active one, the answer is
409with the codeno_previous_version.A version that names a model or a provider pool that is gone since is refused with
422and the codeflow_invalid.
The answer is the version that runs now. The draft is left as it is, and Slen sends the webhook event flow.rolled_back.
The operation code
A request reaches a flow through its operation code, and a flow answers to one code. The binding replaces the code the flow had: from the next request the old code is answered 404 with the code flow_not_found, and it is free for another flow. A code that another flow of the project has answers 409 with the code operation_already_bound, and the flow keeps its own code. Removing the binding leaves the flow without a code.
Executions and analytics
The executions of a flow are listed the newest first and read page by page: with
cursor, or by number —pagewithlimitalso answerstotal, andanchor, the id the first page answered, keeps the next pages from shifting while new executions come in.qfinds one execution by its whole id, digits only; the id of an execution of another flow answers an empty page.statusnarrows the list, andfromandtonarrow it to the executions started between them. Either end may be sent alone, and without them the list goes through the whole history. A value with an offset is the moment it names; a value without one is read as UTC.pathkeeps the executions that walked exactly one path — the node keys of an entry ofrequests_by_pathjoined by>— and is taken only withfromandto.The analytics of a flow are a row for each node by default. With
granularity=flowthe answer is the dashboard of the flow: its requests, costs and outcomes over the window. The window follows the rules of a report — Analytics: at most 31 days for the rows of the nodes, and 366 days for the dashboard. The dashboard is counted over whole hours that have closed: thewindowof the answer ends at the start of the current hour, or earlier while the last hour is still being reconciled. Each entry ofrequests_by_pathcarries how its executions ended (status) and the types of its nodes, and each entry ofcost_by_connectionthe pool the calls went through (pool_id).
Operations
List flows
GET /v1/flows
- Scope:
management:read
Parameters
| Parameter | Description |
|---|---|
limit optionalquery | Page size (default 30).
|
cursor optionalquery | The |
status optionalquery |
|
project optionalquery |
|
Responses
One cursor page of flows
{
"data": [
{
"flow_id": "74438200973594624",
"organization_id": "74438200973594624",
"project_id": "74438200973594624",
"name": "string",
"slug": "string",
"status": "draft",
"active_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"draft_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"operation_codes": [
"string"
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
],
"meta": {
"limit": 1,
"next_cursor": "string"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
Create a flow
POST /v1/flows
- Scope:
flows:write
The flow is created in the caller's organization, in project_id when given — which must be a project of that organization the caller reaches, or 404 project_not_found and nothing is stored — else in the caller's active project.
The slug is derived from name and is unique inside the project: letters outside Latin are transliterated (Cyrillic, Ukrainian included — Первый флоу → pervyy-flou), every other run of non-alphanumerics becomes one hyphen, 120 characters at most, flow when nothing is left. An ASCII name gives the slug it always gave. A slug the project already uses gets the first free numeric suffix (support-answer-2, -3, …) instead of a refusal; 409 flow_slug_taken remains only for two creates racing for the same free slug — retrying succeeds.
Request body
{
"name": "string",
"project_id": "74438200973594624"
}Responses
The created flow
{
"data": {
"flow_id": "74438200973594624",
"organization_id": "74438200973594624",
"project_id": "74438200973594624",
"name": "string",
"slug": "string",
"status": "draft",
"active_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"draft_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"operation_codes": [
"string"
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
Read one flow with its active and draft versions
GET /v1/flows/{id}
- Scope:
management:read
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Responses
The flow
{
"data": {
"flow_id": "74438200973594624",
"organization_id": "74438200973594624",
"project_id": "74438200973594624",
"name": "string",
"slug": "string",
"status": "draft",
"active_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"draft_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"operation_codes": [
"string"
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. |
Headers
X-Request-Id
The body of every error is the error envelope.
Rename or archive a flow
PATCH /v1/flows/{id}
- Scope:
flows:write
An archived flow is not executed: POST /v1/operations/execute for its operation answers 404 flow_not_found. Its binding and its active version are kept, so status: active brings it back as it was, and a publish un-archives it as well. An execution parked before the flow was archived still resumes and finishes on its own version.
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Request body
{
"name": "string",
"status": "active"
}Responses
The updated flow
{
"data": {
"flow_id": "74438200973594624",
"organization_id": "74438200973594624",
"project_id": "74438200973594624",
"name": "string",
"slug": "string",
"status": "draft",
"active_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"draft_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"operation_codes": [
"string"
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
The editable graph
GET /v1/flows/{id}/draft
- Scope:
management:read
Beside the graph and its revision, the settings this installation validates and runs graphs with (FlowBuilderSettings), for the builder to say.
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Responses
The draft graph, its revision and the builder's settings
{
"data": {
"version_number": 1,
"draft_rev": 1,
"from_published": true,
"base_version": 1,
"graph": {
"version": 1,
"nodes": [
{
"key": "string",
"type": "string",
"label": "string",
"config": {},
"position": {
"x": 1,
"y": 1
}
}
],
"edges": [
{
"key": "string",
"source": "string",
"source_port": "string",
"target": "string",
"label": "string"
}
]
},
"settings": {
"max_output_tokens": 1,
"max_parked_hours": 1,
"park_longest_wait_hours": 1,
"frozen_node_types": [
"sdk_request"
]
}
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. |
Headers
X-Request-Id
The body of every error is the error envelope.
Save the draft graph
PUT /v1/flows/{id}/draft
- Scope:
flows:write
draft_rev is the optimistic-lock token read from the draft; a stale one is refused (409 draft_conflict).
draft_rev counts from zero in every draft, so alone it does not tell the draft of one version from the draft of the next. A caller that holds a graph on screen — the builder — also sends the version_number that GET /draft (or its last save) answered: when the draft is of another version — the one it read was published elsewhere and edited again — the save is refused with 409 draft_conflict even when the two revisions coincide, and the graph is not written. Without version_number only the revision is compared, as before.
A flow with no draft is edited from the version that runs, and a rollback replaces that graph without moving either number. The first save of such a flow therefore also names the base_version that GET /draft answered: when the working graph is read from another version by now, or is somebody's draft already, the save is refused with 409 draft_conflict. A refused save of a flow with no draft creates no draft. A caller that names no base is not held to one.
A pool_id in the graph that the caller does not reach is an unknown pool to validation (unknown_pool), exactly like one that does not exist. A pool_id that is not text — a number, a list, an object — is invalid_node_config with the detail ai_model_invalid_pool_id: the runtime routes through a named pool only for a non-empty string. A pool that is switched off is the warning pool_disabled: the graph can be saved and published, and every call of that node is refused provider_not_configured until the pool is enabled.
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Request body
{
"graph": {
"version": 1,
"nodes": [
{
"key": "string",
"type": "string",
"label": "string",
"config": {},
"position": {
"x": 1,
"y": 1
}
}
],
"edges": [
{
"key": "string",
"source": "string",
"source_port": "string",
"target": "string",
"label": "string"
}
]
},
"draft_rev": 1,
"version_number": 1,
"base_version": 1
}Responses
The draft version after the save
{
"data": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
Check the working graph without publishing it
POST /v1/flows/{id}/validate
- Scope:
management:read
Checks the draft when there is one and the version that runs otherwise — the active one, which after a rollback is not the latest published; source in the answer says which. 404 only when the flow has neither.
The body is optional. A caller that is looking at a graph — an editor with the draft on screen — sends back the version_number and draft_rev that GET /draft (or its last save) answered, and for a flow with no draft its base_version as well: when the working graph is no longer that one, the answer is 409 draft_conflict instead of a verdict about a graph the caller has not seen. Without the body whatever the server holds is checked.
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Request body optional
{
"version_number": 1,
"draft_rev": 1,
"base_version": 1
}Responses
Validation issues, if any
{
"data": {
"valid": true,
"issues": [
{
"severity": "error",
"code": "invalid_graph_shape",
"message": "string",
"node_key": "string",
"edge_key": "string",
"params": {
"key": "string"
}
}
],
"source": "draft"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
Make the draft the version that runs
POST /v1/flows/{id}/publish
- Scope:
flows:write
The version is frozen at the draft revision that was validated and compiled. A draft save, or another publish of the same draft, that lands while this one is being prepared is 409 draft_conflict: nothing is published and no flow.published is sent — read the draft again and repeat the call. 404 flow_draft_not_found when the flow has no draft (it was already published and not edited since).
That check sees only what changes *during* the call. A save that landed before it — another tab, another member — is simply the draft, and it is what gets published. A caller that publishes the graph it is looking at sends the optional body: the version_number and draft_rev that GET /draft (or its last save) answered. When the draft is another version or at another revision the answer is the same 409 draft_conflict, before the graph is validated. A body with any other field, or with a value that is not such a number, is 422.
A draft that holds a node of a frozen type is refused like any other invalid graph: 422 flow_invalid that carries a node_type_frozen issue, with the node in node_key and the type in params.type. Which types are frozen is the deployment's setting; http_request is by default. The freeze is about new versions only: a version published with such a node keeps running, an execution parked on it resumes, and a rollback onto it works — but its graph cannot be published again until the node is taken out, and POST /validate says so (valid: false) for a flow whose running version holds one.
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Request body optional
{
"version_number": 1,
"draft_rev": 1
}Responses
The published version and any warnings
{
"data": {
"version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"warnings": [
{
"severity": "error",
"code": "invalid_graph_shape",
"message": "string",
"node_key": "string",
"edge_key": "string",
"params": {
"key": "string"
}
}
]
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( | |
| 422 | The graph cannot be published. |
Headers
X-Request-Id
The body of every error is the error envelope.
Go back to an earlier published version
POST /v1/flows/{id}/rollback
- Scope:
flows:write
Omitting version_number means the published version next below the live one, so repeated calls walk back through the history (version 5, then 4, then 3); going forward again is a rollback by number.
A draft somebody has begun is left alone. A flow with no draft is edited from the version that is live after the rollback.
The answer is the version that is live now — a FlowVersion, not the PublishResult of a publish: a rollback re-checks references only and has no warnings to report. The switch and its flow.rolled_back event are one transaction.
Refusals: 404 flow_version_not_found — the flow has no version of that number; 409 flow_version_not_published — the version named is a draft; 409 no_previous_version — no number was given and there is no published version earlier than the live one (or the flow has no live version). 422 is one of two things: a version_number that is not a positive integer (the field-level {"message", "errors"} answer), or flow_invalid in the error envelope with details.issues, as on a publish — a model or a pool the target version refers to is gone, so it can no longer run.
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Request body optional
{
"version_number": 1
}Responses
The version now active
{
"data": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
Every version of a flow
GET /v1/flows/{id}/versions
- Scope:
management:read
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Responses
Versions, newest first
{
"data": [
{
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z",
"published_by_name": "string"
}
]
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. |
Headers
X-Request-Id
The body of every error is the error envelope.
One version whole — the summary plus the graph it froze (the policy diff reads two of these)
GET /v1/flows/{id}/versions/{version}
- Scope:
management:read
The version of the flow with this number: the summary GET /v1/flows/{id}/versions lists, plus the graph it froze, in the shape the draft endpoint uses. A number the flow never had is 404 flow_version_not_found; a flow out of reach is 404 flow_not_found.
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
version requiredpath |
|
Responses
The version with its graph, in the draft endpoint's shape
{
"data": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z",
"graph": {
"version": 1,
"nodes": [
{
"key": "string",
"type": "string",
"label": "string",
"config": {},
"position": {
"x": 1,
"y": 1
}
}
],
"edges": [
{
"key": "string",
"source": "string",
"source_port": "string",
"target": "string",
"label": "string"
}
]
}
}
}Errors
| Status | Codes | When |
|---|---|---|
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
Give this flow its operation code
PUT /v1/flows/{id}/binding
- Scope:
flows:write
The binding is what POST /v1/operations/execute resolves by. A flow answers to one operation code: the PUT replaces the code the flow had, in one transaction. The previous code answers 404 flow_not_found to POST /v1/operations/execute from the next call and is free for another flow; the code the flow already has is answered as it is. 409 operation_already_bound — the code belongs to another flow of the project, and this flow keeps the code it had.
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Request body
{
"operation_code": "generate_email"
}Responses
The flow with its operation code
{
"data": {
"flow_id": "74438200973594624",
"organization_id": "74438200973594624",
"project_id": "74438200973594624",
"name": "string",
"slug": "string",
"status": "draft",
"active_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"draft_version": {
"version_id": "74438200973594624",
"version_number": 1,
"status": "draft",
"draft_rev": 1,
"published_at": "2026-10-02T09:30:00Z",
"published_by": "string",
"published_by_user_id": "74438200973594624",
"created_at": "2026-10-02T09:30:00Z"
},
"operation_codes": [
"string"
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
Remove the operation binding
DELETE /v1/flows/{id}/binding
- Scope:
flows:write
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Responses
Unbound
No body
Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. |
Headers
X-Request-Id
The body of every error is the error envelope.
Runs of one flow
GET /v1/flows/{id}/executions
- Scope:
management:read
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
limit optionalquery | Page size (default 30).
|
cursor optionalquery | The |
page optionalquery | The page number, from 1. Turns the list into its numbered form: the answer's
|
anchor optionalquery | The
|
status optionalquery |
|
from optionalquery | Only runs started at or after this moment. Either end may be given alone; there is no cap on the width and no default window — without either the list pages through the whole history. A value with an offset is the moment it names; one without is read as UTC. A value that is not a date is a field-validation
|
to optionalquery | Only runs started at or before this moment; see
|
q optionalquery | Search by the full id of an execution — digits only, anything else is a field-validation
|
path optionalquery | Only the runs that walked exactly this path: the node keys in the order they were entered, joined by
|
Responses
One page of executions — a cursor page, or a numbered one with page
{
"data": [
{
"execution_id": "74438200973594624",
"flow_id": "74438200973594624",
"flow_version_id": "74438200973594624",
"operation": "generate_email",
"operation_id": "74438200973594624",
"status": "running",
"error_code": "string",
"error_message": "string",
"cost": {
"estimated": "0.02700000",
"actual": "0.02700000"
},
"cache": {
"hit": true,
"cached_at": "2026-10-02T09:30:00Z"
},
"started_at": "2026-10-02T09:30:00Z",
"finished_at": "2026-10-02T09:30:00Z",
"duration_ms": 1
}
],
"meta": {
"limit": 1,
"next_cursor": "string"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
Per-node stats, or the flow dashboard
GET /v1/flows/{id}/analytics
- Scope:
read
granularity=node (the default) reads the raw node executions of the window; granularity=flow reads the hourly rollup and answers the dashboard. The two shapes differ.
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
from optionalquery | Start of the window an analytics answer covers — a date the server can parse, ISO-8601 recommended (a value without an offset is read as UTC). Unlike the lists, each end is optional on its own and the window is never refused, only fitted (
|
to optionalquery | End of the window an analytics answer covers; now when absent. See
|
granularity optionalquery |
|
Responses
Node rows or the flow dashboard, depending on granularity
{
"data": [
{
"node_key": "string",
"node_type": "string",
"executions": 1,
"failed": 1,
"error_rate": "string",
"cost": "0.02700000",
"avg_duration_ms": 1,
"p95_duration_ms": 1,
"ports": {
"key": 1
},
"saved_cost": "0.02700000"
}
]
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Headers
X-Request-Id
The body of every error is the error envelope.
Examples
List the flows
The active flows of the project.
curl "https://api.slen.ai/api/v1/flows?status=active" \
-H "Authorization: Bearer $SLEN_API_KEY"{
"data": [
{
"flow_id": "74438623450128384",
"organization_id": "74438200973594112",
"project_id": "74438200973594624",
"name": "Follow-up email",
"slug": "follow-up-email",
"status": "active",
"active_version": {
"version_id": "74440987364098048",
"version_number": 3,
"status": "published",
"draft_rev": 7,
"published_at": "2026-10-01T15:40:22+00:00",
"published_by": "74438344587730944",
"published_by_user_id": null,
"created_at": "2026-10-01T14:02:51+00:00"
},
"draft_version": null,
"operation_codes": [
"generate_email"
],
"created_at": "2026-09-30T11:20:05+00:00",
"updated_at": "2026-10-01T15:40:22+00:00"
}
],
"meta": {
"limit": 30,
"next_cursor": null
}
}Create a flow
The new flow is in the project of the key and has no version yet.
curl https://api.slen.ai/api/v1/flows \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Support answer"
}'{
"data": {
"flow_id": "74443519874433024",
"organization_id": "74438200973594112",
"project_id": "74438200973594624",
"name": "Support answer",
"slug": "support-answer",
"status": "draft",
"active_version": null,
"draft_version": null,
"operation_codes": [],
"created_at": "2026-10-02T10:20:44+00:00",
"updated_at": "2026-10-02T10:20:44+00:00"
}
}Read a flow
The answer is the flow with its active version and its draft.
curl https://api.slen.ai/api/v1/flows/74438623450128384 \
-H "Authorization: Bearer $SLEN_API_KEY"{
"data": {
"flow_id": "74438623450128384",
"organization_id": "74438200973594112",
"project_id": "74438200973594624",
"name": "Follow-up email",
"slug": "follow-up-email",
"status": "active",
"active_version": {
"version_id": "74440987364098048",
"version_number": 3,
"status": "published",
"draft_rev": 7,
"published_at": "2026-10-01T15:40:22+00:00",
"published_by": "74438344587730944",
"published_by_user_id": null,
"created_at": "2026-10-01T14:02:51+00:00"
},
"draft_version": {
"version_id": "74443540644626432",
"version_number": 4,
"status": "draft",
"draft_rev": 2,
"published_at": null,
"published_by": null,
"published_by_user_id": null,
"created_at": "2026-10-02T10:25:13+00:00"
},
"operation_codes": [
"generate_email"
],
"created_at": "2026-09-30T11:20:05+00:00",
"updated_at": "2026-10-02T10:31:09+00:00"
}
}Archive a flow
Requests for the operation code of the flow are refused until it is active again.
curl -X PATCH https://api.slen.ai/api/v1/flows/74438623450128384 \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "archived"
}'{
"data": {
"flow_id": "74438623450128384",
"organization_id": "74438200973594112",
"project_id": "74438200973594624",
"name": "Follow-up email",
"slug": "follow-up-email",
"status": "archived",
"active_version": {
"version_id": "74440987364098048",
"version_number": 3,
"status": "published",
"draft_rev": 7,
"published_at": "2026-10-01T15:40:22+00:00",
"published_by": "74438344587730944",
"published_by_user_id": null,
"created_at": "2026-10-01T14:02:51+00:00"
},
"draft_version": null,
"operation_codes": [
"generate_email"
],
"created_at": "2026-09-30T11:20:05+00:00",
"updated_at": "2026-10-02T11:02:37+00:00"
}
}Read the draft
The answer carries the graph, the two numbers a save sends back and the settings of the builder.
curl https://api.slen.ai/api/v1/flows/74438623450128384/draft \
-H "Authorization: Bearer $SLEN_API_KEY"Save the draft
The graph of a request, one model call and the answer, saved at the revision it was read at.
curl -X PUT https://api.slen.ai/api/v1/flows/74438623450128384/draft \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"graph": {
"version": 1,
"nodes": [
{
"key": "request",
"type": "sdk_request",
"label": null,
"config": {},
"position": {
"x": 0,
"y": 0
}
},
{
"key": "model",
"type": "ai_model",
"label": null,
"config": {
"model": "gpt-4o-mini",
"max_output_tokens": 400
},
"position": {
"x": 280,
"y": 0
}
},
{
"key": "answer",
"type": "return_response",
"label": null,
"config": {},
"position": {
"x": 560,
"y": 0
}
}
],
"edges": [
{
"key": "request-model",
"source": "request",
"source_port": "default",
"target": "model"
},
{
"key": "model-answer",
"source": "model",
"source_port": "success",
"target": "answer"
}
]
},
"draft_rev": 2,
"version_number": 4
}'Check the draft
The body names the draft on screen. The answer says whether a publish would pass, and lists what was found.
curl https://api.slen.ai/api/v1/flows/74438623450128384/validate \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"version_number": 4,
"draft_rev": 3
}'Publish the draft
The body names the draft that was checked. A save made since then refuses the publish.
curl https://api.slen.ai/api/v1/flows/74438623450128384/publish \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"version_number": 4,
"draft_rev": 3
}'Roll back
Without a body the flow goes back to the version below the active one.
curl -X POST https://api.slen.ai/api/v1/flows/74438623450128384/rollback \
-H "Authorization: Bearer $SLEN_API_KEY"List the versions
The versions of the flow, the newest first.
curl https://api.slen.ai/api/v1/flows/74438623450128384/versions \
-H "Authorization: Bearer $SLEN_API_KEY"Read one version
The answer is the version with the graph it froze, in the shape of the draft.
curl https://api.slen.ai/api/v1/flows/74438623450128384/versions/3 \
-H "Authorization: Bearer $SLEN_API_KEY"Bind the operation code
From the next request, generate_email runs this flow.
curl -X PUT https://api.slen.ai/api/v1/flows/74438623450128384/binding \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation_code": "generate_email"
}'{
"data": {
"flow_id": "74438623450128384",
"organization_id": "74438200973594112",
"project_id": "74438200973594624",
"name": "Follow-up email",
"slug": "follow-up-email",
"status": "active",
"active_version": {
"version_id": "74440987364098048",
"version_number": 3,
"status": "published",
"draft_rev": 7,
"published_at": "2026-10-01T15:40:22+00:00",
"published_by": "74438344587730944",
"published_by_user_id": null,
"created_at": "2026-10-01T14:02:51+00:00"
},
"draft_version": null,
"operation_codes": [
"generate_email"
],
"created_at": "2026-09-30T11:20:05+00:00",
"updated_at": "2026-10-01T15:40:22+00:00"
}
}Remove the binding
The answer has no body. A request for the old code is answered 404.
curl -X DELETE https://api.slen.ai/api/v1/flows/74438623450128384/binding \
-H "Authorization: Bearer $SLEN_API_KEY"204 No Content
List the executions
The first page of 25 executions of the flow started since the morning of 2 October that ended failed.
curl "https://api.slen.ai/api/v1/flows/74438623450128384/executions?status=failed&from=2026-10-02T06%3A00%3A00Z&page=1&limit=25" \
-H "Authorization: Bearer $SLEN_API_KEY"Read the analytics
The dashboard of the flow over the last seven days.
curl "https://api.slen.ai/api/v1/flows/74438623450128384/analytics?granularity=flow" \
-H "Authorization: Bearer $SLEN_API_KEY"Where to go next
AI flows — the same flows in the console, and how a request finds its flow.
Publish, versions and rollback — drafts, publishing and rolling back in the console.
Executions — the status and the trace of one execution.
How a flow is built — the nodes and the rules a published flow follows.
Built from openapi b070e1f, sdk contract 5cc7055, php ae4a53b, node 84bc62d