Documentation
DocsAPI reference

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.

Updated Sep 24, 2026

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.

json
{
  "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.

json
{
  "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

GET/api/v1/request-approval-pipelinesSession or API keyRequires pipeline.read

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

ParameterTypeDescription
statusquery · enum

Which pipelines to return. live (the default) leaves archived pipelines out, archived returns only them, and all returns both.

One of live, archived, all
limitquery · integer

How many pipelines to return. Defaults to 50.

Response

Response codes

StatusBodyDescription
200object

The pipelines

400ErrorResponse

status is not live, archived, or all

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot read pipelines

Response body200

Example

GET /api/v1/request-approval-pipelines
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "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

GET/api/v1/request-approval-pipelines/organizationSession or API keyRequires pipeline.read

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

StatusBodyDescription
200RequestApprovalPipelineRevision

The active revision

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot read pipelines

404ErrorResponse

No revision is active

Response body200

iduuidrequired
scopeenumrequired

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.

revisionint64required

The revision number. Revisions count up from 1.

is_activebooleanrequired

Whether this is the revision in use.

block_countintegerrequired

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.

created_atdate-timerequired

The blocks, in the order they run. A branch holds its own blocks in config.blocks.

Example

GET /api/v1/request-approval-pipelines/organization
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "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

POST/api/v1/request-approval-pipelines/organization/previewSession or API keyRequires pipeline.write

Runs 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 default escalation path, as in a document. Reported in the result when the outcome is human.

sampleobjectrequired

The request to run the pipeline against.

Response

Response codes

StatusBodyDescription
200RequestApprovalPipelinePreview

What the pipeline would do with the sample

400ErrorResponse

The document or the sample is invalid

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot edit pipelines

Response body200

outcomeenumrequired

The final outcome.

One of approve, deny, human

reason_codeenumrequired

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

POST /api/v1/request-approval-pipelines/organization/preview
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": {}
    }
  }
}'
200 response
{
  "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

GET/api/v1/request-approval-pipelines/organization/revisionsSession or API keyRequires pipeline.read

Returns 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

ParameterTypeDescription
cursorquery · string

The next_cursor from the previous page.

limitquery · integer

How many revisions to return per page. Defaults to 50.

Response

Response codes

StatusBodyDescription
200object

The revisions, newest first

400ErrorResponse

cursor is not a cursor this endpoint issued

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot read pipelines

Response body200

The revisions on this page, newest first.

Present when another page follows.

total_countint64required

How many revisions the pipeline has, across every page.

Example

GET /api/v1/request-approval-pipelines/organization/revisions
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization/revisions" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "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

POST/api/v1/request-approval-pipelines/organization/revisionsSession or API keyRequires pipeline.write

Creates 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

StatusBodyDescription
201RequestApprovalPipelineRevision

The new revision, inactive

400ErrorResponse

The document is invalid

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot edit pipelines

Response body201

iduuidrequired
scopeenumrequired

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.

revisionint64required

The revision number. Revisions count up from 1.

is_activebooleanrequired

Whether this is the revision in use.

block_countintegerrequired

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.

created_atdate-timerequired

The blocks, in the order they run. A branch holds its own blocks in config.blocks.

Example

POST /api/v1/request-approval-pipelines/organization/revisions
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"
}'
201 response
{
  "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

GET/api/v1/request-approval-pipelines/organization/revisions/{revision}Session or API keyRequires pipeline.read

Returns one revision of the organization pipeline, active or not, with its blocks in the order they run.

Request

Parameters

ParameterTypeDescription
revisionrequiredpath · int64

The revision number. Revisions count up from 1.

Response

Response codes

StatusBodyDescription
200RequestApprovalPipelineRevision

The revision

400ErrorResponse

revision is not a positive integer

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot read pipelines

404ErrorResponse

No such revision

Response body200

iduuidrequired
scopeenumrequired

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.

revisionint64required

The revision number. Revisions count up from 1.

is_activebooleanrequired

Whether this is the revision in use.

block_countintegerrequired

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.

created_atdate-timerequired

The blocks, in the order they run. A branch holds its own blocks in config.blocks.

Example

GET /api/v1/request-approval-pipelines/organization/revisions/{revision}
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization/revisions/{revision}" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "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

POST/api/v1/request-approval-pipelines/organization/revisions/{revision}/activateSession or API keyRequires pipeline.activate

Makes 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

ParameterTypeDescription
revisionrequiredpath · int64

The revision number. Revisions count up from 1.

If-Matchrequiredheader · string

The revision you expect to be active, quoted, for example "2". Take it from the ETag of your last read. If the active revision changed in the meantime, the call fails with 412 and nothing changes. Escalation paths and webhook endpoints accept "0" when no revision is active. Pipelines always have an active revision.

Response

Response codes

StatusBodyDescription
200RequestApprovalPipelineRevision

The revision, now active

400ErrorResponse

revision or If-Match is malformed

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot activate pipelines

404ErrorResponse

No such revision

409ErrorResponse

An escalation path or webhook endpoint the revision uses has no active revision. error.code is escalation_path_inactive or webhook_endpoint_inactive, and error.details lists the keys

412ErrorResponse

If-Match does not match the active revision

428ErrorResponse

The If-Match header is missing

Response body200

iduuidrequired
scopeenumrequired

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.

revisionint64required

The revision number. Revisions count up from 1.

is_activebooleanrequired

Whether this is the revision in use.

block_countintegerrequired

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.

created_atdate-timerequired

The blocks, in the order they run. A branch holds its own blocks in config.blocks.

Example

POST /api/v1/request-approval-pipelines/organization/revisions/{revision}/activate
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/organization/revisions/{revision}/activate" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY" \
  -H "If-Match: "2""
200 response
{
  "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

GET/api/v1/request-approval-pipelines/agents/{agent_slug}Session or API keyRequires pipeline.read

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

ParameterTypeDescription
agent_slugrequiredpath · string

The slug of the agent whose pipeline this is.

Response

Response codes

StatusBodyDescription
200RequestApprovalPipelineRevision

The active revision

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot read pipelines

404ErrorResponse

No agent with this slug exists, or the agent and its pipeline are archived

Response body200

iduuidrequired
scopeenumrequired

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.

revisionint64required

The revision number. Revisions count up from 1.

is_activebooleanrequired

Whether this is the revision in use.

block_countintegerrequired

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.

created_atdate-timerequired

The blocks, in the order they run. A branch holds its own blocks in config.blocks.

Example

GET /api/v1/request-approval-pipelines/agents/{agent_slug}
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/agents/{agent_slug}" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "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

POST/api/v1/request-approval-pipelines/agents/{agent_slug}/previewSession or API keyRequires pipeline.write

Runs 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

ParameterTypeDescription
agent_slugrequiredpath · string

The slug of the agent whose pipeline this is.

Request body

The default escalation path, as in a document. Reported in the result when the outcome is human.

sampleobjectrequired

The request to run the pipeline against.

Response

Response codes

StatusBodyDescription
200RequestApprovalPipelinePreview

What the pipeline would do with the sample

400ErrorResponse

The document or the sample is invalid

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot edit pipelines

Response body200

outcomeenumrequired

The final outcome.

One of approve, deny, human

reason_codeenumrequired

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

POST /api/v1/request-approval-pipelines/agents/{agent_slug}/preview
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": {}
    }
  }
}'
200 response
{
  "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

GET/api/v1/request-approval-pipelines/agents/{agent_slug}/revisionsSession or API keyRequires pipeline.read

Returns 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

ParameterTypeDescription
agent_slugrequiredpath · string

The slug of the agent whose pipeline this is.

cursorquery · string

The next_cursor from the previous page.

limitquery · integer

How many revisions to return per page. Defaults to 50.

Response

Response codes

StatusBodyDescription
200object

The revisions, newest first

400ErrorResponse

cursor is not a cursor this endpoint issued

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot read pipelines

Response body200

The revisions on this page, newest first.

Present when another page follows.

total_countint64required

How many revisions the pipeline has, across every page.

Example

GET /api/v1/request-approval-pipelines/agents/{agent_slug}/revisions
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "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

POST/api/v1/request-approval-pipelines/agents/{agent_slug}/revisionsSession or API keyRequires pipeline.write

Creates 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

ParameterTypeDescription
agent_slugrequiredpath · 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

StatusBodyDescription
201RequestApprovalPipelineRevision

The new revision, inactive

400ErrorResponse

The document is invalid

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot edit pipelines

404ErrorResponse

No agent with this slug exists

409ErrorResponse

The agent and its pipeline are archived (request_approval_pipeline_archived)

Response body201

iduuidrequired
scopeenumrequired

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.

revisionint64required

The revision number. Revisions count up from 1.

is_activebooleanrequired

Whether this is the revision in use.

block_countintegerrequired

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.

created_atdate-timerequired

The blocks, in the order they run. A branch holds its own blocks in config.blocks.

Example

POST /api/v1/request-approval-pipelines/agents/{agent_slug}/revisions
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"
}'
201 response
{
  "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

GET/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions/{revision}Session or API keyRequires pipeline.read

Returns one revision of an agent's pipeline, active or not, with its blocks in the order they run.

Request

Parameters

ParameterTypeDescription
agent_slugrequiredpath · string

The slug of the agent whose pipeline this is.

revisionrequiredpath · int64

The revision number. Revisions count up from 1.

Response

Response codes

StatusBodyDescription
200RequestApprovalPipelineRevision

The revision

400ErrorResponse

revision is not a positive integer

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot read pipelines

404ErrorResponse

No such revision

Response body200

iduuidrequired
scopeenumrequired

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.

revisionint64required

The revision number. Revisions count up from 1.

is_activebooleanrequired

Whether this is the revision in use.

block_countintegerrequired

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.

created_atdate-timerequired

The blocks, in the order they run. A branch holds its own blocks in config.blocks.

Example

GET /api/v1/request-approval-pipelines/agents/{agent_slug}/revisions/{revision}
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions/{revision}" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "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

POST/api/v1/request-approval-pipelines/agents/{agent_slug}/revisions/{revision}/activateSession or API keyRequires pipeline.activate

Makes 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

ParameterTypeDescription
agent_slugrequiredpath · string

The slug of the agent whose pipeline this is.

revisionrequiredpath · int64

The revision number. Revisions count up from 1.

If-Matchrequiredheader · string

The revision you expect to be active, quoted, for example "2". Take it from the ETag of your last read. If the active revision changed in the meantime, the call fails with 412 and nothing changes. Escalation paths and webhook endpoints accept "0" when no revision is active. Pipelines always have an active revision.

Response

Response codes

StatusBodyDescription
200RequestApprovalPipelineRevision

The revision, now active

400ErrorResponse

revision or If-Match is malformed

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot activate pipelines

404ErrorResponse

No such revision

409ErrorResponse

An escalation path or webhook endpoint the revision uses has no active revision (escalation_path_inactive or webhook_endpoint_inactive, with the keys in error.details), or the revision or the pipeline was archived with the type's last agent (request_approval_pipeline_archived)

412ErrorResponse

If-Match does not match the active revision

428ErrorResponse

The If-Match header is missing

Response body200

iduuidrequired
scopeenumrequired

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.

revisionint64required

The revision number. Revisions count up from 1.

is_activebooleanrequired

Whether this is the revision in use.

block_countintegerrequired

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.

created_atdate-timerequired

The blocks, in the order they run. A branch holds its own blocks in config.blocks.

Example

POST /api/v1/request-approval-pipelines/agents/{agent_slug}/revisions/{revision}/activate
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""
200 response
{
  "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"
    }
  ]
}