Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: ASAP Platform Automation API
description: 'Public HTTP API for ASAP Platform (api.asap.cool).
All `/v1` responses are wrapped in a standard envelope. Success responses are `{ "data": , "meta": { "request_id", "next_cursor"?, "has_more"? } }` — the schema documented for each operation describes the inner `data` value. Errors are `{ "error": { "code", "message", "field"?, "request_id" } }`. Every response also carries an `X-Request-Id` header matching `meta.request_id` / `error.request_id`.'
license:
name: BUSL-1.1
version: 0.0.0
servers:
- url: https://api.asap.cool
description: Production
tags:
- name: Automation
description: Automation proposals raised by the system, their journal, the autonomy settings per action kind and the org's safety state. Behind the `automation_proposals` flag; the settings, the global pause and the daily digest preference need an interactive session.
paths:
/v1/automation/digest:
get:
tags:
- Automation
summary: GET /v1/automation/digest
operationId: getAutomationDigest
responses:
'200':
description: The caller's daily digest preference.
content:
application/json:
schema:
$ref: '#/components/schemas/DigestView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: The caller is not an interactive session (`session_required`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: 'Automation is not available for this organisation: `app_not_available`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
put:
tags:
- Automation
summary: PUT /v1/automation/digest
operationId: putAutomationDigest
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PutDigestRequest'
required: true
responses:
'200':
description: The updated preference.
content:
application/json:
schema:
$ref: '#/components/schemas/DigestView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: The caller is not an interactive session (`session_required`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: 'Automation is not available for this organisation: `app_not_available`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'422':
description: The hour is outside 0..=23 (`hour_out_of_bounds`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/previews:
get:
tags:
- Automation
summary: GET /v1/automation/previews
operationId: getAutomationPreviews
responses:
'200':
description: 'Counters of what Starter would have had prepared; `locked: false` and no counter on any other plan.'
content:
application/json:
schema:
$ref: '#/components/schemas/PreviewsView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:read scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: 'Automation is not available for this organisation: `app_not_available`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/proposals:
get:
tags:
- Automation
summary: GET /v1/automation/proposals
operationId: listAutomationProposals
parameters:
- name: scope
in: query
description: '`open` (default) for what waits for a decision or a run, `journal` for
the closed ones, `all` for both.'
required: false
schema:
oneOf:
- type: 'null'
- $ref: '#/components/schemas/ScopeParam'
- name: action_kind
in: query
description: Restrict to one action kind (wire name).
required: false
schema:
type:
- string
- 'null'
- name: status
in: query
description: 'Restrict to some statuses: a comma-separated list of `pending`,
`scheduled`, `consumed`, `rejected`, `cancelled`, `expired`, `failed` and
`reverted` (`status=failed,expired`). Combines with `scope`; any other value is a 400.'
required: false
schema:
type:
- string
- 'null'
- name: limit
in: query
description: Page size, 1..=100 (defaults to 50).
required: false
schema:
type:
- integer
- 'null'
format: int32
minimum: 0
responses:
'200':
description: Proposals of the org, newest first.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ProposalView'
'400':
description: Unknown value in `status` (`invalid_status`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:read scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: 'Automation is not available for this organisation: `app_not_available`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'422':
description: Invalid limit or action kind.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/proposals/{id}:
get:
tags:
- Automation
summary: GET /v1/automation/proposals/{id}
operationId: getAutomationProposal
parameters:
- name: id
in: path
description: Proposal identifier.
required: true
schema:
type: string
format: uuid
responses:
'200':
description: The proposal.
content:
application/json:
schema:
$ref: '#/components/schemas/ProposalView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:read scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: Not found or cross-org, or automation is not available for this organisation.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/proposals/{id}/approve:
post:
tags:
- Automation
summary: POST /v1/automation/proposals/{id}/approve
operationId: approveAutomationProposal
parameters:
- name: id
in: path
description: Proposal identifier.
required: true
schema:
type: string
format: uuid
responses:
'200':
description: The approved proposal (status `consumed`).
content:
application/json:
schema:
$ref: '#/components/schemas/ProposalView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'402':
description: Automated dunning requires a paid plan (reminder kind).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:write scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: Not found or cross-org, or automation is not available for this organisation.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'409':
description: Already decided or lapsed (`proposal_not_pending`), a suggestion that cannot be approved (`proposal_not_approvable`), or the document moved on since (`proposal_obsolete`, the proposal is then `failed`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'503':
description: The reminder could not be queued (`reminder_enqueue_failed`); the proposal is `failed`.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/proposals/{id}/cancel:
post:
tags:
- Automation
summary: POST /v1/automation/proposals/{id}/cancel
operationId: cancelAutomationProposal
parameters:
- name: id
in: path
description: Proposal identifier.
required: true
schema:
type: string
format: uuid
responses:
'200':
description: 'The cancelled proposal (status `cancelled`): it will never run.'
content:
application/json:
schema:
$ref: '#/components/schemas/ProposalView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:write scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: Not found or cross-org, or automation is not available for this organisation.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'409':
description: 'The proposal is not scheduled (`proposal_not_scheduled`): it already ran, was cancelled, or never was a scheduled one.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/proposals/{id}/reject:
post:
tags:
- Automation
summary: POST /v1/automation/proposals/{id}/reject
operationId: rejectAutomationProposal
parameters:
- name: id
in: path
description: Proposal identifier.
required: true
schema:
type: string
format: uuid
responses:
'200':
description: The rejected proposal (status `rejected`).
content:
application/json:
schema:
$ref: '#/components/schemas/ProposalView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:write scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: Not found or cross-org, or automation is not available for this organisation.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'409':
description: 'Already decided or lapsed: `proposal_not_pending`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/proposals/{id}/undo:
post:
tags:
- Automation
summary: POST /v1/automation/proposals/{id}/undo
operationId: undoAutomationProposal
parameters:
- name: id
in: path
description: Proposal identifier.
required: true
schema:
type: string
format: uuid
responses:
'200':
description: 'The undone proposal (status `reverted`): the task it created is deleted.'
content:
application/json:
schema:
$ref: '#/components/schemas/ProposalView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:write scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: Not found or cross-org, or automation is not available for this organisation.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'409':
description: 'The proposal cannot be undone (`proposal_not_undoable`): it was not executed or was already undone, its kind has no exact undo, or the task it created was edited, completed or deleted since.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/settings:
get:
tags:
- Automation
summary: GET /v1/automation/settings
operationId: listAutomationSettings
responses:
'200':
description: One row per action kind, in registry order.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/SettingView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:read scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: 'Automation is not available for this organisation: `app_not_available`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/settings/{action_kind}:
put:
tags:
- Automation
summary: '`PUT /v1/automation/settings/{action_kind}`'
operationId: putAutomationSetting
parameters:
- name: action_kind
in: path
description: Wire name of the action kind.
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PutSettingRequest'
required: true
responses:
'200':
description: The setting as stored, with its effective tier.
content:
application/json:
schema:
$ref: '#/components/schemas/SettingView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:write scope, or the caller is not an interactive session (`session_required`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: Unknown action kind, or automation is not available for this organisation.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'422':
description: Unknown tier, or a tier above the ceiling of the kind (`tier_above_ceiling`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
/v1/automation/state:
get:
tags:
- Automation
summary: GET /v1/automation/state
operationId: getAutomationState
responses:
'200':
description: Safety state and global pause of the org.
content:
application/json:
schema:
$ref: '#/components/schemas/OrgStateView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:read scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: 'Automation is not available for this organisation: `app_not_available`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
put:
tags:
- Automation
summary: PUT /v1/automation/state
operationId: putAutomationState
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PutOrgStateRequest'
required: true
responses:
'200':
description: The updated state.
content:
application/json:
schema:
$ref: '#/components/schemas/OrgStateView'
'401':
description: No active org.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'403':
description: Missing automation:write scope, or the caller is not an interactive session (`session_required`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'404':
description: 'Automation is not available for this organisation: `app_not_available`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'422':
description: The cancel window is outside 15..=1440 minutes (`cancel_window_out_of_bounds`), or `state` is not `normal` (`invalid_state`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
components:
schemas:
PutDigestRequest:
type: object
description: Body of `PUT /v1/automation/digest`. A field left out keeps its value.
properties:
enabled:
type:
- boolean
- 'null'
description: Turn the digest on or off.
hour_local:
type:
- integer
- 'null'
format: int32
description: Hour of the day, 0 to 23, in the caller's time zone.
PutOrgStateRequest:
type: object
description: Body of `PUT /v1/automation/state`.
properties:
cancel_window_minutes:
type:
- integer
- 'null'
format: int32
description: 'Cancel window of the scheduled proposals, in minutes, 15 to 1440. It
applies to the proposals raised from then on. Left as it is when absent.'
paused:
type:
- boolean
- 'null'
description: Whether all automation of the org is paused. Left as it is when absent.
state:
type:
- string
- 'null'
description: '`normal` re-enables automation after the breaker demoted the org (it
clears the reason and makes the breaker count from now on). It is the
only value accepted: an org is demoted by the breaker alone, never by a
request (422 `invalid_state`). Left as it is when absent.'
SettingView:
type: object
description: The autonomy of one action kind for the org.
required:
- action_kind
- owner_module
- risk_class
- tier
- plan_ceiling
- effective_tier
- max_tier
- paused
properties:
action_kind:
type: string
description: Wire name of the action kind.
effective_tier:
type: string
description: 'Tier that actually applies: the lowest of the selection, the risk
class, the plan and the safety state.'
lock_reason:
type:
- string
- 'null'
description: 'What caps the highest selectable tier: `plan` only when a better plan
would raise it (the plan is strictly the lowest cap), else `risk_class`,
`kind` or `safety_state`; null when nothing does.'
max_tier:
type: string
description: 'Highest tier the user can select for this kind: the lowest of what the
kind declares, its risk class, the plan and the safety state.'
owner_module:
type: string
description: Slug of the app the kind belongs to.
pause_reason:
type:
- string
- 'null'
description: 'Who paused the kind: `low_acceptance` when the detector did, because most
of its proposals were turned down; null for a pause the user set (or when
the kind is not paused). Resuming clears it.'
paused:
type: boolean
description: Whether the kind is paused.
plan_ceiling:
type: string
description: Highest tier the org's plan allows.
risk_class:
type: string
description: '`internal_reversible`, `external` or `non_delegable`.'
tier:
type: string
description: 'Tier the user selected (`t2` by default): `t0`, `t1`, `t2`, `t3a` or `t3b`.'
DigestView:
type: object
description: The caller's daily digest preference.
required:
- enabled
- hour_local
- timezone
properties:
enabled:
type: boolean
description: Whether the digest is on. Off by default.
hour_local:
type: integer
format: int32
description: Hour of the day, 0 to 23, the digest goes out at (8 by default).
minimum: 0
timezone:
type: string
description: 'IANA time zone the hour is read in: the one on the caller''s calendar
profile, Europe/Paris when they have none.'
ScopeParam:
type: string
description: Which part of the list `GET /v1/automation/proposals` returns.
enum:
- open
- journal
- all
PutSettingRequest:
type: object
description: Body of `PUT /v1/automation/settings/{action_kind}`.
required:
- tier
- paused
properties:
paused:
type: boolean
description: Whether to pause the kind.
tier:
type: string
description: 'Tier to select: `t0`, `t1`, `t2`, `t3a` or `t3b`. It cannot exceed the
highest selectable tier of the kind (see `GET /v1/automation/settings`).'
OrgStateView:
type: object
description: The org's automation state.
required:
- state
- paused
- cancel_window_minutes
properties:
cancel_window_minutes:
type: integer
format: int32
description: 'Minutes a `t3a` / `t3b` proposal waits, cancellable, before it runs:
the org''s choice (15 to 1440), 60 when it never chose.'
changed_at:
type:
- string
- 'null'
format: date-time
description: Last change; null when the org never changed anything.
paused:
type: boolean
description: Global pause for all automation of the org.
reason:
type:
- string
- 'null'
description: 'Why the state changed: a fixed token, never content.'
state:
type: string
description: '`normal`, or `demoted` once the breaker tripped (every kind is then
capped at T2 until the user re-enables automation).'
ErrorBody:
type: object
description: 'Generic error body returned by API handlers when they fail. `code` is a
stable machine-readable token; `message` is human-friendly and meant for
surfacing to end users.'
required:
- code
- message
properties:
code:
type: string
description: Stable machine token (e.g. `invalid_credentials`).
field:
type:
- string
- 'null'
description: Offending request field for validation errors, when known.
message:
type: string
description: Human-readable message.
ProposalView:
type: object
description: A proposal raised by the system.
required:
- id
- action_kind
- owner_module
- risk_class
- reversal
- tier
- status
- reason
- trigger_ref
- preview
- created_at
- expires_at
- edited
- credits_spent
- undoable
properties:
action_kind:
type: string
description: 'Wire name of the action kind (`invoice_overdue_reminder`,
`invoice_overdue_task`, `quote_expiring_task`).'
created_at:
type: string
format: date-time
description: When the proposal was raised.
credits_spent:
type: integer
format: int32
description: Credits the evaluation debited (`0` for a deterministic kind).
decided_at:
type:
- string
- 'null'
format: date-time
description: When it was decided; null while open.
decided_by:
type:
- string
- 'null'
format: uuid
description: Who decided; null while open, or for a token with no user behind it.
edited:
type: boolean
description: Whether the content was changed before approval.
error_class:
type:
- string
- 'null'
description: 'Why the proposal `failed`: a closed token (`document_not_overdue`,
`ladder_exhausted`, `enqueue_failed`...), never content. Null otherwise.'
execute_after:
type:
- string
- 'null'
format: date-time
description: 'End of the cancel window of a `t3a` / `t3b` proposal: when it runs unless
cancelled. Null for a proposal that is not scheduled.'
expires_at:
type: string
format: date-time
description: When it lapses without a decision.
id:
type: string
format: uuid
description: Proposal id.
owner_module:
type: string
description: Slug of the app the card is badged with.
preview:
description: Server-trusted recap of the action.
reason:
type: string
description: One sentence saying why the system proposes this.
reversal:
type: string
description: 'How an executed action can be undone: `undo` (exactly, with no time limit,
through `POST …/undo` while the task it made is still open and untouched),
`compensate` or `none`.'
risk_class:
type: string
description: '`internal_reversible`, `external` or `non_delegable`.'
status:
type: string
description: '`pending`, `scheduled`, `consumed`, `rejected`, `cancelled`, `expired`,
`failed` or `reverted` (executed, then undone).'
tier:
type: string
description: 'Tier applied when the proposal was raised: `t0`, `t1`, `t2`, `t3a` or `t3b`.'
trigger_ref:
description: What caused the proposal and the id of the object concerned.
undoable:
type: boolean
description: 'Whether `POST …/undo` would succeed now: the proposal was executed
(`consumed`), its kind has an exact undo and the task it created is still
open and untouched. Read when the proposal is listed or fetched; `false`
for every other proposal.'
PreviewCountView:
type: object
description: One counter of the locked preview.
required:
- action_kind
- count
properties:
action_kind:
type: string
description: 'The action kind the counter belongs to (`invoice_overdue_reminder` or
`quote_expiring_task`).'
count:
type: integer
format: int32
description: 'How many proposals of this kind a plan that prepares them would have
raised. Never above `count_cap`.'
minimum: 0
PreviewsView:
type: object
description: The locked preview of the org.
required:
- locked
- previews
- count_cap
properties:
count_cap:
type: integer
format: int32
description: 'A counter equal to this value means "at least this many": it is the room
of an org with no open proposal, the most a first scan would raise.'
minimum: 0
locked:
type: boolean
description: '`true` on Starter, where the counters below are given and nothing is
prepared; `false` on a plan that prepares proposals (no counter then).'
previews:
type: array
items:
$ref: '#/components/schemas/PreviewCountView'
description: One counter per previewed kind, in display order. Empty unless `locked`.
securitySchemes:
api_key:
type: http
scheme: bearer
session:
type: apiKey
in: cookie
name: asap_session