Approval pipelines
The ordered list of blocks every approval request runs through. A block can decide the request itself, ask a service of yours, hand it to a person, or, as a branch, run blocks of its own for the requests that match its condition. The hosted edition adds a block that asks a model.
A block returns approve, deny, human, or next. A request that gets
next from every block goes to a person. The block types are always (a fixed
outcome), cel (a condition), webhook (a service of yours), and branch
(blocks that run only when a condition matches); the hosted edition adds
llm_judge (a model), which the open edition refuses as an unknown type. An
always block returns its configured outcome for every request that
reaches it; later blocks do not run.
The organization pipeline runs first, for every request. The request's agent
pipeline runs only if the organization pipeline returns no final outcome.
An always block in the organization pipeline, outside any branch, prevents
agent pipelines from running. Both pipelines have an active revision. To change one,
create a revision, then
activate it with If-Match set to
the revision you expect to be active.
Example: Review remaining requests
Append this block after your specific rules to send every remaining request
to human review. It inherits the pipeline's default reviewers because it
does not set escalation_path.
{
"id": "review-remaining-requests",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
}
}Set outcome to approve or deny for an automatic decision instead.
escalation_path is allowed only with human. Later blocks remain part of
the revision and must be valid, even though they cannot run.
Example: Check refunds in a branch
A branch block runs the blocks in config.blocks only for requests that
match config.when, which takes the same condition as a cel block. Other
requests skip it. A request that enters a branch ends there: if none of its
blocks decides, it goes to a person with the branch's reason and
escalation_path, or the pipeline default, and later blocks do not run.
Blocks inside a branch do not inherit its escalation_path.
{
"id": "refunds",
"name": "Refunds",
"type": "branch",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"when": {
"id": "6c1f0e2a-4b7d-4e35-9a1c-8d2b5f3e7a90",
"field": "/request/tool",
"operator": "equals",
"value": "stripe.refund"
},
"blocks": [
{
"id": "approve-small-refunds",
"name": "Approve small refunds",
"type": "cel",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"when": {
"id": "b2d94f1e-7c3a-4a58-8e06-3f1c9d7b2e45",
"field": "/request/arguments/amount",
"operator": "less_than",
"value": 1000
},
"on_match": "approve",
"reason": "Small refunds are pre-authorized."
}
}
],
"reason": "Refunds that no block decided need Payments review.",
"escalation_path": "payments-review"
}
}A branch needs at least one block, and branches can nest. Block ids and
condition ids must be unique across the whole document, including blocks
inside branches, and the 1,000-block limit counts every one of them. Reads
return the same nesting, with snapshot_id, config_version and
content_hash on every block at every level. In a
preview, a branch the sample enters reports the outcome enter and a
skipped one next; a sample that runs out of a branch ends with
reason_code end_of_branch, and the branch's closing entry has
end_of_branch: true.
List pipelines
Returns every pipeline in the organization: the organization pipeline and one pipeline per agent. Each entry carries the active revision number and a summary of its history.
A pipeline exists as soon as the organization or the agent does, and it always has an active revision. An empty revision passes every request through to a person. The one exception is an archived agent: its pipeline is archived with it, has no active revision, carries archived_at, and is left out unless status says otherwise. Restoring the agent brings it back.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
status | query · enum | Which pipelines to return. One of live, archived, all |
limit | query · integer | How many pipelines to return. Defaults to 50. |
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | object | The pipelines |
400 | ErrorResponse |
|
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot read pipelines |
Response body200
Example
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY"{
"request_approval_pipelines": [
{
"scope": "organization",
"agent_slug": "string",
"active_revision": 1,
"latest_revision": 1,
"revision_count": 1,
"latest_created_at": "2026-09-08T12:02:11Z",
"archived_at": "2026-09-08T12:02:11Z"
}
]
}Retrieve the active organization pipeline
Returns the organization pipeline's active revision, with its blocks in the order they run. The organization pipeline runs first, for every request, before the pipeline of the request's agent.
The ETag header carries the active revision number. Pass it as If-Match when you activate another revision.
Request
No parameters or body.
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | RequestApprovalPipelineRevision | The active revision |
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot read pipelines |
404 | ErrorResponse | No revision is active |
Response body200
Which requests a pipeline applies to. The organization pipeline runs first, for every request. An agent's pipeline runs after it, for that agent's requests.
One of organization, agent
The agent's slug. Present for the agent scope only.
The revision number. Revisions count up from 1.
Whether this is the revision in use.
Every block in the revision, including blocks inside branches.
The escalation path for requests that reach a person without a block naming a path.
The membership or organization-key actor that created the revision.
The blocks, in the order they run. A branch holds its own blocks in config.blocks.
Example
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY"{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"blocks": [
{
"id": "review-remaining-requests",
"snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
},
"config_version": 1,
"content_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}Preview an organization pipeline
/api/v1/request-approval-pipelines/organization/previewSession or API keyRequires pipeline.writeRuns a pipeline document against a sample request and returns what each block would do and the final outcome. Nothing is saved: not the document, not the sample, not the result. Use it to test a revision before you create it.
The sample names an agent by slug and name. For the organization pipeline the slug can be any valid slug; for an agent's pipeline it must be that agent's.
Request
Request body
The blocks, in the order they should run.
The default escalation path, as in a document. Reported in the result when the outcome is human.
The request to run the pipeline against.
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | RequestApprovalPipelinePreview | What the pipeline would do with the sample |
400 | ErrorResponse | The document or the sample is invalid |
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot edit pipelines |
Response body200
Why the run ended. block_outcome: a block decided. block_error: a block failed. end_of_pipeline: the request passed every block. end_of_branch: the request entered a branch and none of its blocks decided.
One of block_outcome, block_error, end_of_pipeline, end_of_branch
The block that ended the run: for end_of_branch, the branch. Absent when the request passed every block.
What each block the request reached did, in order. A branch appears when its condition is evaluated, and once more with end_of_branch when none of its blocks decided.
The path a human outcome goes to, within this scope alone: the ending block's path, or the document's default. Absent when the outcome is automatic or no path applies.
Where the path came from. block_escalation: the ending block named it. pipeline_default: the document's default. no_escalation: no path applies.
One of block_escalation, pipeline_default, no_escalation
Example
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization/preview" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"id": "review-remaining-requests",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
}
}
],
"default_escalation_path": "string",
"sample": {
"request": {
"tool": "issue_refund",
"server": "stripe",
"arguments": {
"amount_cents": 12000
},
"agent_reasoning": "string",
"context": {}
},
"agent": {
"slug": "string",
"name": "string"
},
"previous_metadata": {
"organization": {},
"agent": {}
}
}
}'{
"outcome": "approve",
"reason_code": "block_outcome",
"terminal_block_id": "string",
"blocks": [
{
"block_id": "string",
"position": 0,
"outcome": "next",
"reason": "string",
"metadata": {},
"cost": 0,
"error_code": "string",
"delivery": {
"endpoint_key": "string",
"endpoint_revision": 1,
"endpoint_host": "string",
"http_status": 1,
"duration_ms": 1,
"reason": "string"
},
"trace": [
{
"node_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"matched": true,
"error": true
}
],
"end_of_branch": true
}
],
"escalation_path": "string",
"escalation_path_reason": "block_escalation"
}List organization pipeline revisions
/api/v1/request-approval-pipelines/organization/revisionsSession or API keyRequires pipeline.readReturns the organization pipeline's revisions, newest first, without their blocks.
Paging is by cursor. When more revisions follow, the response carries next_cursor. Pass it back as cursor to get the next page. The last page has no next_cursor. total_count is how many revisions there are across every page.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
cursor | query · string | The |
limit | query · integer | How many revisions to return per page. Defaults to 50. |
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | object | The revisions, newest first |
400 | ErrorResponse |
|
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot read pipelines |
Response body200
The revisions on this page, newest first.
Present when another page follows.
How many revisions the pipeline has, across every page.
Example
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization/revisions" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY"{
"revisions": [
{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"archived_at": "2026-09-08T12:02:11Z"
}
],
"next_cursor": "string",
"total_count": 1
}Create an organization pipeline revision
/api/v1/request-approval-pipelines/organization/revisionsSession or API keyRequires pipeline.writeCreates a new revision of the organization pipeline from a complete document. The document is validated and compiled first. The revision is created inactive, and it never changes. Activate it to put it into use.
Request
Request body
The blocks, in the order they should run. A branch holds its own blocks in config.blocks, and the pipeline may hold at most 1,000 blocks counting those. An empty list passes every request through to a person.
The escalation path for requests that reach a person without a block naming a path. That includes requests that reach the end of the pipeline. The agent's default wins over the organization's. With neither, any reviewer who can decide may take the request from the queue. The path must exist when the revision is created, and be active when it is activated.
Response
Response codes
| Status | Body | Description |
|---|---|---|
201 | RequestApprovalPipelineRevision | The new revision, inactive |
400 | ErrorResponse | The document is invalid |
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot edit pipelines |
Response body201
Which requests a pipeline applies to. The organization pipeline runs first, for every request. An agent's pipeline runs after it, for that agent's requests.
One of organization, agent
The agent's slug. Present for the agent scope only.
The revision number. Revisions count up from 1.
Whether this is the revision in use.
Every block in the revision, including blocks inside branches.
The escalation path for requests that reach a person without a block naming a path.
The membership or organization-key actor that created the revision.
The blocks, in the order they run. A branch holds its own blocks in config.blocks.
Example
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization/revisions" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"id": "review-remaining-requests",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
}
}
],
"default_escalation_path": "string"
}'{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"blocks": [
{
"id": "review-remaining-requests",
"snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
},
"config_version": 1,
"content_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}Retrieve an organization pipeline revision
/api/v1/request-approval-pipelines/organization/revisions/{revision}Session or API keyRequires pipeline.readReturns one revision of the organization pipeline, active or not, with its blocks in the order they run.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
revisionrequired | path · int64 | The revision number. Revisions count up from 1. |
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | RequestApprovalPipelineRevision | The revision |
400 | ErrorResponse |
|
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot read pipelines |
404 | ErrorResponse | No such revision |
Response body200
Which requests a pipeline applies to. The organization pipeline runs first, for every request. An agent's pipeline runs after it, for that agent's requests.
One of organization, agent
The agent's slug. Present for the agent scope only.
The revision number. Revisions count up from 1.
Whether this is the revision in use.
Every block in the revision, including blocks inside branches.
The escalation path for requests that reach a person without a block naming a path.
The membership or organization-key actor that created the revision.
The blocks, in the order they run. A branch holds its own blocks in config.blocks.
Example
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization/revisions/{revision}" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY"{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"blocks": [
{
"id": "review-remaining-requests",
"snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
},
"config_version": 1,
"content_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}Activate an organization pipeline revision
/api/v1/request-approval-pipelines/organization/revisions/{revision}/activateSession or API keyRequires pipeline.activateMakes a revision the active one. New requests use it from then on. Activating an older revision is how you roll back.
Pass the revision you expect to be active in If-Match, quoted, as returned in ETag. If someone activated another revision in the meantime, the call fails with 412 and nothing changes.
Every escalation path the revision names, and every webhook endpoint its blocks post to, must have an active revision. Otherwise the call fails with 409 and nothing changes.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
revisionrequired | path · int64 | The revision number. Revisions count up from 1. |
If-Matchrequired | header · string | The revision you expect to be active, quoted, for example |
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | RequestApprovalPipelineRevision | The revision, now active |
400 | ErrorResponse |
|
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot activate pipelines |
404 | ErrorResponse | No such revision |
409 | ErrorResponse | An escalation path or webhook endpoint the revision uses has no active revision. |
412 | ErrorResponse |
|
428 | ErrorResponse | The |
Response body200
Which requests a pipeline applies to. The organization pipeline runs first, for every request. An agent's pipeline runs after it, for that agent's requests.
One of organization, agent
The agent's slug. Present for the agent scope only.
The revision number. Revisions count up from 1.
Whether this is the revision in use.
Every block in the revision, including blocks inside branches.
The escalation path for requests that reach a person without a block naming a path.
The membership or organization-key actor that created the revision.
The blocks, in the order they run. A branch holds its own blocks in config.blocks.
Example
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization/revisions/{revision}/activate" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY" \
-H "If-Match: "2""{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"blocks": [
{
"id": "review-remaining-requests",
"snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
},
"config_version": 1,
"content_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}Retrieve an agent's active pipeline
Returns the active revision of one agent's pipeline, with its blocks in the order they run. It runs after the organization pipeline, for every request this agent makes.
The ETag header carries the active revision number. Pass it as If-Match when you activate another revision.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
agent_slugrequired | path · string | The slug of the agent whose pipeline this is. |
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | RequestApprovalPipelineRevision | The active revision |
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot read pipelines |
404 | ErrorResponse | No agent with this slug exists, or the agent and its pipeline are archived |
Response body200
Which requests a pipeline applies to. The organization pipeline runs first, for every request. An agent's pipeline runs after it, for that agent's requests.
One of organization, agent
The agent's slug. Present for the agent scope only.
The revision number. Revisions count up from 1.
Whether this is the revision in use.
Every block in the revision, including blocks inside branches.
The escalation path for requests that reach a person without a block naming a path.
The membership or organization-key actor that created the revision.
The blocks, in the order they run. A branch holds its own blocks in config.blocks.
Example
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/agents/{agent_slug}" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY"{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"blocks": [
{
"id": "review-remaining-requests",
"snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
},
"config_version": 1,
"content_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}Preview an agent's pipeline
/api/v1/request-approval-pipelines/agents/{agent_slug}/previewSession or API keyRequires pipeline.writeRuns a pipeline document against a sample request and returns what each block would do and the final outcome. Nothing is saved: not the document, not the sample, not the result. Use it to test a revision before you create it.
The preview runs this document alone. To see what a request would meet end to end, preview the organization pipeline first and pass its metadata in previous_metadata.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
agent_slugrequired | path · string | The slug of the agent whose pipeline this is. |
Request body
The blocks, in the order they should run.
The default escalation path, as in a document. Reported in the result when the outcome is human.
The request to run the pipeline against.
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | RequestApprovalPipelinePreview | What the pipeline would do with the sample |
400 | ErrorResponse | The document or the sample is invalid |
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot edit pipelines |
Response body200
Why the run ended. block_outcome: a block decided. block_error: a block failed. end_of_pipeline: the request passed every block. end_of_branch: the request entered a branch and none of its blocks decided.
One of block_outcome, block_error, end_of_pipeline, end_of_branch
The block that ended the run: for end_of_branch, the branch. Absent when the request passed every block.
What each block the request reached did, in order. A branch appears when its condition is evaluated, and once more with end_of_branch when none of its blocks decided.
The path a human outcome goes to, within this scope alone: the ending block's path, or the document's default. Absent when the outcome is automatic or no path applies.
Where the path came from. block_escalation: the ending block named it. pipeline_default: the document's default. no_escalation: no path applies.
One of block_escalation, pipeline_default, no_escalation
Example
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/agents/{agent_slug}/preview" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"id": "review-remaining-requests",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
}
}
],
"default_escalation_path": "string",
"sample": {
"request": {
"tool": "issue_refund",
"server": "stripe",
"arguments": {
"amount_cents": 12000
},
"agent_reasoning": "string",
"context": {}
},
"agent": {
"slug": "string",
"name": "string"
},
"previous_metadata": {
"organization": {},
"agent": {}
}
}
}'{
"outcome": "approve",
"reason_code": "block_outcome",
"terminal_block_id": "string",
"blocks": [
{
"block_id": "string",
"position": 0,
"outcome": "next",
"reason": "string",
"metadata": {},
"cost": 0,
"error_code": "string",
"delivery": {
"endpoint_key": "string",
"endpoint_revision": 1,
"endpoint_host": "string",
"http_status": 1,
"duration_ms": 1,
"reason": "string"
},
"trace": [
{
"node_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"matched": true,
"error": true
}
],
"end_of_branch": true
}
],
"escalation_path": "string",
"escalation_path_reason": "block_escalation"
}List an agent's pipeline revisions
/api/v1/request-approval-pipelines/agents/{agent_slug}/revisionsSession or API keyRequires pipeline.readReturns the revisions of one agent's pipeline, newest first, without their blocks.
Paging is by cursor. When more revisions follow, the response carries next_cursor. Pass it back as cursor to get the next page. The last page has no next_cursor. total_count is how many revisions there are across every page.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
agent_slugrequired | path · string | The slug of the agent whose pipeline this is. |
cursor | query · string | The |
limit | query · integer | How many revisions to return per page. Defaults to 50. |
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | object | The revisions, newest first |
400 | ErrorResponse |
|
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot read pipelines |
Response body200
The revisions on this page, newest first.
Present when another page follows.
How many revisions the pipeline has, across every page.
Example
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY"{
"revisions": [
{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"archived_at": "2026-09-08T12:02:11Z"
}
],
"next_cursor": "string",
"total_count": 1
}Create an agent's pipeline revision
/api/v1/request-approval-pipelines/agents/{agent_slug}/revisionsSession or API keyRequires pipeline.writeCreates a new revision of one agent's pipeline from a complete document. The document is validated and compiled first. The revision is created inactive, and it never changes. Activate it to put it into use.
The pipeline exists from the moment the agent is created.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
agent_slugrequired | path · string | The slug of the agent whose pipeline this is. |
Request body
The blocks, in the order they should run. A branch holds its own blocks in config.blocks, and the pipeline may hold at most 1,000 blocks counting those. An empty list passes every request through to a person.
The escalation path for requests that reach a person without a block naming a path. That includes requests that reach the end of the pipeline. The agent's default wins over the organization's. With neither, any reviewer who can decide may take the request from the queue. The path must exist when the revision is created, and be active when it is activated.
Response
Response codes
| Status | Body | Description |
|---|---|---|
201 | RequestApprovalPipelineRevision | The new revision, inactive |
400 | ErrorResponse | The document is invalid |
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot edit pipelines |
404 | ErrorResponse | No agent with this slug exists |
409 | ErrorResponse | The agent and its pipeline are archived ( |
Response body201
Which requests a pipeline applies to. The organization pipeline runs first, for every request. An agent's pipeline runs after it, for that agent's requests.
One of organization, agent
The agent's slug. Present for the agent scope only.
The revision number. Revisions count up from 1.
Whether this is the revision in use.
Every block in the revision, including blocks inside branches.
The escalation path for requests that reach a person without a block naming a path.
The membership or organization-key actor that created the revision.
The blocks, in the order they run. A branch holds its own blocks in config.blocks.
Example
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"id": "review-remaining-requests",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
}
}
],
"default_escalation_path": "string"
}'{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"blocks": [
{
"id": "review-remaining-requests",
"snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
},
"config_version": 1,
"content_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}Retrieve an agent's pipeline revision
/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions/{revision}Session or API keyRequires pipeline.readReturns one revision of an agent's pipeline, active or not, with its blocks in the order they run.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
agent_slugrequired | path · string | The slug of the agent whose pipeline this is. |
revisionrequired | path · int64 | The revision number. Revisions count up from 1. |
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | RequestApprovalPipelineRevision | The revision |
400 | ErrorResponse |
|
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot read pipelines |
404 | ErrorResponse | No such revision |
Response body200
Which requests a pipeline applies to. The organization pipeline runs first, for every request. An agent's pipeline runs after it, for that agent's requests.
One of organization, agent
The agent's slug. Present for the agent scope only.
The revision number. Revisions count up from 1.
Whether this is the revision in use.
Every block in the revision, including blocks inside branches.
The escalation path for requests that reach a person without a block naming a path.
The membership or organization-key actor that created the revision.
The blocks, in the order they run. A branch holds its own blocks in config.blocks.
Example
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions/{revision}" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY"{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"blocks": [
{
"id": "review-remaining-requests",
"snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
},
"config_version": 1,
"content_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}Activate an agent's pipeline revision
/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions/{revision}/activateSession or API keyRequires pipeline.activateMakes a revision the active one. New requests use it from then on. Activating an older revision is how you roll back.
Pass the revision you expect to be active in If-Match, quoted, as returned in ETag. If someone activated another revision in the meantime, the call fails with 412 and nothing changes.
Every escalation path the revision names, and every webhook endpoint its blocks post to, must have an active revision. Otherwise the call fails with 409 and nothing changes.
Request
Parameters
| Parameter | Type | Description |
|---|---|---|
agent_slugrequired | path · string | The slug of the agent whose pipeline this is. |
revisionrequired | path · int64 | The revision number. Revisions count up from 1. |
If-Matchrequired | header · string | The revision you expect to be active, quoted, for example |
Response
Response codes
| Status | Body | Description |
|---|---|---|
200 | RequestApprovalPipelineRevision | The revision, now active |
400 | ErrorResponse |
|
401 | ErrorResponse | You are not signed in |
403 | ErrorResponse | You cannot activate pipelines |
404 | ErrorResponse | No such revision |
409 | ErrorResponse | An escalation path or webhook endpoint the revision uses has no active revision ( |
412 | ErrorResponse |
|
428 | ErrorResponse | The |
Response body200
Which requests a pipeline applies to. The organization pipeline runs first, for every request. An agent's pipeline runs after it, for that agent's requests.
One of organization, agent
The agent's slug. Present for the agent scope only.
The revision number. Revisions count up from 1.
Whether this is the revision in use.
Every block in the revision, including blocks inside branches.
The escalation path for requests that reach a person without a block naming a path.
The membership or organization-key actor that created the revision.
The blocks, in the order they run. A branch holds its own blocks in config.blocks.
Example
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions/{revision}/activate" \
-H "Authorization: Bearer $WITHHUMAN_API_KEY" \
-H "If-Match: "2""{
"id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"scope": "organization",
"agent_slug": "string",
"revision": 1,
"is_active": true,
"block_count": 0,
"default_escalation_path": "string",
"created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-08T12:02:11Z",
"blocks": [
{
"id": "review-remaining-requests",
"snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"name": "Review remaining requests",
"type": "always",
"enabled": true,
"timeout": "50ms",
"max_attempts": 1,
"config": {
"outcome": "human",
"reason": "Requests not handled by earlier blocks need human review."
},
"config_version": 1,
"content_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}