Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
resolveTurn a domain, URL or GitHub org into the provider it belongs to.
find_cohortsEvery scored population of providers in the catalog.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/axonflow:axonflow-hitl-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: AxonFlow Agent HITL API
description: REST API for the AxonFlow Agent service - Authentication, Authorization, and Static Policy Enforcement Gateway.
version: 11.1.0
contact:
name: AxonFlow Support
url: https://getaxonflow.com/support
license:
name: Business Source License 1.1
url: https://github.com/getaxonflow/axonflow/blob/main/LICENSE
servers:
- url: https://agent.getaxonflow.com
description: Production (SaaS)
- url: https://axonflow.example.com
description: Self-hosted deployment (agent single entry point, ADR-024)
- url: http://localhost:8080
description: Local Development
tags:
- name: HITL
description: 'Human-in-the-Loop decision queue (EU AI Act Article 14).
Route high-risk AI decisions for human review before execution.'
paths:
/api/v1/hitl/queue:
post:
tags:
- HITL
summary: Create HITL approval request
description: 'Route a high-risk AI decision for human review.
EU AI Act Article 14 requires human oversight for high-risk AI systems.
Requires both `X-Org-ID` and `X-Tenant-ID` headers (stamped by the
auth middleware) — **400** when either is missing.
**Enterprise only** — community builds expose only
`GET /api/v1/hitl/status`.'
operationId: createHITLDecision
parameters:
- $ref: '#/components/parameters/LicenseKey'
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/HITLCreateRequest'
responses:
'201':
description: Approval request created
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
$ref: '#/components/schemas/HITLApprovalRequest'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: 'HITL approvals are disabled for the deployment''s license tier.
Entitled tiers are `Professional`, `Enterprise` and
`Enterprise Plus`; `Community`, `Free`, `Pro`, `Premium` and
`Evaluation` are refused (HITL became Enterprise-only on
2026-08-26; Evaluation was entitled until then). The refusal names
no tier, because it is raised for five of them.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: 'Community SaaS pending-approvals cap reached for the tenant.
Not returned on self-hosted or Enterprise deployments.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
get:
tags:
- HITL
summary: List HITL approval requests
description: 'Retrieve approval requests, filtered and paginated. Tenant scoping
comes from the authenticated request context (RLS), not a query
parameter.'
operationId: listHITLDecisions
parameters:
- $ref: '#/components/parameters/LicenseKey'
- name: status
in: query
description: Comma-separated status filter (e.g. `pending` or `pending,approved`)
schema:
type: string
example: pending
- name: severity
in: query
description: Comma-separated severity filter
schema:
type: string
example: high,critical
- name: policy_id
in: query
schema:
type: string
- name: client_id
in: query
schema:
type: string
- name: user_id
in: query
schema:
type: string
- name: request_type
in: query
description: 'Narrow the listing to one `request_type`.
**Default behaviour without it (#3408):** the listing is the
ACTIONABLE queue and EXCLUDES `wcp_step_gate` entries. Those
mirror a Workflow Control Plane step gate whose approval is
resolved on the workflow plane
(`POST /api/v1/workflows/{workflow_id}/steps/{step_id}/approve`);
approving one here changes a status and releases nothing, so
leaving them in rendered one workflow gate as two rows in the
portal''s merged Approvals queue and double-counted it in the
sidebar badge. They are retained as the EU AI Act Article 14
oversight record and are returned by asking for them explicitly:
`?request_type=wcp_step_gate`.
`meta.total` follows the same predicate as the returned rows, so
a client that renders a badge from it and a page from `data`
cannot disagree.
'
schema:
type: string
example: wcp_step_gate
- name: limit
in: query
schema:
type: integer
default: 50
- name: offset
in: query
schema:
type: integer
default: 0
- name: order_by
in: query
schema:
type: string
- name: order_dir
in: query
schema:
type: string
enum:
- asc
- desc
responses:
'200':
description: List of approval requests
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: array
items:
$ref: '#/components/schemas/HITLApprovalRequest'
meta:
type: object
properties:
total:
type: integer
format: int64
limit:
type: integer
offset:
type: integer
/api/v1/hitl/queue/{id}:
get:
tags:
- HITL
summary: Get HITL approval request
operationId: getHITLDecision
parameters:
- $ref: '#/components/parameters/LicenseKey'
- name: id
in: path
required: true
description: The request UUID (`request_id`)
schema:
type: string
format: uuid
responses:
'200':
description: Approval request details
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
$ref: '#/components/schemas/HITLApprovalRequest'
'400':
description: Invalid request ID (not a UUID)
'404':
description: Request not found
/api/v1/hitl/queue/{id}/approve:
post:
tags:
- HITL
summary: Approve HITL request
description: Approve a pending request to allow AI execution
operationId: approveHITLDecision
parameters:
- $ref: '#/components/parameters/LicenseKey'
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/HITLReviewInput'
responses:
'200':
description: Request approved
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
status:
type: string
example: approved
'400':
description: Invalid request ID or JSON body
'404':
description: Request not found
'409':
description: 'Conflict - the request is not pending (already reviewed or expired).
Also returned when the entry is a Workflow Control Plane step-gate
MIRROR (`request_type: "wcp_step_gate"`). Those are resolved on the
workflow plane -
`POST /api/v1/workflows/{workflow_id}/steps/{step_id}/approve|reject` -
and actioning one here would flip a status and release nothing,
leaving the workflow paused. **This arm fires on a row that IS
pending**, unlike the state conflict above; the response body names
the route that works. Mirrors are excluded from this queue''s
default listing, so a client reaches this only by acting on an id
obtained another way (#3408).
'
/api/v1/hitl/queue/{id}/reject:
post:
tags:
- HITL
summary: Reject HITL request
description: Reject a pending request to block AI execution
operationId: rejectHITLDecision
parameters:
- $ref: '#/components/parameters/LicenseKey'
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/HITLReviewInput'
responses:
'200':
description: Request rejected
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
status:
type: string
example: rejected
'400':
description: Invalid request ID or JSON body
'404':
description: Request not found
'409':
description: 'Conflict - the request is not pending.
Also returned when the entry is a Workflow Control Plane step-gate
MIRROR (`request_type: "wcp_step_gate"`). Those are resolved on the
workflow plane -
`POST /api/v1/workflows/{workflow_id}/steps/{step_id}/approve|reject` -
and actioning one here would flip a status and release nothing,
leaving the workflow paused. **This arm fires on a row that IS
pending**, unlike the state conflict above; the response body names
the route that works. Mirrors are excluded from this queue''s
default listing, so a client reaches this only by acting on an id
obtained another way (#3408).
'
/api/v1/hitl/queue/{id}/override:
post:
tags:
- HITL
summary: Override HITL request
description: 'Record an authorized override of a pending request — the caller takes
responsibility for letting the action proceed outside the normal
approve/reject flow. Requires a justification for the audit trail.'
operationId: overrideHITLDecision
parameters:
- $ref: '#/components/parameters/LicenseKey'
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
justification:
type: string
description: Required by the service — missing justification returns 400
authorized_by_id:
type: string
authorized_by_email:
type: string
authorized_by_role:
type: string
responses:
'200':
description: Request overridden
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
status:
type: string
example: overridden
'400':
description: Invalid request ID/JSON, or missing justification
'404':
description: Request not found
'409':
description: 'Conflict - another reviewer decided this request first (lost race).
Also returned when the entry is a Workflow Control Plane step-gate
MIRROR (`request_type: "wcp_step_gate"`). Those are resolved on the
workflow plane -
`POST /api/v1/workflows/{workflow_id}/steps/{step_id}/approve|reject` -
and actioning one here would flip a status and release nothing,
leaving the workflow paused. **This arm fires on a row that IS
pending**, unlike the state conflict above; the response body names
the route that works. Mirrors are excluded from this queue''s
default listing, so a client reaches this only by acting on an id
obtained another way (#3408).
'
/api/v1/hitl/queue/{id}/history:
get:
tags:
- HITL
summary: Get HITL request audit history
description: Immutable audit-trail entries for a request (create/approve/reject/override/expire actions).
operationId: getHITLDecisionHistory
parameters:
- $ref: '#/components/parameters/LicenseKey'
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: History entries
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: array
items:
$ref: '#/components/schemas/HITLHistoryEntry'
'400':
description: Invalid request ID
/api/v1/hitl/stats:
get:
tags:
- HITL
summary: Pending-queue statistics
description: 'Dashboard summary of the pending queue for the caller''s org.
Requires the `X-Org-ID` header (400 when missing).'
operationId: getHITLStats
parameters:
- $ref: '#/components/parameters/LicenseKey'
responses:
'200':
description: Pending statistics
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
total_pending:
type: integer
format: int64
high_priority:
type: integer
format: int64
critical_priority:
type: integer
format: int64
oldest_pending_hours:
type: number
'400':
description: Missing X-Org-ID header
/api/v1/hitl/status:
get:
tags:
- HITL
summary: HITL feature status
description: 'Reports whether HITL is enabled and which features are available.
This is the **only** HITL endpoint present in community builds
(returns `enabled: false`, `mode: "community"`); Enterprise builds
return `enabled: true`, `mode: "enterprise"` plus a feature map
(queue, approve_reject, override, expiration, audit_history,
pending_summary, notify_url, idempotency_key).'
operationId: getHITLStatus
parameters:
- $ref: '#/components/parameters/LicenseKey'
responses:
'200':
description: Feature status
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
mode:
type: string
enum:
- community
- enterprise
features:
type: object
additionalProperties:
type: boolean
/api/v1/hitl/expire:
post:
tags:
- HITL
summary: Expire stale pending requests
description: 'Sweeps pending requests past their `expires_at` into the `expired`
state and returns the count. Intended for schedulers/ops automation;
the platform also expires lazily.'
operationId: expireHITLDecisions
parameters:
- $ref: '#/components/parameters/LicenseKey'
responses:
'200':
description: Sweep result
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
expired_count:
type: integer
components:
responses:
Unauthorized:
description: 'Missing or invalid authentication. Handler-written 401s use the
`{success, error}` envelope; 401s written by the auth middleware
use the `{"error": {"code", "message"}}` envelope (JSONError).
'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
success: false
error: 'Authentication required: provide Authorization header with Basic auth (clientId:clientSecret)'
BadRequest:
description: Invalid request body or parameters
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
success: false
error: Invalid request body
schemas:
ErrorResponse:
type: object
description: 'Handler-written error envelope. Note the agent has a second error
envelope for middleware-written errors (see JSONError) — clients
should tolerate both shapes on 4xx/5xx.
'
properties:
success:
type: boolean
example: false
error:
type: string
description: Error message
HITLHistoryEntry:
type: object
description: 'Immutable audit-trail entry for a request. Source of truth:
`platform/agent/hitl/repository.go` (ApprovalHistory).
'
properties:
id:
type: integer
format: int64
request_id:
type: string
format: uuid
org_id:
type: string
tenant_id:
type: string
action:
type: string
actor_id:
type: string
actor_email:
type: string
actor_role:
type: string
actor_ip:
type: string
comment:
type: string
justification:
type: string
previous_status:
type: string
new_status:
type: string
created_at:
type: string
format: date-time
HITLCreateRequest:
type: object
description: 'Body for POST /api/v1/hitl/queue. Org/tenant identity comes from the
`X-Org-ID` / `X-Tenant-ID` headers, not the body. Source of truth:
`platform/agent/hitl/handler.go` (CreateRequestInput).
'
required:
- client_id
- original_query
- request_type
- triggered_policy_id
- triggered_policy_name
- trigger_reason
properties:
client_id:
type: string
user_id:
type: string
original_query:
type: string
description: The query/action awaiting human review
request_type:
type: string
description: Request classification (e.g. `mcp_query`, `llm_chat`)
request_context:
type: object
additionalProperties: true
triggered_policy_id:
type: string
triggered_policy_name:
type: string
trigger_reason:
type: string
severity:
type: string
enum:
- low
- medium
- high
- critical
eu_ai_act_article:
type: string
compliance_framework:
type: string
risk_classification:
type: string
expires_in_seconds:
type: integer
description: TTL before the request auto-expires
notify_url:
type: string
format: uri
description: 'Optional outbound webhook URL fired asynchronously after the request
transitions to a terminal state (approved / rejected / overridden /
expired). Must use `https://` or `http://`. The platform signs the
envelope with HMAC-SHA256 over the body keyed by the deployment''s
`AXONFLOW_HITL_WEBHOOK_SIGNING_KEY`, sent as
`X-AxonFlow-Signature: sha256=<hex>`. See the HITLWebhookEnvelope
schema and `docs.getaxonflow.com/docs/governance/hitl` for the full
shape + verification recipe.
'
HITLReviewInput:
type: object
description: 'Body for approve/reject. Source of truth:
`platform/agent/hitl/handler.go` (ReviewInput).
'
properties:
reviewer_id:
type: string
reviewer_email:
type: string
reviewer_role:
type: string
comment:
type: string
HITLApprovalRequest:
type: object
description: 'An approval-queue row. Source of truth:
`platform/agent/hitl/repository.go` (ApprovalRequest).
'
properties:
id:
type: integer
format: int64
request_id:
type: string
format: uuid
org_id:
type: string
tenant_id:
type: string
client_id:
type: string
user_id:
type: string
original_query:
type: string
request_type:
type: string
request_context:
type: object
additionalProperties: true
triggered_policy_id:
type: string
triggered_policy_name:
type: string
trigger_reason:
type: string
severity:
type: string
eu_ai_act_article:
type: string
compliance_framework:
type: string
risk_classification:
type: string
status:
type: string
enum:
- pending
- approved
- rejected
- overridden
- expired
reviewer_id:
type: string
reviewer_email:
type: string
reviewer_role:
type: string
review_comment:
type: string
reviewed_at:
type: string
format: date-time
override_justification:
type: string
override_authorized_by:
type: string
notify_url:
type: string
expires_at:
type: string
format: date-time
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
parameters:
IdempotencyKey:
name: Idempotency-Key
in: header
required: false
description: 'Optional per-request dedup token. When supplied on POST /api/v1/mcp/check-input,
POST /api/v1/audit/tool-call, or POST /api/v1/hitl/queue, the platform caches
the original response for 24h and returns it byte-for-byte on subsequent
requests carrying the same key + same authenticated tenant + same endpoint.
Format: 1-256 chars, `^[A-Za-z0-9_.:\-/]+$`. Workflow IDs from n8n, ADK,
or generic SDKs all fall inside this set. A malformed key returns 400
before the handler runs.
Cache rules: 2xx + 4xx responses are cached; 5xx is NOT cached so the
caller''s retry can hit a fresh attempt. A cache hit returns the original
response plus an `Idempotent-Replayed: true` response header.
Cross-tenant collisions are impossible: tenant_id participates in the
primary key + an RLS policy on the storage table. Two tenants using
the same key value get distinct rows.
'
schema:
type: string
minLength: 1
maxLength: 256
pattern: ^[A-Za-z0-9_.:\-/]+$
example: n8n-exec-abc123-node-Approve
LicenseKey:
name: Authorization
in: header
required: true
description: 'OAuth2-style Basic authentication header.
Format: `Basic base64(clientId:clientSecret)`
- `clientId`: Your organization identifier (required)
- `clientSecret`: Authentication credential (optional for community mode)
Not required when `DEPLOYMENT_MODE=community`.
'
schema:
type: string
example: Basic bXktb3JnOkFYT04tVjIteHh4
securitySchemes:
BasicAuth:
type: http
scheme: basic
description: "OAuth2-style Basic authentication using `clientId:clientSecret` credentials.\n\n**Header format:** `Authorization: Basic base64(clientId:clientSecret)`\n\n- `clientId` (required): Your organization/client identifier\n- `clientSecret` (optional): Authentication credential. Optional for community/self-hosted mode.\n\n**Example:**\n```bash\n# With clientSecret (enterprise)\ncurl -H \"Authorization: Basic $(echo -n 'my-org:AXON-V2-xxx' | base64)\" ...\n\n# Without clientSecret (community mode)\ncurl -H \"Authorization: Basic $(echo -n 'my-org:' | base64)\" ...\n```\n\n## Per-user identity behind a shared credential\n\nThis credential authenticates an ORGANIZATION or client, not a person.\nBehind one such credential can sit many human principals, each\noptionally forwarding a **per-user token** that proves who they are.\nWhere that token is read depends on the envelope: the `user_token`\nfield of the request body on `POST /api/v1/decide` and the four MCP\nREST routes, and the `X-User-Token` header on the MCP-server JSON-RPC\nplane. The two spellings are deliberately not interchangeable.\n\n**A presented per-user token that fails to validate is a refused\naccess attempt, not a legacy caller** (`401`, audited\n`user_token_rejected`). It is never downgraded to a shared service\nidentity, so revocation, expiry, algorithm pinning and signature\nchecks take effect on every plane that reads one.\n\n**Whether presenting a token is REQUIRED is a per-organization\nposture, `require_user_token`, and it is off by default (#3476).**\nWith it off, an enterprise caller that presents no token at all is\nserved under a synthetic org-scoped service identity\n(`<client-id>@axonflow.local`, role `service`), which is the correct\nanswer for an infrastructure gateway acting as a Policy Enforcement\nPoint with no end-user token to forward. With it on, that caller is\nrefused at AUTHENTICATION, before any policy is evaluated (`401`,\naudited `user_token_required`).\n\nThe posture exists because a policy that names a PERSON - a\nprincipal-scoped constraint or permission in the organization's typed\ndocument (PRD v11 §1.6) - is only meaningful if a caller cannot CHOOSE\nto arrive without an identity: with the posture off such a policy\nstill applies to everyone who presents a token, but a caller can\ndecline to present one and be decided as the credential\n(`subject_type=Client`). Governance segments (ADR-060) decide on no\nagent route since v11.0.0 (#4253). Two levers set it, and an explicit\nper-organization row wins over the deployment-wide default in EITHER\ndirection:\n\n- `organizations.require_user_token`, per organization, default\n `false`.\n- `AXONFLOW_REQUIRE_USER_TOKEN`, deployment-wide, default `false`.\n\nA posture change takes up to one cache window to become live\n(`AXONFLOW_REQUIRE_USER_TOKEN_TTL_SECONDS`, default 60 seconds,\nclamped to `[5, 600]`). A posture that cannot be READ resolves to\nREQUIRED rather than not-required, so a database outage cannot\nquietly switch the control off; a genuinely absent organization row\nis not a read failure and falls through to the deployment default.\n\n`POST /v1/chat/completions` is outside this guarantee: it mirrors\nOpenAI's wire shape and carries no per-user token field at all, so it\nkeeps the synthetic-identity fallback regardless of the posture.\nCommunity and community-SaaS deployments never reach any of the above.\n"
InternalServiceID:
type: apiKey
in: header
name: X-Internal-Service-ID
description: 'Internal-service (operator lane) credential — **part one of two**.
Must be sent together with `X-Internal-Service-Token`; either header
alone is not a credential.
This is the HMAC identity the Orchestrator and the Enterprise
customer-portal use to call agent endpoints without holding a
customer license. `apiAuthMiddleware` lifts both headers (plus an
optional `X-Tenant-ID` scope) into `AuthHints`
(`internalServiceHints` in `platform/agent/auth.go`) and
`Authenticate()` validates them before any mode-specific auth
(`platform/agent/authenticator.go:120-155`).
Value: the service id, `orchestrator-internal`.
⚠️ An invalid or expired token is **not** an error by itself — it
falls through to the deployment''s normal auth
(`platform/agent/authenticator.go:153-154`). Send the internal-service
headers on their own: paired with an `Authorization: Basic` header, a
stale token silently yields a *tenant*-scoped answer that looks like a
successful operator call.
'
InternalServiceToken:
type: apiKey
in: header
name: X-Internal-Service-Token
description: 'Internal-service (operator lane) credential — **part two of two**.
Must be sent together with `X-Internal-Service-ID`.
Format: `AXON-INTERNAL-{unix_ts}-{sig}`, where `sig` is the first 16
hex characters of HMAC-SHA256 over `orchestrator-internal:{unix_ts}`
keyed with `AXONFLOW_INTERNAL_SERVICE_SECRET`. Validated by
`platform/shared/serviceauth` within a 5-minute clock-skew window, so
it must be re-minted per session. See
`technical-docs/runbooks/RUNBOOK_CONNECTOR_CONFIGURATION.md` for the
exact minting snippet.
'