Approvals
The queue of actions waiting for a second person, and the decision.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
GET /v1/approvals |
List Approvals | read |
|
POST /v1/approvals/{approval_id}/approve |
Approve a request | approve |
|
POST /v1/approvals/{approval_id}/reject |
Reject a request | approve |
|
POST /v1/approvals/{approval_id}/withdraw |
Withdraw one's own request | admin |
|
GET /v1/approvers |
The approver register | read |
|
POST /v1/me/approver-acknowledgement |
Confirm that my approvals are recorded in my name | read |
GET /v1/approvals
List Approvals
The approval requests of the organization with the given status, oldest first. A
request is created when the approval policy sends an action to a second person, e.g. a
journal entry or a new vendor; object_type and object_id say what it is about. With
for_me, also one's own steps the AI reviewer held that one releases oneself, being the
only person who could (ADR-0101).
| Required scope | read (admin implies every scope) |
| Operation id | list_approvals_v1_approvals_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | status |
"approved" | "pending" | "rejected" or null |
pending (default), approved or rejected. (default "pending") |
|
| query | entity |
string or null | Only the requests of this entity (code), the ones the Role Center counts for it. Default: the whole organization. | |
| query | for_me |
boolean | Only the requests the caller may decide under the four-eyes principle: not requested, created, prepared or submitted by the caller (whichever key or session); none for a key the system issued. The Role Center's pending_for_me. (default false) |
Example request
curl "$ERP_URL/v1/approvals" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ApprovalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"object_type": "string",
"object_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"action": "string",
"status": "string",
"requested_by": {},
"reason": "string",
"summary": {},
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"comment": "string",
"created_at": "2026-09-21T09:30:00Z",
"withdrawn": false,
"reason_words": {
"words": "string",
"code": "string",
"details": null,
"text": "string"
}
}
]
POST /v1/approvals/{approval_id}/approve
Approve a request
Approve a pending request; the action it asked for then happens (a journal entry is posted, a vendor activated). The body, with an optional comment, may be left out.
Refused with 403 when the caller is the person or key that made the request (four_eyes:
a second person must approve, whatever the caller's scopes), lacks the approve scope or is
denied by the policy (agents are); with 409
when the request is no longer pending, or when the action fails, e.g. the period is closed
or a credit note now exceeds what is left to credit on the document it credits (another
credit note took it while this one waited; 422 when its lines without an obligation would
credit more than the invoice's other lines left).
| Required scope | approve (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | approve_v1_approvals__approval_id__approve_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | approval_id |
string (uuid) | yes | Approval request id (GET /v1/approvals). |
Request body
application/json (optional): Decision or null
Example request
curl -X POST "$ERP_URL/v1/approvals/3fa85f64-5717-4562-b3fc-2c963f66afa6/approve" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"comment": "Checked against the vendor statement"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ApprovalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"object_type": "string",
"object_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"action": "string",
"status": "string",
"requested_by": {},
"reason": "string",
"summary": {},
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"comment": "string",
"created_at": "2026-09-21T09:30:00Z",
"withdrawn": false,
"reason_words": {
"words": "string",
"code": "string",
"details": {},
"text": "string"
}
}
POST /v1/approvals/{approval_id}/reject
Reject a request
Reject a pending request; the action does not happen (a journal entry becomes
rejected and is never posted). The body, with an optional comment saying why, may be
left out. The same refusals as approving apply: four-eyes (403), scope or policy (403),
already decided (409). The person who asked to raise or remove a posting approval limit or
to relax the approval policy takes her request back with POST .../withdraw instead.
| Required scope | approve (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | reject_v1_approvals__approval_id__reject_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | approval_id |
string (uuid) | yes | Approval request id (GET /v1/approvals). |
Request body
application/json (optional): Decision or null
Example request
curl -X POST "$ERP_URL/v1/approvals/3fa85f64-5717-4562-b3fc-2c963f66afa6/reject" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"comment": "Amount does not match the bill"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ApprovalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"object_type": "string",
"object_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"action": "string",
"status": "string",
"requested_by": {},
"reason": "string",
"summary": {},
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"comment": "string",
"created_at": "2026-09-21T09:30:00Z",
"withdrawn": false,
"reason_words": {
"words": "string",
"code": "string",
"details": {},
"text": "string"
}
}
POST /v1/approvals/{approval_id}/withdraw
Withdraw one's own request
Take back one's own pending request to raise or remove a company's posting approval limit
(object_type posting_limit) or to relax the approval policy (approval_policy). Four
eyes is about approving: withdrawing relaxes nothing, and the limit or policy in force stays.
Without it, a request nobody else may decide (an administrator whose colleagues are not
administrators) would wait forever and block every later one (409
posting_limit_change_waiting, approval_policy_change_waiting).
The request becomes rejected, decided by the person who asked, with the comment
withdrawn (followed by the body's optional comment) and withdrawn true, and is audited
as approval.withdrawn. When her next change of the same kind applies at once (she is alone
in the organization; for the posting limit also when she lowers it or the policy allows the
change), it withdraws her older request the same way, without this call.
Refused with 403 approval_withdraw_own_only for anyone but the person who asked (whichever
key or session she uses, and whatever its scopes; a second person rejects instead) and for an
agent; 403 approval_withdraw_admin_only for her without the admin scope; 409
approval_not_withdrawable for another kind of request and approval_decided when it is no
longer pending.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | withdraw_v1_approvals__approval_id__withdraw_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | approval_id |
string (uuid) | yes | Approval request id (GET /v1/approvals). |
Request body
application/json (optional): Decision or null
Example request
curl -X POST "$ERP_URL/v1/approvals/3fa85f64-5717-4562-b3fc-2c963f66afa6/withdraw" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"comment": "Asked too early"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ApprovalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"object_type": "string",
"object_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"action": "string",
"status": "string",
"requested_by": {},
"reason": "string",
"summary": {},
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"comment": "string",
"created_at": "2026-09-21T09:30:00Z",
"withdrawn": false,
"reason_words": {
"words": "string",
"code": "string",
"details": {},
"text": "string"
}
}
GET /v1/approvers
The approver register
Everyone who may approve (or could, before being disabled), ordered by email, with what
the administrator declared when giving them the right (who the person is, their relationship
to the company and firm, confirmed to be a different person), who declared it and when, when
the person confirmed that their approvals are recorded in their name, and every change of
their access. For an administrator and an auditor (the admin or audit scope; 403
otherwise). See ADR-0098.
| Required scope | read (admin implies every scope) |
| Operation id | list_approvers_v1_approvers_get |
Example request
curl "$ERP_URL/v1/approvers" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ApproverOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"email": "string",
"name": "string",
"status": "string",
"scopes": [
"string"
],
"declaration": {},
"declared_by": {},
"declared_at": "2026-09-21T09:30:00Z",
"acknowledged_at": "2026-09-21T09:30:00Z",
"changes": [
{}
]
}
]
POST /v1/me/approver-acknowledgement
Confirm that my approvals are recorded in my name
A person an administrator named an approver confirms, once, that their approvals and
rejections are recorded in their name for the company's auditor; they decide nothing before
(409 approver_acknowledgement_needed). GET /v1/me says when it is due
(approver_acknowledgement_needed). Recorded in the audit trail (approver.acknowledge);
nothing changes when it is not due. 409 approver_not_a_person for an API key or agent.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | acknowledge_approver_v1_me_approver_acknowledgement_post |
Example request
curl -X POST "$ERP_URL/v1/me/approver-acknowledgement" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | No content | |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Schemas
ApproverOut
One person who may approve, in the approver register.
| Field | Type | Required | Description |
|---|---|---|---|
user_id |
string (uuid) | yes | Id (uuid) of the user. |
email |
string | yes | The person's sign-in address. |
name |
string or null | yes | The person's display name. |
status |
string | yes | invited, active or disabled. |
scopes |
array of string | yes | The rights the person holds now. |
declaration |
object or null | yes | What was declared when the person got the right to approve (person_name, relationship, organisation, note); null for a person who got it before declarations or through the platform's provisioning. |
declared_by |
object or null | yes | The administrator who declared it (type, id, name). |
declared_at |
string (date-time) or null | yes | When it was declared. |
acknowledged_at |
string (date-time) or null | yes | When the person confirmed that their approvals are recorded in their name; null until they do (they decide nothing before). |
changes |
array of object | yes | The changes of the person's access, oldest first: action (user.invite, user.update, approver.declare, approver.acknowledge), at, by and data. |
Decision
| Field | Type | Required | Description |
|---|---|---|---|
comment |
string or null | Why you approve or reject; stored with the decision and the audit event. Null when omitted. |