DocsAPI reference
OpenAPI schema
Read or download the OpenAPI contract for the published withHuman API.
Updated Oct 26, 2026
openapi.yaml
openapi: 3.1.0
info:
title: withHuman provider API
version: 1.0.0
description: Agent Approval Protocol and reviewer product endpoints.
servers:
- url: /
tags:
- name: Agent Approval Protocol
description: What an adapter calls on behalf of a running agent. Create an instance and get its
credential, ask for approval of a tool call, and wait for the answer.
- name: Review queue
description: The review queue is where approval requests wait for a person to approve or deny them.
These endpoints list the queue, retrieve one request, claim it while you look at it, and
record your decision.
- name: Agent enrollment
description: Connect an agent from a developer machine. The agent asks for a code, a person approves
it in the browser, and the agent exchanges the code for a credential. A withHuman extension,
not part of the protocol.
- name: Account
description: Who the session belongs to. Read the current member, their organization and permissions.
- name: Agents
description: Agents are the AI systems that ask for approval. Each agent has instances, and each
instance holds a credential it uses to call the API.
- name: Approval pipelines
description: 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.
- name: Escalation paths
description: An escalation path decides who reviews a request once the pipeline hands it to a
person, and what happens when nobody answers in time.
- name: Webhook endpoints
description: A webhook endpoint is a URL on a service of yours that a pipeline's webhook block can
ask for an outcome.
- name: Members
description: The people in the organization, their status, and their permissions.
- name: Teams
description: Groups of members. A team can be an escalation target, and its members inherit the
team's permissions.
- name: Invitations
description: Invite people to the organization by email.
- name: API keys
description: "Personal API keys let a script act as you outside the browser: a whk_ bearer that
carries a subset of your own permissions, changes with your permissions, and dies with your
membership. Mint one, list yours, revoke it, and verify what a key can do."
- name: Audit
description: The organization's append-only log of what happened and who did it, and the request
timeline it tells.
paths:
/auth/agent_enrollments:
post:
tags:
- Agent enrollment
summary: Start an enrollment
description: Starts an enrollment and returns two codes. The `enrollment_code` stays on the machine.
The `user_code` is for a person to enter at the `verification_uri`. Both expire at
`expires_at`, ten minutes after this call. The person selects an existing agent or creates
one in the browser, then authorizes the instance. An optional requested agent name fixes the
target within the organization they sign into.
operationId: beginAgentEnrollment
x-withhuman-permission: public
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- runtime
- instance_name
properties:
runtime:
type: string
minLength: 1
example: claude-code
description: The runtime being installed, independent of its parent agent.
requested_agent_slug:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: codex
description: Optional slug of an existing agent. The browser must authorize this target; it cannot
create or substitute another agent.
instance_name:
type: string
example: eces-macbook
description: A name for this instance, unique within the agent. The CLI defaults to the machine's
hostname plus a unique suffix. Reviewers see it next to the agent's name.
instance_metadata:
type: object
additionalProperties: true
example:
hostname: eces-macbook
os: darwin
description: Any JSON object to store with the instance, such as the hostname or operating system.
responses:
"201":
description: The codes and where to use them
content:
application/json:
schema:
$ref: "#/components/schemas/EnrollmentCode"
"400":
description: A required field is missing or blank
/auth/agent_enrollments/token:
post:
tags:
- Agent enrollment
summary: Exchange an enrollment code
description: Exchanges an authorized enrollment code for the new instance and its credential. Call
this every few seconds after starting an enrollment. It returns `404` until a person has
authorized the user code. Once it succeeds, the code is used up and a second call returns
`404` as well. The credential's token appears only in this response, so store it right away.
operationId: exchangeAgentEnrollment
x-withhuman-permission: public
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- enrollment_code
properties:
enrollment_code:
type: string
example: whe_7c1f2a9e-4b3d-4f2e-9a1c-2d6e8b5f0a11_3f6b9c1d0e7a4b2c
description: The `enrollment_code` returned when the enrollment started.
responses:
"200":
description: The instance and its credential. The credential's token appears only in this response.
content:
application/json:
schema:
$ref: "#/components/schemas/AgentEnrollmentCredential"
"401":
description: The enrollment code is not in the expected format
"403":
description: The agent or the instance has been disabled since it was authorized
"404":
description: The code has not been authorized yet, has expired, or has already been exchanged
/api/aap/v1/instances:
post:
summary: Create an agent instance
description: "Creates a new instance of the agent the provisioner token belongs to and returns it
together with its agent credential. Use this from a fleet or platform that starts agent
replicas itself, so each replica gets a credential of its own. The bearer token must be a
provisioner token (`whp_`); an agent credential is rejected. Requires an `Idempotency-Key`
header scoped to the provisioner: repeating the same submission with the same key within
seven days answers `200` with the original instance and credential, without creating another
instance, renewing the credential or verifying the receiver again. Reusing a key with
different input answers `409` with code `idempotency_conflict`, a key whose attempt is still
running answers `409` with code `idempotency_in_progress`, and replaying a key whose
instance was deleted answers `409` with code `instance_deleted`. Pass `delivery` to use the
Agent Approval Protocol's asynchronous mode: before the instance is created, withHuman sends
a signed `webhook.verification` message to the receiver, which must answer `200` with the
same `challenge` within ten seconds, or the call fails with `422` and code
`webhook_verification_failed` and no instance is created (the same key may be retried). A
malformed or non-public receiver URL answers `400` with code `invalid_webhook_url`.
Afterwards, every request of the instance that becomes approved, denied, expired or
cancelled sends one signed `request.resolved` notification to the receiver, retried with
backoff for up to seven days until it answers `2xx`; notifications wait while the instance
is disabled. The notification carries only IDs; read the decision with `GET
/api/aap/v1/requests/{id}`. Both messages are signed with the Standard Webhooks scheme
(`webhook-id`, `webhook-timestamp` and `webhook-signature` headers). Requires the
deployment's secrets key; without it, delivery is refused with `501`."
operationId: mintAgentInstance
x-withhuman-permission: public
tags:
- Agent Approval Protocol
security:
- bearerAuth: []
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: false
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
instance_name:
type: string
example: ci-runner-07
description: A name for the instance, unique within the agent. Reviewers see it next to the agent's
name.
instance_metadata:
type: object
additionalProperties: true
example:
region: eu-west
description: Any JSON object to store with the instance, such as a region or pod name.
ttl_seconds:
type: integer
format: int64
minimum: 0
example: 86400
description: How long the credential stays valid, in seconds. A value above the provider's default
is reduced to the default.
delivery:
$ref: "#/components/schemas/AgentInstanceDeliveryInput"
responses:
"200":
description: The instance and credential an earlier submission with the same idempotency key created.
content:
application/json:
schema:
$ref: "#/components/schemas/AAPProvisionedInstance"
"201":
description: The new instance and its credential. Keep the credential's token; only a replay with
the same provisioner token and idempotency key returns it again.
content:
application/json:
schema:
$ref: "#/components/schemas/AAPProvisionedInstance"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
"422":
$ref: "#/components/responses/Error"
"501":
$ref: "#/components/responses/Error"
/api/aap/v1/instances/{id}:
get:
summary: Retrieve an agent instance
description: Returns an instance of the provisioner token's agent with its receiver, if any. The
signing secret and credentials are never returned. A deleted instance, or one of another
agent, answers `404`.
operationId: getProvisionedInstance
x-withhuman-permission: public
tags:
- Agent Approval Protocol
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: The instance
content:
application/json:
schema:
$ref: "#/components/schemas/AAPInstance"
"401":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
patch:
summary: Update an agent instance
description: "Renames an instance of the provisioner token's agent, replaces its receiver, or
removes it. A `delivery` object replaces the whole configuration, signing secret included,
and is verified first exactly as at creation: the old configuration and name stay in place
until verification succeeds, and a failure answers `422` with code
`webhook_verification_failed`. Queued notifications then go to the new receiver with the new
secret, with unchanged IDs and bodies. `delivery: null` removes the receiver and ends
outstanding deliveries and retries. Requires an `Idempotency-Key` header scoped to the
provisioner and the instance: repeating the same submission answers the original response
without applying it again. A deleted instance answers `404`."
operationId: updateProvisionedInstance
x-withhuman-permission: public
tags:
- Agent Approval Protocol
security:
- bearerAuth: []
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
minProperties: 1
properties:
instance_name:
type: string
minLength: 1
example: ci-runner-07
delivery:
description: Omit to keep the receiver, pass a complete configuration to replace it, or `null` to
remove it.
oneOf:
- $ref: "#/components/schemas/AgentInstanceDeliveryInput"
- type: "null"
responses:
"200":
description: The updated instance
content:
application/json:
schema:
$ref: "#/components/schemas/AAPInstance"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
"422":
$ref: "#/components/responses/Error"
"501":
$ref: "#/components/responses/Error"
delete:
summary: Delete an agent instance
description: "Retires an instance of the provisioner token's agent: its credentials are revoked, its
receiver is removed with every outstanding delivery, and each of its pending requests is
cancelled without a notification. Requests that already reached an outcome are unchanged.
The instance stays listed in the product, disabled, and cannot be enabled again. Deleting it
again answers `204`."
operationId: deleteProvisionedInstance
x-withhuman-permission: public
tags:
- Agent Approval Protocol
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"204":
description: The instance is deleted
"401":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
/api/aap/v1/requests:
post:
summary: Create an approval request
operationId: createApprovalRequest
x-withhuman-permission: request.create
description: "Asks for approval to run a tool call. The organization's approval pipeline evaluates
the request first and may approve or deny it on the spot, in which case the response already
carries the decision. Otherwise the request goes to reviewers and comes back as `pending`;
retrieve it with a `wait` to learn the outcome. A request nobody decides before its timeout
becomes `expired`: the adapter does not run the call and tells the agent the approval timed
out. An adapter that stops waiting should cancel the request so reviewers stop seeing it.
Requires an `Idempotency-Key` header scoped to this operation and the authenticated
instance. Reusing it with changed input returns 409."
tags:
- Agent Approval Protocol
security:
- bearerAuth: []
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateApproval"
responses:
"201":
description: The request, either already decided by the pipeline or pending review
content:
application/json:
schema:
$ref: "#/components/schemas/AAPApprovalRequest"
examples:
pending:
$ref: "#/components/examples/AAPPendingRequest"
approved:
$ref: "#/components/examples/AAPApprovedRequest"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"409":
description: The idempotency key was already used for a different request
"429":
description: The organization has reached its limit of pending requests
/api/aap/v1/requests/{id}:
get:
summary: Retrieve an approval request
operationId: getApprovalDecision
x-withhuman-permission: request.read
description: Returns the request and, once it has been decided, its decision. Pass `wait` to hold
the response until the request is decided or the wait runs out, whichever comes first. Only
the instance that created the request may retrieve it. A request stops changing once it is
`approved`, `denied`, `expired`, or `cancelled`.
tags:
- Agent Approval Protocol
security:
- bearerAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
- name: wait
in: query
description: How long to hold the response for a decision, as a duration such as `30s`. Capped at 30
seconds. Omit to return immediately.
schema:
type: string
example: 30s
responses:
"200":
description: The request in its current state
content:
application/json:
schema:
$ref: "#/components/schemas/AAPApprovalRequest"
examples:
pending:
$ref: "#/components/examples/AAPPendingRequest"
approved:
$ref: "#/components/examples/AAPApprovedRequest"
"401":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
delete:
summary: Cancel an approval request
operationId: cancelApprovalRequest
x-withhuman-permission: request.create
description: "Withdraws a pending request. Use it when the runtime that asked stops waiting for the
answer: it was interrupted, timed out locally, or is shutting down. Reviewers stop seeing
the request, notifications stop, and anyone holding it is released. Only the instance that
created the request may cancel it; another instance of the same agent gets a `403`. A
cancelled request is final and answers `cancelled` with a protocol decision, so a long poll
in flight returns at once. Cancelling again returns the same cancelled request. A request
that was already `approved`, `denied`, or `expired` is unchanged and answers `409` with code
`already_terminal`. This is an optional capability of the Agent Approval Protocol: adapters
must treat a `404` or `405` from a provider that does not offer it as nothing to do."
tags:
- Agent Approval Protocol
security:
- bearerAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
responses:
"200":
description: The request, now cancelled
content:
application/json:
schema:
$ref: "#/components/schemas/AAPApprovalRequest"
examples:
cancelled:
$ref: "#/components/examples/AAPCancelledRequest"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
description: The request was already decided or expired (code `already_terminal`)
/api/aap/v1/requests/{id}/consume:
post:
summary: Consume an approval
operationId: consumeApprovalRequest
x-withhuman-permission: request.create
description: "Marks an approved request as used, right before the adapter runs the call. Each
approval runs one call: only one consume succeeds, so a second attempt to run the same call
is refused. Call it after checking `decision.expires_at`, and do not start the call until it
succeeds. Only the instance that created the request may consume it; another instance gets a
`403`. Requires an `Idempotency-Key` header that identifies this execution attempt:
repeating the key returns the request as it is now, even after `expires_at`. The request
takes no body; an empty body or `{}` is accepted. Answers `409` with code `already_consumed`
when another key already consumed it, `not_approved` when the request is not `approved`, and
`approval_expired` once `expires_at` has passed."
tags:
- Agent Approval Protocol
security:
- bearerAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: false
content:
application/json:
schema:
type: object
additionalProperties: false
responses:
"200":
description: The request, now consumed
content:
application/json:
schema:
$ref: "#/components/schemas/AAPApprovalRequest"
examples:
consumed:
$ref: "#/components/examples/AAPConsumedRequest"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
description: The approval was already used (code `already_consumed`), the request is not approved
(`not_approved`), or the approval expired (`approval_expired`)
/api/aap/v1/requests/{id}/outcome:
post:
summary: Report an approved call's outcome
operationId: reportApprovalOutcome
x-withhuman-permission: request.create
description: "Records whether the approved call succeeded, after the adapter consumed the approval
and ran it. The outcome is for the record only: it never changes the request's status or
decision. Report it once. Repeating the same body returns the same request; a different
outcome answers `409` with code `outcome_conflict`. A request that was not consumed answers
`409` with code `not_consumed`. Only the instance that created the request may report its
outcome; another instance gets a `403`."
tags:
- Agent Approval Protocol
security:
- bearerAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ReportApprovalOutcome"
responses:
"200":
description: The request with the outcome
content:
application/json:
schema:
$ref: "#/components/schemas/AAPApprovalRequest"
examples:
reported:
$ref: "#/components/examples/AAPReportedRequest"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
description: The approval was not consumed (code `not_consumed`), or a different outcome was already
reported (`outcome_conflict`)
/api/v1/requests:
get:
summary: List the review queue
description: >-
Returns the list of approval requests that reached human review: pending, decided by a
person, or expired while waiting. Approval requests the pipeline decided automatically are
not included. They stay in the audit log and can still be retrieved by id. Requests are
returned newest first.
Pass `agent_slug` to list one agent's requests instead. That list includes the requests the
pipeline decided automatically.
Pass `per_agent` to list every agent's recent history instead: at most that many of each
agent's newest requests, automatic decisions included, newest first overall.
operationId: listReviewerQueue
x-withhuman-permission: request.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Review queue
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- name: limit
in: query
description: How many requests to return. Defaults to 50, at most 200.
schema:
type: integer
minimum: 1
maximum: 200
- name: agent_slug
in: query
required: false
description: Show one agent's requests instead of the queue, including the ones the pipeline decided.
schema:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
- name: per_agent
in: query
required: false
description: Show each agent's newest requests instead of the queue, at most this many per agent,
including the ones the pipeline decided. `limit` still caps the whole list, and the
other filters apply to those newest requests.
schema:
type: integer
minimum: 1
maximum: 50
- name: offset
in: query
description: Number of visible matching requests to skip before returning this page.
schema:
type: integer
minimum: 0
default: 0
- name: q
in: query
description: Case-insensitive literal substring of the tool or instance name.
schema:
type: string
- name: status
in: query
description: Return only requests with this outcome.
schema:
type: string
enum:
- pending
- approved
- denied
- expired
- cancelled
- name: scope
in: query
description: "Which layer of the queue to return, from the caller's standpoint. `notified` is what
the caller was told about: requests a notification addressed to them, or a colleague
handed them. `decidable` is what the caller may decide outright: requests routed to them
directly, through a team, or by being added, plus unrouted requests where any reviewer
may take them. `visible` (the default) is everything the caller may see, whoever decides
it. Each layer contains the one before it. Ignored for API keys, which have no
membership."
schema:
type: string
enum:
- notified
- decidable
- visible
responses:
"200":
description: The requests, newest first
content:
application/json:
schema:
type: object
required:
- requests
properties:
requests:
type: array
items:
$ref: "#/components/schemas/QueueApprovalRequest"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
description: You cannot read requests
/api/v1/me:
get:
summary: Retrieve the current member
description: "Returns who the session belongs to. The `principal` is what the API checks permissions
against on every call. The rest is for display: the person's name and email, the
organization, and the role assignments the membership holds. Call this after signing in to
learn what the session can do."
operationId: currentPrincipal
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Account
security:
- cookieAuth: []
- apiKeyAuth: []
responses:
"200":
description: The member behind the session
content:
application/json:
schema:
type: object
required:
- principal
properties:
principal:
$ref: "#/components/schemas/Principal"
user:
type: object
description: The person who signed in.
properties:
display_name:
type: string
example: Ada Lovelace
description: The name shown in the product.
email:
type: string
example: [email protected]
description: The primary email address on the account.
has_password:
type: boolean
example: true
description: Whether the account has a password. False for an account that only signs in through an
identity provider; such an account cannot change or reset a password.
organization:
type: object
description: The organization the session is scoped to.
properties:
name:
type: string
example: Northwind
description: The organization's display name.
slug:
type: string
example: northwind
description: The organization's URL-safe identifier.
assignments:
type: array
description: "The membership's role assignments: its own, plus the ones it inherits through its
teams. The `grants` in the principal are flattened from these."
items:
$ref: "#/components/schemas/RoleAssignment"
onboarding:
type: object
description: Whether the product's guided setup should open for this person.
required:
- required
- checklist
- completed_at
properties:
required:
type: boolean
example: false
description: "`true` while nobody in the organization has finished or skipped the guided setup and
this person has the permissions to run it."
checklist:
type:
- object
- "null"
description: What the organization has set up, however it was set up. Reported once the guided setup
no longer opens on its own, to a person with the permissions to run it;
`null` otherwise. The app offers the setup again while any item is
`false`.
required:
- agent_connected
- rules_written
- tested
properties:
agent_connected:
type: boolean
example: true
description: An active agent has an active instance holding an unrevoked, unexpired credential.
rules_written:
type: boolean
example: false
description: The entry pipeline can hold or deny a call instead of approving every one.
tested:
type: boolean
example: false
description: A person has decided one of the organization's requests.
completed_at:
type:
- string
- "null"
format: date-time
description: When the organization finished or skipped the guided setup. `null` until then.
"401":
description: No session cookie, or the session has expired or been signed out
/api/v1/requests/{id}:
get:
summary: Retrieve a request
description: Returns one approval request. The response includes its current status, its decision
once there is one, and any claim a reviewer holds on it. Pass `wait` to hold the response
until the request is decided.
operationId: getProductApprovalRequest
x-withhuman-permission: request.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Review queue
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
- name: wait
in: query
description: How long to hold the response for a decision, as a duration such as `30s`. Capped at 30
seconds. Omit to return immediately.
schema:
type: string
example: 30s
responses:
"200":
description: The request in its current state
content:
application/json:
schema:
$ref: "#/components/schemas/QueueApprovalRequest"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
description: You cannot read requests
"404":
$ref: "#/components/responses/Error"
description: No such request
/api/v1/requests/{id}/claim:
post:
summary: Claim a request
operationId: claimApprovalRequest
x-withhuman-permission: request.claim
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Review queue
description: >-
Marks a pending request as being looked at by you. Other reviewers see who has it, and its
notifications pause. A claim is advisory: anyone allowed to decide the request can still
decide it.
A claim lapses after the organization's claim timeout, 15 minutes by default. It also lapses
at the request's deadline if that comes first. Claiming a request you already hold returns
the same claim. You cannot claim a request another reviewer holds. Wait for their claim to
lapse, or for them to release it.
Owners and reviewers can claim. If the request was routed to an escalation path, only the
people on that path can claim it. Owners can always claim.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
responses:
"200":
description: The claim you hold
content:
application/json:
schema:
type: object
required:
- claim
properties:
claim:
$ref: "#/components/schemas/Claim"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
description: You cannot claim this request. Your membership is inactive, you lack permission to
decide, or the request was routed to other people
"404":
$ref: "#/components/responses/Error"
description: No such request
"409":
$ref: "#/components/responses/Error"
description: Another reviewer holds the claim, or the request is no longer pending
delete:
summary: Release a claim
operationId: releaseApprovalRequestClaim
x-withhuman-permission: request.claim
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Review queue
description: Releases your claim on a request. Other reviewers then see it as unclaimed. Only the
reviewer holding a claim can release it. Other reviewers' claims lapse on their own.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
responses:
"204":
description: The claim is released
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
description: Another reviewer holds the claim
"404":
$ref: "#/components/responses/Error"
description: No live claim on this request
/api/v1/requests/{id}/decision:
post:
summary: Approve or deny a request
operationId: decideApprovalRequest
x-withhuman-permission: request.decide
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Review queue
description: >-
Records your decision on a pending request and returns once it is durably stored. The agent
waiting on the request sees the decision on its next read.
Every call needs an `Idempotency-Key` header. Repeating a call with the same key returns the
decision already recorded.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- status
properties:
status:
type: string
enum:
- approved
- denied
description: The decision.
note:
type: string
example: OK, but flag this account for review.
description: A note for the agent. It is returned with the decision.
channel:
type: string
default: web
example: web
description: Where the decision was made, recorded on the decision, for example `web` or `slack`.
Defaults to `web`.
outside_routing:
type: boolean
default: false
description: The reviewer's explicit acknowledgement that they are deciding outside the request's
routing (break glass). Required, alongside request.decide.unrouted, when routing
did not hand them the request; without it such a decision is refused with reason
outside_routing_required. Part of the decision's intent under the idempotency
key.
responses:
"200":
description: The recorded decision
content:
application/json:
schema:
$ref: "#/components/schemas/DecisionOperation"
"400":
$ref: "#/components/responses/Error"
description: The `Idempotency-Key` header is missing, or `status` is not `approved` or `denied`
"401":
$ref: "#/components/responses/Error"
"403":
description: You cannot decide this request. Your membership is inactive, you lack permission to
decide, the request was routed to other people, or your sign-in is too old or not strong
enough. A routing refusal carries RoutingRejectionDetails in error.details, whose reason
is not_targeted, break_glass_only, or outside_routing_required.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
$ref: "#/components/responses/Error"
description: No such request
"409":
$ref: "#/components/responses/Error"
description: The request already has a decision or has expired, or the idempotency key was already
used with different parameters
/api/v1/requests/{id}/routing:
get:
summary: Retrieve a request's routing
operationId: getApprovalRouting
x-withhuman-permission: request.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Review queue
description: >-
Returns who was asked to review a request when it entered human review. If an escalation
path took the request, the people on that path were notified, and only they can decide it.
If no path applied, every reviewer who can decide was notified, and any of them can decide
it. Routing is fixed when the request enters human review. Later changes to pipelines or
paths do not move it.
Requests the pipeline decided automatically never entered human review and have no routing.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
responses:
"200":
description: Where the request was routed
content:
application/json:
schema:
$ref: "#/components/schemas/ApprovalRouting"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
description: You cannot read requests
"404":
$ref: "#/components/responses/Error"
description: No such request, or it never reached human review
/api/v1/requests/{id}/reviewer-candidates:
get:
summary: List who a request could be handed to
operationId: listReviewerCandidates
x-withhuman-permission: request.decide
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Review queue
description: Active members who hold request.decide and are not yet reviewers of the request. Gated
like deciding rather than by membership.read, so a targeted reviewer without the members
page can still hand off.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
responses:
"200":
description: Candidates sorted by name
content:
application/json:
schema:
type: object
required:
- candidates
properties:
candidates:
type: array
items:
type: object
required:
- membership_id
- display_name
- email
properties:
membership_id:
type: string
format: uuid
display_name:
type: string
email:
type: string
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
/api/v1/requests/{id}/reviewers:
post:
summary: Add a reviewer to a pending request
operationId: addApprovalReviewer
x-withhuman-permission: request.decide
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Review queue
description: The handoff. Someone the request was routed to (or a holder of request.decide.unrouted)
pulls one more active member who may decide into the request's routing. Widening never
removes anyone and is audited as request.reviewer_added. Refused when the request is not
pending or was never routed (409), when the actor is not targeted (403), or when the member
is inactive or cannot decide (400).
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- membership_id
properties:
membership_id:
type: string
format: uuid
responses:
"200":
description: The request's review after the widening
content:
application/json:
schema:
$ref: "#/components/schemas/ApprovalReview"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
/api/v1/agents:
get:
summary: List agents
description: >-
Returns the organization's agents. Each entry carries the agent's instance counts, when it
was last seen, and how many requests it has made. Active agents come first, then disabled
ones, then archived ones, each group sorted by name. Archived agents are left out unless
`status` says otherwise.
An agent is live when one of its instances holds a credential that is neither expired nor
revoked. `last_seen_at` is the newest authenticated call from any of its instances. Agents
only call in when they have something to approve, so a quiet agent is not a broken one.
operationId: listAgents
x-withhuman-permission: agent.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- name: status
in: query
required: false
description: Which agents to return. `live` (the default) returns active and disabled agents,
`archived` returns only archived ones, and `all` returns every agent.
schema:
type: string
enum:
- live
- archived
- all
default: live
- name: limit
in: query
description: How many agents to return. Defaults to 50.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: The agents
content:
application/json:
schema:
type: object
required:
- agents
properties:
agents:
type: array
items:
$ref: "#/components/schemas/AgentSummary"
"400":
$ref: "#/components/responses/Error"
description: "`status` is not live, archived, or all"
"403":
$ref: "#/components/responses/Error"
description: You cannot read agents
post:
summary: Create an agent
description: >-
Creates an agent. An agent is a stable identity for one AI system, such as a support bot or
a deploy assistant. Its slug is the identity: chosen here, never changed, never reused.
Agents own no pipeline of their own: every request starts in the organization's entry
pipeline, and rules for one agent are a branch or a delegate on `agent.slug` there.
Creating an agent does not issue a credential. Register an instance, or create a provisioner
token, to let it call the API.
operationId: createAgent
x-withhuman-permission: agent.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- slug
- name
properties:
slug:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: support-bot
description: The agent's identity. Lowercase letters, digits, dots, underscores and hyphens, up to
63 characters. It cannot change and is never reused, so a slug an archived agent
holds is refused.
name:
type: string
example: Support bot
description: A display name. Reviewers see it on every request the agent makes. It can be changed
later and must be unique among the organization's live agents.
kind:
type: string
enum:
- service
- personal
default: service
description: Whose behalf the agent's instances act on. `service` for an agent the organization
runs, `personal` for one people run for themselves; see the Agent schema. It
cannot be changed later.
responses:
"201":
description: The new agent
content:
application/json:
schema:
$ref: "#/components/schemas/Agent"
"403":
$ref: "#/components/responses/Error"
description: You cannot create agents
"409":
$ref: "#/components/responses/Error"
description: The slug is taken by a live or archived agent (`agent_slug_taken`), or a live agent
already has the name (`agent_name_taken`)
"429":
description: The organization is at its agent limit
/api/v1/agents/{slug}:
get:
summary: Retrieve an agent
description: Returns one agent with a page of instances, each instance's credentials, and its
provisioner tokens. Secrets are never included. Instances are sorted by last seen, then
creation time and id, newest first. Counts and connection state cover all instances,
independent of the page and filters.
operationId: getAgent
x-withhuman-permission: agent.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/AgentSlug"
- name: instance_limit
in: query
description: Number of instances in this page.
schema:
type: integer
minimum: 1
maximum: 200
default: 25
- name: instance_offset
in: query
description: Number of matching instances to skip.
schema:
type: integer
minimum: 0
default: 0
- name: instance_q
in: query
description: Case-insensitive literal substring of the instance name.
schema:
type: string
- name: instance_status
in: query
description: Filter by whether an instance is enabled or disabled, independently of its credentials.
schema:
type: string
enum:
- active
- disabled
- name: provisioner_limit
in: query
description: Number of provisioner tokens in this page, sorted by issued time and id, newest first.
schema:
type: integer
minimum: 1
maximum: 200
default: 25
- name: provisioner_offset
in: query
description: Number of matching provisioner tokens to skip.
schema:
type: integer
minimum: 0
default: 0
- name: provisioner_q
in: query
description: Case-insensitive literal substring of the token identifier or displayed identifier
suffix. Searches identifiers, never secrets.
schema:
type: string
- name: provisioner_status
in: query
description: Token state, with revocation taking precedence over expiry.
schema:
type: string
enum:
- active
- expired
- revoked
responses:
"200":
description: The agent
content:
application/json:
schema:
$ref: "#/components/schemas/AgentDetail"
"400":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
description: You cannot read agents
"404":
$ref: "#/components/responses/Error"
description: No such agent
patch:
summary: Update an agent
operationId: updateAgent
x-withhuman-permission: agent.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Changes an agent's status or its display name, the two fields that can change after
creation. The slug never changes. Who manages an agent is decided by the role assignments at
the agent, not by a property of it.
Disabling an agent keeps its instances and credentials in place, but none of them can
authenticate until the agent is enabled again. An archived agent is past both switches;
restore it first.
parameters:
- $ref: "#/components/parameters/AgentSlug"
requestBody:
required: true
content:
application/json:
schema:
type: object
minProperties: 1
properties:
status:
type: string
enum:
- active
- disabled
description: "`active` or `disabled`."
name:
type: string
example: Support bot
description: A new display name, unique among the organization's live agents. Audited as
`agent.renamed`.
responses:
"200":
description: The updated agent
content:
application/json:
schema:
$ref: "#/components/schemas/Agent"
"400":
$ref: "#/components/responses/Error"
description: Neither status nor name was given, or the status is invalid
"403":
$ref: "#/components/responses/Error"
description: You cannot change agents
"404":
$ref: "#/components/responses/Error"
description: No such agent
"409":
$ref: "#/components/responses/Error"
description: The agent is archived (`agent_archived`), or a live agent already has the name
(`agent_name_taken`)
delete:
summary: Archive an agent
operationId: archiveAgent
x-withhuman-permission: agent.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
description: "Archives the agent. Every live credential and provisioner token is revoked. Nothing is
deleted: the agent is still listed with `status=archived` or `all`, it can still be
retrieved with its instances and revoked credentials, and its requests and audit trail stay
readable. But no instance or provisioner token can be created for it and its status cannot
change. Its name is freed for a new agent; its slug is not, and never will be. Pipelines are
the organization's, not the agent's, and are untouched."
parameters:
- $ref: "#/components/parameters/AgentSlug"
responses:
"204":
description: The agent is archived
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot change agents
"404":
$ref: "#/components/responses/Error"
description: No such agent
"409":
$ref: "#/components/responses/Error"
description: The agent is already archived (`agent_archived`)
/api/v1/agents/{slug}/restore:
post:
summary: Restore an archived agent
operationId: restoreAgent
x-withhuman-permission: agent.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
description: Brings an archived agent back to `active`. Credentials and provisioner tokens revoked
when the agent was archived stay revoked; register instances or connect them again to let it
call the API. The name must still be free among the organization's live agents.
parameters:
- $ref: "#/components/parameters/AgentSlug"
responses:
"200":
description: The restored agent
content:
application/json:
schema:
$ref: "#/components/schemas/Agent"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot change agents
"404":
$ref: "#/components/responses/Error"
description: No such agent
"409":
$ref: "#/components/responses/Error"
description: The agent is not archived (`agent_not_archived`), or a live agent took its name
meanwhile (`agent_name_taken`)
/api/v1/agent_instances/{id}:
patch:
summary: Update an instance
operationId: updateAgentInstance
x-withhuman-permission: agent.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Changes an instance's status or its display name, the two fields that can change after
enrollment. The agent and its other instances are not affected.
Disabling an instance keeps its credentials, but none of them can authenticate until it is
enabled again. Renaming changes what the instance is called from now on: nothing addresses
an instance by name after enrollment, so the running agent needs no change, and requests
already made keep the name the instance had at the time.
parameters:
- $ref: "#/components/parameters/AgentInstanceID"
requestBody:
required: true
content:
application/json:
schema:
type: object
minProperties: 1
properties:
status:
type: string
enum:
- active
- disabled
description: "`active` or `disabled`."
name:
type: string
example: support-server-01
description: A new display name, unique within the agent. Audited as `agent_instance.renamed`.
responses:
"200":
description: The updated instance
content:
application/json:
schema:
$ref: "#/components/schemas/AgentInstance"
"400":
$ref: "#/components/responses/Error"
description: Neither status nor name was given, or the status is invalid
"403":
$ref: "#/components/responses/Error"
description: You cannot change agents
"404":
$ref: "#/components/responses/Error"
description: No such instance
"409":
$ref: "#/components/responses/Error"
description: The instance's agent is archived (`agent_archived`), or another instance of the agent
already has the name (`agent_instance_name_taken`)
/api/v1/agents/{slug}/instances:
post:
summary: Register an instance
description: >-
Registers an instance of an agent and issues its credential. An instance is one running copy
of the agent, such as a CI runner or a pod. The credential's token is returned once, in this
response. Store it where the instance can read it. It cannot be retrieved again.
Use this when you set up an instance by hand. For a fleet that starts replicas itself,
create a provisioner token instead and let each replica create its own instance through the
Agent Approval Protocol (AAP).
operationId: createAgentInstance
x-withhuman-permission: agent.credential.issue
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/AgentSlug"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
example: ci-runner-07
description: A name for the instance, unique within the agent. Reviewers see it next to the agent's
name.
metadata:
type: object
additionalProperties: true
example:
region: eu-west
description: Any JSON object to store with the instance, such as a region or pod name.
delivery:
$ref: "#/components/schemas/AgentInstanceRegistrationDelivery"
responses:
"201":
description: The new instance and its credential. The token appears only in this response.
content:
application/json:
schema:
$ref: "#/components/schemas/AgentInstanceCredential"
"400":
$ref: "#/components/responses/Error"
description: An organization key tried to register an instance of a personal agent, which belongs to
a person (`agent_owner_required`), or the receiver URL is not allowed
(`invalid_webhook_url`)
"403":
$ref: "#/components/responses/Error"
description: You cannot issue credentials
"404":
$ref: "#/components/responses/Error"
description: No such agent, or no registration you started for it
"409":
$ref: "#/components/responses/Error"
description: The agent is archived (`agent_archived`)
"410":
$ref: "#/components/responses/Error"
description: The registration expired or was used (`instance_registration_expired`). Start a new one.
"422":
$ref: "#/components/responses/Error"
description: The receiver did not answer the verification (`webhook_verification_failed`). The
registration is kept, so you can fix the receiver and try again.
"501":
$ref: "#/components/responses/Error"
description: This server has no secrets key, so it cannot deliver webhooks (`webhook_unconfigured`)
/api/v1/agents/{slug}/instance_registrations:
post:
summary: Start a webhook registration
description: >-
Starts registering an instance that is notified when its requests are decided, so the agent
can pause instead of polling. The response holds a registration ID and a signing secret. The
secret appears only in this response.
Configure your receiver with both, then register the instance with `delivery` naming this
registration and the receiver URL. The receiver must answer a signed `webhook.verification`
challenge before the instance is created. The registration expires after one hour.
operationId: createAgentInstanceRegistration
x-withhuman-permission: agent.credential.issue
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/AgentSlug"
responses:
"201":
description: The registration. The signing secret appears only in this response.
content:
application/json:
schema:
$ref: "#/components/schemas/AgentInstanceRegistration"
"400":
$ref: "#/components/responses/Error"
description: An organization key tried to register an instance of a personal agent, which belongs to
a person (`agent_owner_required`)
"403":
$ref: "#/components/responses/Error"
description: You cannot issue credentials
"404":
$ref: "#/components/responses/Error"
description: No such agent
"409":
$ref: "#/components/responses/Error"
description: The agent is archived (`agent_archived`)
"501":
$ref: "#/components/responses/Error"
description: This server has no secrets key, so it cannot deliver webhooks (`webhook_unconfigured`)
/api/v1/agents/{slug}/provisioners:
post:
summary: Create a provisioner token
description: >-
Creates a provisioner token for an agent. A provisioner token lets a fleet or platform
create instances of this agent on its own, each with a credential of its own, through the
Agent Approval Protocol (AAP). It cannot create approval requests itself.
The token is returned once, in this response. It cannot be retrieved again.
operationId: createAgentProvisioner
x-withhuman-permission: agent.credential.issue
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/AgentSlug"
responses:
"201":
description: The provisioner and its token. The token appears only in this response.
content:
application/json:
schema:
$ref: "#/components/schemas/AgentProvisionerCredential"
"400":
$ref: "#/components/responses/Error"
description: The agent is personal, and personal agents have no provisioner tokens (`personal_agent`)
"403":
$ref: "#/components/responses/Error"
description: You cannot issue credentials
"404":
$ref: "#/components/responses/Error"
description: No such agent
"409":
$ref: "#/components/responses/Error"
description: The agent is archived (`agent_archived`)
/api/v1/agent-provisioners/{id}:
delete:
summary: Revoke a provisioner token
description: Revokes a provisioner token. It stops working immediately. The instances it already
created, and their credentials, are not affected.
operationId: revokeAgentProvisioner
x-withhuman-permission: agent.credential.revoke
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/ProvisionerID"
responses:
"204":
description: The token is revoked
"403":
$ref: "#/components/responses/Error"
description: You cannot revoke credentials
"404":
$ref: "#/components/responses/Error"
description: No such provisioner
/api/v1/agent-credentials/{id}:
delete:
summary: Revoke a credential
description: Revokes one instance credential. It stops working immediately. The instance stays
registered, and the agent and its other instances are not affected.
operationId: revokeAgentCredential
x-withhuman-permission: agent.credential.revoke
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Agents
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/CredentialID"
responses:
"204":
description: The credential is revoked
"403":
$ref: "#/components/responses/Error"
description: You cannot revoke credentials
"404":
$ref: "#/components/responses/Error"
description: No such credential
/api/v1/oauth_authorizations/preview:
post:
summary: Preview an MCP client's authorization request
operationId: previewOAuthAuthorization
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
description: "What the consent page shows for the request an MCP client sent the browser to: the
client, the permissions it asked for that the person could grant, the person's full
vocabulary, and whether keys may be scoped or are allowed at all. The whole request is
validated first, so a redirect URI the client did not register is refused before anything is
shown."
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OAuthAuthorizationRequest"
responses:
"200":
description: The consent preview
content:
application/json:
schema:
$ref: "#/components/schemas/OAuthAuthorizationPreview"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
/api/v1/oauth_authorizations:
post:
summary: Approve or decline an MCP client
operationId: createOAuthAuthorization
x-withhuman-permission: api_key.issue
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
description: The person's decision on the consent page. Approval mints a personal API key named
after the client, narrowed to the chosen permissions where scoping is available, under the
same freshness and MFA rules as a hand-made key, and answers the client's redirect URI
carrying a single-use code. Declining answers the redirect URI carrying access_denied. The
browser navigates to the answer.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OAuthAuthorizationDecision"
responses:
"200":
description: Where the browser goes next
content:
application/json:
schema:
type: object
required:
- redirect_url
properties:
redirect_url:
type: string
format: uri
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
/api/v1/request-approval-pipelines:
get:
summary: List pipelines
description: >-
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.
operationId: listRequestApprovalPipelines
x-withhuman-permission: pipeline.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- name: status
in: query
required: false
description: Which pipelines to return. `live` (the default) leaves archived pipelines out,
`archived` returns only them, and `all` returns both.
schema:
type: string
enum:
- live
- archived
- all
default: live
- name: limit
in: query
description: How many pipelines to return. Defaults to 50.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: The pipelines
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- request_approval_pipelines
properties:
request_approval_pipelines:
type: array
items:
$ref: "#/components/schemas/RequestApprovalPipelineSummary"
"400":
$ref: "#/components/responses/Error"
description: "`status` is not live, archived, or all"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read pipelines
post:
summary: Create a pipeline
description: >-
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.
operationId: createRequestApprovalPipeline
x-withhuman-permission: pipeline.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
security:
- cookieAuth: []
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelineCreate"
responses:
"201":
description: The new pipeline's first revision, active
headers:
ETag:
description: The active revision number, quoted
schema:
type: string
example: '"1"'
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelineRevision"
"400":
$ref: "#/components/responses/Error"
description: The key, the name, or the document is invalid, or a delegate names a pipeline that does
not exist (`request_approval_pipeline_not_found`)
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot create pipelines
"409":
$ref: "#/components/responses/Error"
description: The key was ever used (`request_approval_pipeline_exists`), or is `entry`
(`request_approval_pipeline_entry`)
/api/v1/request-approval-pipelines/{pipeline_key}:
get:
summary: Retrieve a pipeline's active revision
description: >-
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.
operationId: getActiveRequestApprovalPipeline
x-withhuman-permission: pipeline.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
responses:
"200":
description: The active revision
headers:
ETag:
description: The active revision number, quoted
schema:
type: string
example: '"3"'
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelineRevision"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read this pipeline
"404":
$ref: "#/components/responses/Error"
description: No pipeline with this key exists, or it is archived
delete:
summary: Archive a pipeline
description: >-
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.
operationId: archiveRequestApprovalPipeline
x-withhuman-permission: pipeline.activate
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
responses:
"204":
description: The pipeline is archived
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot archive this pipeline
"404":
$ref: "#/components/responses/Error"
description: No pipeline with this key exists
"409":
$ref: "#/components/responses/Error"
description: 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`)
/api/v1/request-approval-pipelines/{pipeline_key}/uses:
get:
summary: List where a pipeline is delegated to
description: Returns the active pipeline revisions whose delegate blocks hand requests to this
pipeline. The same set refuses archiving it.
operationId: listRequestApprovalPipelineUses
x-withhuman-permission: pipeline.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
responses:
"200":
description: The uses
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- uses
properties:
uses:
type: array
items:
$ref: "#/components/schemas/PipelineUse"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read this pipeline
/api/v1/request-approval-pipelines/{pipeline_key}/preview:
post:
summary: Preview a pipeline
operationId: previewRequestApprovalPipeline
x-withhuman-permission: pipeline.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
description: >-
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`.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelinePreviewRequest"
responses:
"200":
description: What the pipeline would do with the sample
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelinePreview"
"400":
$ref: "#/components/responses/Error"
description: 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
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit this pipeline
/api/v1/request-approval-pipelines/{pipeline_key}/revisions:
get:
summary: List pipeline revisions
description: >-
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.
operationId: listRequestApprovalPipelineRevisions
x-withhuman-permission: pipeline.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
- name: cursor
in: query
description: The `next_cursor` from the previous page.
schema:
type: string
- name: limit
in: query
description: How many revisions to return per page. Defaults to 50.
schema:
type: integer
minimum: 1
maximum: 200
responses:
"200":
description: The revisions, newest first
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- revisions
- total_count
properties:
revisions:
type: array
description: The revisions on this page, newest first.
items:
$ref: "#/components/schemas/RequestApprovalPipelineRevisionSummary"
next_cursor:
type: string
description: Present when another page follows.
total_count:
type: integer
format: int64
description: How many revisions the pipeline has, across every page.
"400":
$ref: "#/components/responses/Error"
description: "`cursor` is not a cursor this endpoint issued"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read this pipeline
post:
summary: Create a pipeline revision
operationId: createRequestApprovalPipelineRevision
x-withhuman-permission: pipeline.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
description: 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.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelineDocument"
responses:
"201":
description: The new revision, inactive
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelineRevision"
"400":
$ref: "#/components/responses/Error"
description: The document is invalid, or a delegate names a pipeline that does not exist
(`request_approval_pipeline_not_found`) or this pipeline itself
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit this pipeline
"404":
$ref: "#/components/responses/Error"
description: No pipeline with this key exists
"409":
$ref: "#/components/responses/Error"
description: The pipeline is archived (`request_approval_pipeline_archived`)
/api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}:
get:
summary: Retrieve a pipeline revision
description: Returns one revision of the pipeline, active or not, with its blocks in the order they run.
operationId: getRequestApprovalPipelineRevision
x-withhuman-permission: pipeline.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
- $ref: "#/components/parameters/PipelineRevision"
responses:
"200":
description: The revision
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelineRevision"
"400":
$ref: "#/components/responses/Error"
description: "`revision` is not a positive integer"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read this pipeline
"404":
$ref: "#/components/responses/Error"
description: No such revision
put:
summary: Update a draft pipeline revision
operationId: updateRequestApprovalPipelineRevision
x-withhuman-permission: pipeline.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
description: 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.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
- $ref: "#/components/parameters/PipelineRevision"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelineDocument"
responses:
"200":
description: The updated draft
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelineRevision"
"400":
$ref: "#/components/responses/Error"
description: The document is invalid, or a delegate names a pipeline that does not exist
(`request_approval_pipeline_not_found`) or this pipeline itself
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit this pipeline
"404":
$ref: "#/components/responses/Error"
description: No such revision
"409":
$ref: "#/components/responses/Error"
description: The revision has been active (`revision_not_draft`), or the pipeline is archived
(`request_approval_pipeline_archived`)
/api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}/references:
get:
summary: Report what a pipeline revision depends on
operationId: getRequestApprovalPipelineRevisionReferences
x-withhuman-permission: pipeline.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
description: 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.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
- $ref: "#/components/parameters/PipelineRevision"
responses:
"200":
description: Live state of every dependency
content:
application/json:
schema:
$ref: "#/components/schemas/PipelineReferences"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
/api/v1/request-approval-pipelines/{pipeline_key}/revisions/{revision}/activate:
post:
summary: Activate a pipeline revision
operationId: activateRequestApprovalPipelineRevision
x-withhuman-permission: pipeline.activate
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Approval pipelines
description: >-
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.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PipelineKey"
- $ref: "#/components/parameters/PipelineRevision"
- $ref: "#/components/parameters/ActiveRevisionIfMatch"
responses:
"200":
description: The revision, now active
headers:
ETag:
description: The active revision number, quoted
schema:
type: string
example: '"3"'
content:
application/json:
schema:
$ref: "#/components/schemas/RequestApprovalPipelineRevision"
"400":
$ref: "#/components/responses/Error"
description: "`revision` or `If-Match` is malformed"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot activate this pipeline
"404":
$ref: "#/components/responses/Error"
description: No such revision
"409":
$ref: "#/components/responses/Error"
description: 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`)
"412":
$ref: "#/components/responses/PreconditionFailed"
description: "`If-Match` does not match the active revision"
"428":
$ref: "#/components/responses/PreconditionRequired"
description: The `If-Match` header is missing
/api/v1/escalation-paths:
get:
summary: List escalation paths
operationId: listEscalationPaths
x-withhuman-permission: escalation_path.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Escalation paths
description: Returns the organization's escalation paths, one entry per key, with the active
revision number and a summary of the history. Live paths are returned unless `status` says
otherwise; an archived path carries `archived_at`.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- name: status
in: query
required: false
description: Which paths to return. `live` (the default) leaves archived paths out, `archived`
returns only them, and `all` returns both.
schema:
type: string
enum:
- live
- archived
- all
default: live
- name: limit
in: query
description: How many paths to return. Defaults to 50.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: The paths
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- escalation_paths
properties:
escalation_paths:
type: array
items:
$ref: "#/components/schemas/EscalationPathSummary"
"400":
$ref: "#/components/responses/Error"
description: "`status` is not live, archived, or all"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read escalation paths
post:
summary: Create an escalation path
operationId: createEscalationPath
x-withhuman-permission: escalation_path.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Escalation paths
description: >-
Creates a path and its first revision in one call, from a complete document plus the key
pipelines will reference. The document is validated first, including that every person and
team it targets exists. The revision is created as a draft, which can be changed in place
until it is activated. Activate it to put it into use.
A key can be used once. A key that belongs to a path, archived or not, is refused with 409
`escalation_path_exists`.
The response may carry warnings, for example a team whose members cannot decide requests. A
warning never blocks creation.
security:
- cookieAuth: []
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EscalationPathCreate"
responses:
"201":
description: The path's first revision, inactive
content:
application/json:
schema:
$ref: "#/components/schemas/EscalationPathRevision"
"400":
$ref: "#/components/responses/Error"
description: The document is invalid, a target does not exist, or the key is malformed
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit escalation paths
"409":
$ref: "#/components/responses/Error"
description: The key is already in use
/api/v1/escalation-paths/{path_key}:
get:
summary: Retrieve an escalation path
description: >-
Returns the path's active revision with its full document. This is the revision new requests
are routed with.
The `ETag` header carries the active revision number. Pass it as `If-Match` when you
activate another revision.
operationId: getActiveEscalationPath
x-withhuman-permission: escalation_path.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Escalation paths
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PathKey"
responses:
"200":
description: The active revision
headers:
ETag:
description: The active revision number, quoted
schema:
type: string
example: '"3"'
content:
application/json:
schema:
$ref: "#/components/schemas/EscalationPathRevision"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read escalation paths
"404":
$ref: "#/components/responses/Error"
description: No such path, or no revision is active (an archived path never has one)
delete:
summary: Archive an escalation path
operationId: archiveEscalationPath
x-withhuman-permission: escalation_path.activate
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Escalation paths
description: >-
Archives the path and every revision it has. Nothing is deleted: the path is still listed
with `status=archived` or `all`, its revisions can still be retrieved, and a request already
routed with one of them keeps it. But the path is never active again, it takes no new
revisions, and no pipeline can name it. Its key cannot be used for a new path.
A path that an active pipeline revision names, in a block or as the pipeline default, cannot
be archived. The call fails with 409 `escalation_path_in_use` and lists those revisions in
`details.uses`; replace them first.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PathKey"
responses:
"204":
description: The path is archived
"400":
$ref: "#/components/responses/Error"
description: "`path_key` is malformed"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot archive escalation paths
"404":
$ref: "#/components/responses/Error"
description: No such path
"409":
$ref: "#/components/responses/Error"
description: An active pipeline revision names the path (`escalation_path_in_use`), or the path is
already archived (`escalation_path_archived`)
/api/v1/escalation-paths/{path_key}/revisions:
get:
summary: List escalation path revisions
description: >-
Returns the path's revisions, newest first, without their documents. An archived path's
revisions carry `archived_at`.
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.
operationId: listEscalationPathRevisions
x-withhuman-permission: escalation_path.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Escalation paths
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PathKey"
- name: cursor
in: query
description: The `next_cursor` from the previous page.
schema:
type: string
- name: limit
in: query
description: How many revisions to return per page. Defaults to 50.
schema:
type: integer
minimum: 1
maximum: 200
responses:
"200":
description: The revisions, newest first
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- revisions
- total_count
properties:
revisions:
type: array
description: The revisions on this page, newest first.
items:
$ref: "#/components/schemas/EscalationPathRevisionSummary"
next_cursor:
type: string
description: Present when another page follows.
total_count:
type: integer
format: int64
description: How many revisions the path has, across every page.
"400":
$ref: "#/components/responses/Error"
description: "`cursor` is not a cursor this endpoint issued"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read escalation paths
post:
summary: Create an escalation path revision
operationId: createEscalationPathRevision
x-withhuman-permission: escalation_path.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Escalation paths
description: >-
Creates a new revision of an existing path from a complete document. The document is
validated first, including that every person and team it targets exists. The revision is
created as a draft, which can be changed in place until it is activated. Activate it to put
it into use.
The path must exist and not be archived. A new path is created with `POST
/api/v1/escalation-paths`.
The response may carry warnings, for example a team whose members cannot decide requests. A
warning never blocks creation.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PathKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EscalationPathDocument"
responses:
"201":
description: The new revision, inactive
content:
application/json:
schema:
$ref: "#/components/schemas/EscalationPathRevision"
"400":
$ref: "#/components/responses/Error"
description: The document is invalid, or a target does not exist
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit escalation paths
"404":
$ref: "#/components/responses/Error"
description: No such path
"409":
$ref: "#/components/responses/Error"
description: The path is archived
/api/v1/escalation-paths/{path_key}/revisions/{revision}:
get:
summary: Retrieve an escalation path revision
description: Returns one revision of a path, active or not, with its full document. Revisions of an
archived path can still be retrieved.
operationId: getEscalationPathRevision
x-withhuman-permission: escalation_path.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Escalation paths
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PathKey"
- $ref: "#/components/parameters/PipelineRevision"
responses:
"200":
description: The revision
content:
application/json:
schema:
$ref: "#/components/schemas/EscalationPathRevision"
"400":
$ref: "#/components/responses/Error"
description: "`revision` is not a positive integer"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read escalation paths
"404":
$ref: "#/components/responses/Error"
description: No such revision
put:
summary: Update a draft escalation path revision
operationId: updateEscalationPathRevision
x-withhuman-permission: escalation_path.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Escalation paths
description: Replaces the name and document of a draft revision, one that has never been active. The
revision keeps its number. The document is checked exactly as a new revision's. Once a
revision has been active it can no longer change.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PathKey"
- $ref: "#/components/parameters/PipelineRevision"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EscalationPathDocument"
responses:
"200":
description: The updated draft
content:
application/json:
schema:
$ref: "#/components/schemas/EscalationPathRevision"
"400":
$ref: "#/components/responses/Error"
description: The document is invalid or names a target that does not exist
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit escalation paths
"404":
$ref: "#/components/responses/Error"
description: No such revision
"409":
$ref: "#/components/responses/Error"
description: The revision has been active (`revision_not_draft`), or the path is archived
(`escalation_path_archived`)
/api/v1/escalation-paths/{path_key}/revisions/{revision}/activate:
post:
summary: Activate an escalation path revision
operationId: activateEscalationPathRevision
x-withhuman-permission: escalation_path.activate
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Escalation paths
description: >-
Makes a revision the active one. Requests that reach a person from then on are routed with
it. A request already waiting for review stays with the revision it was routed with, so its
reviewers and timers do not change. 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`. Pass
`"0"` if no revision is active. If the active revision changed in the meantime, the call
fails with 412 and nothing changes.
A revision of an archived path cannot be activated; the call fails with 409
`escalation_path_archived`.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/PathKey"
- $ref: "#/components/parameters/PipelineRevision"
- $ref: "#/components/parameters/ActiveRevisionIfMatch"
responses:
"200":
description: The revision, now active
headers:
ETag:
description: The active revision number, quoted
schema:
type: string
example: '"3"'
content:
application/json:
schema:
$ref: "#/components/schemas/EscalationPathRevision"
"400":
$ref: "#/components/responses/Error"
description: "`revision` or `If-Match` is malformed"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot activate escalation paths
"404":
$ref: "#/components/responses/Error"
description: No such revision
"409":
$ref: "#/components/responses/Error"
description: The path is archived
"412":
$ref: "#/components/responses/PreconditionFailed"
description: "`If-Match` does not match the active revision"
"428":
$ref: "#/components/responses/PreconditionRequired"
description: The `If-Match` header is missing
/api/v1/me/api_keys:
get:
summary: List your API keys
description: "Every personal API key you minted, newest first, with its status, and what the create
form needs: whether the organization allows personal keys, whether this deployment and plan
let a key carry a permission list, and the permissions you could place on one. Never the
secret."
operationId: listMyAPIKeys
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
responses:
"200":
description: Your keys
content:
application/json:
schema:
$ref: "#/components/schemas/MyAPIKeysEnvelope"
"401":
$ref: "#/components/responses/Error"
post:
summary: Create an API key
description: Mints a personal API key bound to your membership. The raw token is returned exactly
once; withHuman stores only its hash. Without `permissions` the key inherits everything you
hold, live, so a role change reaches the key on its next request. With `permissions` (hosted
edition) the key is narrowed to that list; every entry must be a permission you hold, and
the list can only remove, never add. A key that could decide requests or use a dangerous
permission is minted only from a session that passes the organization's re-authentication
and MFA rules right now, and the key keeps that session's assurance for later decisions. 403
api_keys_disabled when the organization has turned personal keys off, 403
api_key_scoping_unavailable when this edition cannot scope keys, 403 plan_required when the
plan cannot, 403 authentication_stale or authentication_weak when the session must sign in
again or with MFA first.
operationId: createAPIKey
x-withhuman-permission: api_key.issue
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateAPIKeyRequest"
responses:
"201":
description: The key and, once, its token
content:
application/json:
schema:
$ref: "#/components/schemas/IssuedAPIKeyEnvelope"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
/api/v1/api_keys/verify:
get:
summary: Verify an API key
description: The first call a script makes with a fresh key. Answers the key's own row and the
permissions it holds right now, after narrowing and your current roles. Only a request
authenticated with a key can verify one; a session answers 400.
operationId: verifyAPIKey
x-withhuman-permission: self
x-withhuman-credential-kinds:
- personal_api_key
- organization_api_key
tags:
- API keys
security:
- apiKeyAuth: []
responses:
"200":
description: The key behind this request
content:
application/json:
schema:
$ref: "#/components/schemas/VerifiedAPIKeyEnvelope"
"400":
$ref: "#/components/responses/Error"
description: The request did not authenticate with an API key
"401":
$ref: "#/components/responses/Error"
/api/v1/me/api_keys/{id}:
delete:
summary: Revoke one of your API keys
description: Revokes a key you minted. It stops working immediately. Revoking an already revoked key
answers 204 again.
operationId: revokeMyAPIKey
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
parameters:
- $ref: "#/components/parameters/APIKeyID"
responses:
"204":
description: The key is revoked
"401":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
description: No such key of yours
/api/v1/api_keys:
get:
summary: List every API key in the organization
description: The oversight list for owners, admins, and auditors. Both personal and organization
keys, newest first, with creator provenance. Never the secret.
operationId: listOrganizationAPIKeys
x-withhuman-permission: api_key.read
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
parameters:
- name: kind
in: query
schema:
type: string
enum:
- personal
- organization
description: Omit to return both key kinds.
responses:
"200":
description: The organization's keys
content:
application/json:
schema:
$ref: "#/components/schemas/APIKeysEnvelope"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
post:
summary: Create an organization API key
description: Delegates explicit scoped grants to an organization-owned key. Requires a fresh human
session. The first response shows the secret once; an idempotent retry returns metadata
without the secret. Keys survive creator offboarding.
operationId: createOrganizationAPIKey
x-withhuman-permission: api_key.organization.issue
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateOrganizationAPIKeyRequest"
responses:
"200":
description: Already created. secret_available is false; the secret cannot be recovered.
content:
application/json:
schema:
$ref: "#/components/schemas/OrganizationAPIKeyCreated"
"201":
description: Created, including the one-time secret. Cache-Control is no-store.
content:
application/json:
schema:
$ref: "#/components/schemas/OrganizationAPIKeyCreated"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
description: Idempotency key reused for different intent
x-withhuman-edition: hosted
/api/v1/api_keys/{id}:
get:
summary: Inspect an API key
description: Returns metadata, configured grants, effective organization-key grants, and policy
availability. Never returns a secret.
operationId: getAPIKey
x-withhuman-permission: api_key.read
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
parameters:
- $ref: "#/components/parameters/APIKeyID"
responses:
"200":
description: Key metadata
content:
application/json:
schema:
$ref: "#/components/schemas/APIKey"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
delete:
summary: Revoke any API key
description: Revokes anyone's key. It stops working immediately and the audit log records who revoked it.
operationId: revokeOrganizationAPIKey
x-withhuman-permission: api_key.revoke
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
parameters:
- $ref: "#/components/parameters/APIKeyID"
responses:
"204":
description: The key is revoked
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
description: No such key
/api/v1/members:
get:
summary: List members
description: Returns every member of the organization, with their status and the roles they hold,
directly or through their teams.
operationId: listMembers
x-withhuman-permission: membership.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Members
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- name: limit
in: query
description: How many members to return. Defaults to 200.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: The members
content:
application/json:
schema:
type: object
required:
- members
properties:
members:
type: array
items:
$ref: "#/components/schemas/Member"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read members
/api/v1/roles:
get:
summary: List predefined roles
operationId: listRoles
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Members
security:
- cookieAuth: []
- apiKeyAuth: []
description: The predefined roles a person can be invited with or given, owner first, with the
permissions each holds. They are the same in every organization.
responses:
"200":
description: The predefined roles
content:
application/json:
schema:
type: object
required:
- roles
properties:
roles:
type: array
items:
$ref: "#/components/schemas/Role"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
/api/v1/members/{id}/role:
put:
summary: Give a member a predefined role
operationId: setMemberRole
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- Members
security:
- cookieAuth: []
description: >-
Gives the member one predefined role everywhere in the organization, replacing every
permission they hold directly, custom permission policies included. What they inherit from
teams and what your directory manages stay.
You cannot change your own role. Only an owner may grant the owner role or change a member
who holds it, the organization must keep at least one active owner, and a role holding a
sensitive permission needs a fresh sign-in.
parameters:
- $ref: "#/components/parameters/MembershipID"
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- role
properties:
role:
$ref: "#/components/schemas/RoleKey"
responses:
"200":
description: The updated membership
content:
application/json:
schema:
type: object
required:
- member
properties:
member:
$ref: "#/components/schemas/Membership"
"400":
$ref: "#/components/responses/Error"
description: The role is unknown
"403":
$ref: "#/components/responses/Error"
description: You cannot change permissions, this is your own membership, the role or the member is
an owner and you are not, or the role needs a fresher sign-in
"404":
$ref: "#/components/responses/Error"
description: No such member
"409":
$ref: "#/components/responses/Error"
description: The member is managed by your directory, or is the last active owner
/api/v1/members/{id}:
patch:
summary: Suspend or reactivate a member
operationId: updateMember
x-withhuman-permission: membership.write
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Members
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Sets a member's status to `suspended` or `active`. A suspended member cannot sign in or act
until reactivated. Permissions are changed through `PUT /api/v1/members/{id}/role` or, where
the edition offers custom permissions, the member's permission policies.
You cannot change your own membership. Changing a member who holds the owner role requires
holding it yourself. Members managed by your directory cannot be changed here, and the
organization must keep at least one active owner.
Suspending a member that an active escalation path or team escalation policy still targets
is refused with `membership_in_escalation_path`; the paths are listed in
`details.escalation_paths` and the policies in `details.team_escalation_policies`. Pass
`force` to suspend anyway. Those targets then reach nobody until the path or policy is
edited.
parameters:
- $ref: "#/components/parameters/MembershipID"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- status
properties:
status:
type: string
enum:
- active
- suspended
description: "`active` or `suspended`."
force:
type: boolean
default: false
description: Suspend even while an active escalation path targets the member.
responses:
"200":
description: The updated membership
content:
application/json:
schema:
type: object
required:
- member
properties:
member:
$ref: "#/components/schemas/Membership"
"400":
$ref: "#/components/responses/Error"
description: "`status` is not `active` or `suspended`"
"403":
$ref: "#/components/responses/Error"
description: You cannot change members, this is your own membership, or the member holds the owner
role and you do not
"404":
$ref: "#/components/responses/Error"
description: No such member
"409":
$ref: "#/components/responses/Error"
description: "The member is managed by your directory, is already removed, or is the last active
owner. Or an active escalation path targets the member: `error.code` is
`membership_in_escalation_path` and `error.details.escalation_paths` lists the paths"
delete:
summary: Remove a member
operationId: removeMember
x-withhuman-permission: membership.write
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Members
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Removes a member from the organization. Their sessions are revoked at once. The membership
record is kept with status `deprovisioned`, because decisions and audit events refer to it.
You cannot remove yourself. Removing a member who holds the owner role requires holding it
yourself, and the organization must keep at least one active owner.
Removing a member that an active escalation path or team escalation policy still targets is
refused with `membership_in_escalation_path`; the paths are listed in
`details.escalation_paths` and the policies in `details.team_escalation_policies`. Pass
`force=true` to remove anyway. Those targets then reach nobody until the path or policy is
edited, and the audit event records which paths and policies were left behind.
parameters:
- $ref: "#/components/parameters/MembershipID"
- name: force
in: query
required: false
schema:
type: boolean
default: false
description: Remove even while an active escalation path targets the member.
responses:
"204":
description: The member is removed
"403":
$ref: "#/components/responses/Error"
description: You cannot change members, this is your own membership, or the member holds the owner
role and you do not
"404":
$ref: "#/components/responses/Error"
description: No such member
"409":
$ref: "#/components/responses/Error"
description: "The member is managed by your directory or is the last active owner. Or an active
escalation path targets the member: `error.code` is `membership_in_escalation_path` and
`error.details.escalation_paths` lists the paths"
/api/v1/permissions:
get:
summary: List permissions
operationId: listPermissions
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Members
security:
- cookieAuth: []
- apiKeyAuth: []
description: Human permission vocabulary. Requires permission-policy read or write access at any scope.
responses:
"200":
description: The permissions
content:
application/json:
schema:
type: object
required:
- permissions
properties:
permissions:
type: array
items:
$ref: "#/components/schemas/PermissionDescriptor"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read roles
/api/v1/webhook_endpoints:
get:
summary: List webhook endpoints
operationId: listWebhookEndpoints
x-withhuman-permission: webhook.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: Returns every webhook endpoint in the organization, one entry per key. Each entry
carries the latest revision's name and URL, the active revision number, and the active
pipeline revisions that post to it. Signing secrets are never included.
parameters:
- name: limit
in: query
description: How many endpoints to return. Defaults to 200.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: The endpoints
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- webhook_endpoints
properties:
webhook_endpoints:
type: array
items:
$ref: "#/components/schemas/WebhookEndpointSummary"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read webhook endpoints
"501":
$ref: "#/components/responses/Error"
description: This deployment has no secrets key, so webhook endpoints are unavailable
/api/v1/webhook_endpoints/{endpoint_key}:
get:
summary: Retrieve a webhook endpoint
operationId: getActiveWebhookEndpoint
x-withhuman-permission: webhook.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Returns the endpoint's active revision. This is where deliveries go right now.
The `ETag` header carries the active revision number. Pass it as `If-Match` when you
activate another revision or archive the endpoint.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
responses:
"200":
description: The active revision
headers:
ETag:
description: The active revision number, quoted
schema:
type: string
example: '"3"'
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEndpointRevision"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read webhook endpoints
"404":
$ref: "#/components/responses/Error"
description: No such endpoint, or no revision is active
/api/v1/webhook_endpoints/{endpoint_key}/revisions:
get:
summary: List webhook endpoint revisions
operationId: listWebhookEndpointRevisions
x-withhuman-permission: webhook.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Returns the endpoint's revisions, newest first. Signing secrets are never included.
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.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
- name: cursor
in: query
description: The `next_cursor` from the previous page.
schema:
type: string
- name: limit
in: query
description: How many revisions to return per page. Defaults to 50.
schema:
type: integer
minimum: 1
maximum: 200
responses:
"200":
description: The revisions, newest first
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- revisions
- total_count
properties:
revisions:
type: array
description: The revisions on this page, newest first.
items:
$ref: "#/components/schemas/WebhookEndpointRevision"
next_cursor:
type: string
description: Present when another page follows.
total_count:
type: integer
format: int64
description: How many revisions the endpoint has, across every page.
"400":
$ref: "#/components/responses/Error"
description: "`cursor` is not a cursor this endpoint issued"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read webhook endpoints
post:
summary: Create a webhook endpoint revision
operationId: createWebhookEndpointRevision
x-withhuman-permission: webhook.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Creates a new revision of an endpoint from a name and a URL. If the key is new, this creates
the endpoint and its signing secret. The secret is returned once, in this response, as
`signing_secret`. Store it where your receiver can read it. It cannot be retrieved again.
Later revisions change the name or the URL only. The secret stays the same, so your receiver
keeps working. The URL must be a public HTTPS address and is checked against the egress
policy before the revision is created. The revision is created inactive. Activate it to put
it into use.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEndpointDocument"
responses:
"201":
description: The new revision, inactive. `signing_secret` is present only for the endpoint's first
revision
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEndpointRevision"
"400":
$ref: "#/components/responses/Error"
description: The name is empty, or the URL fails the egress policy
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit webhook endpoints
"501":
$ref: "#/components/responses/Error"
description: This deployment has no secrets key, so webhook endpoints are unavailable
/api/v1/webhook_endpoints/{endpoint_key}/revisions/{revision}:
get:
summary: Retrieve a webhook endpoint revision
operationId: getWebhookEndpointRevision
x-withhuman-permission: webhook.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: Returns one revision of an endpoint, active or not. The signing secret is never included.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
- $ref: "#/components/parameters/PipelineRevision"
responses:
"200":
description: The revision
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEndpointRevision"
"400":
$ref: "#/components/responses/Error"
description: "`revision` is not a positive integer"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read webhook endpoints
"404":
$ref: "#/components/responses/Error"
description: No such revision
put:
summary: Update a draft webhook endpoint revision
operationId: updateWebhookEndpointRevision
x-withhuman-permission: webhook.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: Replaces the name and URL of a draft revision, one that has never been active. The
revision keeps its number and the endpoint keeps its signing secret, which is never
included. Once a revision has been active it can no longer change.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
- $ref: "#/components/parameters/PipelineRevision"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEndpointDocument"
responses:
"200":
description: The updated draft
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEndpointRevision"
"400":
$ref: "#/components/responses/Error"
description: The name is empty, or the URL fails the egress policy
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit webhook endpoints
"404":
$ref: "#/components/responses/Error"
description: No such revision
"409":
$ref: "#/components/responses/Error"
description: The revision has been active (`revision_not_draft`)
"501":
$ref: "#/components/responses/Error"
description: This deployment has no secrets key, so webhook endpoints are unavailable
/api/v1/webhook_endpoints/{endpoint_key}/revisions/{revision}/activate:
post:
summary: Activate a webhook endpoint revision
operationId: activateWebhookEndpointRevision
x-withhuman-permission: webhook.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Makes a revision the active one. Deliveries go to its URL from the next request on. A
request that is paused on a webhook block and resumes later delivers to whichever revision
is active at that moment. 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`. Pass
`"0"` if no revision is active. If the active revision changed in the meantime, the call
fails with 412 and nothing changes.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
- $ref: "#/components/parameters/PipelineRevision"
- $ref: "#/components/parameters/ActiveRevisionIfMatch"
responses:
"200":
description: The revision, now active
headers:
ETag:
description: The active revision number, quoted
schema:
type: string
example: '"3"'
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEndpointRevision"
"400":
$ref: "#/components/responses/Error"
description: "`revision` or `If-Match` is malformed"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit webhook endpoints
"404":
$ref: "#/components/responses/Error"
description: No such revision
"412":
$ref: "#/components/responses/PreconditionFailed"
description: "`If-Match` does not match the active revision"
"428":
$ref: "#/components/responses/PreconditionRequired"
description: The `If-Match` header is missing
"501":
$ref: "#/components/responses/Error"
description: This deployment has no secrets key, so webhook endpoints are unavailable
/api/v1/webhook_endpoints/{endpoint_key}/revisions/{revision}/test:
post:
summary: Test a webhook endpoint revision
operationId: testWebhookEndpointRevision
x-withhuman-permission: webhook.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Sends one signed test delivery to the revision's URL and reports what came back. The payload
has type `webhook_endpoint.test` and is flagged as a preview, so your receiver can tell it
from a real request. It is signed with the endpoint's secret, so the test also checks your
signature verification. The call waits up to 10 seconds for an answer.
You can test a revision before activating it. Each test is recorded in the audit log.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
- $ref: "#/components/parameters/PipelineRevision"
responses:
"200":
description: What the endpoint answered
content:
application/json:
schema:
type: object
required:
- test
properties:
test:
$ref: "#/components/schemas/WebhookEndpointTest"
"400":
$ref: "#/components/responses/Error"
description: "`revision` is not a positive integer"
"403":
$ref: "#/components/responses/Error"
description: You cannot edit webhook endpoints
"404":
$ref: "#/components/responses/Error"
description: No such revision
"501":
$ref: "#/components/responses/Error"
description: This deployment has no secrets key, so webhook endpoints are unavailable
/api/v1/webhook_endpoints/{endpoint_key}/uses:
get:
summary: List where a webhook endpoint is used
operationId: listWebhookEndpointUses
x-withhuman-permission: webhook.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: Returns the active pipeline revisions that post to this endpoint, one entry per webhook
block. While this list is not empty, the endpoint cannot be archived.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
responses:
"200":
description: The active uses. Empty when the endpoint can be archived
content:
application/json:
schema:
type: object
required:
- uses
properties:
uses:
type: array
items:
$ref: "#/components/schemas/PipelineUse"
"400":
$ref: "#/components/responses/Error"
description: "`endpoint_key` is malformed"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read webhook endpoints
/api/v1/webhook_endpoints/{endpoint_key}/active:
delete:
summary: Archive a webhook endpoint
operationId: archiveWebhookEndpoint
x-withhuman-permission: webhook.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Deactivates the endpoint's current revision, so the endpoint has no active revision. Its
history and its signing secret are kept, and you can activate a revision again later.
Archiving is refused while an active pipeline revision posts to the endpoint. Activate
pipeline revisions without that block first. While the endpoint is archived, no pipeline
revision that posts to it can be activated. A request that resumes onto it goes to a person,
with the block error `endpoint_unavailable`.
Pass the active revision in `If-Match`, quoted, as returned in `ETag`.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
- $ref: "#/components/parameters/ActiveRevisionIfMatch"
responses:
"204":
description: The endpoint is archived
"400":
$ref: "#/components/responses/Error"
description: '`If-Match` is malformed or `"0"`'
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot edit webhook endpoints
"409":
$ref: "#/components/responses/Error"
description: An active pipeline revision posts to this endpoint. `error.code` is
`webhook_endpoint_in_use`, and `error.details.uses` lists the revisions
"412":
$ref: "#/components/responses/PreconditionFailed"
description: "`If-Match` does not match the active revision"
"428":
$ref: "#/components/responses/PreconditionRequired"
description: The `If-Match` header is missing
/api/v1/webhook_endpoints/{endpoint_key}/rotate_secret:
post:
summary: Rotate the signing secret
operationId: rotateWebhookEndpointSecret
x-withhuman-permission: webhook.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Webhook endpoints
security:
- cookieAuth: []
- apiKeyAuth: []
description: Replaces the endpoint's signing secret with a new one. The old secret stops working at
once, for every revision of the endpoint. The new secret is returned once, in this response.
Update your receiver right away, or its signature checks will fail. Each rotation is
recorded in the audit log.
parameters:
- $ref: "#/components/parameters/WebhookEndpointKey"
responses:
"200":
description: The new secret. It appears only in this response
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEndpointSecret"
"403":
$ref: "#/components/responses/Error"
description: You cannot edit webhook endpoints
"404":
$ref: "#/components/responses/Error"
description: No such endpoint
"501":
$ref: "#/components/responses/Error"
description: This deployment has no secrets key, so webhook endpoints are unavailable
/api/v1/teams:
get:
summary: List teams
operationId: listTeams
x-withhuman-permission: team.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Teams
security:
- cookieAuth: []
- apiKeyAuth: []
description: Returns the teams you can read. With an organization-wide grant, that is every team.
With a grant scoped to a team, only that team.
parameters:
- name: status
in: query
description: Select live teams (the default), archived teams, or all teams.
schema:
type: string
enum:
- live
- archived
- all
default: live
- name: limit
in: query
description: How many teams to return. Defaults to 200.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: The teams
content:
application/json:
schema:
type: object
required:
- teams
properties:
teams:
type: array
items:
$ref: "#/components/schemas/Team"
"400":
$ref: "#/components/responses/Error"
description: The status filter is invalid
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read teams
post:
summary: Create a team
operationId: createTeam
x-withhuman-permission: team.write
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Teams
security:
- cookieAuth: []
- apiKeyAuth: []
description: Creates an empty team. Add members and roles afterwards. Names are unique among live
teams in the organization.
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
example: Payments approvers
description: The team's name. Unique among live teams in the organization.
responses:
"201":
description: The new team
content:
application/json:
schema:
type: object
required:
- team
properties:
team:
$ref: "#/components/schemas/Team"
"400":
$ref: "#/components/responses/Error"
description: The name is empty
"403":
$ref: "#/components/responses/Error"
description: You cannot create teams
"409":
$ref: "#/components/responses/Error"
description: A team with this name already exists
/api/v1/teams/{id}:
get:
summary: Retrieve a team
operationId: getTeam
x-withhuman-permission: team.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Teams
security:
- cookieAuth: []
- apiKeyAuth: []
description: Returns one team with its members. The list includes members added by hand and members
added by your directory, and says which for each.
parameters:
- $ref: "#/components/parameters/TeamID"
responses:
"200":
description: The team and its members
content:
application/json:
schema:
type: object
required:
- team
- members
properties:
team:
$ref: "#/components/schemas/Team"
members:
type: array
items:
$ref: "#/components/schemas/TeamMember"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"404":
$ref: "#/components/responses/Error"
description: No such team
patch:
summary: Rename a team
operationId: renameTeam
x-withhuman-permission: team.write
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Teams
security:
- cookieAuth: []
- apiKeyAuth: []
description: Changes a team's name. Teams managed by your directory cannot be renamed here, because
the directory owns their names.
parameters:
- $ref: "#/components/parameters/TeamID"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
example: Payments approvers
description: The new name. Unique among live teams in the organization.
responses:
"200":
description: The renamed team
content:
application/json:
schema:
type: object
required:
- team
properties:
team:
$ref: "#/components/schemas/Team"
"400":
$ref: "#/components/responses/Error"
description: The name is empty
"403":
$ref: "#/components/responses/Error"
description: You cannot change teams
"404":
$ref: "#/components/responses/Error"
description: No such team
"409":
$ref: "#/components/responses/Error"
description: The team is archived or managed by your directory, or a live team with this name
already exists
delete:
summary: Archive a team
operationId: archiveTeam
x-withhuman-permission: team.write
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Teams
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Archives a team permanently. Its identity, roster, and policy history stay readable. Its
members lose inherited roles and the active policy is deactivated. Archived teams cannot be
edited or targeted by routing. Repeating this operation succeeds without changing the
archive date.
Archiving is refused while the team is managed by your directory, is targeted by an active
escalation path, is targeted by the path revision of a request still waiting for review.
parameters:
- $ref: "#/components/parameters/TeamID"
responses:
"204":
description: The team is archived
"403":
$ref: "#/components/responses/Error"
description: You cannot archive teams
"404":
$ref: "#/components/responses/Error"
description: No such team
"409":
$ref: "#/components/responses/Error"
description: The team is managed by your directory, is targeted by an escalation path or a pending
request
/api/v1/teams/{id}/members:
put:
summary: Replace a team's members
operationId: setTeamMembers
x-withhuman-permission: team.member.write
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Teams
security:
- cookieAuth: []
- apiKeyAuth: []
description: >-
Replaces the members added by hand with the given set. Send an empty list to remove them
all. Members added by your directory are not touched. A team's members are the union of
both.
Removing a member that the team's active escalation policy still names is refused with
`team_member_in_escalation_policy`; `details` carries `team_id`, `revision`, and the
`membership_ids` being removed. Pass `force` to remove them anyway. The level naming them
then skips them until the policy is edited.
parameters:
- $ref: "#/components/parameters/TeamID"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- membership_ids
properties:
membership_ids:
type: array
description: The membership ids of the people to add by hand.
items:
type: string
format: uuid
force:
type: boolean
default: false
description: Remove members the active escalation policy still names.
responses:
"200":
description: The team's members after the change
content:
application/json:
schema:
type: object
required:
- members
properties:
members:
type: array
items:
$ref: "#/components/schemas/TeamMember"
"400":
$ref: "#/components/responses/Error"
description: A membership id does not belong to this organization
"403":
$ref: "#/components/responses/Error"
description: You cannot change team members
"404":
$ref: "#/components/responses/Error"
description: No such team
"409":
$ref: "#/components/responses/Error"
description: "The team is archived, or its active escalation policy still names a member being
removed: `error.code` is `team_member_in_escalation_policy`"
/api/v1/teams/{id}/escalation-policy:
get:
summary: Read the team's active escalation policy revision
operationId: getActiveTeamEscalationPolicy
x-withhuman-permission: team.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Teams
description: The revision a path level that targets the team runs. Warnings name user targets who
are no longer active members and members who cannot decide. 404 while no revision is active,
in which case a level that targets the team notifies every member at once.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/TeamID"
responses:
"200":
description: Active revision with its full document
headers:
ETag:
description: Quoted active revision for a subsequent If-Match mutation
schema:
type: string
example: '"3"'
content:
application/json:
schema:
$ref: "#/components/schemas/TeamEscalationPolicyRevision"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
/api/v1/teams/{id}/escalation-policy/revisions:
get:
summary: List immutable team escalation policy revisions
operationId: listTeamEscalationPolicyRevisions
x-withhuman-permission: team.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Teams
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/TeamID"
- name: limit
in: query
description: How many revisions to return. Defaults to 50.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: Revision history ordered newest first
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- revisions
properties:
revisions:
type: array
items:
$ref: "#/components/schemas/TeamEscalationPolicyRevisionSummary"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
post:
summary: Create an inactive immutable team escalation policy revision
operationId: createTeamEscalationPolicyRevision
x-withhuman-permission: team.escalation.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Teams
description: Validates the complete document before creating the next inactive revision. Levels name
members of the team by membership id or broadcast to the whole team; a user target who is
not an active member of the team is refused, and team targets belong to organization
escalation paths.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/TeamID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TeamEscalationPolicyDocument"
responses:
"201":
description: The new revision, a draft
content:
application/json:
schema:
$ref: "#/components/schemas/TeamEscalationPolicyRevision"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
description: The team is archived
/api/v1/teams/{id}/escalation-policy/revisions/{revision}:
get:
summary: Read one team escalation policy revision
operationId: getTeamEscalationPolicyRevision
x-withhuman-permission: team.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Teams
description: Warnings name user targets who are no longer active members, so a rollback candidate
can be judged before it is activated.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/TeamID"
- $ref: "#/components/parameters/PipelineRevision"
responses:
"200":
description: Historical revision with its full document
content:
application/json:
schema:
$ref: "#/components/schemas/TeamEscalationPolicyRevision"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
put:
summary: Update a draft team escalation policy revision
operationId: updateTeamEscalationPolicyRevision
x-withhuman-permission: team.escalation.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Teams
description: Replaces the document of a draft revision, one that has never been active. The revision
keeps its number. The document is checked exactly as a new revision's. Once a revision has
been active it can no longer change.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/TeamID"
- $ref: "#/components/parameters/PipelineRevision"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TeamEscalationPolicyDocument"
responses:
"200":
description: The updated draft
content:
application/json:
schema:
$ref: "#/components/schemas/TeamEscalationPolicyRevision"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
description: The revision has been active (`revision_not_draft`), or the team is archived
/api/v1/teams/{id}/escalation-policy/revisions/{revision}/activate:
post:
summary: Activate or roll back to a team escalation policy revision
operationId: activateTeamEscalationPolicyRevision
x-withhuman-permission: team.escalation.activate
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Teams
description: Atomically switches the team's active revision. If-Match must be "0" when no revision
is active. The stored document is checked against the roster again, so a revision naming
someone who has since left the team is refused. Requests already routed keep the revision
they pinned.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/TeamID"
- $ref: "#/components/parameters/PipelineRevision"
- $ref: "#/components/parameters/ActiveRevisionIfMatch"
responses:
"200":
description: Activated revision
headers:
ETag:
description: Quoted active revision
schema:
type: string
example: '"3"'
content:
application/json:
schema:
$ref: "#/components/schemas/TeamEscalationPolicyRevision"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
description: The team is archived
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/api/v1/teams/{id}/escalation-policy/active:
delete:
summary: Archive the team's escalation policy by deactivating its current revision
operationId: archiveTeamEscalationPolicy
x-withhuman-permission: team.escalation.activate
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Teams
description: Refused when the team is archived. Otherwise, a path level that targets the team falls
back to notifying every member at once; requests already routed keep their pinned revision.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/TeamID"
- $ref: "#/components/parameters/ActiveRevisionIfMatch"
responses:
"204":
description: Current revision deactivated
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
description: The team is archived
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/api/v1/invitations:
get:
summary: List invitations
operationId: listInvitations
x-withhuman-permission: membership.invite
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Invitations
security:
- cookieAuth: []
- apiKeyAuth: []
description: Returns the organization's invitations in every status, newest first.
parameters:
- name: limit
in: query
description: How many invitations to return. Defaults to 100.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: The invitations
content:
application/json:
schema:
type: object
required:
- invitations
properties:
invitations:
type: array
items:
$ref: "#/components/schemas/Invitation"
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot invite people
post:
summary: Invite a person
operationId: createInvitation
x-withhuman-permission: membership.invite
x-withhuman-credential-kinds:
- session
tags:
- Invitations
security:
- cookieAuth: []
description: Invite a member with a predefined role, or, where the edition offers custom permissions
(`custom_permissions_enabled` in the runtime config), with explicit permission policies.
Send exactly one of `role` and `permission_policies`. Only an owner may invite an owner,
every permission must be held by the inviter at the selected scope or wider, and a role or
policy holding a sensitive permission needs a fresh sign-in.
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
properties:
email:
type: string
format: email
description: Where to send the invitation. The person must accept with this address.
role:
$ref: "#/components/schemas/RoleKey"
description: The predefined role the person receives everywhere in the organization.
permission_policies:
type: array
minItems: 1
items:
$ref: "#/components/schemas/PermissionPolicySpec"
description: Custom permissions instead of a role. Refused with 403 `custom_permissions_unavailable`
where the edition offers predefined roles alone.
additionalProperties: false
responses:
"201":
description: The invitation and its link. The link appears only in this response
content:
application/json:
schema:
$ref: "#/components/schemas/InvitationWithURL"
"400":
$ref: "#/components/responses/Error"
description: Neither or both of `role` and `permission_policies` were sent, or the role is unknown
"403":
$ref: "#/components/responses/Error"
description: You cannot invite people, a grant exceeds what you hold or needs a fresher sign-in, or
custom permissions are not available in this edition (`custom_permissions_unavailable`)
"409":
$ref: "#/components/responses/Error"
description: A member or a pending invitation with this email already exists, or the organization is
at its seat limit
/api/v1/invitations/{id}/resend:
post:
summary: Resend an invitation
operationId: resendInvitation
x-withhuman-permission: membership.invite
x-withhuman-credential-kinds:
- session
tags:
- Invitations
security:
- cookieAuth: []
description: Issues a new link for a pending invitation and extends its expiry by seven days. The
previous link stops working. The new link appears only in this response.
parameters:
- $ref: "#/components/parameters/InvitationID"
responses:
"200":
description: The invitation and its new link. The link appears only in this response
content:
application/json:
schema:
$ref: "#/components/schemas/InvitationWithURL"
"403":
$ref: "#/components/responses/Error"
description: You cannot invite people
"404":
$ref: "#/components/responses/Error"
description: No such invitation, or it is no longer pending
/api/v1/invitations/{id}:
delete:
summary: Revoke an invitation
operationId: revokeInvitation
x-withhuman-permission: membership.invite
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Invitations
security:
- cookieAuth: []
- apiKeyAuth: []
description: Revokes a pending invitation. Its link stops working. A revoked invitation cannot be
resent. Invite the person again instead.
parameters:
- $ref: "#/components/parameters/InvitationID"
responses:
"204":
description: The invitation is revoked
"403":
$ref: "#/components/responses/Error"
description: You cannot invite people
"404":
$ref: "#/components/responses/Error"
description: No such invitation, or it is no longer pending
/api/v1/members/{id}/permission_policies:
get:
operationId: getMembersPermissionPolicies
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Members
summary: Get members permission policies
security:
- cookieAuth: []
- apiKeyAuth: []
responses:
"200":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPoliciesPage"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
post:
operationId: postMembersPermissionPolicies
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- Members
summary: Post members permission policies
description: Refused with 403 `custom_permissions_unavailable` where the edition offers predefined
roles alone (`custom_permissions_enabled` false).
security:
- cookieAuth: []
responses:
"201":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicy"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicySpec"
/api/v1/members/{id}/permission_policies/{policy_id}:
get:
operationId: getMembersPermissionPolicy
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Members
summary: Get members permission policy
security:
- cookieAuth: []
- apiKeyAuth: []
responses:
"200":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicy"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: policy_id
in: path
required: true
schema:
type: string
format: uuid
put:
operationId: putMembersPermissionPolicy
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- Members
summary: Put members permission policy
description: Refused with 403 `custom_permissions_unavailable` where the edition offers predefined
roles alone (`custom_permissions_enabled` false).
security:
- cookieAuth: []
responses:
"200":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicy"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: policy_id
in: path
required: true
schema:
type: string
format: uuid
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicyInput"
delete:
operationId: deleteMembersPermissionPolicy
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- Members
summary: Delete members permission policy
description: Refused with 403 `custom_permissions_unavailable` where the edition offers predefined
roles alone (`custom_permissions_enabled` false).
security:
- cookieAuth: []
responses:
"204":
description: Policy operation completed
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: policy_id
in: path
required: true
schema:
type: string
format: uuid
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
- name: revision
in: query
required: true
schema:
type: integer
minimum: 1
/api/v1/teams/{id}/permission_policies:
get:
operationId: getTeamsPermissionPolicies
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Teams
summary: Get teams permission policies
security:
- cookieAuth: []
- apiKeyAuth: []
responses:
"200":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPoliciesPage"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
post:
operationId: postTeamsPermissionPolicies
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- Teams
summary: Post teams permission policies
description: Refused with 403 `custom_permissions_unavailable` where the edition offers predefined
roles alone (`custom_permissions_enabled` false).
security:
- cookieAuth: []
responses:
"201":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicy"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicySpec"
/api/v1/teams/{id}/permission_policies/{policy_id}:
get:
operationId: getTeamsPermissionPolicy
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
- personal_api_key
tags:
- Teams
summary: Get teams permission policy
security:
- cookieAuth: []
- apiKeyAuth: []
responses:
"200":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicy"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: policy_id
in: path
required: true
schema:
type: string
format: uuid
put:
operationId: putTeamsPermissionPolicy
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- Teams
summary: Put teams permission policy
description: Refused with 403 `custom_permissions_unavailable` where the edition offers predefined
roles alone (`custom_permissions_enabled` false).
security:
- cookieAuth: []
responses:
"200":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicy"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: policy_id
in: path
required: true
schema:
type: string
format: uuid
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicyInput"
delete:
operationId: deleteTeamsPermissionPolicy
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- Teams
summary: Delete teams permission policy
description: Refused with 403 `custom_permissions_unavailable` where the edition offers predefined
roles alone (`custom_permissions_enabled` false).
security:
- cookieAuth: []
responses:
"204":
description: Policy operation completed
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: policy_id
in: path
required: true
schema:
type: string
format: uuid
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
- name: revision
in: query
required: true
schema:
type: integer
minimum: 1
/api/v1/agent_instances/self/tool_catalog:
put:
tags:
- Agents
summary: Report the calling instance's tool catalogue
operationId: reportToolCatalog
x-withhuman-permission: self
x-withhuman-credential-kinds:
- agent_instance
security:
- bearerAuth: []
description: Replaces complete source snapshots in one discovery context. Failed sources retain
their last successful definitions. The instance credential supplies organization and
instance identity. No arguments or credentials belong in a report. This product endpoint is
outside AAP. Maximum body size is 8 MiB.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ToolCatalogReport"
responses:
"204":
description: Catalogue report recorded
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
/api/v1/tool_catalog:
get:
tags:
- Agents
summary: List known tool definitions
operationId: listToolCatalog
x-withhuman-permission: agent.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
security:
- cookieAuth: []
- apiKeyAuth: []
description: Returns definitions within the caller's agent scope. An edition's own tool source (the
hosted gateway) is refreshed on read through a five-minute shared cache, with that edition's
live grants applied. Failed refreshes retain the last successful definitions and mark their
source stale. Catalogue information is advisory and never changes approval decisions.
parameters:
- name: agent_slug
in: query
schema:
type: string
- name: q
in: query
schema:
type: string
maxLength: 200
- name: source
in: query
schema:
type: string
description: "Only tools from this source: `builtin`, `mcp`, or a source an edition adds (the hosted
gateway's `gateway`)."
- name: status
in: query
schema:
type: string
enum:
- available
- unavailable
- stale
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 500
default: 50
- name: offset
in: query
schema:
type: integer
minimum: 0
default: 0
- name: refresh
in: query
schema:
type: boolean
default: false
description: Bypass the edition's tool source discovery cache. Local tools require a CLI report.
responses:
"200":
description: Known definitions and source discovery status
content:
application/json:
schema:
$ref: "#/components/schemas/ToolCatalogPage"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
/api/v1/tool_catalog/{id}:
get:
tags:
- Agents
summary: Read a tool definition
operationId: getToolCatalogEntry
x-withhuman-permission: agent.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: agent_slug
in: query
schema:
type: string
responses:
"200":
description: Definition and its discovery provenance
content:
application/json:
schema:
$ref: "#/components/schemas/ToolCatalogEntry"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
/api/v1/tool_definitions:
get:
tags:
- Agents
summary: List distinct tool definitions
operationId: listToolDefinitions
x-withhuman-permission: agent.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
security:
- cookieAuth: []
- apiKeyAuth: []
description: "Returns the catalogue within the caller's agent scope in one response, folded to
distinct definitions: a tool name on a server with one schema and description appears once,
listing every runtime and instance that offers it, however many agents see it. Also returns
every server a catalogue source names, including sources where discovery found no tools. The
edition's own tool source is refreshed as for the catalogue listing. Useful for suggesting
tools and argument fields while writing pipeline conditions; the information is advisory and
never changes approval decisions."
responses:
"200":
description: Distinct definitions and the servers the catalogue names
content:
application/json:
schema:
$ref: "#/components/schemas/ToolDefinitionList"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
/api/v1/audit:
get:
summary: List audit events
description: >-
Returns one page of the organization's audit log, newest first by default. Every filter is
optional and they combine.
Paging is by cursor. When more events follow, the response carries `next_cursor`. Pass it
back as `cursor` with the same sort, order and filters to get the next page. The last page
has no `next_cursor`. `total_count` is how many events match the filters across every page.
operationId: listAuditEvents
x-withhuman-permission: audit.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Audit
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- name: sort
in: query
description: What to sort by. Events with the same value are ordered newest first.
schema:
type: string
enum:
- occurred
- event_type
- actor
default: occurred
- name: order
in: query
description: "`desc` for newest first, `asc` for oldest first."
schema:
type: string
enum:
- asc
- desc
default: desc
- name: cursor
in: query
description: The `next_cursor` from the previous page. Use the same sort, order and filters.
schema:
type: string
- $ref: "#/components/parameters/AuditQuery"
- $ref: "#/components/parameters/AuditEventType"
- $ref: "#/components/parameters/AuditActorType"
- $ref: "#/components/parameters/AuditActorID"
- $ref: "#/components/parameters/AuditSubjectType"
- $ref: "#/components/parameters/AuditSubjectID"
- $ref: "#/components/parameters/AuditOutcome"
- $ref: "#/components/parameters/AuditFromAt"
- $ref: "#/components/parameters/AuditToAt"
- name: limit
in: query
description: How many events to return. Defaults to 100.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: One page of events
content:
application/json:
schema:
type: object
required:
- events
- total_count
properties:
events:
type: array
description: The events on this page.
items:
$ref: "#/components/schemas/AuditEvent"
next_cursor:
type: string
description: Present when another page follows.
total_count:
type: integer
format: int64
description: How many events match the filters, across every page.
"400":
$ref: "#/components/responses/Error"
description: A filter is malformed, or `subject_id` was given without `subject_type`
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read the audit log
x-withhuman-edition: hosted
/api/v1/audit/stream:
get:
summary: Stream new audit events
description: >-
Server-sent events carrying the organization's audit events as they are recorded. Takes the
same filters as the listing.
Each `audit` event's data is `{"events": [...]}`, oldest first, and its `id` is the highest
`sequence` sent so far. A comment line keeps the connection open while nothing new arrives.
Pass the `sequence` of the newest event you already have as `after_sequence`; a reconnecting
client's `Last-Event-ID` header resumes from its last frame.
operationId: streamAuditEvents
x-withhuman-permission: audit.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Audit
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- name: after_sequence
in: query
description: Only events recorded after the event with this `sequence`. Leave it out to receive
every matching event from the start of the log.
schema:
type: integer
format: int64
minimum: 0
- $ref: "#/components/parameters/AuditQuery"
- $ref: "#/components/parameters/AuditEventType"
- $ref: "#/components/parameters/AuditActorType"
- $ref: "#/components/parameters/AuditActorID"
- $ref: "#/components/parameters/AuditSubjectType"
- $ref: "#/components/parameters/AuditSubjectID"
- $ref: "#/components/parameters/AuditOutcome"
- $ref: "#/components/parameters/AuditFromAt"
- $ref: "#/components/parameters/AuditToAt"
- name: Last-Event-ID
in: header
description: Sent by a reconnecting client. Resumes after this `sequence` when it is later than
`after_sequence`.
schema:
type: string
responses:
"200":
description: Event stream
content:
text/event-stream:
schema:
type: string
"400":
$ref: "#/components/responses/Error"
description: A filter or `after_sequence` is malformed
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read the audit log
x-withhuman-edition: hosted
/api/v1/audit/{id}:
get:
summary: Retrieve an audit event
description: One entry of the organization's audit log by id, the record behind a deep link. An id
from another organization is reported as not found.
operationId: getAuditEvent
x-withhuman-permission: audit.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Audit
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/AuditEventID"
responses:
"200":
description: The event
content:
application/json:
schema:
$ref: "#/components/schemas/AuditEvent"
"400":
$ref: "#/components/responses/Error"
description: The id is not a UUID
"401":
$ref: "#/components/responses/Error"
description: You are not signed in
"403":
$ref: "#/components/responses/Error"
description: You cannot read the audit log
"404":
$ref: "#/components/responses/Error"
description: No such event in this organization
x-withhuman-edition: hosted
/api/v1/audit/event_types:
get:
summary: List the event types this organization has recorded
description: The vocabulary for the audit log's event filter, sorted. Unlike a fixed list it can
never name a type that does not occur.
operationId: listAuditEventTypes
x-withhuman-permission: audit.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Audit
security:
- cookieAuth: []
- apiKeyAuth: []
responses:
"200":
description: Distinct event types
content:
application/json:
schema:
type: object
required:
- event_types
properties:
event_types:
type: array
items:
type: string
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
x-withhuman-edition: hosted
/api/v1/requests/{id}/timeline:
get:
summary: Read why a request needs a human and what has happened to it since
operationId: getRequestTimeline
x-withhuman-permission: request.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
tags:
- Review queue
description: Product read only, gated by request.read alone. The pipeline's conclusion and the block
that asked for a human, every judge assessment, the escalation steps, and the claim history,
told from the request's own audit events, so a reviewer does not need audit.read to
understand why they are being asked. Raw evaluation traces stay in the audit log. Routing,
added reviewers, and the reader's standing are the review (`GET
/api/v1/requests/{id}/review`), which every edition serves.
security:
- cookieAuth: []
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/RequestID"
responses:
"200":
description: The request's timeline
content:
application/json:
schema:
$ref: "#/components/schemas/RequestTimeline"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
x-withhuman-edition: hosted
/api/v1/agents/{slug}/tool-access:
get:
summary: Read an agent's gateway tool access
operationId: listAgentToolAccess
tags:
- Agents
x-withhuman-permission: gateway.read
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
security:
- cookieAuth: []
- apiKeyAuth: []
description: Requires gateway.read and agent.read for the named agent. Unconfigured servers grant no
access. All instances inherit these grants.
parameters:
- name: slug
in: path
required: true
schema:
type: string
responses:
"200":
description: Saved grants, including no-access grants retained for revision checks
content:
application/json:
schema:
type: object
required:
- grants
properties:
grants:
type: array
items:
$ref: "#/components/schemas/AgentToolAccess"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"501":
$ref: "#/components/responses/Error"
x-withhuman-edition: hosted
/api/v1/agents/{slug}/tool-access/{server_slug}:
put:
summary: Replace an agent's access to a server's tools
operationId: setAgentToolAccess
tags:
- Agents
x-withhuman-permission: gateway.write
x-withhuman-credential-kinds:
- session
- personal_api_key
- organization_api_key
security:
- cookieAuth: []
- apiKeyAuth: []
description: "Requires gateway.write and agent.read for the named agent. Grants exact tool names on
this server, all current and future tools, or no tools, and the scopes the agent may call
them with: any granted scope works with any granted tool. Every scope must be one of the
server's current scopes. A member-held scope can be granted to a personal agent only (400
otherwise); its instances call with each owner's own connection. Supply the current
revision, or zero for an unconfigured pair. A stale revision returns 412; an identical retry
of the last write returns its committed result without another audit event. This version is
the retry identity, so no Idempotency-Key is required. Archived agents and servers refuse
edits. Inactive servers can be configured. The next MCP tool listing reflects changes; calls
check live access before execution, including after approval. Already dispatched calls
cannot be undone."
parameters:
- name: slug
in: path
required: true
schema:
type: string
- name: server_slug
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentToolAccessInput"
responses:
"200":
description: Saved grant with its new revision
content:
application/json:
schema:
$ref: "#/components/schemas/AgentToolAccess"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
"412":
$ref: "#/components/responses/Error"
"501":
$ref: "#/components/responses/Error"
x-withhuman-edition: hosted
/api/v1/api_keys/options:
get:
summary: Read organization key creation choices
description: Grantable permissions and scopes, policy availability, and the deployment lifetime cap.
Does not require permission to read the key inventory.
operationId: organizationAPIKeyOptions
x-withhuman-permission: api_key.organization.issue
x-withhuman-credential-kinds:
- session
tags:
- API keys
security:
- cookieAuth: []
responses:
"200":
description: Available creation choices
content:
application/json:
schema:
$ref: "#/components/schemas/OrganizationAPIKeyOptions"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
x-withhuman-edition: hosted
/api/v1/api_keys/{id}/permission_policies:
get:
operationId: getAPIKeyPermissionPolicies
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
tags:
- API keys
summary: Get organization key permission policies
security:
- cookieAuth: []
responses:
"200":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPoliciesPage"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
description: Manage permission policies assigned directly to an organization key. Requires a human
session, policy authority at each affected scope, and api_key.organization.issue for
mutations. Mutations require fresh authentication and the current MFA policy. Expired and
revoked keys are read-only.
x-withhuman-edition: hosted
post:
operationId: postAPIKeyPermissionPolicies
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- API keys
summary: Post organization key permission policies
security:
- cookieAuth: []
responses:
"201":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicy"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicySpec"
description: Manage permission policies assigned directly to an organization key. Requires a human
session, policy authority at each affected scope, and api_key.organization.issue for
mutations. Mutations require fresh authentication and the current MFA policy. Expired and
revoked keys are read-only.
x-withhuman-edition: hosted
/api/v1/api_keys/{id}/permission_policies/{policy_id}:
get:
operationId: getAPIKeyPermissionPolicy
x-withhuman-permission: self
x-withhuman-credential-kinds:
- session
tags:
- API keys
summary: Get organization key permission policy
security:
- cookieAuth: []
responses:
"200":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicy"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: policy_id
in: path
required: true
schema:
type: string
format: uuid
description: Manage permission policies assigned directly to an organization key. Requires a human
session, policy authority at each affected scope, and api_key.organization.issue for
mutations. Mutations require fresh authentication and the current MFA policy. Expired and
revoked keys are read-only.
x-withhuman-edition: hosted
put:
operationId: putAPIKeyPermissionPolicy
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- API keys
summary: Put organization key permission policy
security:
- cookieAuth: []
responses:
"200":
description: Policy operation completed
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicy"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: policy_id
in: path
required: true
schema:
type: string
format: uuid
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PermissionPolicyInput"
description: Manage permission policies assigned directly to an organization key. Requires a human
session, policy authority at each affected scope, and api_key.organization.issue for
mutations. Mutations require fresh authentication and the current MFA policy. Expired and
revoked keys are read-only.
x-withhuman-edition: hosted
delete:
operationId: deleteAPIKeyPermissionPolicy
x-withhuman-permission: permission_policy.write
x-withhuman-credential-kinds:
- session
tags:
- API keys
summary: Delete organization key permission policy
security:
- cookieAuth: []
responses:
"204":
description: Policy operation completed
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"404":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: policy_id
in: path
required: true
schema:
type: string
format: uuid
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
- name: revision
in: query
required: true
schema:
type: integer
minimum: 1
description: Manage permission policies assigned directly to an organization key. Requires a human
session, policy authority at each affected scope, and api_key.organization.issue for
mutations. Mutations require fresh authentication and the current MFA policy. Expired and
revoked keys are read-only.
x-withhuman-edition: hosted
components:
schemas:
EnrollmentCode:
type: object
description: The two codes of a pending enrollment.
required:
- enrollment_code
- user_code
- verification_uri
- expires_at
properties:
enrollment_code:
type: string
example: whe_7c1f2a9e-4b3d-4f2e-9a1c-2d6e8b5f0a11_3f6b9c1d0e7a4b2c
description: The secret the machine keeps. Send it to the exchange endpoint. Never show it to the
person.
user_code:
type: string
example: K7M-3PQ2
description: The short code the person enters in the browser. Three letters, a hyphen, and four
letters or digits. Case does not matter.
verification_uri:
type: string
format: uri
example: https://app.withhuman.ai/connect/authorize
description: The page where the person enters the user code.
expires_at:
type: string
format: date-time
description: When both codes stop working. Start a new enrollment after this.
AgentEnrollmentCredential:
type: object
description: The selected parent agent, its new instance, and the instance credential shown once.
required:
- agent
- agent_instance
- credential
properties:
agent:
$ref: "#/components/schemas/Agent"
agent_instance:
$ref: "#/components/schemas/AgentInstance"
credential:
$ref: "#/components/schemas/IssuedCredential"
Agent:
type: object
description: "An agent identity. The slug identifies it everywhere: URLs, role scopes, pipeline
scopes, audit data and conditions. The name is a label for people."
required:
- slug
- organization_id
- created_by_actor_id
- name
- kind
- status
- created_at
- updated_at
properties:
slug:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: support-bot
description: The agent's identity, chosen once at creation. It never changes and is never reused,
not even after the agent is archived.
organization_id:
type: string
format: uuid
created_by_actor_id:
type: string
format: uuid
description: The membership or organization-key actor that created the agent.
name:
type: string
example: Support bot
description: The display name. Reviewers see it on every request. Editable, and unique among the
organization's live agents.
kind:
type: string
enum:
- service
- personal
description: "Whose behalf the agent's instances act on, chosen once. A `service` agent is one the
organization runs, such as a support bot: its instances share one identity,
administrators enroll them, and provisioner tokens mint them. A `personal` agent is one
people run for themselves, such as a coding assistant: every instance belongs to the
member who enrolled it, any active member may connect their own, and the instances are
disabled when that member is removed. Personal agents have no provisioner tokens."
status:
type: string
enum:
- active
- disabled
- archived
description: "A `disabled` agent's instances cannot authenticate until it is enabled again. An
`archived` agent is retired: nothing of it authenticates, nothing can be enrolled under
it, and its status cannot change until it is restored."
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
archived_at:
type: string
format: date-time
description: When the agent was archived. Present exactly when `status` is `archived`.
AgentInstance:
type: object
description: One running copy of an agent.
required:
- id
- organization_id
- agent_slug
- name
- status
- metadata
- created_at
- last_seen_at
properties:
id:
type: string
format: uuid
organization_id:
type: string
format: uuid
agent_slug:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The slug of the agent this is an instance of.
membership_id:
type: string
format: uuid
description: The member this instance belongs to and acts for. Present exactly when the agent is
`personal`.
name:
type: string
example: ci-runner-07
description: Unique within the agent.
status:
type: string
enum:
- active
- disabled
description: A disabled instance cannot create requests.
metadata:
type: object
additionalProperties: true
example:
region: eu-west
description: The JSON stored when the instance was created.
created_at:
type: string
format: date-time
last_seen_at:
type: string
format: date-time
description: The last time this instance called the API.
delivery:
$ref: "#/components/schemas/AgentInstanceDelivery"
AgentInstanceDelivery:
type: object
description: The verified receiver of an instance using asynchronous mode, returned when the
instance is created. The signing secret is never returned.
required:
- type
- url
- registration_id
properties:
type:
type: string
enum:
- webhook
url:
type: string
format: uri
example: https://harness.example.com/aap/events
registration_id:
type: string
format: uuid
IssuedCredential:
type: object
required:
- id
- token
- expires_at
properties:
id:
type: string
format: uuid
description: Identifies the credential, for example when revoking it.
token:
type: string
description: The secret. It appears only in this response and cannot be retrieved again.
example: whc_live_3f6b9c1d0e7a4b2c
expires_at:
type: string
format: date-time
description: When the credential stops working.
AgentInstanceDeliveryInput:
type: object
description: "A webhook receiver for asynchronous mode. Install the registration ID and signing
secret on the receiver before calling: the verification message names the registration so
the receiver can pick the secret before it knows the instance ID."
required:
- type
- url
- registration_id
- signing_secret
additionalProperties: false
properties:
type:
type: string
enum:
- webhook
description: The only delivery type offered. Another value answers `400` with code
`unsupported_delivery_type`.
url:
type: string
format: uri
example: https://harness.example.com/aap/events
description: The exact HTTPS receiver URL. Private, loopback and other non-public addresses are
refused, and redirects are not followed.
registration_id:
type: string
format: uuid
description: A UUID you generate for this configuration and associate with the secret on the
receiver. It is echoed in the verification message.
signing_secret:
type: string
writeOnly: true
pattern: ^whsec_[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$
description: "`whsec_` followed by 32 random bytes in padded base64. Use a separate secret for each
instance. It is never returned."
AAPProvisionedInstance:
example:
instance:
id: b29a43a7-1949-4b9f-9d61-b7ae66f85d31
name: ci-runner-07
credential:
token: whc_live_3f6b9c1d0e7a4b2c
expires_at: 2026-09-18T12:00:00Z
type: object
additionalProperties: false
required:
- instance
- credential
description: A provisioned instance and its credential.
properties:
instance:
$ref: "#/components/schemas/AAPInstance"
credential:
type: object
additionalProperties: false
required:
- token
properties:
token:
type: string
minLength: 1
description: The instance credential. Only a replay with the same provisioner token and idempotency
key returns it again.
expires_at:
type: string
format: date-time
description: When the credential stops working.
AAPInstance:
example:
id: b29a43a7-1949-4b9f-9d61-b7ae66f85d31
name: ci-runner-07
delivery:
type: webhook
url: https://harness.example.com/aap/events
registration_id: 8246931c-4ce0-4c15-a94a-e9c078ad06e2
type: object
additionalProperties: false
required:
- id
- name
description: An instance as the Agent Approval Protocol returns it to a provisioner. Credentials and
the signing secret are never included.
properties:
id:
type: string
format: uuid
name:
type: string
minLength: 1
description: Unique within the agent.
delivery:
$ref: "#/components/schemas/AgentInstanceDelivery"
description: Absent when the instance has no receiver.
ErrorResponse:
type: object
additionalProperties: false
required:
- error
properties:
error:
type: object
additionalProperties: false
required:
- type
- code
- message
- request_id
properties:
type:
type: string
enum:
- invalid_request
- authentication
- authorization
- not_found
- conflict
- rate_limit
- dependency
- internal
code:
type: string
message:
type: string
param:
type: string
request_id:
type: string
details:
description: Structured detail for codes that carry one. quota_exceeded carries
QuotaExceededDetails; a forbidden error from a permission check carries
ForbiddenDetails; a decision the routing rule refused carries
RoutingRejectionDetails.
anyOf:
- $ref: "#/components/schemas/QuotaExceededDetails"
- $ref: "#/components/schemas/ForbiddenDetails"
- $ref: "#/components/schemas/RoutingRejectionDetails"
- type: object
QuotaExceededDetails:
description: The details object on a quota_exceeded error, so a client can offer the purchase that
resolves it.
type: object
additionalProperties: false
required:
- dimension
- limit
- used
properties:
dimension:
type: string
description: The plan dimension that is full, such as seats or agents.
limit:
type: integer
format: int64
minimum: 0
used:
type: integer
format: int64
minimum: 0
ForbiddenDetails:
description: The details on a 403 from a permission check. They name the permission that was needed
and the thing it was checked against.
type: object
additionalProperties: false
required:
- permission
- resource_kind
properties:
permission:
type: string
description: The permission you needed.
example: request.decide
resource_kind:
type: string
enum:
- organization
- team
- agent
- pipeline
- request
description: What kind of thing the check was about.
resource_id:
type: string
description: The team, agent slug, pipeline key, or request the check was about. Absent for the
organization.
RoutingRejectionDetails:
description: The details object on a forbidden error raised by the routing rule at decision time.
not_targeted means the request was routed to other people; break_glass_only means the
organization sends unrouted requests to holders of request.decide.unrouted;
outside_routing_required means the reviewer holds break glass but did not send
outside_routing.
type: object
additionalProperties: false
required:
- reason
properties:
reason:
type: string
enum:
- not_targeted
- break_glass_only
- outside_routing_required
CreateApproval:
type: object
additionalProperties: false
required:
- tool
- arguments
- timeout
properties:
tool:
type: string
example: issue_refund
description: "The name under which the tool is defined: the name an MCP server advertises in
`tools/list`, or the runtime's own name for a built-in tool such as `Bash`. Never the
runtime's joined spelling such as `mcp__stripe__issue_refund`."
server:
type: string
example: stripe
description: The alias of the MCP server that defines the tool, as the runtime configured it.
Present only for tools served over MCP. A label the adapter observed, not a verified
identity.
arguments:
type: object
additionalProperties: true
description: The exact arguments the tool will run with if approved. Reviewers see this as the
description of the action, so it must be complete.
example:
amount: 4900
reason: duplicate_charge
agent_reasoning:
type: string
description: The agent's own explanation of why it wants to do this. Reviewers see it as a claim
from the agent, separate from the arguments.
example: Refunding the duplicate charge for [email protected].
context:
type: object
additionalProperties: true
description: "Where the call comes from, as observed by the adapter rather than stated by the agent:
for example the runtime, session id, or working directory."
example:
run_id: "4821"
framework: claude-code
timeout:
type: string
example: 30m
description: How long the request may wait for a decision, as a duration such as `30m` or `24h`.
Between one second and seven days. Once it passes, the request expires.
AAPApprovalRequest:
example:
id: 7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71
tool: issue_refund
arguments:
amount: 4900
reason: duplicate_charge
timeout: 30m
status: pending
created_at: 2026-09-17T12:00:00Z
deadline_at: 2026-09-17T12:30:00Z
type: object
additionalProperties: false
required:
- id
- tool
- arguments
- timeout
- status
- created_at
- deadline_at
description: Submitted fields are immutable. Timeout preserves the submitted duration text. Request
idempotency is scoped to the authenticated instance; another instance cannot read or cancel
it.
properties:
id:
type: string
format: uuid
tool:
type: string
server:
type: string
arguments:
type: object
additionalProperties: true
timeout:
type: string
example: 300s
agent_reasoning:
type: string
context:
type: object
additionalProperties: true
status:
type: string
enum:
- pending
- approved
- denied
- expired
- cancelled
created_at:
type: string
format: date-time
deadline_at:
type: string
format: date-time
decision:
$ref: "#/components/schemas/AAPDecision"
consumption:
$ref: "#/components/schemas/AAPConsumption"
oneOf:
- properties:
status:
const: pending
decision: false
consumption: false
- required:
- decision
properties:
status:
const: approved
decision:
properties:
status:
const: approved
- required:
- decision
properties:
status:
const: denied
decision:
properties:
status:
const: denied
consumption: false
- required:
- decision
properties:
status:
const: expired
decision:
properties:
status:
const: expired
consumption: false
- required:
- decision
properties:
status:
const: cancelled
decision:
properties:
status:
const: cancelled
consumption: false
AAPDecision:
example:
status: approved
note: Refund the duplicate charge.
decided_at: 2026-09-17T12:02:00Z
expires_at: 2026-09-17T12:07:00Z
type: object
additionalProperties: false
required:
- status
- decided_at
description: "Immutable decision. An approved call must start before expires_at: 30 minutes after
decided_at when the instance had webhook delivery at decision time, five minutes otherwise.
Reads and retries never extend expires_at."
properties:
status:
type: string
enum:
- approved
- denied
- expired
- cancelled
note:
type: string
decided_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
oneOf:
- properties:
status:
const: approved
required:
- expires_at
- properties:
status:
enum:
- denied
- expired
- cancelled
expires_at: false
AAPConsumption:
example:
consumed_at: 2026-09-17T12:02:04Z
outcome:
status: succeeded
note: Refund issued
reported_at: 2026-09-17T12:02:06Z
type: object
additionalProperties: false
required:
- consumed_at
description: The creating instance used the approval. Present only once an approved request was
consumed. An adapter that finds a consumption it did not make treats the approval as used.
properties:
consumed_at:
type: string
format: date-time
outcome:
$ref: "#/components/schemas/AAPOutcome"
AAPOutcome:
example:
status: succeeded
note: Refund issued
reported_at: 2026-09-17T12:02:06Z
type: object
additionalProperties: false
required:
- status
- reported_at
description: How the approved call went, as the adapter reported it. For the record only.
properties:
status:
type: string
enum:
- succeeded
- failed
note:
type: string
minLength: 1
maxLength: 1000
reported_at:
type: string
format: date-time
ReportApprovalOutcome:
example:
status: succeeded
note: Refund issued
type: object
additionalProperties: false
required:
- status
properties:
status:
type: string
enum:
- succeeded
- failed
description: Whether the approved call succeeded.
note:
type: string
minLength: 1
maxLength: 1000
description: A short note about the result, such as an error message or a reference.
QueueApprovalRequest:
description: "An approval request as reviewers see it: the request itself plus the claim a reviewer
holds on it, if any. Agents never see claims."
allOf:
- $ref: "#/components/schemas/ApprovalRequest"
- type: object
required:
- updated_at
properties:
updated_at:
type: string
format: date-time
example: 2026-09-08T12:05:00Z
description: Moves on every change a reviewer should notice, escalation progress included, so a
client refetches the review when it changes.
claim:
description: The live claim on the request. Absent when nobody holds one.
$ref: "#/components/schemas/Claim"
requester_display_name:
type: string
example: Chris
description: The name of the member a personal instance acted for. Absent for a service agent's
request.
presentation:
description: "How to show the arguments: the resolved tool presentation, from the organization's own
entries or the shipped catalog. Absent when neither names the tool, in which case
the client renders the arguments by their shape. A reading aid only: the arguments
stay the complete description of the action, and a presentation never hides one."
$ref: "#/components/schemas/ResolvedToolPresentation"
summary:
description: A one-line description of the request written for reviewers, on editions that write
them, for requests that reached a person. Absent until one is written. A reading aid
only, like the presentation.
$ref: "#/components/schemas/RequestSummary"
ApprovalRequest:
type: object
required:
- id
- organization_id
- agent_slug
- agent_instance_id
- agent_name
- agent_instance_name
- tool
- arguments
- status
- deadline_at
- created_at
properties:
id:
type: string
format: uuid
organization_id:
type: string
format: uuid
agent_slug:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: support-agent
description: The slug of the agent that made the request.
agent_instance_id:
type: string
format: uuid
description: The running copy of the agent that made the request.
agent_name:
type: string
example: support-agent
description: The agent's name when the request was made.
agent_instance_name:
type: string
example: ci-runner-07
description: The instance's name when the request was made.
requester_membership_id:
type: string
format: uuid
description: "The member the instance acts for, when the agent is personal: the owner of the
instance at the time the request was made. Absent for a service agent's request, and
absent from the response that creates the request."
tool:
type: string
example: issue_refund
description: The tool the agent wants to call, as the server that defines it names it.
server:
type: string
example: stripe
description: The MCP server that defines the tool, as the adapter reported it. Absent for a
runtime's built-in tools.
arguments:
example:
amount: 4900
reason: duplicate_charge
description: The exact arguments the tool will run with if approved.
agent_reasoning:
type: string
example: Refunding the duplicate charge for [email protected].
description: The agent's own explanation, if it gave one.
context:
example:
run_id: "4821"
framework: claude-code
description: Where the call comes from, as recorded by the adapter.
status:
type: string
enum:
- pending
- approved
- denied
- expired
- cancelled
example: approved
description: "`pending` while waiting for a decision, then one of the four final states. `cancelled`
means the requesting instance withdrew it; treat it as a denial."
deadline_at:
type: string
format: date-time
example: 2026-09-09T12:00:00Z
description: "When the request expires if nobody has decided: `created_at` plus the timeout."
created_at:
type: string
format: date-time
example: 2026-09-08T12:00:00Z
decision:
description: Present once the request has been decided.
$ref: "#/components/schemas/Decision"
consumption:
description: Present once the agent used the approval to run the call, with the call's outcome if
the agent reported it.
$ref: "#/components/schemas/AAPConsumption"
Decision:
type: object
description: The outcome of an approval request and who produced it.
required:
- id
- request_id
- organization_id
- status
- channel
- authentication
- idempotency_key
- decided_at
properties:
id:
type: string
format: uuid
request_id:
type: string
format: uuid
organization_id:
type: string
format: uuid
membership_id:
type: string
format: uuid
description: The member who decided. Absent when the pipeline decided, the request expired, or the
agent cancelled it.
status:
type: string
enum:
- approved
- denied
- expired
- cancelled
note:
type: string
example: OK, but flag this account for review.
description: A note the reviewer left for the agent, if any.
channel:
type: string
example: web
description: Where the decision was made, as reported by the client, for example `web` or `slack`.
`pipeline` when the pipeline decided, `system` when the request expired, `aap` when the
agent cancelled it.
reviewer_display_name:
type: string
example: Chris
description: The reviewer's name, when a person decided.
reviewer_email:
type: string
format: email
description: The reviewer's email, when a person decided.
authentication:
$ref: "#/components/schemas/AuthContext"
description: How the reviewer was signed in at the time.
idempotency_key:
type: string
example: decision-7ab8c8ec
description: The key the decision was recorded with.
decided_at:
type: string
format: date-time
AuthContext:
type: object
description: How a principal was signed in when it acted.
required:
- method
- assurance
- authenticated_at
properties:
method:
type: string
example: local_password
description: How the principal signed in, for example `local_password`, `sso`, or `social_google`.
assurance:
type: string
example: strong
description: "The strength of that sign-in: `single_factor` or `strong`."
authenticated_at:
type: string
format: date-time
description: When the sign-in happened.
session_id:
type: string
format: uuid
description: The session that acted, when a person did.
credential_id:
type: string
format: uuid
description: The credential that acted, when an agent did.
Claim:
type: object
description: "A reviewer's claim on a pending request: an advisory marker that they are looking at
it. Absent once released, lapsed, or the request is no longer pending."
required:
- id
- membership_id
- claimed_at
- expires_at
properties:
id:
type: string
format: uuid
membership_id:
type: string
format: uuid
description: The reviewer holding the claim.
display_name:
type: string
example: Chris
description: The reviewer's name.
claimed_at:
type: string
format: date-time
example: 2026-09-08T12:01:30Z
expires_at:
type: string
format: date-time
example: 2026-09-08T12:16:30Z
description: When the claim lapses on its own.
ResolvedToolPresentation:
type: object
description: The presentation a request renders with and where it came from.
required:
- source
- fields
properties:
source:
type: string
enum:
- built_in
- organization
description: "`organization` for one of the organization's own entries, `built_in` for the shipped
catalog."
id:
type: string
format: uuid
description: The organization entry, when the source is `organization`.
title:
type: string
example: Edit {{/file_path}}
fields:
type: array
items:
$ref: "#/components/schemas/ToolPresentationField"
ToolPresentationField:
type: object
description: One rendered argument. A field whose path is absent from a request's arguments is
skipped; arguments no field names still show after the named ones, rendered by shape.
required:
- kind
properties:
path:
type: string
example: /body
description: A JSON pointer into the arguments. Required unless the field is a diff pair. Inside a
list's `item` it is relative to the element (no leading slash; empty means the element
itself).
label:
type: string
example: Body
description: Replaces the humanised key.
kind:
$ref: "#/components/schemas/ToolPresentationKind"
language:
type: string
example: python
description: Labels a `code` field for the reader.
before:
type: string
example: /old_string
description: "For `diff`: the pointer to the text before the change. Requires `after`; excludes
`path`."
after:
type: string
example: /new_string
description: "For `diff`: the pointer to the text after the change."
item:
description: "For `list`: how each element renders, with paths relative to the element. Omitted,
each element is text. An item cannot itself be a list of items."
$ref: "#/components/schemas/ToolPresentationField"
role:
type: string
enum:
- primary
- detail
default: primary
description: "`primary` fields make up the block a reviewer reads first; `detail` fields fold away
underneath it."
ToolPresentationKind:
type: string
description: How one argument renders. `text` is a short value; `prose` long plain text; `markdown`
and `html` are rendered (HTML in a sandbox that runs no script and loads nothing, with the
source one click away); `code` is a mono block labelled with `language`; `shell` a command
line; `diff` a unified diff at `path` or the pair at `before` and `after`; `path` a file
path; `url` a link shown, never followed; `email` an address; `reference` an opaque
identifier; `enum` a badge; `datetime` an instant in the reader's locale; `list` an array
rendered per `item`; `json` pretty-printed structure.
enum:
- text
- prose
- markdown
- html
- code
- shell
- diff
- path
- url
- email
- reference
- enum
- datetime
- list
- json
RequestSummary:
type: object
required:
- text
- generated_at
properties:
text:
type: string
maxLength: 140
example: Refund $49.00 on charge ch_3N1x for a faulty bag
generated_at:
type: string
format: date-time
example: 2026-09-08T12:00:03Z
Principal:
type: object
description: The identity the API checks permissions against. A person acting through a session, or
a machine acting through a credential.
required:
- actor_type
- actor_id
- organization_id
- permissions
- grants
- auth
properties:
actor_type:
type: string
enum:
- human
- agent
- system
- gateway
example: human
description: "`human` for a signed-in person, `agent` for an agent credential, `gateway` for the
chokepoint MCP gateway acting for the organization with the deployment's gateway token,
`system` for actions the provider takes on its own."
actor_id:
type: string
format: uuid
description: The user's id for a person, an internal id for an agent (address agents by
`agent_slug`), or the literal `gateway` for the gateway.
organization_id:
type: string
format: uuid
description: The organization every call is scoped to.
membership_id:
type: string
format: uuid
description: The person's membership in the organization. Present for a person only.
agent_instance_id:
type: string
format: uuid
description: The instance the credential belongs to. Present for an agent only.
agent_slug:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: claude-code
description: The agent's slug. Present for an agent only.
permissions:
type: array
description: Every permission the actor holds at any scope, such as `request.decide`, sorted and
without duplicates. Use it to decide what to show. Whether an action is allowed depends
on `grants`.
items:
type: string
example:
- request.read
- request.decide
- agent.credential.issue
grants:
type: array
description: Every permission the actor holds, each with the scope it applies at. Flattened from the
role assignments.
items:
$ref: "#/components/schemas/Grant"
team_ids:
type: array
description: The teams the member belongs to. Escalation paths can route requests to a team.
items:
type: string
format: uuid
auth:
$ref: "#/components/schemas/AuthContext"
Grant:
type: object
required:
- permission
- scope_kind
properties:
permission:
type: string
description: A registry key such as request.decide
example: request.decide
scope_kind:
type: string
enum:
- organization
- team
- agent
- pipeline
scope_id:
type: string
description: The team id, agent slug, or pipeline key the grant applies to; absent at organization
scope
RoleAssignment:
type: object
description: Permissions granted at one scope, with provenance.
required:
- id
- organization_id
- principal_kind
- principal_id
- scope_kind
- source
- created_at
- permissions
- is_owner
properties:
id:
type: string
format: uuid
organization_id:
type: string
format: uuid
principal_kind:
type: string
enum:
- membership
- team
- agent
description: What holds the role.
principal_id:
type: string
format: uuid
description: The membership, team or agent that holds it.
scope_kind:
type: string
enum:
- organization
- team
- agent
description: Where the role applies.
scope_id:
type: string
description: The team id or the agent slug. Absent for `organization`.
scope_name:
type: string
description: The team's name, for a team scope.
source:
type: string
enum:
- manual
- directory
description: Whether a person granted it or your directory did.
source_key:
type: string
description: The directory group that produced it. Present for `directory` only.
granted_by_actor_id:
type: string
format: uuid
description: The member who granted it. Present for `manual` only.
via_team_id:
type: string
format: uuid
description: The team the role is inherited through. Present when a member holds it through a team
rather than directly.
via_team_name:
type: string
description: That team's name.
created_at:
type: string
format: date-time
permissions:
type: array
items:
type: string
is_owner:
type: boolean
built_in_role:
$ref: "#/components/schemas/RoleKey"
description: The predefined role this assignment grants. Absent for a custom permission policy.
permission_policy_id:
type: string
format: uuid
RoleKey:
type: string
enum:
- owner
- admin
- reviewer
- auditor
description: A predefined role. `owner` is full control and only an owner may grant it; `admin`
manages agents, pipelines, paths, members and teams; `reviewer` decides the requests routed
to them; `auditor` reads everything without changing anything.
DecisionOperation:
type: object
description: The record of one decision call and its outcome.
required:
- id
- organization_id
- request_id
- status
- created_at
properties:
id:
type: string
format: uuid
description: The operation's id. The decision itself has its own.
organization_id:
type: string
format: uuid
request_id:
type: string
format: uuid
description: The request that was decided.
status:
type: string
enum:
- pending
- applied
- rejected
example: applied
description: "`applied` once the decision is stored. A `200` response always says `applied`: the
call waits for the outcome, and a rejected decision comes back as an error."
error_code:
type: string
description: Why the decision was rejected. Set for `rejected` only.
error_message:
type: string
description: The rejection in plain text. Set for `rejected` only.
decision:
description: The stored decision.
$ref: "#/components/schemas/Decision"
created_at:
type: string
format: date-time
example: 2026-09-08T12:02:11Z
completed_at:
type: string
format: date-time
example: 2026-09-08T12:02:11Z
description: When the decision was stored.
ApprovalRouting:
type: object
additionalProperties: false
description: Who was asked to review a request when it entered human review. agent_owner sends it to
the person who owns the agent instance that made it; escalated pins an escalation path
revision and the people it reaches for this request; default_queue means any authorized
reviewer.
required:
- outcome
- reason
- urgency
- targets
- added_reviewers
- team_policies
- escalation
- created_at
properties:
outcome:
type: string
enum:
- agent_owner
- escalated
- default_queue
example: escalated
description: "`agent_owner` when the agent's owner took the request; the path fields then describe
the fallback pinned beside it. `escalated` when an escalation path took the request.
`default_queue` when no path applied and every reviewer who can decide was asked."
reason:
type: string
enum:
- block_escalation
- pipeline_default
- no_escalation
- path_inactive
- evaluation_error
example: block_escalation
description: "How the outcome came about. `block_escalation`: the pipeline block that asked for a
person named the route. `pipeline_default`: the pipeline's default route took it.
`no_escalation`: the pipeline named no path. `path_inactive`: the named path had no
active revision. `evaluation_error`: the path could not be resolved. The last three mean
no path applied."
urgency:
type: string
enum:
- standard
- interrupt
example: standard
description: The urgency the request was routed with. Always `standard` today.
escalation_path_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: oncall
description: The path that took the request, or for `agent_owner` the fallback pinned beside the
owner. Absent for `default_queue`.
escalation_path_name:
type: string
example: On-call engineers
description: The name of the path revision that took the request, or the fallback revision for
`agent_owner`. Absent for `default_queue`.
escalation_path_revision_id:
type: string
format: uuid
description: The path revision that was active at the time. Absent for `default_queue`, and for
`agent_owner` when the fallback path had no active revision.
pipeline_revision_id:
type: string
format: uuid
description: The pipeline revision that routed the request. Absent for `default_queue`.
block_id:
type: string
format: uuid
description: The pipeline block that named the path. Present for `block_escalation` only.
agent_owner:
$ref: "#/components/schemas/ApprovalRoutingTarget"
description: The person the request was sent to as the owner of the agent instance that made it.
Present for `agent_owner` only.
agent_owner_fallback:
type: string
enum:
- no_agent_owner
- agent_owner_inactive
- agent_owner_cannot_decide
description: "Why a route that asks the agent's owner went to its fallback path when the request
arrived. `no_agent_owner`: a service agent made it. `agent_owner_inactive`: the owner
was suspended or removed. `agent_owner_cannot_decide`: the owner may not decide
requests."
agent_owner_handed_off_at:
type: string
format: date-time
description: When the agent's owner was suspended or removed and the fallback path took over the
request. Present for `agent_owner` only.
targets:
type: array
description: "Who may claim or decide the request. For `agent_owner`, the owner alone until a
handoff. Otherwise the people and teams the pinned path reaches for this request, in
path order: the levels whose request conditions hold for it, plus both branches of any
working-hours or urgency test, at every level regardless of timing. Only these people,
and `added_reviewers`, may claim or decide. Empty for `default_queue`. Empty for
`escalated` means the path reaches nobody for this request and only a break-glass
reviewer may decide it."
items:
$ref: "#/components/schemas/ApprovalRoutingTarget"
added_reviewers:
type: array
description: People added to the request's routing after the snapshot was pinned, oldest first.
items:
$ref: "#/components/schemas/ApprovalReviewer"
team_policies:
type: array
description: The team escalation policy revisions pinned beside the path, one per team the path
reaches for this request that had an active policy at routing time. A team absent here
is notified all at once when a level reaches it. Empty for `default_queue`.
items:
$ref: "#/components/schemas/PinnedTeamPolicy"
escalation:
description: The escalation executor's current position. Null before its first tick and for
`default_queue`.
oneOf:
- $ref: "#/components/schemas/EscalationProgress"
- type: "null"
created_at:
type: string
format: date-time
example: 2026-09-08T12:00:00Z
description: When the request entered human review.
ApprovalRoutingTarget:
type: object
additionalProperties: false
description: One person or team the pinned path reaches for the request.
required:
- type
- id
- name
properties:
type:
type: string
enum:
- user
- team
description: "`user` names a membership, `team` a team."
id:
type: string
format: uuid
name:
type: string
description: The person's display name or the team's name.
ApprovalReviewer:
type: object
additionalProperties: false
description: A person pulled into a pending request's routing after the fact by someone it was routed to.
required:
- membership_id
- display_name
- added_by_membership_id
- added_by_display_name
- created_at
properties:
membership_id:
type: string
format: uuid
display_name:
type: string
added_by_membership_id:
type: string
format: uuid
added_by_display_name:
type: string
created_at:
type: string
format: date-time
PinnedTeamPolicy:
type: object
additionalProperties: false
description: One team escalation policy revision an escalated request runs inline when its pinned
path reaches the team.
required:
- team_id
- team_name
- policy_revision_id
- revision
properties:
team_id:
type: string
format: uuid
team_name:
type: string
policy_revision_id:
type: string
format: uuid
revision:
type: integer
format: int64
minimum: 1
EscalationProgress:
type: object
additionalProperties: false
description: Where the escalation machine stands after its latest tick. The organization path's
walker is flattened onto the object; teams carries one entry per team policy running inline.
required:
- sequence
- phase
- urgency
- level_ordinal
- level_count
- repeat
- updated_at
- teams
properties:
sequence:
type: integer
format: int64
description: Monotonic tick counter; a later tick always carries a higher value.
phase:
$ref: "#/components/schemas/EscalationPhase"
urgency:
type: string
enum:
- standard
- interrupt
description: The request's current urgency, raised by a level that asked to.
current_node_id:
type: string
format: uuid
description: The path node the walker stands on; absent once exhausted.
level_ordinal:
type: integer
description: 1-based position of the current level in walk order; 0 before the first level.
level_count:
type: integer
description: Level nodes in the path document.
repeat:
type: integer
description: How many times the path has been repeated so far.
next_escalation_at:
type: string
format: date-time
description: When the path walker's armed timer fires; absent while paused, exhausted, or waiting on
nothing.
updated_at:
type: string
format: date-time
teams:
type: array
items:
$ref: "#/components/schemas/EscalationWalkerProgress"
EscalationPhase:
type: string
enum:
- level
- deferred
- repeat_wait
- paused
- exhausted
description: "Where a walker stands. `level`: a level is notified and its timer armed. `deferred`:
waiting for a working hours window to open. `repeat_wait`: the program ran out and is
waiting to repeat. `paused`: a claim holds escalation still. `exhausted`: nothing left to
do."
EscalationWalkerProgress:
type: object
additionalProperties: false
description: One team policy walker's position while its policy runs inline.
required:
- team_id
- phase
- level_ordinal
- level_count
- repeat
properties:
team_id:
type: string
format: uuid
phase:
$ref: "#/components/schemas/EscalationPhase"
current_node_id:
type: string
format: uuid
description: The node the walker stands on; absent once exhausted.
level_ordinal:
type: integer
description: 1-based position of the current level in walk order; 0 before the first level.
level_count:
type: integer
description: Level nodes in the document.
repeat:
type: integer
description: How many times the program has been repeated so far.
next_escalation_at:
type: string
format: date-time
description: When the armed timer fires; absent while paused, exhausted, or waiting on nothing.
ApprovalReview:
type: object
additionalProperties: false
description: The request's routing and the reader's standing on it. routing is absent for requests
the pipeline decided automatically.
required:
- added_reviewers
- viewer
properties:
routing:
$ref: "#/components/schemas/ApprovalRouting"
added_reviewers:
type: array
description: People added to the request's routing after it was pinned, oldest first. The same list
the routing carries.
items:
$ref: "#/components/schemas/ApprovalReviewer"
viewer:
$ref: "#/components/schemas/ReviewViewer"
ReviewViewer:
type: object
additionalProperties: false
description: The reading person's own standing on the request. targeted says whether the pinned path
reaches them for this request (directly, through a team, or by a later widening). decidable
says whether a decision from them would pass the routing rule outright (yes), only with the
outside_routing acknowledgement and request.decide.unrouted (break_glass), or not at all
(no). reason names the rule in the way; it is absent when decidable is yes or the request is
no longer pending. The decision itself still checks membership status, self-approval, and
authentication freshness.
required:
- targeted
- decidable
properties:
targeted:
type: boolean
decidable:
type: string
enum:
- yes
- break_glass
- no
reason:
type: string
enum:
- not_targeted
- break_glass_only
- no_permission
AgentSummary:
description: One row of the agent list. An agent with its instance and request counts.
allOf:
- $ref: "#/components/schemas/Agent"
- type: object
required:
- instance_count
- live_instance_count
- recent_request_count
- pending_request_count
- request_trend
properties:
instance_count:
type: integer
minimum: 0
description: How many instances the agent has.
live_instance_count:
type: integer
minimum: 0
description: How many instances are active and hold a credential that is neither expired nor
revoked.
last_seen_at:
type: string
format: date-time
description: The newest authenticated call from any instance. Absent when the agent has no
instances.
recent_request_count:
type: integer
minimum: 0
description: Requests made in the last seven days.
pending_request_count:
type: integer
minimum: 0
description: Requests still waiting for a decision.
request_trend:
type: array
minItems: 7
maxItems: 7
items:
type: integer
minimum: 0
description: The recent requests split into one bucket per day, oldest first. The last entry is the
past 24 hours.
AgentDetail:
description: An agent with a page of instances, aggregate counts, and provisioner tokens.
allOf:
- $ref: "#/components/schemas/Agent"
- type: object
required:
- created_by_display_name
- instances
- provisioners
- provisioner_count
- matching_provisioner_count
- instance_count
- live_instance_count
- matching_instance_count
- request_count
- recent_request_count
- pending_request_count
properties:
provisioner_count:
type: integer
minimum: 0
description: All provisioner tokens owned by this agent.
matching_provisioner_count:
type: integer
minimum: 0
description: Tokens matching the current identifier and status filters before pagination.
instance_count:
type: integer
minimum: 0
description: All instances owned by this agent.
live_instance_count:
type: integer
minimum: 0
description: Active instances with at least one live credential across all pages.
matching_instance_count:
type: integer
minimum: 0
description: Instances matching the current search and status filters before pagination.
request_count:
type: integer
minimum: 0
description: All requests made by this agent.
created_by_actor_type:
type: string
enum:
- human
- api_key
created_by_display_name:
type: string
description: The name of the human or organization key that created the agent.
instances:
type: array
description: The requested page of instances, sorted by last seen, creation time, and id, newest
first.
items:
$ref: "#/components/schemas/AgentInstanceDetail"
provisioners:
type: array
description: The requested page of provisioner tokens, sorted by issued time and id, newest first.
Secrets are never included.
items:
$ref: "#/components/schemas/AgentProvisioner"
recent_request_count:
type: integer
minimum: 0
description: Requests made in the last seven days.
pending_request_count:
type: integer
minimum: 0
description: Requests still waiting for a decision.
AgentInstanceDetail:
description: An instance with its credential history.
allOf:
- $ref: "#/components/schemas/AgentInstance"
- type: object
required:
- credentials
- recent_request_count
- request_trend
properties:
owner_display_name:
type: string
description: The name of the member a personal instance belongs to. Absent for a service agent's
instance.
credentials:
description: Every credential the instance has held, newest first. Tokens are never included.
type: array
items:
$ref: "#/components/schemas/AgentCredentialState"
recent_request_count:
type: integer
minimum: 0
description: Requests this instance made in the last seven days.
request_trend:
type: array
minItems: 7
maxItems: 7
items:
type: integer
minimum: 0
description: The instance's recent requests split into one bucket per day, oldest first. The last
entry is the past 24 hours.
AgentCredentialState:
type: object
description: One instance credential as the agent page shows it. The token is never included. A
credential is live while it is neither expired nor revoked.
required:
- id
- issued_at
- expires_at
properties:
id:
type: string
format: uuid
issued_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
description: When the credential stops working.
last_used_at:
type: string
format: date-time
description: The last authenticated call made with it.
revoked_at:
type: string
format: date-time
description: When it was revoked. Absent while it is not.
AgentProvisioner:
type: object
description: A provisioner token as the agent page shows it. The token is never included.
required:
- id
- organization_id
- agent_slug
- created_by_actor_id
- issued_at
- expires_at
properties:
id:
type: string
format: uuid
organization_id:
type: string
format: uuid
agent_slug:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The slug of the agent the token creates instances of.
created_by_actor_id:
type: string
format: uuid
description: The membership or organization-key actor that created the token.
issued_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
description: When the token stops working.
last_used_at:
type: string
format: date-time
description: The last time the token created an instance.
revoked_at:
type: string
format: date-time
description: When it was revoked. Absent while it is not.
AgentInstanceRegistrationDelivery:
type: object
description: The receiver for a registration you started. It is verified before the instance is created.
required:
- registration_id
- url
properties:
registration_id:
type: string
format: uuid
description: The registration's ID.
url:
type: string
format: uri
example: https://harness.example.com/aap/events
description: The exact HTTPS receiver URL. Private, loopback and other non-public addresses are
refused, and redirects are not followed.
AgentInstanceCredential:
type: object
description: A newly registered instance and its credential.
required:
- agent_instance
- credential
properties:
agent_instance:
$ref: "#/components/schemas/AgentInstance"
credential:
$ref: "#/components/schemas/IssuedCredential"
AgentInstanceRegistration:
type: object
description: A pending webhook registration. Configure your receiver with the registration ID and
signing secret.
required:
- registration_id
- signing_secret
- expires_at
properties:
registration_id:
type: string
format: uuid
description: The ID the verification message names. Your receiver uses it to choose the signing
secret.
signing_secret:
type: string
pattern: ^whsec_[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$
description: "The receiver's signing secret: `whsec_` followed by 32 random bytes in padded base64.
It is never returned again."
expires_at:
type: string
format: date-time
description: When the registration can no longer be completed.
AgentProvisionerCredential:
type: object
description: A newly created provisioner and its token.
required:
- provisioner
- credential
properties:
provisioner:
$ref: "#/components/schemas/AgentProvisioner"
credential:
$ref: "#/components/schemas/IssuedCredential"
OAuthAuthorizationRequest:
type: object
required:
- client_id
- redirect_uri
- code_challenge
- code_challenge_method
properties:
client_id:
type: string
description: A registered client's ID, or the https URL of the client's metadata document
(draft-ietf-oauth-client-id-metadata-document).
redirect_uri:
type: string
format: uri
state:
type: string
code_challenge:
type: string
code_challenge_method:
type: string
enum:
- S256
scope:
type: string
description: Space-separated permission keys the client asked for.
resource:
type: string
format: uri
response_type:
type: string
enum:
- code
OAuthAuthorizationPreview:
type: object
required:
- client
- requested_permissions
- bearable_permissions
- scoping_enabled
- personal_api_keys_allowed
properties:
client:
type: object
required:
- id
- name
- redirect_uri
properties:
id:
type: string
format: uuid
name:
type: string
client_uri:
type: string
redirect_uri:
type: string
format: uri
requested_permissions:
type: array
items:
type: string
bearable_permissions:
type: array
items:
$ref: "#/components/schemas/APIKeyPermission"
scoping_enabled:
type: boolean
personal_api_keys_allowed:
type: boolean
APIKeyPermission:
type: object
additionalProperties: false
description: One permission the caller could place on a key.
required:
- key
- area
- description
- dangerous
properties:
key:
type: string
example: pipeline.write
area:
type: string
example: pipeline
description:
type: string
dangerous:
type: boolean
description: Minting a key that carries it needs a fresh, strong session.
OAuthAuthorizationDecision:
allOf:
- $ref: "#/components/schemas/OAuthAuthorizationRequest"
- type: object
required:
- approve
properties:
approve:
type: boolean
permissions:
type: array
items:
type: string
description: The permissions to place on the key; omit to inherit everything the person holds.
expires_at:
type: string
format: date-time
RequestApprovalPipelineSummary:
type: object
additionalProperties: false
description: One pipeline and a summary of its history.
required:
- pipeline_key
- name
- latest_revision
- revision_count
- latest_created_at
- referenced_by
properties:
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The pipeline's key. `entry` is the pipeline every request starts in.
name:
type: string
description: "The pipeline's display name: the active revision's, or the latest revision's for an
archived pipeline."
active_revision:
type: integer
format: int64
minimum: 1
description: The revision in use. Absent only for an archived pipeline.
latest_revision:
type: integer
format: int64
minimum: 1
description: The newest revision, active or not.
revision_count:
type: integer
format: int64
minimum: 1
latest_created_at:
type: string
format: date-time
description: When the newest revision was created.
archived_at:
type: string
format: date-time
description: "Present when the pipeline was archived: readable, never active again, its key taken
for good."
referenced_by:
type: array
description: Every delegate block in an active pipeline revision that hands requests to this
pipeline, the entry pipeline first, then by pipeline key and block position. Empty when
no request can reach it.
items:
$ref: "#/components/schemas/PipelineDelegation"
PipelineDelegation:
type: object
additionalProperties: false
description: One delegate block in an active pipeline revision.
required:
- pipeline_key
- pipeline_name
- revision
- block_key
- block_name
properties:
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The key of the pipeline holding the delegate block.
pipeline_name:
type: string
description: That pipeline's name as of its active revision.
revision:
type: integer
format: int64
minimum: 1
description: The active revision holding the block.
block_key:
type: string
description: The delegate block's key, unique within the revision.
block_name:
type: string
description: The delegate block's name.
RequestApprovalPipelineCreate:
type: object
additionalProperties: false
description: A new pipeline and the content of its first revision.
required:
- pipeline_key
- name
properties:
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: payments
description: The pipeline's key. Taken for good once used, archived pipelines included; never `entry`.
name:
type: string
minLength: 1
maxLength: 120
example: Payments
description: The display name.
blocks:
type: array
maxItems: 1000
description: 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.
items:
$ref: "#/components/schemas/RequestApprovalBlockDefinition"
default_escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The escalation path for requests that reach a person from this pipeline without a block
naming a path, as in a document.
default_ask_agent_owner:
type: boolean
description: "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."
RequestApprovalBlockDefinition:
description: One block of a pipeline document. The type picks the kind of block and the shape of its
config. These are the kinds every edition has (`outcome`, `branch`, `delegate`, `webhook`);
"if this, then that" is a branch whose blocks end in an outcome block; an edition may add
kinds of its own with their own config shapes (the hosted edition adds `llm_judge`), and a
document naming a kind this deployment lacks is refused as invalid.
oneOf:
- $ref: "#/components/schemas/OutcomeBlockDefinition"
- $ref: "#/components/schemas/WebhookBlockDefinition"
- $ref: "#/components/schemas/BranchBlockDefinition"
- $ref: "#/components/schemas/DelegateBlockDefinition"
- $ref: "#/components/schemas/LLMJudgeBlockDefinition"
discriminator:
propertyName: type
mapping:
outcome: "#/components/schemas/OutcomeBlockDefinition"
webhook: "#/components/schemas/WebhookBlockDefinition"
branch: "#/components/schemas/BranchBlockDefinition"
delegate: "#/components/schemas/DelegateBlockDefinition"
llm_judge: "#/components/schemas/LLMJudgeBlockDefinition"
OutcomeBlockDefinition:
type: object
additionalProperties: false
description: A block that returns its configured outcome for every request that reaches it.
required:
- id
- name
- type
- enabled
- timeout
- max_attempts
- config
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: review-remaining-requests
description: A key for the block, unique within the pipeline including blocks inside branches.
name:
type: string
minLength: 1
example: Review remaining requests
description: A display name.
type:
type: string
const: outcome
description: Always `outcome`.
enabled:
type: boolean
const: true
description: Always `true`. Leave disabled blocks out of the document.
timeout:
type: string
example: 50ms
description: How long execution may take, as a duration. At most `1s`.
max_attempts:
type: integer
const: 1
description: Always 1.
config:
$ref: "#/components/schemas/OutcomeBlockConfig"
OutcomeBlockConfig:
type: object
additionalProperties: false
description: Returns outcome for every request that reaches this block. Later blocks do not run.
required:
- outcome
- reason
example:
outcome: human
reason: Requests not handled by earlier blocks need human review.
dependentSchemas:
escalation_path:
properties:
outcome:
const: human
properties:
outcome:
type: string
enum:
- approve
- deny
- human
description: The outcome returned unconditionally.
reason:
type: string
minLength: 1
description: The note recorded on decisions this block makes.
escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: Where the request goes for a `human` outcome. Allowed only when `outcome` is `human`.
Defaults to the pipeline's default path.
ask_agent_owner:
type: boolean
description: Send the request to the person who owns the agent instance that made it, instead of
`escalation_path`. The path, if set, is then the fallback when the request has no owner
able to decide it; without one the fallback is the pipeline default.
WebhookBlockDefinition:
type: object
additionalProperties: false
description: A block that asks a webhook endpoint of yours for an outcome.
required:
- id
- name
- type
- enabled
- timeout
- max_attempts
- config
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: A key for the block, unique within the pipeline including blocks inside branches.
name:
type: string
minLength: 1
description: A display name.
type:
type: string
const: webhook
description: Always `webhook`.
enabled:
type: boolean
const: true
description: Always `true`. Leave disabled blocks out of the document.
timeout:
type: string
example: 5s
description: How long one attempt may take, from connecting to the full response, as a duration. At
most `30s`.
max_attempts:
type: integer
minimum: 1
maximum: 3
description: How many times to try, from 1 to 3.
config:
$ref: "#/components/schemas/WebhookBlockConfig"
WebhookBlockConfig:
type: object
additionalProperties: false
required:
- endpoint_key
- allowed_outcomes
- include
- reason
description: The block posts the request to a webhook endpoint and the endpoint answers with an
outcome. The endpoint is a separate resource, named here by its key. Deliveries go to the
key's active revision. If the answer is not in allowed_outcomes, or the call fails, the
request goes to a person.
properties:
endpoint_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The webhook endpoint to call.
allowed_outcomes:
type: array
minItems: 1
uniqueItems: true
items:
type: string
enum:
- next
- human
- approve
- deny
description: The outcomes the endpoint may return. Any other answer sends the request to a person.
`approve` and `deny` must be listed explicitly. The editor defaults to `next` and
`human`.
include:
type: object
additionalProperties: false
description: What to send with the request.
required:
- agent_reasoning
- context
- previous_metadata
properties:
agent_reasoning:
type: boolean
description: Include the agent's own explanation of the call.
context:
type: boolean
description: Include the runtime metadata the adapter reported.
previous_metadata:
type: boolean
description: Include the metadata earlier blocks returned.
reason:
type: string
minLength: 1
description: The note recorded on decisions this block makes. The endpoint's own reason is kept as
evidence.
escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: Where the request goes when the endpoint answers `human`, or when the call fails.
Defaults to the pipeline's default path.
ask_agent_owner:
type: boolean
description: Send the request to the person who owns the agent instance that made it, instead of
`escalation_path`. The path, if set, is then the fallback when the request has no owner
able to decide it; without one the fallback is the pipeline default.
BranchBlockDefinition:
type: object
additionalProperties: false
description: A block that runs its own blocks for the requests its condition matches and is skipped
by every other request. A request that enters a branch is decided inside it, or goes to a
person if none of its blocks decides; nothing after the branch runs for that request.
required:
- id
- name
- type
- enabled
- timeout
- max_attempts
- config
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: refunds
description: A key for the block, unique within the pipeline including blocks inside branches.
name:
type: string
minLength: 1
example: Refunds
description: A display name.
type:
type: string
const: branch
description: Always `branch`.
enabled:
type: boolean
const: true
description: Always `true`. Leave disabled blocks out of the document.
timeout:
type: string
example: 50ms
description: How long the condition may take, as a duration. At most `1s`.
max_attempts:
type: integer
const: 1
description: Always 1.
config:
$ref: "#/components/schemas/BranchBlockConfig"
BranchBlockConfig:
type: object
additionalProperties: false
description: When the condition matches, the branch's blocks run in order and the first to decide
ends the pipeline. If none decides, the request goes to a person with this reason and
escalation path. When the condition does not match, the branch is skipped and the request
passes to the next block. A condition that cannot be evaluated sends the request to a person
on the pipeline's default path. "If this, then that" is a branch whose blocks end in an
outcome block; such a branch never runs out, so it needs no reason or path.
required:
- when
- blocks
properties:
when:
$ref: "#/components/schemas/CELCondition"
blocks:
type: array
minItems: 1
maxItems: 999
description: The branch's blocks, in the order they run. Any type, including further branches. They
count toward the pipeline's 1,000 blocks.
items:
$ref: "#/components/schemas/RequestApprovalBlockDefinition"
reason:
type: string
description: The note recorded when none of the branch's blocks decides; empty when the branch has
none.
escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: Where the request goes when none of the branch's blocks decides. Defaults to the
pipeline's default path. Blocks inside the branch that name no path of their own use the
pipeline's default, not this one.
ask_agent_owner:
type: boolean
description: Send the request to the person who owns the agent instance that made it, instead of
`escalation_path`. The path, if set, is then the fallback when the request has no owner
able to decide it; without one the fallback is the pipeline default.
CELCondition:
description: A condition tree. A leaf tests one field. all and any combine conditions. some and
every test the items of a list field.
oneOf:
- $ref: "#/components/schemas/CELLeafCondition"
- $ref: "#/components/schemas/CELAllCondition"
- $ref: "#/components/schemas/CELAnyCondition"
- $ref: "#/components/schemas/CELSomeCondition"
- $ref: "#/components/schemas/CELEveryCondition"
CELLeafCondition:
type: object
additionalProperties: false
description: A test of one field.
required:
- id
- field
- operator
properties:
id:
type: string
format: uuid
description: An id for the condition, unique within the pipeline.
field:
type: string
example: /request/arguments/amount_cents
description: A JSON pointer into the request, such as `/request/tool` or
`/request/arguments/amount_cents`.
operator:
type: string
enum:
- exists
- not_exists
- equals
- not_equals
- is_null
- is_not_null
- in
- not_in
- contains
- not_contains
- starts_with
- ends_with
- regex
- greater_than
- greater_than_or_equal
- less_than
- less_than_or_equal
- is_true
- is_false
- contains_any
- contains_all
- is_empty
- is_not_empty
description: How to compare the field. `contains`, `contains_any` and `contains_all` test substrings
of a string field and elements of an array field. `starts_with` takes an array of
prefixes and holds when the field begins with any of them.
value:
description: The value to compare with. Not used by operators that take none, such as `exists`.
CELAllCondition:
type: object
additionalProperties: false
description: Holds when every nested condition holds.
required:
- id
- all
properties:
id:
type: string
format: uuid
description: An id for the condition, unique within the pipeline.
all:
type: array
minItems: 1
description: The conditions that must all hold.
items:
$ref: "#/components/schemas/CELCondition"
CELAnyCondition:
type: object
additionalProperties: false
description: Holds when at least one nested condition holds.
required:
- id
- any
properties:
id:
type: string
format: uuid
description: An id for the condition, unique within the pipeline.
any:
type: array
minItems: 1
description: The conditions, of which at least one must hold.
items:
$ref: "#/components/schemas/CELCondition"
CELSomeCondition:
type: object
additionalProperties: false
description: Holds when at least one item of a list field matches.
required:
- id
- some
properties:
id:
type: string
format: uuid
description: An id for the condition, unique within the pipeline.
some:
$ref: "#/components/schemas/CELQuantifier"
CELQuantifier:
type: object
additionalProperties: false
description: A list field and the condition each of its items is tested against.
required:
- field
- where
properties:
field:
type: string
description: A JSON pointer to a list field.
where:
$ref: "#/components/schemas/CELCondition"
CELEveryCondition:
type: object
additionalProperties: false
description: Holds when every item of a list field matches.
required:
- id
- every
properties:
id:
type: string
format: uuid
description: An id for the condition, unique within the pipeline.
every:
$ref: "#/components/schemas/CELQuantifier"
DelegateBlockDefinition:
type: object
additionalProperties: false
description: A block that hands the requests its condition matches to another pipeline, named by
key, and is skipped by every other request. A request that enters the delegated pipeline is
decided inside it, or goes to a person under that pipeline's own default escalation path
(else the defaults of the pipelines that delegated into it) if none of its blocks decides;
nothing after the delegate runs for that request. The condition belongs to the pipeline
holding the block; everything after a match belongs to the target pipeline's owners.
required:
- id
- name
- type
- enabled
- timeout
- max_attempts
- config
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: to-payments
description: A key for the block, unique within the pipeline including blocks inside branches.
name:
type: string
minLength: 1
example: Payments team
description: A display name.
type:
type: string
const: delegate
description: Always `delegate`.
enabled:
type: boolean
const: true
description: Always `true`. Leave disabled blocks out of the document.
timeout:
type: string
example: 50ms
description: How long the condition may take, as a duration. At most `1s`.
max_attempts:
type: integer
const: 1
description: Always 1.
config:
$ref: "#/components/schemas/DelegateBlockConfig"
DelegateBlockConfig:
type: object
additionalProperties: false
description: When the condition matches, the request continues in the named pipeline's active
revision and is settled there. When it does not match, the delegate is skipped and the
request passes to the next block. A condition that cannot be evaluated sends the request to
a person on the default path of the pipeline holding the block. The named pipeline must
exist when the revision is created and be active when it is activated; it can never be
`entry`, and the activation refuses a delegation that would loop.
required:
- when
- pipeline_key
properties:
when:
$ref: "#/components/schemas/CELCondition"
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: payments
description: The pipeline that takes matching requests.
LLMJudgeBlockDefinition:
type: object
additionalProperties: false
description: A block that asks a model to review the request against your written policy.
required:
- id
- name
- type
- enabled
- timeout
- max_attempts
- config
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: A key for the block, unique within the pipeline including blocks inside branches.
name:
type: string
minLength: 1
description: A display name.
type:
type: string
const: llm_judge
description: Always `llm_judge`.
enabled:
type: boolean
const: true
description: Always `true`. Leave disabled blocks out of the document.
timeout:
type: string
example: 30s
description: How long one attempt may take, as a duration. At most `60s`.
max_attempts:
type: integer
minimum: 1
maximum: 3
description: How many times to try, from 1 to 3.
config:
$ref: "#/components/schemas/LLMJudgeBlockConfig"
x-withhuman-edition: hosted
LLMJudgeBlockConfig:
type: object
additionalProperties: false
required:
- effort
- instructions
- include
- on_verdict
- automatic_decisions_require_confidence
- reason
description: The model answers with a verdict of approve, deny, or escalate. The on_verdict table
maps each verdict to what the pipeline does. A verdict can never map to the opposite
decision, and escalate never decides on its own. The deployment decides which judge models
are offered, and each decision records the model that made it.
properties:
model:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The key of the judge model to use, such as `gpt-5.6-terra`. Leave it out to use the
deployment's default model, which follows the deployment when its default changes. A key
the deployment does not offer is refused with `judge_model_unavailable`.
effort:
type: string
enum:
- low
- medium
- high
description: "How much reasoning the model spends: `low`, `medium`, or `high`."
instructions:
type: string
minLength: 1
maxLength: 8192
description: Your review policy, in plain language, up to 8,192 characters. It goes into the system
prompt after withHuman's fixed framing.
include:
type: object
additionalProperties: false
description: What to show the model.
required:
- agent_reasoning
- context
- previous_metadata
properties:
agent_reasoning:
type: boolean
description: Include the agent's own explanation of the call.
context:
type: boolean
description: Include the runtime metadata the adapter reported.
previous_metadata:
type: boolean
description: Include the metadata earlier blocks returned.
on_verdict:
type: object
additionalProperties: false
description: What the pipeline does with each verdict.
required:
- approve
- deny
- escalate
properties:
approve:
type: string
enum:
- approve
- next
- human
description: What an `approve` verdict does.
deny:
type: string
enum:
- deny
- next
- human
description: What a `deny` verdict does.
escalate:
type: string
enum:
- human
- next
description: What an `escalate` verdict does.
automatic_decisions_require_confidence:
type: string
enum:
- high
- medium
- low
description: The confidence the model must report before its verdict can decide on its own. Below
it, the verdict is treated as `escalate`.
reason:
type: string
minLength: 1
description: The note recorded on decisions this block makes. The model's reasoning is kept as
evidence.
escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: Where the request goes when a verdict maps to `human`. Defaults to the pipeline's
default path.
RequestApprovalPipelineRevision:
type: object
additionalProperties: false
description: One pipeline revision with its blocks in the order they run.
required:
- id
- pipeline_key
- name
- revision
- is_active
- block_count
- created_by_actor_id
- created_at
- blocks
properties:
id:
type: string
format: uuid
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The pipeline's key.
name:
type: string
description: The pipeline's display name as of this revision.
revision:
type: integer
format: int64
minimum: 1
description: The revision number. Revisions count up from 1.
is_active:
type: boolean
description: Whether this is the revision in use.
block_count:
type: integer
minimum: 0
maximum: 1000
description: Every block in the revision, including blocks inside branches.
default_escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The escalation path for requests that reach a person from this pipeline without a block
naming a path.
default_ask_agent_owner:
type: boolean
description: 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.
created_by_actor_id:
type: string
format: uuid
description: The membership or organization-key actor that created the revision.
created_at:
type: string
format: date-time
activated_at:
type: string
format: date-time
description: When the revision was first active. Absent for a draft, which can still be changed.
archived_at:
type: string
format: date-time
description: Present when the pipeline was archived.
blocks:
type: array
maxItems: 1000
description: 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`.
items:
$ref: "#/components/schemas/StoredRequestApprovalBlock"
StoredRequestApprovalBlock:
description: One block as saved in a revision. The same shape as its definition, plus the fields of
the saved copy.
oneOf:
- $ref: "#/components/schemas/StoredOutcomeBlock"
- $ref: "#/components/schemas/StoredWebhookBlock"
- $ref: "#/components/schemas/StoredBranchBlock"
- $ref: "#/components/schemas/StoredDelegateBlock"
- $ref: "#/components/schemas/StoredLLMJudgeBlock"
discriminator:
propertyName: type
mapping:
outcome: "#/components/schemas/StoredOutcomeBlock"
webhook: "#/components/schemas/StoredWebhookBlock"
branch: "#/components/schemas/StoredBranchBlock"
llm_judge: "#/components/schemas/StoredLLMJudgeBlock"
StoredOutcomeBlock:
type: object
additionalProperties: false
description: An outcome block as saved in a revision.
required:
- id
- snapshot_id
- name
- type
- enabled
- timeout
- max_attempts
- config
- config_version
- content_hash
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: review-remaining-requests
description: The block's key, unique within the pipeline including blocks inside branches.
snapshot_id:
type: string
format: uuid
description: The id of the saved copy.
name:
type: string
minLength: 1
example: Review remaining requests
description: The display name.
type:
type: string
const: outcome
description: Always `outcome`.
enabled:
type: boolean
const: true
description: Always `true`.
timeout:
type: string
example: 50ms
description: How long execution may take.
max_attempts:
type: integer
const: 1
description: Always 1.
config:
$ref: "#/components/schemas/OutcomeBlockConfig"
config_version:
type: integer
const: 1
description: The version of the config format. Always 1.
content_hash:
type: string
pattern: ^[0-9a-f]{64}$
example: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
description: A SHA-256 hash of the block's content.
StoredWebhookBlock:
type: object
additionalProperties: false
description: A webhook block as saved in a revision.
required:
- id
- snapshot_id
- name
- type
- enabled
- timeout
- max_attempts
- config
- config_version
- content_hash
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The block's key, unique within the pipeline including blocks inside branches.
snapshot_id:
type: string
format: uuid
description: The id of the saved copy.
name:
type: string
minLength: 1
description: The display name.
type:
type: string
const: webhook
description: Always `webhook`.
enabled:
type: boolean
const: true
description: Always `true`.
timeout:
type: string
example: 5s
description: How long one attempt may take.
max_attempts:
type: integer
minimum: 1
maximum: 3
description: How many times to try.
config:
$ref: "#/components/schemas/WebhookBlockConfig"
config_version:
type: integer
const: 1
description: The version of the config format. Always 1.
content_hash:
type: string
pattern: ^[0-9a-f]{64}$
description: A SHA-256 hash of the block's content.
StoredBranchBlock:
type: object
additionalProperties: false
description: A branch as saved in a revision, with its blocks as saved.
required:
- id
- snapshot_id
- name
- type
- enabled
- timeout
- max_attempts
- config
- config_version
- content_hash
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The block's key, unique within the pipeline including blocks inside branches.
snapshot_id:
type: string
format: uuid
description: The id of the saved copy.
name:
type: string
minLength: 1
description: The display name.
type:
type: string
const: branch
description: Always `branch`.
enabled:
type: boolean
const: true
description: Always `true`.
timeout:
type: string
example: 50ms
description: How long the condition may take.
max_attempts:
type: integer
const: 1
description: Always 1.
config:
$ref: "#/components/schemas/StoredBranchBlockConfig"
config_version:
type: integer
const: 1
description: The version of the config format. Always 1.
content_hash:
type: string
pattern: ^[0-9a-f]{64}$
description: A SHA-256 hash of the branch's own content. Each of its blocks has its own.
StoredBranchBlockConfig:
type: object
additionalProperties: false
description: A branch configuration as saved, with its blocks as saved.
required:
- when
- blocks
- reason
properties:
when:
$ref: "#/components/schemas/CELCondition"
blocks:
type: array
minItems: 1
description: The branch's blocks, in the order they run.
items:
$ref: "#/components/schemas/StoredRequestApprovalBlock"
reason:
type: string
description: The note recorded when none of the branch's blocks decides; empty when the branch has
none.
escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: Where the request goes when none of the branch's blocks decides.
ask_agent_owner:
type: boolean
description: Send the request to the person who owns the agent instance that made it, instead of
`escalation_path`. The path, if set, is then the fallback when the request has no owner
able to decide it; without one the fallback is the pipeline default.
StoredLLMJudgeBlock:
type: object
additionalProperties: false
description: A model review block as saved in a revision.
required:
- id
- snapshot_id
- name
- type
- enabled
- timeout
- max_attempts
- config
- config_version
- content_hash
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The block's key, unique within the pipeline including blocks inside branches.
snapshot_id:
type: string
format: uuid
description: The id of the saved copy.
name:
type: string
minLength: 1
description: The display name.
type:
type: string
const: llm_judge
description: Always `llm_judge`.
enabled:
type: boolean
const: true
description: Always `true`.
timeout:
type: string
example: 30s
description: How long one attempt may take.
max_attempts:
type: integer
minimum: 1
maximum: 3
description: How many times to try.
config:
$ref: "#/components/schemas/LLMJudgeBlockConfig"
config_version:
type: integer
const: 1
description: The version of the config format. Always 1.
content_hash:
type: string
pattern: ^[0-9a-f]{64}$
description: A SHA-256 hash of the block's content.
x-withhuman-edition: hosted
StoredDelegateBlock:
type: object
additionalProperties: false
description: A delegate as saved in a revision.
required:
- id
- snapshot_id
- name
- type
- enabled
- timeout
- max_attempts
- config
- config_version
- content_hash
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The block's key, unique within the pipeline including blocks inside branches.
snapshot_id:
type: string
format: uuid
description: The id of the saved copy.
name:
type: string
minLength: 1
description: The display name.
type:
type: string
const: delegate
description: Always `delegate`.
enabled:
type: boolean
const: true
description: Always `true`.
timeout:
type: string
example: 50ms
description: How long the condition may take.
max_attempts:
type: integer
const: 1
description: Always 1.
config:
$ref: "#/components/schemas/DelegateBlockConfig"
config_version:
type: integer
const: 1
description: The version of the config format. Always 1.
content_hash:
type: string
pattern: ^[0-9a-f]{64}$
description: A SHA-256 hash of the block's content.
PipelineUse:
type: object
required:
- pipeline_id
- pipeline_key
- revision
description: One place an active pipeline revision uses a shared object, an escalation path, a
webhook endpoint, or another pipeline.
properties:
pipeline_id:
type: string
format: uuid
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The pipeline whose revision uses the object.
revision:
type: integer
format: int64
description: The active revision number.
block_key:
type: string
description: The block that uses the object. Absent when the revision names an escalation path as
its default.
RequestApprovalPipelinePreviewRequest:
type: object
additionalProperties: false
description: A pipeline document and a sample request to run it against.
required:
- blocks
- sample
properties:
blocks:
type: array
maxItems: 1000
description: The blocks, in the order they should run.
items:
$ref: "#/components/schemas/RequestApprovalBlockDefinition"
default_escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The default escalation path, as in a document. Reported in the result when the outcome
is `human`.
default_ask_agent_owner:
type: boolean
description: Whether the default asks the agent's owner first, as in a document.
sample:
type: object
additionalProperties: false
description: The request to run the pipeline against.
required:
- request
- agent
properties:
request:
type: object
additionalProperties: false
description: The tool call.
required:
- tool
- arguments
properties:
tool:
type: string
minLength: 1
example: issue_refund
description: The tool the sample agent is calling.
server:
type: string
example: stripe
description: The MCP server that defines the tool, as an adapter would report it. Leave it out for a
built-in tool.
arguments:
type: object
additionalProperties: true
example:
amount_cents: 12000
description: The tool's arguments.
agent_reasoning:
type: string
description: The agent's own explanation of the call. Only blocks that include it see it.
context:
type: object
additionalProperties: true
description: Runtime metadata, as an adapter would report it. Only blocks that include it see it.
agent:
type: object
additionalProperties: false
description: The agent making the call.
required:
- slug
- name
properties:
slug:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The agent's slug.
name:
type: string
minLength: 1
description: A name for the sample agent.
previous_metadata:
type: object
description: Metadata as if earlier blocks had returned it, keyed by pipeline key and then by block
key, exactly as conditions read it under
`/previous_metadata/<pipeline_key>/<block_key>`.
additionalProperties:
type: object
additionalProperties:
type: object
additionalProperties: true
description: Metadata from one pipeline, by block key.
RequestApprovalPipelinePreview:
type: object
additionalProperties: false
description: What the pipeline did with the sample.
required:
- outcome
- reason_code
- blocks
properties:
outcome:
type: string
enum:
- approve
- deny
- human
description: The final outcome.
reason_code:
type: string
enum:
- block_outcome
- block_error
- block_timeout
- retries_exhausted
- invalid_block_output
- end_of_pipeline
- end_of_branch
description: "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."
terminal_block_id:
type: string
description: "The block that ended the run: for `end_of_branch`, the branch or the delegate. Absent
when the request passed every block."
blocks:
type: array
description: 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.
items:
$ref: "#/components/schemas/RequestApprovalPipelinePreviewBlock"
escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: "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."
escalation_path_reason:
type: string
enum:
- block_escalation
- pipeline_default
- no_escalation
description: "Where the path came from. `block_escalation`: the ending block named it.
`pipeline_default`: a pipeline's default. `no_escalation`: no path applies."
escalation_path_pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The pipeline whose block or default supplied the path. Absent with `no_escalation`.
ask_agent_owner:
type: boolean
description: 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.
RequestApprovalPipelinePreviewBlock:
type: object
additionalProperties: false
description: What one block did in a preview.
required:
- block_id
- pipeline_key
- position
- outcome
- cost
- trace
properties:
block_id:
type: string
description: The block's key.
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: "The pipeline the block belongs to: the previewed document's key, or a delegated
pipeline's."
position:
type: integer
minimum: 0
description: The block's position in the effective pipeline, from 0, counting in document order with
each branch or delegate before the blocks it holds.
outcome:
type: string
enum:
- next
- approve
- deny
- human
- enter
description: What the block returned. A branch or delegate returns `enter` when its condition
matched and `next` when it did not.
reason:
type: string
description: The note the block recorded.
metadata:
type: object
additionalProperties: true
description: The metadata the block returned, if any.
cost:
type: integer
format: int64
minimum: 0
description: The cost the block recorded.
error_code:
type: string
description: "Why the block failed, if it did: `invalid_input`, `input_too_large`,
`evaluation_error`, `cost_limit_exceeded`, `resource_limit_exceeded`, `timeout`,
`invalid_block_output`, `internal_error`, `provider_error`, `endpoint_unreachable`,
`endpoint_error`, `endpoint_rejected`, `outcome_not_allowed`, `endpoint_unavailable`, or
a code a kind the edition adds reports (the hosted judge's `model_unavailable`)."
delivery:
type: object
additionalProperties: false
required:
- endpoint_key
- endpoint_host
- duration_ms
description: For webhook blocks. What was called and what came back. Never the bodies, never the
secret.
properties:
endpoint_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The endpoint that was called.
endpoint_revision:
type: integer
format: int64
minimum: 1
description: The revision that was delivered to. Absent when the endpoint had no active revision.
endpoint_host:
type: string
description: The host that was called.
http_status:
type: integer
description: The HTTP status the endpoint returned.
duration_ms:
type: integer
format: int64
description: How long the call took, in milliseconds.
reason:
type: string
description: The endpoint's own explanation, at most 1,024 characters.
trace:
type: array
description: How the condition was evaluated, node by node. For condition blocks and branches.
items:
type: object
additionalProperties: false
required:
- node_id
- matched
- error
properties:
node_id:
type: string
format: uuid
description: The condition's id.
matched:
type: boolean
description: Whether the condition held.
error:
type: boolean
description: Whether evaluating it failed.
end_of_branch:
type: boolean
description: Present and true on the step that closes a branch the request entered when none of its
blocks decided. The outcome is `human`.
RequestApprovalPipelineRevisionSummary:
type: object
additionalProperties: false
description: One pipeline revision, without its blocks.
required:
- id
- pipeline_key
- name
- revision
- is_active
- block_count
- created_by_actor_id
- created_at
properties:
id:
type: string
format: uuid
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The pipeline's key.
name:
type: string
description: The pipeline's display name as of this revision.
revision:
type: integer
format: int64
minimum: 1
description: The revision number. Revisions count up from 1.
is_active:
type: boolean
description: Whether this is the revision in use.
block_count:
type: integer
minimum: 0
maximum: 1000
description: Every block in the revision, including blocks inside branches.
default_escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: The escalation path for requests that reach a person from this pipeline without a block
naming a path.
default_ask_agent_owner:
type: boolean
description: 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.
created_by_actor_id:
type: string
format: uuid
description: The membership or organization-key actor that created the revision.
created_at:
type: string
format: date-time
activated_at:
type: string
format: date-time
description: When the revision was first active. Absent for a draft, which can still be changed.
archived_at:
type: string
format: date-time
description: Present when the pipeline was archived. An archived revision can be read but never
activated.
RequestApprovalPipelineDocument:
type: object
additionalProperties: false
description: The complete content of a new pipeline revision.
required:
- blocks
properties:
name:
type: string
minLength: 1
maxLength: 120
description: A new display name for the pipeline. Omit to keep the active revision's.
blocks:
type: array
maxItems: 1000
description: 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.
items:
$ref: "#/components/schemas/RequestApprovalBlockDefinition"
default_escalation_path:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: 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.
default_ask_agent_owner:
type: boolean
description: "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."
PipelineReferences:
type: object
required:
- escalation_paths
- webhook_endpoints
- pipelines
- blocks
- activatable
description: What one pipeline revision depends on, and whether each dependency is in place right
now. activatable is false while any entry would refuse activation.
properties:
pipelines:
type: array
description: The pipelines the revision's delegate blocks hand requests to.
items:
type: object
required:
- pipeline_key
- block_keys
- exists
- active
properties:
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
name:
type: string
description: The active revision's name. Absent while none is active.
block_keys:
type: array
items:
type: string
description: The delegate blocks that name the pipeline.
exists:
type: boolean
description: Whether the pipeline has a live revision (neither never created nor archived).
active:
type: boolean
description: Whether a revision of the pipeline is active.
escalation_paths:
type: array
description: The escalation paths the revision names.
items:
type: object
required:
- path_key
- block_keys
- default
- exists
- active
properties:
path_key:
type: string
block_keys:
type: array
items:
type: string
description: The blocks whose `human` outcome names the path.
default:
type: boolean
description: Whether the revision names the path as its default.
exists:
type: boolean
description: Whether at least one revision of the path exists.
active:
type: boolean
description: Whether a revision of the path is active.
webhook_endpoints:
type: array
description: The webhook endpoints the revision posts to.
items:
type: object
required:
- endpoint_key
- block_keys
- exists
- active
properties:
endpoint_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
name:
type: string
description: The active revision's name. Absent while none is active.
block_keys:
type: array
items:
type: string
description: The webhook blocks that post to the endpoint.
exists:
type: boolean
description: Whether at least one revision of the endpoint exists.
active:
type: boolean
description: Whether a revision of the endpoint is active.
blocks:
type: array
description: Blocks the deployment or the plan cannot run right now.
items:
type: object
required:
- block_key
- type
- problem
properties:
block_key:
type: string
type:
type: string
description: The kind of block.
problem:
type: string
description: "Why it cannot run. `plan`: the plan does not include it. `webhook_unconfigured`: the
deployment has no secrets key. A kind the edition adds reports its own codes (the
hosted judge's `llm_judge_unconfigured` and `judge_model_unavailable`)."
activatable:
type: boolean
description: Whether activating the revision would succeed right now.
EscalationPathSummary:
type: object
additionalProperties: false
description: One escalation path and a summary of its history.
required:
- path_key
- name
- latest_revision
- revision_count
- latest_created_at
properties:
path_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
name:
type: string
description: The name from the latest revision.
active_revision:
type: integer
format: int64
minimum: 1
description: The revision requests are routed with. Absent while no revision is active, which is
always the case for an archived path.
latest_revision:
type: integer
format: int64
minimum: 1
description: The newest revision, active or not.
revision_count:
type: integer
format: int64
minimum: 1
latest_created_at:
type: string
format: date-time
description: When the newest revision was created.
archived_at:
type: string
format: date-time
description: When the path was archived. Absent for a live path.
EscalationPathCreate:
type: object
additionalProperties: false
description: The first revision's document plus the key it creates.
required:
- path_key
- name
- working_hours
- nodes
properties:
path_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
name:
type: string
working_hours:
type: array
items:
$ref: "#/components/schemas/WorkingHoursSet"
nodes:
type: array
minItems: 1
items:
$ref: "#/components/schemas/EscalationPathNode"
repeat:
$ref: "#/components/schemas/EscalationPathRepeat"
WorkingHoursSet:
type: object
additionalProperties: false
description: A named set of weekly hours in one timezone.
required:
- id
- name
- timezone
- intervals
properties:
id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
example: uk-office
description: A key for the set, unique within the document.
name:
type: string
example: UK office hours
description: A display name.
timezone:
type: string
example: Europe/London
description: An IANA timezone name, such as `Europe/London`.
intervals:
type: array
minItems: 1
description: The open windows. At least one.
items:
$ref: "#/components/schemas/WeekdayInterval"
WeekdayInterval:
type: object
additionalProperties: false
description: One window that repeats every week.
required:
- weekdays
- start
- end
properties:
weekdays:
type: array
minItems: 1
description: The days the window applies to.
items:
type: string
enum:
- mon
- tue
- wed
- thu
- fri
- sat
- sun
start:
type: string
pattern: ^([01][0-9]|2[0-3]):[0-5][0-9]$
example: 09:00
description: When the window opens, as `HH:MM` in 24-hour time.
end:
type: string
pattern: ^([01][0-9]|2[0-3]):[0-5][0-9]$
example: 18:00
description: When the window closes, as `HH:MM` in 24-hour time.
EscalationPathNode:
type: object
additionalProperties: false
description: One node of an escalation path. The type picks the kind of node, and only that kind's
fields may be set. A level node notifies its targets and waits for a decision. If nobody
decides before escalate_after runs out, the path moves to the next node. An if_else node
runs then when every condition holds, otherwise else. A defer node parks the request until a
working-hours window opens, notifying nobody.
required:
- id
- type
properties:
id:
type: string
format: uuid
description: An id for the node, unique within the path.
type:
type: string
enum:
- level
- if_else
- defer
description: The kind of node.
targets:
type: array
description: "`level` only. The people and teams to notify."
items:
$ref: "#/components/schemas/EscalationTarget"
escalate_after:
type: string
example: 15m
description: "`level` only. How long to wait for a decision before moving to the next node. A
duration from `1m` to `168h`."
raise_urgency:
type: boolean
description: "`level` only. Raise the request to `interrupt` urgency before notifying."
conditions:
type: array
minItems: 1
maxItems: 10
description: "`if_else` only. Every condition must hold for `then` to run. From 1 to 10."
items:
$ref: "#/components/schemas/EscalationPathCondition"
then:
type: array
description: "`if_else` only. The nodes to run when every condition holds. An empty list moves on to
the next node."
items:
$ref: "#/components/schemas/EscalationPathNode"
else:
type: array
description: "`if_else` only. The nodes to run otherwise. An empty list moves on to the next node."
items:
$ref: "#/components/schemas/EscalationPathNode"
working_hours_id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: "`defer` only. The working-hours set to wait for."
standard_only:
type: boolean
description: "`defer` only. Let `interrupt` requests skip the wait."
EscalationTarget:
type: object
additionalProperties: false
description: Who a level notifies. user names a membership and team names a team; both carry an id.
broadcast notifies every current member of the team that owns a team escalation policy and
carries no id. An organization escalation path accepts user and team; a team escalation
policy accepts user (members of that team only) and broadcast.
required:
- type
properties:
type:
type: string
enum:
- user
- team
- broadcast
id:
type: string
format: uuid
description: Required for user and team, absent for broadcast.
EscalationPathCondition:
type: object
additionalProperties: false
description: One test in an if_else node. The type picks the kind of test, and only that kind's
fields may be set.
required:
- type
properties:
type:
type: string
enum:
- working_hours
- urgency
- request
description: The kind of test. `working_hours` tests whether a named window is open. `urgency` tests
the request's current urgency. `request` tests the request itself.
working_hours_id:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
description: "`working_hours` only. The set to test."
active:
type: boolean
description: "`working_hours` only. `true` tests that the window is open, `false` that it is closed."
urgency:
type: string
enum:
- standard
- interrupt
description: "`urgency` only. The urgency the request must have."
when:
$ref: "#/components/schemas/CELCondition"
EscalationPathRepeat:
type: object
additionalProperties: false
description: Run the path again from the top when the nodes run out without a decision. Every
condition is evaluated again on each run.
required:
- times
- after
properties:
times:
type: integer
minimum: 1
maximum: 9
description: How many extra runs, from 1 to 9.
after:
type: string
example: 1h
description: How long to wait before each extra run. A duration from `1m` to `168h`.
EscalationPathRevision:
description: One escalation path revision with its full document.
allOf:
- $ref: "#/components/schemas/EscalationPathRevisionSummary"
- type: object
required:
- working_hours
- nodes
- document_version
- warnings
properties:
working_hours:
type: array
description: The named working-hours sets the nodes refer to.
items:
$ref: "#/components/schemas/WorkingHoursSet"
nodes:
type: array
description: The nodes, in order.
items:
$ref: "#/components/schemas/EscalationPathNode"
repeat:
$ref: "#/components/schemas/EscalationPathRepeat"
document_version:
type: integer
minimum: 1
description: The version of the document format.
warnings:
type: array
description: "Advisory findings about the document's targets. They are computed when a revision is
created or activated, and the list is empty on plain reads. A warning never blocks
anything: a path narrows who decides, it never grants."
items:
$ref: "#/components/schemas/EscalationPathWarning"
EscalationPathRevisionSummary:
type: object
additionalProperties: false
description: One escalation path revision, without its document.
required:
- id
- path_key
- revision
- is_active
- name
- created_by_actor_id
- created_at
properties:
id:
type: string
format: uuid
path_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
revision:
type: integer
format: int64
minimum: 1
description: The revision number. Revisions count up from 1.
is_active:
type: boolean
description: Whether requests are routed with this revision.
name:
type: string
example: On-call engineers
description: The display name.
created_by_actor_id:
type: string
format: uuid
description: The membership or organization-key actor that created the revision.
created_at:
type: string
format: date-time
activated_at:
type: string
format: date-time
description: When the revision was first active. Absent for a draft, which can still be changed.
archived_at:
type: string
format: date-time
description: When the path was archived. Absent for a revision of a live path; an archived revision
can no longer be activated.
EscalationPathWarning:
type: object
description: One advisory finding about the document. The team fields are present only for findings
about a team target.
required:
- code
properties:
code:
type: string
enum:
- team_members_cannot_decide
- path_may_reach_nobody
description: "`team_members_cannot_decide`: `count` of the team's `member_count` members cannot
decide requests, so routing to the team reaches fewer deciders than its size suggests.
`path_may_reach_nobody`: some request's conditions leave no level to run, so such a
request is routed to nobody and only break-glass reviewers can decide it; the team
fields are absent."
team_id:
type: string
format: uuid
team_name:
type: string
count:
type: integer
description: Members of the team who cannot decide requests.
member_count:
type: integer
description: Active members of the team.
EscalationPathDocument:
type: object
additionalProperties: false
description: The complete content of a new escalation path revision.
required:
- name
- working_hours
- nodes
properties:
name:
type: string
example: On-call engineers
description: A display name. Reviewers see it on the requests the path routes.
working_hours:
type: array
description: Named working-hours sets that `if_else` and `defer` nodes refer to by id. May be empty.
items:
$ref: "#/components/schemas/WorkingHoursSet"
nodes:
type: array
minItems: 1
description: The nodes, in order. At least one.
items:
$ref: "#/components/schemas/EscalationPathNode"
repeat:
$ref: "#/components/schemas/EscalationPathRepeat"
MyAPIKeysEnvelope:
type: object
additionalProperties: false
required:
- keys
- personal_api_keys_allowed
- scoping_enabled
- bearable_permissions
properties:
keys:
type: array
items:
$ref: "#/components/schemas/APIKey"
personal_api_keys_allowed:
type: boolean
description: The organization's switch.
scoping_enabled:
type: boolean
description: Whether a key may carry a permission list here. False in the open edition, where keys
inherit the member's role.
bearable_permissions:
type: array
items:
$ref: "#/components/schemas/APIKeyPermission"
APIKey:
type: object
additionalProperties: false
description: An API key without its secret. Personal prefixes start with `whk_`; organization
prefixes start with `who_`.
required:
- grants
- usable
- id
- prefix
- kind
- name
- permissions
- assurance_at_issue
- status
- created_at
- expires_at
- last_used_at
- revoked_at
properties:
grants:
type: array
items:
$ref: "#/components/schemas/Grant"
description: Current organization-key grants projected from RBAC permission policies. Empty for
personal keys.
effective_grants:
type: array
items:
$ref: "#/components/schemas/Grant"
description: On detail responses, currently allowed organization-key grants.
usable:
type: boolean
description: Whether the key is active and not blocked by organization policy or status.
blocked_reason:
type: string
enum:
- organization_inactive
- api_keys_disabled
id:
type: string
format: uuid
prefix:
type: string
example: whk_4f1c9a2e-7b3d-4e8f-a1c5-2d6b8e0f9a31
kind:
type: string
enum:
- personal
- organization
description: Personal keys act as a member; organization keys act as themselves.
membership_id:
type: string
format: uuid
description: The member a personal key acts as.
created_by:
type: object
additionalProperties: false
description: Who minted it. Present on the organization-wide list.
required:
- membership_id
- display_name
- email
properties:
membership_id:
type: string
format: uuid
display_name:
type: string
email:
type: string
name:
type: string
permissions:
type:
- array
- "null"
items:
type: string
description: The narrowing list, or `null` when the key inherits everything its member holds. The
key's effective permissions are always this list intersected with the member's current
grants.
assurance_at_issue:
type: string
description: The assurance of the session that minted the key (`strong`, `single_factor`), presented
as the key's own when it decides.
oauth_client:
type: object
additionalProperties: false
description: The MCP client the key was minted for through the consent page. Absent for a key made
by hand.
required:
- id
- name
properties:
id:
type: string
format: uuid
name:
type: string
status:
type: string
enum:
- active
- expired
- revoked
created_at:
type: string
format: date-time
expires_at:
type:
- string
- "null"
format: date-time
description: "`null` when the key lives until revoked."
last_used_at:
type:
- string
- "null"
format: date-time
revoked_at:
type:
- string
- "null"
format: date-time
revocation_reason:
type: string
enum:
- manual
- membership_deprovisioned
CreateAPIKeyRequest:
type: object
additionalProperties: false
required:
- name
properties:
name:
type: string
minLength: 1
maxLength: 100
example: terraform
permissions:
type: array
items:
type: string
minItems: 1
description: Narrow the key to these permissions. Omit to inherit everything you hold. Each must be
a permission you hold; only in the hosted edition.
example:
- pipeline.read
- pipeline.write
- pipeline.activate
expires_at:
type: string
format: date-time
description: When the key stops working. Omit for a key that lives until revoked, unless the
deployment caps key lifetime.
IssuedAPIKeyEnvelope:
type: object
additionalProperties: false
required:
- key
- token
properties:
key:
$ref: "#/components/schemas/APIKey"
token:
type: string
description: "The raw key, shown once: `whk_<id>_<secret>`."
example: whk_4f1c9a2e-7b3d-4e8f-a1c5-2d6b8e0f9a31_x5nH9v…
VerifiedAPIKeyEnvelope:
type: object
additionalProperties: false
required:
- key
- effective_permissions
properties:
effective_grants:
type: array
items:
$ref: "#/components/schemas/Grant"
key:
$ref: "#/components/schemas/APIKey"
effective_permissions:
type: array
items:
type: string
description: What the key may do right now, after narrowing and the member's current roles.
APIKeysEnvelope:
type: object
additionalProperties: false
required:
- keys
properties:
keys:
type: array
items:
$ref: "#/components/schemas/APIKey"
CreateOrganizationAPIKeyRequest:
type: object
additionalProperties: false
required:
- name
- permission_policies
properties:
name:
type: string
minLength: 1
maxLength: 100
permission_policies:
type: array
minItems: 1
maxItems: 20
items:
$ref: "#/components/schemas/PermissionPolicySpec"
description: Policies assigned to the key through RBAC. Every permission must allow organization
keys and be held by the issuer at each selected resource or wider.
expires_at:
type:
- string
- "null"
format: date-time
PermissionPolicySpec:
type: object
additionalProperties: false
required:
- scope_kind
- resource_ids
- permissions
properties:
scope_kind:
type: string
enum:
- organization
- team
- agent
resource_ids:
type: array
items:
type: string
maxItems: 200
uniqueItems: true
description: Empty for organization scope; otherwise the selected team IDs or agent slugs.
permissions:
type: array
items:
type: string
minItems: 1
uniqueItems: true
OrganizationAPIKeyCreated:
type: object
additionalProperties: false
description: An API key without its secret. Personal prefixes start with `whk_`; organization
prefixes start with `who_`.
required:
- secret_available
- grants
- usable
- id
- prefix
- kind
- name
- permissions
- assurance_at_issue
- status
- created_at
- expires_at
- last_used_at
- revoked_at
properties:
secret_available:
type: boolean
token:
type: string
writeOnly: false
description: Returned only in the first creation response.
grants:
type: array
items:
$ref: "#/components/schemas/Grant"
description: Current organization-key grants projected from RBAC permission policies. Empty for
personal keys.
effective_grants:
type: array
items:
$ref: "#/components/schemas/Grant"
description: On detail responses, currently allowed organization-key grants.
usable:
type: boolean
description: Whether the key is active and not blocked by organization policy or status.
blocked_reason:
type: string
enum:
- organization_inactive
- api_keys_disabled
id:
type: string
format: uuid
prefix:
type: string
example: whk_4f1c9a2e-7b3d-4e8f-a1c5-2d6b8e0f9a31
kind:
type: string
enum:
- personal
- organization
description: Personal keys act as a member; organization keys act as themselves.
membership_id:
type: string
format: uuid
description: The member a personal key acts as.
created_by:
type: object
additionalProperties: false
description: Who minted it. Present on the organization-wide list.
required:
- membership_id
- display_name
- email
properties:
membership_id:
type: string
format: uuid
display_name:
type: string
email:
type: string
name:
type: string
permissions:
type:
- array
- "null"
items:
type: string
description: The narrowing list, or `null` when the key inherits everything its member holds. The
key's effective permissions are always this list intersected with the member's current
grants.
assurance_at_issue:
type: string
description: The assurance of the session that minted the key (`strong`, `single_factor`), presented
as the key's own when it decides.
status:
type: string
enum:
- active
- expired
- revoked
created_at:
type: string
format: date-time
expires_at:
type:
- string
- "null"
format: date-time
description: "`null` when the key lives until revoked."
last_used_at:
type:
- string
- "null"
format: date-time
revoked_at:
type:
- string
- "null"
format: date-time
revocation_reason:
type: string
enum:
- manual
- membership_deprovisioned
Member:
type: object
description: A member as shown in the member list, with their name and email.
required:
- id
- display_name
- email
- status
- assignments
- admission_source
- directory_managed
- team_ids
- created_at
properties:
id:
type: string
format: uuid
description: The membership id. Other endpoints refer to a member by this id.
display_name:
type: string
example: Ada Lovelace
email:
type: string
example: [email protected]
status:
type: string
enum:
- active
- suspended
- deprovisioned
description: "`active` can sign in and act. `suspended` cannot until reactivated. `deprovisioned`
was removed and is kept for the record."
assignments:
type: array
description: The roles the member holds, directly and through their teams.
items:
$ref: "#/components/schemas/RoleAssignment"
admission_source:
type: string
enum:
- manual
- invite
- sso_jit
- directory
description: "How the person joined: added by hand, by accepting an invitation, on first SSO
sign-in, or by directory sync."
directory_managed:
type: boolean
description: Whether your directory owns this membership. Status changes here are refused.
team_ids:
type: array
description: The teams the member is on.
items:
type: string
format: uuid
created_at:
type: string
format: date-time
Role:
type: object
required:
- key
- name
- description
- permissions
- dangerous
properties:
key:
$ref: "#/components/schemas/RoleKey"
name:
type: string
example: Reviewer
description:
type: string
permissions:
type: array
items:
type: string
description: Every permission the role holds, including the ones this edition registers.
dangerous:
type: boolean
description: The role holds a sensitive permission, so granting it needs a fresh sign-in.
Membership:
type: object
description: A person's membership of the organization, as stored.
required:
- id
- user_id
- organization_id
- status
- admission_source
- directory_managed
- assignments
properties:
id:
type: string
format: uuid
description: The membership id. Other endpoints refer to a member by this id.
user_id:
type: string
format: uuid
description: The person's user id, shared across organizations.
organization_id:
type: string
format: uuid
status:
type: string
enum:
- active
- suspended
- deprovisioned
description: "`active` can sign in and act. `suspended` cannot until reactivated. `deprovisioned`
was removed and is kept for the record."
admission_source:
type: string
enum:
- manual
- invite
- sso_jit
- directory
description: "How the person joined: added by hand, by accepting an invitation, on first SSO
sign-in, or by directory sync."
directory_managed:
type: boolean
description: Whether your directory owns this membership. Status changes here are refused.
assignments:
type: array
description: The roles the member holds, directly and through their teams.
items:
$ref: "#/components/schemas/RoleAssignment"
team_ids:
type: array
description: The teams the member is on.
items:
type: string
format: uuid
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
PermissionDescriptor:
type: object
description: One permission a role can carry.
required:
- key
- area
- description
- holders
- resource
- dangerous
properties:
key:
type: string
example: request.decide
description: The permission key, shaped `resource.action`.
area:
type: string
example: request
description: The key's first segment, for grouping.
description:
type: string
example: Approve or deny requests routed to you
description: What the permission allows.
holders:
type: array
description: "Who can hold it: people, agents, or the gateway."
items:
type: string
enum:
- human
- agent
- gateway
- api_key
- organization_api_key
resource:
type: string
enum:
- organization
- team
- agent
- request
- scope
description: What kind of thing a check on this permission is about. A grant must cover that thing's
scope.
dangerous:
type: boolean
description: Whether granting or using it changes what other people can do, or cannot be undone.
Such permissions need a fresh, strong sign-in to use and never arrive through directory
sync or first SSO sign-in.
WebhookEndpointSummary:
type: object
additionalProperties: false
required:
- endpoint_key
- name
- url
- latest_revision
- revision_count
- latest_created_at
- secret_rotated_at
- uses
description: One webhook endpoint and a summary of its history.
properties:
endpoint_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
name:
type: string
description: The name from the latest revision.
url:
type: string
format: uri
description: The URL from the latest revision.
active_revision:
type: integer
format: int64
minimum: 1
description: The revision deliveries go to. Absent while the endpoint is archived or no revision has
been activated yet.
archived_at:
type: string
format: date-time
description: When the endpoint was archived. Absent for a live endpoint, including one whose
revisions are all drafts. Cleared when a revision is activated again.
latest_revision:
type: integer
format: int64
minimum: 1
description: The newest revision, active or not.
revision_count:
type: integer
format: int64
minimum: 1
latest_created_at:
type: string
format: date-time
description: When the newest revision was created.
secret_rotated_at:
type: string
format: date-time
description: When the signing secret was last created or rotated.
uses:
type: array
description: The active pipeline revisions that post to the endpoint. Empty when it can be archived.
items:
$ref: "#/components/schemas/PipelineUse"
WebhookEndpointRevision:
type: object
additionalProperties: false
required:
- id
- endpoint_key
- revision
- is_active
- name
- url
- created_by_actor_id
- created_at
description: One revision of a webhook endpoint, a name and a URL. The signing secret belongs to the
endpoint key, not to a revision. It is never included, except once, in the response that
created the endpoint's first revision.
properties:
id:
type: string
format: uuid
endpoint_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
revision:
type: integer
format: int64
minimum: 1
description: The revision number. Revisions count up from 1.
is_active:
type: boolean
description: Whether deliveries go to this revision.
name:
type: string
example: Fraud check
description: The display name.
url:
type: string
format: uri
example: https://fraud.example.com/approvals
description: Where deliveries go.
created_by_actor_id:
type: string
format: uuid
description: The membership or organization-key actor that created the revision.
created_at:
type: string
format: date-time
activated_at:
type: string
format: date-time
description: When the revision was first active. Absent for a draft, which can still be changed.
signing_secret:
type: string
example: whsec_9f2c1b7e4d3a4f8b
description: Present only in the response that created the endpoint's first revision. Starts with
`whsec_`. Your receiver uses it to verify the `WithHuman-Signature` header.
WebhookEndpointDocument:
type: object
additionalProperties: false
description: The content of a new webhook endpoint revision.
required:
- name
- url
properties:
name:
type: string
minLength: 1
example: Fraud check
description: A display name. Pipeline authors see it when they pick an endpoint.
url:
type: string
format: uri
example: https://fraud.example.com/approvals
description: An absolute public HTTPS URL. It is checked against the egress policy, which refuses
private and internal addresses.
WebhookEndpointTest:
type: object
required:
- status_code
- duration_ms
description: What one test delivery produced. problem is absent when the endpoint answered with a
valid outcome.
properties:
status_code:
type: integer
description: The HTTP status the endpoint returned.
duration_ms:
type: integer
format: int64
description: How long the call took, in milliseconds.
outcome:
type: string
enum:
- next
- human
- approve
- deny
description: The outcome the endpoint answered with, when its answer was valid.
reason:
type: string
description: The reason the endpoint gave, if any.
problem:
type: string
enum:
- blocked
- response_too_large
- timeout
- unreachable
- status
- invalid_answer
description: "What went wrong, if anything. `blocked`: the URL fails the egress policy.
`response_too_large`: the body was over 64 KiB. `timeout`: no answer within 10 seconds.
`unreachable`: the connection failed. `status`: the endpoint answered with a status
outside 2xx. `invalid_answer`: the body was not a valid outcome document."
message:
type: string
description: Details about the problem, in plain words.
WebhookEndpointSecret:
type: object
additionalProperties: false
description: A freshly rotated signing secret.
required:
- endpoint_key
- signing_secret
- secret_rotated_at
properties:
endpoint_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
signing_secret:
type: string
example: whsec_9f2c1b7e4d3a4f8b
description: The new secret. It appears only in this response. Starts with `whsec_`. Your receiver
uses it to verify the `WithHuman-Signature` header.
secret_rotated_at:
type: string
format: date-time
description: When the secret was rotated.
Team:
type: object
description: A group of members. A team can be an escalation target, and its members inherit the
roles it holds. A team may also own an escalation policy that runs when a path level names
it.
required:
- id
- name
- directory_managed
- member_count
- assignments
- escalation_policy
- created_at
- archived_at
properties:
id:
type: string
format: uuid
name:
type: string
example: Payments approvers
directory_managed:
type: boolean
description: Whether your directory owns this team. Such a team cannot be renamed or archived here.
member_count:
type: integer
description: How many members the team has, from every source.
assignments:
type: array
description: The roles the team holds. Every member inherits them.
items:
$ref: "#/components/schemas/RoleAssignment"
escalation_policy:
$ref: "#/components/schemas/TeamEscalationPolicyStatus"
archived_at:
type:
- string
- "null"
format: date-time
description: When the team was archived. Null for a live team.
created_at:
type: string
format: date-time
TeamEscalationPolicyStatus:
type: object
additionalProperties: false
description: The team's escalation policy lineage. Both revisions are null when the team has never
had a policy; active_revision alone is null when every revision is archived. Without an
active revision a path level that targets the team notifies every member at once.
required:
- active_revision
- latest_revision
properties:
active_revision:
type:
- integer
- "null"
format: int64
minimum: 1
latest_revision:
type:
- integer
- "null"
format: int64
minimum: 1
TeamMember:
type: object
description: One member of a team.
required:
- membership_id
- display_name
- email
- status
- sources
properties:
membership_id:
type: string
format: uuid
display_name:
type: string
example: Ada Lovelace
email:
type: string
example: [email protected]
status:
type: string
enum:
- active
- suspended
- deprovisioned
description: The member's status in the organization.
sources:
type: array
description: "How the person got on the team: added by hand, by your directory, or both."
items:
type: string
enum:
- manual
- directory
TeamEscalationPolicyRevision:
allOf:
- $ref: "#/components/schemas/TeamEscalationPolicyRevisionSummary"
- type: object
required:
- working_hours
- nodes
- document_version
- warnings
properties:
working_hours:
type: array
items:
$ref: "#/components/schemas/WorkingHoursSet"
nodes:
type: array
items:
$ref: "#/components/schemas/EscalationPathNode"
repeat:
$ref: "#/components/schemas/EscalationPathRepeat"
document_version:
type: integer
minimum: 1
warnings:
type: array
description: "Advisory findings about the document against the team's roster and grants. Never a
refusal: the policy narrows who is asked within the team, it never grants. Creation
and activation report members who cannot decide; reads of the active revision report
that and every target who left the team; reads of a historical revision report
departed targets only."
items:
$ref: "#/components/schemas/TeamEscalationPolicyWarning"
TeamEscalationPolicyRevisionSummary:
type: object
additionalProperties: false
required:
- id
- team_id
- revision
- is_active
- created_by_actor_id
- created_at
properties:
id:
type: string
format: uuid
team_id:
type: string
format: uuid
revision:
type: integer
format: int64
minimum: 1
is_active:
type: boolean
created_by_actor_id:
type: string
format: uuid
created_at:
type: string
format: date-time
activated_at:
type: string
format: date-time
description: When the revision was first active. Absent for a draft, which can still be changed.
TeamEscalationPolicyWarning:
type: object
required:
- code
- count
- member_count
properties:
code:
type: string
enum:
- team_members_cannot_decide
- target_not_member
description: team_members_cannot_decide means count of the team's member_count members hold no
request.decide grant. target_not_member means the user target in membership_id is no
longer an active member of the team, so the level naming them skips them.
membership_id:
type: string
format: uuid
description: The departed target. Present for target_not_member only.
display_name:
type: string
description: The departed target's name, while the roster still lists them under another status.
count:
type: integer
description: Members of the team who cannot decide requests; 0 for target_not_member.
member_count:
type: integer
description: Active members of the team; 0 for target_not_member.
TeamEscalationPolicyDocument:
type: object
additionalProperties: false
description: The node program a team runs when a path level targets it. The same grammar as an
escalation path document, without a name, whose levels name members of the team or broadcast
to all of them.
required:
- working_hours
- nodes
properties:
working_hours:
type: array
items:
$ref: "#/components/schemas/WorkingHoursSet"
nodes:
type: array
minItems: 1
items:
$ref: "#/components/schemas/EscalationPathNode"
repeat:
$ref: "#/components/schemas/EscalationPathRepeat"
Invitation:
type: object
description: An invitation to join the organization.
required:
- id
- organization_id
- email
- grants
- invited_by_membership_id
- status
- expires_at
- created_at
- updated_at
properties:
id:
type: string
format: uuid
organization_id:
type: string
format: uuid
email:
type: string
example: [email protected]
description: The address the invitation was sent to.
grants:
type: array
description: The roles the person receives when they accept.
items:
$ref: "#/components/schemas/InvitationGrant"
invited_by_membership_id:
type: string
format: uuid
description: The member who sent it.
status:
type: string
enum:
- pending
- accepted
- revoked
- expired
description: "`pending` can still be accepted. `accepted` and `revoked` are final. `expired` passed
its expiry without being accepted."
expires_at:
type: string
format: date-time
description: When the link stops working. Seven days from creation or the last resend.
accepted_membership_id:
type: string
format: uuid
description: The membership created on acceptance. Present for `accepted` only.
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
InvitationGrant:
type: object
description: Permissions granted at one scope, with provenance.
required:
- scope_kind
- permissions
properties:
role:
$ref: "#/components/schemas/RoleKey"
description: The predefined role the grant makes. Absent for custom permissions.
role_name:
type: string
description: That role's name.
scope_kind:
type: string
enum:
- organization
- team
- agent
description: Where the role applies.
scope_id:
type: string
description: The team id or the agent slug. Absent for `organization`.
permissions:
type: array
items:
type: string
InvitationWithURL:
type: object
description: An invitation and, on a deployment without email delivery, the link to send. Where
email is configured the link goes only to the invitee's mailbox and this field is absent.
required:
- invitation
properties:
invitation:
$ref: "#/components/schemas/Invitation"
invite_url:
type: string
format: uri
description: The link to send to the person. It contains the invitation's secret token, appears only
in this response, and only on a deployment with no email delivery configured.
PermissionPoliciesPage:
type: object
required:
- name
- policies
- resources
- permissions
- can_create
- is_owner
properties:
name:
type: string
policies:
type: array
items:
$ref: "#/components/schemas/PermissionPolicy"
resources:
type: array
items:
$ref: "#/components/schemas/PermissionResource"
permissions:
type: array
items:
$ref: "#/components/schemas/PermissionDescriptor"
can_create:
type: boolean
is_owner:
type: boolean
read_only_reason:
type: string
PermissionPolicy:
type: object
required:
- id
- revision
- scope_kind
- resource_ids
- permissions
- resources
- source
- can_edit
properties:
scope_kind:
type: string
enum:
- organization
- team
- agent
resource_ids:
type: array
items:
type: string
maxItems: 200
uniqueItems: true
description: Empty for organization scope; otherwise the selected team IDs or agent slugs.
permissions:
type: array
items:
type: string
minItems: 1
uniqueItems: true
id:
type: string
format: uuid
revision:
type: integer
format: int64
resources:
type: array
items:
$ref: "#/components/schemas/PermissionResource"
source:
type: string
enum:
- manual
- directory
via_team_id:
type: string
format: uuid
via_team_name:
type: string
can_edit:
type: boolean
read_only_reason:
type: string
PermissionResource:
type: object
required:
- id
- name
- kind
properties:
id:
type: string
name:
type: string
kind:
type: string
enum:
- organization
- team
- agent
PermissionPolicyInput:
type: object
additionalProperties: false
required:
- scope_kind
- resource_ids
- permissions
- revision
properties:
scope_kind:
type: string
enum:
- organization
- team
- agent
resource_ids:
type: array
items:
type: string
maxItems: 200
uniqueItems: true
description: Empty for organization scope; otherwise the selected team IDs or agent slugs.
permissions:
type: array
items:
type: string
minItems: 1
uniqueItems: true
revision:
type: integer
format: int64
minimum: 1
description: Required when updating an existing policy.
ToolCatalogReport:
type: object
additionalProperties: false
required:
- runtime
- discovery_context
- complete
- sources
properties:
runtime:
type: string
minLength: 1
maxLength: 128
discovery_context:
type: string
minLength: 1
maxLength: 256
description: Stable opaque identifier for this local project or runtime context
complete:
type: boolean
description: Whether all configured sources in this context were enumerated
sources:
type: array
maxItems: 200
items:
$ref: "#/components/schemas/ToolCatalogSourceReport"
ToolCatalogSourceReport:
type: object
additionalProperties: false
required:
- source
- complete
- tools
properties:
source:
type: string
enum:
- builtin
- mcp
server:
type: string
maxLength: 256
complete:
type: boolean
error:
type: string
maxLength: 1024
tools:
type: array
maxItems: 5000
items:
$ref: "#/components/schemas/ToolDefinition"
ToolDefinition:
type: object
additionalProperties: false
required:
- name
properties:
name:
type: string
minLength: 1
maxLength: 256
description: Exact tool name sent in approval requests
display_name:
type: string
maxLength: 512
description:
type: string
maxLength: 65536
input_schema:
type: object
additionalProperties: true
description: Original JSON Schema, at most 256 KiB. Absence means unknown.
annotations:
$ref: "#/components/schemas/OnboardingToolAnnotations"
OnboardingToolAnnotations:
type: object
properties:
title:
type: string
read_only_hint:
type: boolean
destructive_hint:
type: boolean
idempotent_hint:
type: boolean
open_world_hint:
type: boolean
ToolCatalogPage:
type: object
required:
- tools
- sources
- total
- next_offset
properties:
tools:
type: array
items:
$ref: "#/components/schemas/ToolCatalogEntry"
sources:
type: array
items:
$ref: "#/components/schemas/ToolCatalogSource"
total:
type: integer
minimum: 0
next_offset:
type:
- integer
- "null"
ToolCatalogEntry:
allOf:
- $ref: "#/components/schemas/ToolCatalogEntryDefinition"
- type: object
required:
- id
- agent_slug
- discovery_context
- runtime
- source
- available
- stale
- schema_fingerprint
- last_seen_at
- last_attempt_at
properties:
id:
type: string
format: uuid
agent_slug:
type: string
agent_instance_id:
type: string
format: uuid
instance_name:
type: string
discovery_context:
type: string
runtime:
type: string
source:
type: string
description: "`builtin`, `mcp`, or a source an edition adds (the hosted gateway's `gateway`)"
server:
type: string
available:
type: boolean
stale:
type: boolean
error:
type: string
schema_fingerprint:
type: string
last_seen_at:
type: string
format: date-time
last_attempt_at:
type: string
format: date-time
ToolCatalogEntryDefinition:
type: object
required:
- name
properties:
name:
type: string
display_name:
type: string
description:
type: string
input_schema:
type: object
additionalProperties: true
annotations:
$ref: "#/components/schemas/OnboardingToolAnnotations"
ToolCatalogSource:
type: object
required:
- id
- agent_slug
- available
- runtime
- discovery_context
- source
- last_attempt_at
- last_success_at
properties:
id:
type: string
format: uuid
agent_slug:
type: string
agent_instance_id:
type: string
format: uuid
instance_name:
type: string
description: The instance that reported the source, when an instance did
available:
type: boolean
description: "Whether the source's owner can serve now: the reporting instance is active with a live
credential, or the edition reports the external source live"
runtime:
type: string
discovery_context:
type: string
source:
type: string
description: "`builtin`, `mcp`, or a source an edition adds (the hosted gateway's `gateway`)"
server:
type: string
last_attempt_at:
type: string
format: date-time
last_success_at:
type:
- string
- "null"
format: date-time
error:
type: string
description: Why the last discovery failed, as the reporting instance or the edition described it.
Empty after a successful discovery
ToolDefinitionList:
type: object
required:
- tool_definitions
- servers
properties:
tool_definitions:
type: array
items:
$ref: "#/components/schemas/CatalogToolDefinition"
servers:
type: array
items:
type: string
CatalogToolDefinition:
type: object
required:
- name
- uses
properties:
name:
type: string
description: Exact tool name sent in approval requests
server:
type: string
description:
type: string
input_schema:
type: object
additionalProperties: true
uses:
type: array
items:
$ref: "#/components/schemas/ToolDefinitionUse"
ToolDefinitionUse:
type: object
required:
- runtime
- available
- stale
properties:
runtime:
type: string
description: The runtime that offers the definition, or the source an edition adds (the hosted
gateway's `gateway`)
instance_name:
type: string
description: The instance that reported the definition, when an instance did
available:
type: boolean
stale:
type: boolean
AuditEvent:
type: object
description: One entry in the audit log. Entries are never changed or deleted.
required:
- sequence
- id
- organization_id
- event_type
- actor_type
- subject_type
- subject_id
- data
- occurred_at
properties:
sequence:
type: integer
format: int64
description: The event's position in the organization's log. Later events have higher numbers.
id:
type: string
format: uuid
organization_id:
type: string
format: uuid
event_type:
type: string
description: What happened, as a dotted key such as `membership.suspended`.
actor_type:
type: string
description: "Who did it: `human`, `agent`, `system`, or `api_key`."
actor_id:
type: string
format: uuid
description: The person, agent, or key that acted. Absent for `system`.
actor_display_name:
type: string
description: The actor's name at the time.
subject_type:
type: string
description: What kind of thing the event is about, such as a request or a membership.
subject_id:
type: string
format: uuid
description: The thing the event is about.
data:
description: Details specific to the event type, as JSON.
occurred_at:
type: string
format: date-time
description: When it happened.
RequestTimeline:
type: object
additionalProperties: false
description: The request's story from its audit events. pipeline is absent while a suspended
pipeline is still waiting on a judge.
required:
- assessments
- claims
- escalations
- digests
- audit_event_count
properties:
pipeline:
$ref: "#/components/schemas/PipelineReview"
assessments:
type: array
items:
$ref: "#/components/schemas/JudgeAssessment"
claims:
type: array
items:
$ref: "#/components/schemas/ClaimEvent"
escalations:
type: array
description: What the escalation executor did for the request, oldest first. Empty for a default
queue request.
items:
$ref: "#/components/schemas/EscalationEvent"
digests:
type: array
description: Prompts for the request that a notification window held and sent when it closed, oldest
first, one per person and contact kind.
items:
$ref: "#/components/schemas/DigestEvent"
audit_event_count:
type: integer
description: How many audit events the request has in total, for the link into the audit log.
PipelineReview:
type: object
additionalProperties: false
description: The completed evaluation as a reviewer reads it. The terminal block is the one whose
outcome ended the pipeline; its fields are absent when the pipeline fell through its end or
never ran.
required:
- outcome
- reason_code
- blocks_evaluated
- completed_at
properties:
outcome:
type: string
enum:
- approve
- deny
- human
reason_code:
type: string
description: Why the pipeline stopped, for example block_outcome, block_timeout, retries_exhausted,
end_of_branch (the request entered a branch and none of its blocks decided),
no_active_revision.
blocks_evaluated:
type: integer
terminal_block_key:
type: string
terminal_block_name:
type: string
terminal_block_type:
type: string
enum:
- always
- cel
- llm_judge
- webhook
- branch
- delegate
terminal_block_reason:
type: string
description: The sentence the pipeline author configured on the terminal block, written for the
reviewer.
endpoint_reason:
type: string
description: A webhook endpoint's own explanation of its answer, when the terminal block was a
webhook that gave one.
terminal_block_error_code:
type: string
description: Present when the terminal block failed instead of answering, for example
`evaluation_error` or `timeout`. A failed block always sends the request to a human, so
a reviewer should not read it as a match.
terminal_pipeline:
type: object
additionalProperties: false
description: The pipeline that held the terminal block (the entry pipeline, or one a delegate handed
the request to), at the revision pinned for the request. Enough to read that revision
and find the block in it.
required:
- pipeline_key
- revision
properties:
pipeline_key:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
name:
type: string
description: The pipeline's name when the request ran.
revision:
type: integer
format: int64
completed_at:
type: string
format: date-time
JudgeAssessment:
type: object
additionalProperties: false
description: One judge block's verdict on the request. Evidence for the reviewer, never a decision.
required:
- block_key
- verdict
- confidence
- rationale
- downgraded_for_confidence
- evaluated_at
properties:
block_key:
type: string
block_name:
type: string
verdict:
type: string
enum:
- approve
- deny
- escalate
confidence:
type: string
enum:
- low
- medium
- high
rationale:
type: string
model_label:
type: string
description: The judge model's name when it judged the request.
provider:
type: string
model:
type: string
downgraded_for_confidence:
type: boolean
description: True when the confidence fell below the block's threshold and the verdict was treated
as escalate.
evaluated_at:
type: string
format: date-time
ClaimEvent:
type: object
additionalProperties: false
description: One moment in the request's claim history.
required:
- kind
- occurred_at
properties:
kind:
type: string
enum:
- claimed
- released
- lapsed
actor_display_name:
type: string
occurred_at:
type: string
format: date-time
EscalationEvent:
type: object
additionalProperties: false
description: One step of the request's escalation. Only the fields the kind carries are present.
team_id marks a team policy's step and is absent for the organization path's.
required:
- kind
- occurred_at
properties:
kind:
type: string
enum:
- level_entered
- urgency_raised
- deferred
- repeated
- exhausted
team_id:
type: string
node_id:
type: string
level_ordinal:
type: integer
description: The level's position within its own document, one-based (level_entered only).
level_count:
type: integer
description: How many levels the document has (level_entered only).
urgency:
type: string
enum:
- standard
- interrupt
description: The urgency the level was entered at, or the urgency raised to.
until:
type: string
format: date-time
description: When a deferred walker resumes (deferred only).
iteration:
type: integer
description: Which repeat of the document is starting (repeated only).
occurred_at:
type: string
format: date-time
DigestEvent:
type: object
additionalProperties: false
description: A prompt a notification window held and sent when it closed, alone (request_count 1) or
in a digest naming request_count requests.
required:
- kind
- request_count
- window_opened_at
- occurred_at
properties:
membership_id:
type: string
display_name:
type: string
description: The person's name when the window closed.
kind:
type: string
description: The contact kind the window belongs to, such as email, slack, discord, sms, whatsapp or
push.
request_count:
type: integer
description: How many requests the message named.
window_opened_at:
type: string
format: date-time
occurred_at:
type: string
format: date-time
AgentToolAccess:
type: object
required:
- server_id
- server_slug
- mode
- tools
- scopes
- revision
properties:
server_id:
type: string
format: uuid
server_slug:
type: string
mode:
type: string
enum:
- none
- selected
- all
tools:
type: array
items:
type: string
scopes:
type: array
items:
type: string
revision:
type: integer
format: int64
minimum: 1
AgentToolAccessInput:
type: object
additionalProperties: false
required:
- mode
- tools
- scopes
- revision
properties:
mode:
type: string
enum:
- none
- selected
- all
tools:
type: array
maxItems: 1000
uniqueItems: true
description: Exact downstream tool names, meaningful only in selected mode. Empty for none or all;
no wildcard matching.
items:
type: string
minLength: 1
maxLength: 256
scopes:
type: array
maxItems: 20
uniqueItems: true
description: The scopes the agent may call with; any granted scope works with any granted tool.
Empty exactly when mode is none.
items:
type: string
pattern: ^[a-z0-9][a-z0-9_]{0,62}$
revision:
type: integer
format: int64
minimum: 0
description: Expected current version; zero for an unconfigured agent/server pair.
OrganizationAPIKeyOptions:
type: object
additionalProperties: false
required:
- organization_api_keys_allowed
- included_in_plan
- resources
- permissions
- max_ttl
properties:
organization_api_keys_allowed:
type: boolean
included_in_plan:
type: boolean
resources:
type: array
items:
$ref: "#/components/schemas/PermissionResource"
permissions:
type: array
items:
$ref: "#/components/schemas/PermissionDescriptor"
max_ttl:
type: string
description: Maximum lifetime as a duration. 0s means no cap.
securitySchemes:
bearerAuth:
type: http
scheme: bearer
cookieAuth:
type: apiKey
in: cookie
name: withhuman_session
apiKeyAuth:
type: http
scheme: bearer
bearerFormat: whk_ / who_
description: "A personal whk_ or organization who_ API key sent as Authorization: Bearer <token>.
Personal keys act as their member with live, optionally narrowed access. Organization keys
act as themselves with live RBAC permission policies. The x-withhuman-credential-kinds
extension lists the kinds supported by each operation. Revocation, expiry, organization
status, and the policy switch for that key kind are checked on every request."
parameters:
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
description: A key of your choosing that identifies this call, so a retry does not act twice. See
Idempotency in the API overview.
schema:
type: string
minLength: 1
example: 4f1c9a2e-7b3d-4e8f-a1c5-2d6b8e0f9a31
RequestID:
name: id
in: path
required: true
description: The request's id.
schema:
type: string
format: uuid
AgentSlug:
name: slug
in: path
required: true
description: "The agent's slug: the identity chosen when it was created, never changed and never reused."
schema:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
AgentInstanceID:
name: id
in: path
required: true
description: The instance's id.
schema:
type: string
format: uuid
ProvisionerID:
name: id
in: path
required: true
description: The provisioner's id.
schema:
type: string
format: uuid
CredentialID:
name: id
in: path
required: true
description: The credential's id.
schema:
type: string
format: uuid
PipelineKey:
name: pipeline_key
in: path
required: true
description: 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.
schema:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
PipelineRevision:
name: revision
in: path
required: true
description: The revision number. Revisions count up from 1.
schema:
type: integer
format: int64
minimum: 1
ActiveRevisionIfMatch:
name: If-Match
in: header
required: true
description: 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.
schema:
type: string
pattern: ^"[0-9]+"$
example: '"2"'
PathKey:
name: path_key
in: path
required: true
description: The path's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63
characters. You choose it when you create the first revision.
schema:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
APIKeyID:
name: id
in: path
required: true
description: The API key's id, the part after `whk_` or `who_` in the token.
schema:
type: string
format: uuid
MembershipID:
name: id
in: path
required: true
description: The membership's id.
schema:
type: string
format: uuid
WebhookEndpointKey:
name: endpoint_key
in: path
required: true
description: The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63
characters. You choose it when you create the first revision.
schema:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,62}$
TeamID:
name: id
in: path
required: true
description: The team's id.
schema:
type: string
format: uuid
InvitationID:
name: id
in: path
required: true
description: The invitation's id.
schema:
type: string
format: uuid
AuditQuery:
name: q
in: query
description: Free text. Matches the event type, the actor's name and id, the subject id, the event
id, and the event's data. Case does not matter.
schema:
type: string
AuditEventType:
name: event_type
in: query
description: Only events of these types. Repeat the parameter to pass several.
schema:
type: array
items:
type: string
style: form
explode: true
AuditActorType:
name: actor_type
in: query
description: Only events by this kind of actor.
schema:
type: string
enum:
- human
- agent
- system
- api_key
AuditActorID:
name: actor_id
in: query
description: Only events by one actor. A membership id also matches events recorded under its user
id, and the other way round. An agent is given by its slug.
schema:
type: string
AuditSubjectType:
name: subject_type
in: query
description: Only events about this kind of thing, such as a request or a membership.
schema:
type: string
AuditSubjectID:
name: subject_id
in: query
description: Only events about one subject. Requires `subject_type`. An agent subject is given by
its slug; every other subject by its id.
schema:
type: string
AuditOutcome:
name: outcome
in: query
description: Only events whose action ended this way. Resolved from the event's data, so you do not
need to know its shape.
schema:
type: string
enum:
- approved
- denied
- expired
- cancelled
- human
- blocked
AuditFromAt:
name: from_at
in: query
description: Only events at or after this time.
schema:
type: string
format: date-time
AuditToAt:
name: to_at
in: query
description: Only events before this time.
schema:
type: string
format: date-time
AuditEventID:
name: id
in: path
required: true
description: The audit event's id.
schema:
type: string
format: uuid
responses:
Error:
description: Error response. A 403 from a permission check carries ForbiddenDetails in error.details.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
PreconditionFailed:
description: If-Match no longer equals the active revision
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
PreconditionRequired:
description: The required If-Match header was omitted
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
AAPPendingRequest:
summary: Pending review
value:
id: 7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71
tool: issue_refund
arguments:
amount: 4900
reason: duplicate_charge
timeout: 30m
status: pending
created_at: 2026-09-17T12:00:00Z
deadline_at: 2026-09-17T12:30:00Z
AAPApprovedRequest:
summary: Approved, with the window to start the call
value:
id: 7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71
tool: issue_refund
arguments:
amount: 4900
reason: duplicate_charge
timeout: 30m
status: approved
created_at: 2026-09-17T12:00:00Z
deadline_at: 2026-09-17T12:30:00Z
decision:
status: approved
note: Refund the duplicate charge.
decided_at: 2026-09-17T12:02:00Z
expires_at: 2026-09-17T12:07:00Z
AAPCancelledRequest:
summary: Cancelled by the adapter
value:
id: 7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71
tool: issue_refund
arguments:
amount: 4900
reason: duplicate_charge
timeout: 30m
status: cancelled
created_at: 2026-09-17T12:00:00Z
deadline_at: 2026-09-17T12:30:00Z
decision:
status: cancelled
note: The agent stopped waiting.
decided_at: 2026-09-17T12:02:00Z
AAPConsumedRequest:
summary: Approved and consumed
value:
id: 7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71
tool: issue_refund
arguments:
amount: 4900
reason: duplicate_charge
timeout: 30m
status: approved
created_at: 2026-09-17T12:00:00Z
deadline_at: 2026-09-17T12:30:00Z
decision:
status: approved
note: Refund the duplicate charge.
decided_at: 2026-09-17T12:02:00Z
expires_at: 2026-09-17T12:07:00Z
consumption:
consumed_at: 2026-09-17T12:02:04Z
AAPReportedRequest:
summary: Consumed with its outcome
value:
id: 7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71
tool: issue_refund
arguments:
amount: 4900
reason: duplicate_charge
timeout: 30m
status: approved
created_at: 2026-09-17T12:00:00Z
deadline_at: 2026-09-17T12:30:00Z
decision:
status: approved
note: Refund the duplicate charge.
decided_at: 2026-09-17T12:02:00Z
expires_at: 2026-09-17T12:07:00Z
consumption:
consumed_at: 2026-09-17T12:02:04Z
outcome:
status: succeeded
note: Refund issued
reported_at: 2026-09-17T12:02:06Z