Documentation
DocsAPI reference

Approval pipelines

The rules every approval request runs through, as an ordered list of blocks. A block can approve or deny a request, send it to a person, or pass it to the next block.

Updated Oct 22, 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 outcome (a fixed outcome), webhook (a service of yours), branch (blocks that run only when a condition matches), and delegate (hands matching requests to another pipeline); the hosted edition adds llm_judge (a model), which the open edition refuses as an unknown type. An outcome block returns its configured outcome for every request that reaches it; later blocks do not run.

Every request runs the entry pipeline, which every organization has and which cannot be created or archived. Other pipelines are created with a pipeline_key you choose once and can never reuse, and a request reaches them only through a delegate block. Every live pipeline has an active revision. To change one, create a revision, then activate it with If-Match set to the revision you expect to be active. Archiving a pipeline is permanent, and is refused for entry and while an active revision still delegates to it; its uses list those revisions.

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": "outcome",
  "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. 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. A branch whose blocks end in an outcome block never runs out, so its reason is optional. 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": "branch",
        "enabled": true,
        "timeout": "50ms",
        "max_attempts": 1,
        "config": {
          "when": {
            "id": "b2d94f1e-7c3a-4a58-8e06-3f1c9d7b2e45",
            "field": "/request/arguments/amount",
            "operator": "less_than",
            "value": 1000
          },
          "blocks": [
            {
              "id": "approve-small-refund",
              "name": "Approve the refund",
              "type": "outcome",
              "enabled": true,
              "timeout": "50ms",
              "max_attempts": 1,
              "config": {
                "outcome": "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.

Example: Hand Stripe requests to another pipeline

A delegate block hands the requests that match config.when to the pipeline named in config.pipeline_key, which continues with that pipeline's active revision. Other requests skip it. A request that is handed over is settled in that pipeline: if none of its blocks decides, it goes to a person with that pipeline's default escalation path, else the defaults of the pipelines that delegated into it, nearest first, and nothing after the delegate runs. A delegate has no reason or escalation_path, and can never name entry or its own pipeline.

json
{
  "id": "to-payments",
  "name": "Stripe requests",
  "type": "delegate",
  "enabled": true,
  "timeout": "50ms",
  "max_attempts": 1,
  "config": {
    "when": {
      "id": "0f3a7c52-91d4-4b6e-8a2f-5c7e1d9b3a64",
      "field": "/request/server",
      "operator": "equals",
      "value": "stripe"
    },
    "pipeline_key": "payments"
  }
}

The named pipeline must exist when you create the revision and be active when you activate it. Activation also refuses delegation that would loop or nest more than eight pipelines deep. A preview runs the document you send in place of this pipeline and expands each delegate the sample matches into its target's active revision; every result block carries its pipeline_key. Metadata earlier blocks returned is read as /previous_metadata/<pipeline_key>/<block_key>.

List pipelines

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

Returns every pipeline in the organization: the entry pipeline, where every request starts, first, then every pipeline a delegate block can hand requests to. Each entry carries the key, the name, the active revision number and a summary of its history.

A pipeline always has an active revision. An empty revision passes every request that enters the pipeline through to a person. An archived pipeline has no active revision, carries archived_at, and is left out unless status says otherwise. The list is narrowed to the pipelines you may read.

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": [
    {
      "pipeline_key": "string",
      "name": "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",
      "referenced_by": [
        {
          "pipeline_key": "string",
          "pipeline_name": "string",
          "revision": 1,
          "block_key": "string",
          "block_name": "string"
        }
      ]
    }
  ]
}

Create a pipeline

POST/api/v1/request-approval-pipelinesSession or API keyRequires pipeline.write

Creates a pipeline that a delegate block in another pipeline can hand requests to, with its first revision active at once. You choose the key; it is taken for good, archived pipelines included, and can never be entry, which every organization already has.

Creating a pipeline is an organization-wide act: it needs pipeline.write at the organization. Grant a team pipeline.read, pipeline.write and pipeline.activate at pipeline:<key> and they can edit the new pipeline and nothing else.

Request

Request body

pipeline_keystringrequired

The pipeline's key. Taken for good once used, archived pipelines included; never entry.

namestringrequired

The display name.

The first revision's blocks, in the order they should run. Omit for a pipeline that passes every request that enters it through to a person until it is edited.

The escalation path for requests that reach a person from this pipeline without a block naming a path, as in a document.

Send those requests to the person who owns the agent instance that made them instead. default_escalation_path, if set, is then the fallback for a request whose agent has no owner able to decide it (a service agent, or an owner who was deactivated); without it the fallback is the enclosing pipeline's default, else any authorized reviewer. The owner is the only reviewer: the request never moves to the path because the owner has not answered.

Response

Response codes

StatusBodyDescription
201RequestApprovalPipelineRevision

The new pipeline's first revision, active

400ErrorResponse

The key, the name, or the document is invalid, or a delegate names a pipeline that does not exist (request_approval_pipeline_not_found)

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot create pipelines

409ErrorResponse

The key was ever used (request_approval_pipeline_exists), or is entry (request_approval_pipeline_entry)

Response body201

iduuidrequired
pipeline_keystringrequired

The pipeline's key.

namestringrequired

The pipeline's display name as of this revision.

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 from this pipeline without a block naming a path.

Send requests that reach a person without a block naming a path to the person who owns the agent instance that made them, instead of default_escalation_path. The path, if any, is then the fallback for a request whose agent has no owner able to decide it; without one the fallback is the enclosing pipeline's default, else any authorized reviewer.

The membership or organization-key actor that created the revision.

created_atdate-timerequired
activated_atdate-time

When the revision was first active. Absent for a draft, which can still be changed.

archived_atdate-time

Present when the pipeline was archived.

The blocks, in the order they run. A branch holds its own blocks in config.blocks; a delegate names the pipeline it hands matching requests to in config.pipeline_key.

Example

POST /api/v1/request-approval-pipelines
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "pipeline_key": "payments",
  "name": "Payments",
  "blocks": [
    {
      "id": "review-remaining-requests",
      "name": "Review remaining requests",
      "type": "outcome",
      "enabled": true,
      "timeout": "50ms",
      "max_attempts": 1,
      "config": {
        "outcome": "human",
        "reason": "Requests not handled by earlier blocks need human review."
      }
    }
  ],
  "default_escalation_path": "string",
  "default_ask_agent_owner": true
}'
201 response
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "pipeline_key": "string",
  "name": "string",
  "revision": 1,
  "is_active": true,
  "block_count": 0,
  "default_escalation_path": "string",
  "default_ask_agent_owner": true,
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "activated_at": "2026-09-08T12:02:11Z",
  "archived_at": "2026-09-08T12:02:11Z",
  "blocks": [
    {
      "id": "review-remaining-requests",
      "snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "name": "Review remaining requests",
      "type": "outcome",
      "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 a pipeline's active revision

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

Returns the pipeline's active revision, with its blocks in the order they run. entry is the pipeline every request starts in; any other pipeline runs for the requests a delegate block hands to it.

The ETag header carries the active revision number. Pass it as If-Match when you activate another revision.

Request

Parameters

ParameterTypeDescription
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

Response

Response codes

StatusBodyDescription
200RequestApprovalPipelineRevision

The active revision

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot read this pipeline

404ErrorResponse

No pipeline with this key exists, or it is archived

Response body200

iduuidrequired
pipeline_keystringrequired

The pipeline's key.

namestringrequired

The pipeline's display name as of this revision.

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 from this pipeline without a block naming a path.

Send requests that reach a person without a block naming a path to the person who owns the agent instance that made them, instead of default_escalation_path. The path, if any, is then the fallback for a request whose agent has no owner able to decide it; without one the fallback is the enclosing pipeline's default, else any authorized reviewer.

The membership or organization-key actor that created the revision.

created_atdate-timerequired
activated_atdate-time

When the revision was first active. Absent for a draft, which can still be changed.

archived_atdate-time

Present when the pipeline was archived.

The blocks, in the order they run. A branch holds its own blocks in config.blocks; a delegate names the pipeline it hands matching requests to in config.pipeline_key.

Example

GET /api/v1/request-approval-pipelines/{pipeline_key}
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "pipeline_key": "string",
  "name": "string",
  "revision": 1,
  "is_active": true,
  "block_count": 0,
  "default_escalation_path": "string",
  "default_ask_agent_owner": true,
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "activated_at": "2026-09-08T12:02:11Z",
  "archived_at": "2026-09-08T12:02:11Z",
  "blocks": [
    {
      "id": "review-remaining-requests",
      "snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "name": "Review remaining requests",
      "type": "outcome",
      "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"
    }
  ]
}

Archive a pipeline

DELETE/api/v1/request-approval-pipelines/{pipeline_key}Session or API keyRequires pipeline.activate

Archives the pipeline: every revision is stamped, none is active, and its key stays taken. Its revisions remain readable with status=archived or all.

The entry pipeline cannot be archived, and neither can a pipeline that a delegate block in an active revision still hands requests to; remove those blocks first. GET .../uses lists them.

Request

Parameters

ParameterTypeDescription
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

Response

Response codes

StatusBodyDescription
204

The pipeline is archived

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot archive this pipeline

404ErrorResponse

No pipeline with this key exists

409ErrorResponse

The pipeline is the entry pipeline (request_approval_pipeline_entry), is already archived (request_approval_pipeline_archived), or an active revision delegates to it (request_approval_pipeline_in_use, with the uses in error.details)

Example

DELETE /api/v1/request-approval-pipelines/{pipeline_key}
curl -X DELETE "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"

List where a pipeline is delegated to

GET/api/v1/request-approval-pipelines/{pipeline_key}/usesSession or API keyRequires pipeline.read

Returns the active pipeline revisions whose delegate blocks hand requests to this pipeline. The same set refuses archiving it.

Request

Parameters

ParameterTypeDescription
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

Response

Response codes

StatusBodyDescription
200object

The uses

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot read this pipeline

Response body200

usesarray<PipelineUse>required

Example

GET /api/v1/request-approval-pipelines/{pipeline_key}/uses
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}/uses" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "uses": [
    {
      "pipeline_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "pipeline_key": "string",
      "revision": 1,
      "block_key": "string"
    }
  ]
}

Preview a pipeline

POST/api/v1/request-approval-pipelines/{pipeline_key}/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 document stands in for this pipeline. Delegate blocks expand to their target pipelines' current active revisions, so the preview walks what a request would; a skipped delegate calls nothing. The sample names an agent by slug and name; any valid slug will do, and conditions read it as agent.slug.

Request

Parameters

ParameterTypeDescription
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

Request body

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

Whether the default asks the agent's owner first, as in a document.

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, a delegate names a pipeline with no active revision (request_approval_pipeline_not_found), or the delegation would loop

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot edit this pipeline

Response body200

outcomeenumrequired

The final outcome.

One of approve, deny, human

reason_codeenumrequired

Why the run ended. block_outcome: a block decided. block_error, block_timeout, retries_exhausted, invalid_block_output: a block failed. end_of_pipeline: the request passed every block. end_of_branch: the request entered a branch or a delegated pipeline and none of its blocks decided.

One of block_outcome, block_error, block_timeout, retries_exhausted, invalid_block_output, end_of_pipeline, end_of_branch

The block that ended the run: for end_of_branch, the branch or the delegate. Absent when the request passed every block.

What each block the request reached did, in order, each with the pipeline it belongs to. A branch or delegate appears when its condition is evaluated, and once more with end_of_branch when none of the blocks inside decided.

The path a human outcome goes to: the ending block's path, else the default of the pipeline the request was in, else the defaults of the pipelines that delegated into it. Absent when the outcome is automatic or no path applies.

Where the path came from. block_escalation: the ending block named it. pipeline_default: a pipeline's default. no_escalation: no path applies.

One of block_escalation, pipeline_default, no_escalation

The pipeline whose block or default supplied the path. Absent with no_escalation.

Present when the route asks the agent's owner, with escalation_path as the fallback. A preview has no real agent instance, so it names the route, never a person.

Example

POST /api/v1/request-approval-pipelines/{pipeline_key}/preview
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}/preview" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "blocks": [
    {
      "id": "review-remaining-requests",
      "name": "Review remaining requests",
      "type": "outcome",
      "enabled": true,
      "timeout": "50ms",
      "max_attempts": 1,
      "config": {
        "outcome": "human",
        "reason": "Requests not handled by earlier blocks need human review."
      }
    }
  ],
  "default_escalation_path": "string",
  "default_ask_agent_owner": true,
  "sample": {
    "request": {
      "tool": "issue_refund",
      "server": "stripe",
      "arguments": {
        "amount_cents": 12000
      },
      "agent_reasoning": "string",
      "context": {}
    },
    "agent": {
      "slug": "string",
      "name": "string"
    },
    "previous_metadata": {}
  }
}'
200 response
{
  "outcome": "approve",
  "reason_code": "block_outcome",
  "terminal_block_id": "string",
  "blocks": [
    {
      "block_id": "string",
      "pipeline_key": "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",
  "escalation_path_pipeline_key": "string",
  "ask_agent_owner": true
}

List pipeline revisions

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

Returns the 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
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

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 this pipeline

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/{pipeline_key}/revisions
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}/revisions" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "revisions": [
    {
      "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "pipeline_key": "string",
      "name": "string",
      "revision": 1,
      "is_active": true,
      "block_count": 0,
      "default_escalation_path": "string",
      "default_ask_agent_owner": true,
      "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "created_at": "2026-09-08T12:02:11Z",
      "activated_at": "2026-09-08T12:02:11Z",
      "archived_at": "2026-09-08T12:02:11Z"
    }
  ],
  "next_cursor": "string",
  "total_count": 1
}

Create a pipeline revision

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

Creates a new revision of the pipeline from a complete document. The document is validated and compiled first, and every escalation path, webhook endpoint and pipeline it names must exist. The revision is created as a draft, which can be changed in place until it is activated. Activate it to put it into use. Omit name to keep the active revision's.

Request

Parameters

ParameterTypeDescription
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

Request body

namestring

A new display name for the pipeline. Omit to keep the active revision's.

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 that enters the pipeline through to a person.

The escalation path for requests that reach a person from this pipeline without a block naming a path. That includes requests that run out of it. A delegated pipeline without one inherits the defaults of the pipelines that delegated into it, nearest first; with none anywhere, 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.

Send those requests to the person who owns the agent instance that made them instead. default_escalation_path, if set, is then the fallback for a request whose agent has no owner able to decide it (a service agent, or an owner who was deactivated); without it the fallback is the enclosing pipeline's default, else any authorized reviewer. The owner is the only reviewer: the request never moves to the path because the owner has not answered.

Response

Response codes

StatusBodyDescription
201RequestApprovalPipelineRevision

The new revision, inactive

400ErrorResponse

The document is invalid, or a delegate names a pipeline that does not exist (request_approval_pipeline_not_found) or this pipeline itself

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot edit this pipeline

404ErrorResponse

No pipeline with this key exists

409ErrorResponse

The pipeline is archived (request_approval_pipeline_archived)

Response body201

iduuidrequired
pipeline_keystringrequired

The pipeline's key.

namestringrequired

The pipeline's display name as of this revision.

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 from this pipeline without a block naming a path.

Send requests that reach a person without a block naming a path to the person who owns the agent instance that made them, instead of default_escalation_path. The path, if any, is then the fallback for a request whose agent has no owner able to decide it; without one the fallback is the enclosing pipeline's default, else any authorized reviewer.

The membership or organization-key actor that created the revision.

created_atdate-timerequired
activated_atdate-time

When the revision was first active. Absent for a draft, which can still be changed.

archived_atdate-time

Present when the pipeline was archived.

The blocks, in the order they run. A branch holds its own blocks in config.blocks; a delegate names the pipeline it hands matching requests to in config.pipeline_key.

Example

POST /api/v1/request-approval-pipelines/{pipeline_key}/revisions
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}/revisions" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "blocks": [
    {
      "id": "review-remaining-requests",
      "name": "Review remaining requests",
      "type": "outcome",
      "enabled": true,
      "timeout": "50ms",
      "max_attempts": 1,
      "config": {
        "outcome": "human",
        "reason": "Requests not handled by earlier blocks need human review."
      }
    }
  ],
  "default_escalation_path": "string",
  "default_ask_agent_owner": true
}'
201 response
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "pipeline_key": "string",
  "name": "string",
  "revision": 1,
  "is_active": true,
  "block_count": 0,
  "default_escalation_path": "string",
  "default_ask_agent_owner": true,
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "activated_at": "2026-09-08T12:02:11Z",
  "archived_at": "2026-09-08T12:02:11Z",
  "blocks": [
    {
      "id": "review-remaining-requests",
      "snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "name": "Review remaining requests",
      "type": "outcome",
      "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 a pipeline revision

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

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

Request

Parameters

ParameterTypeDescription
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

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 this pipeline

404ErrorResponse

No such revision

Response body200

iduuidrequired
pipeline_keystringrequired

The pipeline's key.

namestringrequired

The pipeline's display name as of this revision.

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 from this pipeline without a block naming a path.

Send requests that reach a person without a block naming a path to the person who owns the agent instance that made them, instead of default_escalation_path. The path, if any, is then the fallback for a request whose agent has no owner able to decide it; without one the fallback is the enclosing pipeline's default, else any authorized reviewer.

The membership or organization-key actor that created the revision.

created_atdate-timerequired
activated_atdate-time

When the revision was first active. Absent for a draft, which can still be changed.

archived_atdate-time

Present when the pipeline was archived.

The blocks, in the order they run. A branch holds its own blocks in config.blocks; a delegate names the pipeline it hands matching requests to in config.pipeline_key.

Example

GET /api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "pipeline_key": "string",
  "name": "string",
  "revision": 1,
  "is_active": true,
  "block_count": 0,
  "default_escalation_path": "string",
  "default_ask_agent_owner": true,
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "activated_at": "2026-09-08T12:02:11Z",
  "archived_at": "2026-09-08T12:02:11Z",
  "blocks": [
    {
      "id": "review-remaining-requests",
      "snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "name": "Review remaining requests",
      "type": "outcome",
      "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"
    }
  ]
}

Update a draft pipeline revision

PUT/api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}Session or API keyRequires pipeline.write

Replaces the content of a draft revision, one that has never been active, with a complete document. The revision keeps its number. The document is checked exactly as a new revision's. Omit name to keep the draft's. Once a revision has been active it can no longer change.

Request

Parameters

ParameterTypeDescription
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

revisionrequiredpath · int64

The revision number. Revisions count up from 1.

Request body

namestring

A new display name for the pipeline. Omit to keep the active revision's.

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 that enters the pipeline through to a person.

The escalation path for requests that reach a person from this pipeline without a block naming a path. That includes requests that run out of it. A delegated pipeline without one inherits the defaults of the pipelines that delegated into it, nearest first; with none anywhere, 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.

Send those requests to the person who owns the agent instance that made them instead. default_escalation_path, if set, is then the fallback for a request whose agent has no owner able to decide it (a service agent, or an owner who was deactivated); without it the fallback is the enclosing pipeline's default, else any authorized reviewer. The owner is the only reviewer: the request never moves to the path because the owner has not answered.

Response

Response codes

StatusBodyDescription
200RequestApprovalPipelineRevision

The updated draft

400ErrorResponse

The document is invalid, or a delegate names a pipeline that does not exist (request_approval_pipeline_not_found) or this pipeline itself

401ErrorResponse

You are not signed in

403ErrorResponse

You cannot edit this pipeline

404ErrorResponse

No such revision

409ErrorResponse

The revision has been active (revision_not_draft), or the pipeline is archived (request_approval_pipeline_archived)

Response body200

iduuidrequired
pipeline_keystringrequired

The pipeline's key.

namestringrequired

The pipeline's display name as of this revision.

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 from this pipeline without a block naming a path.

Send requests that reach a person without a block naming a path to the person who owns the agent instance that made them, instead of default_escalation_path. The path, if any, is then the fallback for a request whose agent has no owner able to decide it; without one the fallback is the enclosing pipeline's default, else any authorized reviewer.

The membership or organization-key actor that created the revision.

created_atdate-timerequired
activated_atdate-time

When the revision was first active. Absent for a draft, which can still be changed.

archived_atdate-time

Present when the pipeline was archived.

The blocks, in the order they run. A branch holds its own blocks in config.blocks; a delegate names the pipeline it hands matching requests to in config.pipeline_key.

Example

PUT /api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}
curl -X PUT "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "blocks": [
    {
      "id": "review-remaining-requests",
      "name": "Review remaining requests",
      "type": "outcome",
      "enabled": true,
      "timeout": "50ms",
      "max_attempts": 1,
      "config": {
        "outcome": "human",
        "reason": "Requests not handled by earlier blocks need human review."
      }
    }
  ],
  "default_escalation_path": "string",
  "default_ask_agent_owner": true
}'
200 response
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "pipeline_key": "string",
  "name": "string",
  "revision": 1,
  "is_active": true,
  "block_count": 0,
  "default_escalation_path": "string",
  "default_ask_agent_owner": true,
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "activated_at": "2026-09-08T12:02:11Z",
  "archived_at": "2026-09-08T12:02:11Z",
  "blocks": [
    {
      "id": "review-remaining-requests",
      "snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "name": "Review remaining requests",
      "type": "outcome",
      "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"
    }
  ]
}

Report what a pipeline revision depends on

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

Reports the escalation paths, webhook endpoints and pipelines the revision names with their current state, and any block the deployment or plan cannot run, so a rollback can be explained before it is attempted. The activation itself re-checks inside its transaction.

Request

Parameters

ParameterTypeDescription
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

revisionrequiredpath · int64

The revision number. Revisions count up from 1.

Response

Response codes

StatusBodyDescription
200PipelineReferences

Live state of every dependency

400ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

401ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

403ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

404ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

Response body200

pipelinesarray<object>required

The pipelines the revision's delegate blocks hand requests to.

escalation_pathsarray<object>required

The escalation paths the revision names.

webhook_endpointsarray<object>required

The webhook endpoints the revision posts to.

blocksarray<object>required

Blocks the deployment or the plan cannot run right now.

activatablebooleanrequired

Whether activating the revision would succeed right now.

Example

GET /api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}/references
curl -X GET "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}/references" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
200 response
{
  "pipelines": [
    {
      "pipeline_key": "string",
      "name": "string",
      "block_keys": [
        "string"
      ],
      "exists": true,
      "active": true
    }
  ],
  "escalation_paths": [
    {
      "path_key": "string",
      "block_keys": [
        "string"
      ],
      "default": true,
      "exists": true,
      "active": true
    }
  ],
  "webhook_endpoints": [
    {
      "endpoint_key": "string",
      "name": "string",
      "block_keys": [
        "string"
      ],
      "exists": true,
      "active": true
    }
  ],
  "blocks": [
    {
      "block_key": "string",
      "type": "string",
      "problem": "string"
    }
  ],
  "activatable": true
}

Activate a pipeline revision

POST/api/v1/request-approval-pipelines/{pipeline_key}/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, every webhook endpoint its blocks post to, and every pipeline its delegate blocks hand requests to must have an active revision, and the delegation graph must stay a tree: no pipeline may reach itself again, no chain may be deeper than 8 delegations, and the flattened program from the entry pipeline may hold at most 10,000 blocks. Otherwise the call fails with 409 and nothing changes.

Request

Parameters

ParameterTypeDescription
pipeline_keyrequiredpath · string

The pipeline's key. entry is the pipeline every request starts in; every other key was chosen when the pipeline was created. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters.

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 this pipeline

404ErrorResponse

No such revision

409ErrorResponse

A reference has no active revision (escalation_path_inactive, webhook_endpoint_inactive, or request_approval_pipeline_inactive, with the keys in error.details), the delegation graph would loop (request_approval_pipeline_delegation_cycle, the keys on the cycle in error.details.pipelines), run too deep (request_approval_pipeline_delegation_too_deep) or too large (request_approval_pipeline_too_large), or the pipeline is archived (request_approval_pipeline_archived)

412ErrorResponse

If-Match does not match the active revision

428ErrorResponse

The If-Match header is missing

Response body200

iduuidrequired
pipeline_keystringrequired

The pipeline's key.

namestringrequired

The pipeline's display name as of this revision.

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 from this pipeline without a block naming a path.

Send requests that reach a person without a block naming a path to the person who owns the agent instance that made them, instead of default_escalation_path. The path, if any, is then the fallback for a request whose agent has no owner able to decide it; without one the fallback is the enclosing pipeline's default, else any authorized reviewer.

The membership or organization-key actor that created the revision.

created_atdate-timerequired
activated_atdate-time

When the revision was first active. Absent for a draft, which can still be changed.

archived_atdate-time

Present when the pipeline was archived.

The blocks, in the order they run. A branch holds its own blocks in config.blocks; a delegate names the pipeline it hands matching requests to in config.pipeline_key.

Example

POST /api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}/activate
curl -X POST "$WITHHUMAN_URL/api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}/activate" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY" \
  -H "If-Match: "2""
200 response
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "pipeline_key": "string",
  "name": "string",
  "revision": 1,
  "is_active": true,
  "block_count": 0,
  "default_escalation_path": "string",
  "default_ask_agent_owner": true,
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "activated_at": "2026-09-08T12:02:11Z",
  "archived_at": "2026-09-08T12:02:11Z",
  "blocks": [
    {
      "id": "review-remaining-requests",
      "snapshot_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "name": "Review remaining requests",
      "type": "outcome",
      "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"
    }
  ]
}