# approvals

Generic approval requests: a change that needs a second person waits here until it is approved, declined, or expires.

**Amfelt REST API** · v1.13.0 · `https://api.amfelt.com`

---
### GET `/v1/approval-requests` {#listapprovalrequests}

**List approval requests.**

Returns a page of the organization's approval requests, newest first, optionally filtered by state and by kind. Approval requests are generic: `kind` names what is being approved and `payload` carries its details, so agent version policy changes and other promotions share this resource.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `state` | query | No | ApprovalRequestState | Only requests in this state. |
| `kind` | query | No | string | Only requests of this kind, for example `agent_version_policy_change`. |
| `cursor` | query | No | string | Opaque cursor for the next page; pass back the 'next_cursor' from a prior response. |
| `limit` | query | No | integer | Maximum number of items to return per page. |

#### Responses

**200** — A page of approval requests.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [ApprovalRequest](#approval-request)[] | Yes | The page of approval requests. |
| `next_cursor` | [string null] | No | Opaque cursor for the next page, or null when this is the final page. Examples: `eyJpZCI6OTk5fQ`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/approval-requests/{id}/decline` {#declineapprovalrequest}

**Decline an approval request.**

Declines a pending request. Nothing it asks for is applied.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `id` | path | Yes | string | Identifier of the approval request. |

#### Responses

**200** — The declined request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `decided_at` | [string null] | No | When the request was approved or declined; null otherwise. |
| `decided_by` | [string null] | No | Identifier of the account that approved or declined the request; null while it is pending or once it expired. |
| `expires_at` | string | Yes | When a still-pending request becomes `expired`. |
| `id` | string | Yes | Approval request identifier. |
| `kind` | string | Yes | What is being approved. An open vocabulary, so a new kind needs no contract change. Examples: `agent_version_policy_change`. |
| `payload` | object | Yes | The kind-specific details of the request. |
| `requested_at` | string | Yes | When the request was made. |
| `requested_by` | string | Yes | Identifier of the account that made the request. |
| `self_approved` | boolean | Yes | True when the requester approved their own request, which is allowed only in an organization with exactly one owner or admin. |
| `state` | [ApprovalRequestState](#approval-request-state) | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The decline was refused; the request is unchanged.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `message` | string | Yes | Human-readable explanation, safe to show an operator. Examples: `A second owner or admin must approve this request.`. |
| `reason` | string | Yes | Machine-readable refusal reason. `approval_same_actor` — the approver is the requester, and the organization has more than one owner or admin, so a second person must approve. `approval_not_pending` — the request is already approved, declined or expired. Valid values are: `approval_same_actor`, `approval_not_pending`. Examples: `approval_same_actor`. |


#### Security

bearer_jwt

---

### POST `/v1/approval-requests/{id}/approve` {#approveapprovalrequest}

**Approve an approval request.**

Approves a pending request and applies what it asks for. The approver's authority is checked when they approve, not when the request was made, so an approver who has since left the organization or lost the role is refused with 403.
The approver must be a second person. A requester approving their own request is refused with `approval_same_actor`, unless the organization has exactly one owner or admin: then the approval is accepted and recorded with `self_approved: true`.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `id` | path | Yes | string | Identifier of the approval request. |

#### Responses

**200** — The approved request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `decided_at` | [string null] | No | When the request was approved or declined; null otherwise. |
| `decided_by` | [string null] | No | Identifier of the account that approved or declined the request; null while it is pending or once it expired. |
| `expires_at` | string | Yes | When a still-pending request becomes `expired`. |
| `id` | string | Yes | Approval request identifier. |
| `kind` | string | Yes | What is being approved. An open vocabulary, so a new kind needs no contract change. Examples: `agent_version_policy_change`. |
| `payload` | object | Yes | The kind-specific details of the request. |
| `requested_at` | string | Yes | When the request was made. |
| `requested_by` | string | Yes | Identifier of the account that made the request. |
| `self_approved` | boolean | Yes | True when the requester approved their own request, which is allowed only in an organization with exactly one owner or admin. |
| `state` | [ApprovalRequestState](#approval-request-state) | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The approval was refused; the request is unchanged.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `message` | string | Yes | Human-readable explanation, safe to show an operator. Examples: `A second owner or admin must approve this request.`. |
| `reason` | string | Yes | Machine-readable refusal reason. `approval_same_actor` — the approver is the requester, and the organization has more than one owner or admin, so a second person must approve. `approval_not_pending` — the request is already approved, declined or expired. Valid values are: `approval_same_actor`, `approval_not_pending`. Examples: `approval_same_actor`. |


#### Security

bearer_jwt

---

### GET `/v1/approval-requests/{id}` {#getapprovalrequest}

**Get an approval request.**

Returns one of the organization's approval requests.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `id` | path | Yes | string | Identifier of the approval request. |

#### Responses

**200** — The approval request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `decided_at` | [string null] | No | When the request was approved or declined; null otherwise. |
| `decided_by` | [string null] | No | Identifier of the account that approved or declined the request; null while it is pending or once it expired. |
| `expires_at` | string | Yes | When a still-pending request becomes `expired`. |
| `id` | string | Yes | Approval request identifier. |
| `kind` | string | Yes | What is being approved. An open vocabulary, so a new kind needs no contract change. Examples: `agent_version_policy_change`. |
| `payload` | object | Yes | The kind-specific details of the request. |
| `requested_at` | string | Yes | When the request was made. |
| `requested_by` | string | Yes | Identifier of the account that made the request. |
| `self_approved` | boolean | Yes | True when the requester approved their own request, which is allowed only in an organization with exactly one owner or admin. |
| `state` | [ApprovalRequestState](#approval-request-state) | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

