Beta. Every endpoint on this page is live and documented as shipped. Shapes can still change before general availability.
Overview
A Finn Operator case can stop and ask a person to approve what it is about to do: send a quote, book a slot, refund a fee. The Approvals API lists and reads those approvals and lets a key decide them, within strict limits.
Base URL: https://api.hirefinn.ai/api/v1. Auth: Authorization: Bearer finn_live_.... See authentication and scopes.
Finn passes these requests on to Finn Operator and returns its answer, with no { "success": true, "data": ... } envelope.
Endpoints
| Method | Path | Scope | Rate limit (per API key) |
|---|---|---|---|
GET | /approvals | cases:read | 300 per minute, shared by all reads in Records, Cases and Approvals |
GET | /approvals/{id} | cases:read | Read bucket |
POST | /approvals/{id}/decide | approvals:write | 120 per minute, shared with the Records writes |
POST | /approvals/{id}/approve | approvals:write | Write bucket |
POST | /approvals/{id}/reject | approvals:write | Write bucket |
What a key may decide
A key with approvals:write decides one approval at a time, as asked:
- Only approvals asked of the approver role, or of a role that isn't a Finn Operator role (such as
finance). Not approvals assigned to a named person. - Never a payment. Payment approvals return
403; a person decides them in Finn. - Never an edited proposal. The key approves or rejects the proposal as it is. To change it, edit it in Finn.
- Never in bulk.
The decision is recorded as decided_by: "api_key:<key_id>" and decided_via: "api", so you can tell it apart from a person's.
A key created before scopes existed can read approvals but can't decide them. Create a key with approvals:write.
The approval object
{
"id": "5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a",
"case_id": "3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e",
"case_title": "Renewal follow-up",
"agent_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"agent_name": "Renewals",
"contact_id": "6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b",
"question": "Send the renewal quote of 12,000 to Ravi Rao?",
"allowed_answers": ["approve", "reject"],
"status": "open",
"assignee_role": "approver",
"assignee_user_id": null,
"proposal": { "summary": "Email the renewal quote" },
"payment": null,
"answer": null,
"comment": null,
"decided_by": null,
"decided_via": null,
"decided_at": null,
"deadline_at": "2026-11-04T09:00:00.000Z",
"created_at": "2026-11-03T09:00:00.000Z"
}
| Field | Notes |
|---|---|
question | What the person is asked. |
allowed_answers | The answers decide accepts. |
status | open until decided, expired or cancelled. |
assignee_role, assignee_user_id, assignee_user_ids | Who is asked. |
proposal | What the agent will do. why holds its reason. |
payment | Set for a payment approval. A key can't decide these. |
final | The proposal as finally approved, when a person edited it. |
answer, comment, decided_by, decided_via, decided_at | The decision. |
deadline_at, reminders, escalated_to | Timing. |
List approvals
curl "https://api.hirefinn.ai/api/v1/approvals?limit=50" \
-H "Authorization: Bearer $FINN_API_KEY"
| Query | Notes |
|---|---|
scope | mine (default): open approvals this key may answer. team: every approval in the organization; needs approvals:write. |
status | all adds decided, expired and cancelled approvals. Default: open only. |
contact_id | Approvals about one record. |
limit | 1 to 200. Default 50. |
cursor | next_cursor of the previous page. |
Returns { "approvals": [ ... ], "next_cursor": "..." }. next_cursor is null on the last page. Approvals of test cases are not listed.
Retrieve an approval
curl https://api.hirefinn.ai/api/v1/approvals/5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a \
-H "Authorization: Bearer $FINN_API_KEY"
Approve or reject
curl -X POST https://api.hirefinn.ai/api/v1/approvals/5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a/approve \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "comment": "Checked against the policy." }'
| Field | Type | Required | Notes |
|---|---|---|---|
comment | string | No | Kept with the decision. Up to 2,000 characters. |
approve picks the approval's own approve answer from allowed_answers (approve, approved, yes, accept or confirm, any case). reject picks reject, rejected, decline, declined, no or deny. If there is none, you get 422 no_such_answer with allowed_answers; use decide.
Returns the decided approval.
Decide with another answer
For approvals that offer a choice between options:
curl -X POST https://api.hirefinn.ai/api/v1/approvals/5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a/decide \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "answer": "tuesday_slot", "comment": "Customer prefers Tuesday." }'
| Field | Type | Required | Notes |
|---|---|---|---|
answer | string | Yes | One of allowed_answers. |
comment | string | No | Up to 2,000 characters. |
Retries. Deciding is not repeatable: a second decision on the same approval returns 409 conflict. After a timeout, read the approval and check status before retrying.
Webhook
approval.requested is sent once when an approval opens. Read it with GET /approvals/{id} and decide it here. See webhook-events.
Errors
Errors raised by Finn use { "error", "message" }. Errors from Finn Operator use { "code", "message", "details" }. Read error ?? code.
| Status | Code | When |
|---|---|---|
| 400 | invalid_id | The ID isn't a UUID. |
| 400 | answer_required | decide without answer. |
| 400 | invalid | Malformed body or query. |
| 403 | insufficient_scope | The key lacks the scope. |
| 403 | forbidden | The approval isn't the key's to answer, it is a payment, the key predates scopes, or scope=team without approvals:write. |
| 404 | not_found | No such approval in your organization. |
| 409 | conflict | The approval is no longer open. |
| 422 | no_such_answer | approve or reject on an approval without that answer. |
| 422 | invalid | The answer isn't in allowed_answers, the body carries an edited proposal, or the cursor is bad. |
| 429 | rate_limited | Over the limit. Wait Retry-After seconds. |
| 502, 503 | operator_unavailable | Finn Operator is down or not set up here. |