# Approvals API Overview

> Set up approval flows, move templates through stages and approve them from your own code. Available on Enterprise plans, or through first-party apps.

- **URL**: https://orshot.com/docs/api-reference/approvals-overview

---

The Approvals API is available on Enterprise plans, or through first-party apps (the Orshot app and the Orshot MCP server). For an API key or your own OAuth app, each access level is turned on separately.
See [Enterprise pricing](https://orshot.com/pricing) to get access.

The Approvals API does what the Approvals page in your workspace does. You set up approval flows and their stages, add templates, move them between stages, approve them or request changes, comment, and read the activity.

When a flow needs approval for automation, its templates render through the API only once they're approved. A blocked render renders nothing and costs no credits: see [Approval Errors](https://orshot.com/docs/error-reference#approval-errors) for what it returns.

**Base URL:** `https://api.orshot.com/v1`

## Authentication

Every request sends `Authorization: Bearer <token>`, with an API key or an OAuth access token.

| Credential | Acts as | Good for |
| ---------- | ------- | -------- |
| API key | The workspace. The activity history shows its changes "via API". With the `X-Orshot-User-Id` header, one person on the team (see [Acting for a team member](#acting-for-a-team-member)). | Automations and backend jobs: adding templates to a flow, moving them, reading approval status before a render. Internal tools that act for the person using them. |
| OAuth access token | The person who connected your app, with their own access in each approval flow. | Apps people sign in to, and endpoints that act for a person: Waiting on me, following a template, notifications. |

A few endpoints act for one person: Waiting on me, following a template, notifications and notification settings. They take that person's OAuth token, or an API key with their user id in `X-Orshot-User-Id`. An API key without the header gets `403 permission_denied`, code `approval.person_required`. Their pages say so.

A token that covers several workspaces picks one with the `x-workspace-id` header. API keys always act in their own workspace.

OAuth apps ask for these scopes when the person connects. See [Scopes](https://orshot.com/docs/developers/scopes) for the others.

| Scope | Allows |
| ----- | ------ |
| `workspace:approvals:read` | See approval flows, stages, where templates sit, comments and activity |
| `workspace:approvals:write` | Add and move templates, comment, and manage your notifications |
| `workspace:approvals:decide` | Approve or request changes where you're an approver |
| `workspace:approvals:admin` | Create and change approval flows, stages, groups and approvers |

`admin` includes `write` and `read`, and `write` and `decide` each include `read`. No scope includes `decide`: approving is always asked for on its own.

## Access levels

Your Enterprise plan turns on each level separately. A call without the level it needs gets `403 enterprise_api_required`, with the level in `required`. The callout at the top of every endpoint page names its level.

| Level | What it allows |
| ----- | -------------- |
| `read` | List and read approval flows, stages, access, groups, templates in flows, a template's approval status, comments, people to mention and the activity history. Your own OAuth app also needs it for Waiting on me, following a template, notifications and notification settings, and an API key acting for a team member needs it to read their Waiting on me, notifications and notification settings. |
| `move` | Add templates to a flow, move them between stages, and write, edit, resolve and delete comments. An API key acting for a team member needs it as well as `read` to follow or mute a template for them, mark or archive their notifications, or change their notification settings. |
| `decide` | Approve, request changes and withdraw an approval. Also needed to move a template into a stage that only approvers can put templates in. Never used by a call acting for a team member: approvals come from the person. |
| `manage` | Create, change, copy and archive approval flows and stages, set who has access, manage groups, and take templates out of a flow. |

Levels don't include each other. An automation that reads a flow and then moves templates needs `read` and `move`.

With an API key, a level covers every approval flow in the workspace. With an OAuth token, or an API key acting for a team member, the person's own access applies too: someone without Approve access in a stage can't approve there, whatever levels the workspace has. Reviewers stay inside the approval flows they were given, and get `404 not_found` for everything else.

## Acting for a team member

An API key acts for the workspace. To make a call for one person instead, send their user id in the `X-Orshot-User-Id` header:

```js
await fetch("https://api.orshot.com/v1/approvals/inbox", {
  headers: {
    Authorization: "Bearer <ORSHOT_API_KEY>",
    "X-Orshot-User-Id": "9a4c7e2b-1d6f-4e3a-8b5c-2f7d0e9a4c61",
  },
});
```

- **Who.** An owner, admin or member of the key's workspace, as they are now. Anyone else gets `403 permission_denied`, code `approval.member_header_not_member`: reviewers, people who left, and people outside the workspace. Reviewers use their own OAuth token or the Orshot app. A value that isn't a user id gets `422 validation_failed`, code `approval.member_header_invalid`.
- **What it can do.** The call needs both the key's access level and that person's own access, so it never does more than either. An automation with `move` acting for someone who can't move templates into a stage can't move them there either.
- **What is theirs.** Waiting on me, notifications, following a template and notification settings answer for that person, as their own OAuth token would get them, in the key's workspace only. Reading them needs `read`. Changing them (following or muting a template, marking or archiving notifications, notification settings) needs `read` and `move`, so a read-only key only reads. Like your own OAuth app, the key changes notification settings for its workspace only (send `workspaceId`) and can't change the person's time zone.
- **Credit.** Comments, moves and other changes are credited to the person: people see "Priya Shah, via API key" in comments, the activity history, notifications and emails. The person isn't notified about their own change. The key is recorded too: comments and events carry it in `postedVia`, and webhooks keep it as the actor with the person in `onBehalfOf`.
- **Comments.** The key can edit and delete the comments it posted. The person can delete a comment posted in their name in Orshot, but not edit it. Owners and admins can hide it, as any comment.
- **Approving.** Approving, requesting changes, withdrawing an approval, and putting or reordering a template in an approved stage always come from the person, through their OAuth token or the Orshot app. With the header they get `403 permission_denied`, code `approval.decision_person_required`.
- **OAuth tokens.** They already act for the person who connected them, so they can't send the header: `403 permission_denied`, code `approval.member_header_not_allowed`.

Rate limits count the key, whoever it acts for.

## Retries and idempotency keys

Writes that aren't safe to repeat, like adding, moving or approving a template, accept an `Idempotency-Key` header. Use up to 255 visible characters, one key per request, and send the same key again when you retry it.

- A repeat within 24 hours returns the first response with the `Idempotent-Replayed: true` header, and changes nothing.
- The same key with a different request gets `422 validation_failed`, code `approval.idempotency_key_reused`.
- A retry while the first request is still running gets `409 state_conflict`, code `approval.request_in_progress`. Try again in a moment.
- A refused request doesn't use up its key, so you can fix it and send it again with the same key.

Reads, and writes that are safe to repeat as they are (updates, archiving, marking notifications read), don't take a key. Each endpoint's Headers table shows whether it takes one.

## Revisions and 409 conflicts

Each template in a flow has a `revision` that goes up every time it moves. Send the `revision` you last saw when you move a template. If someone moved it first, you get `409 state_conflict`, and `current` carries its stage and revision now:

```json
{
  "error": "state_conflict",
  "code": "state_conflict",
  "message": "Someone else moved this template first. Refresh to see where it is now.",
  "current": {
    "item": 1,
    "revision": 3,
    "reviewRound": 1,
    "stage": { "id": 3, "slug": "legal-review", "name": "Legal review", "category": "review" }
  },
  "helpUrl": "https://orshot.com/docs/error-reference#state-conflict"
}
```

Check that the move still makes sense, then send it again with the new revision. Bulk moves list conflicting templates in `failed` and move the rest.

Approving and requesting changes pin the design the same way. Send `reviewedVersion`, the `template.currentVersion` you looked at. If the design changed since, you get `409 stale_review`, and `current.currentVersion` is the version to review instead.

## Destructive calls and lists

Archiving a flow, stage or group, and taking a template out of a flow, need `"confirm": true` in the body. Agents should ask the person before they send it.

Lists come in pages. Pass `nextCursor` back as `cursor` until it's `null`. `limit` is 1 to 100. A cursor the API can't read starts again from the first page, and one longer than 200 characters is refused with `422 validation_failed`.

## Errors

Every approvals error has the same body, on the API, in the dashboard and in agents:

- `error`: the family, like `permission_denied` or `state_conflict`. Branch on this.
- `code`: more specific, like `approval.item.decide_denied` or `insufficient_scope`.
- `message`: plain words you can show to people as they are.
- `helpUrl`: the error's section in the [error reference](https://orshot.com/docs/error-reference#approvals-action-errors).
- When they apply: `required` (the missing permission, scope or level), `role`, `context`, `issues` (invalid fields), `current` (conflicts), `blockers` and `violations` (blocked renders).

Writes also return `warnings`, notices that didn't stop the change, like `no_approver` when a stage has nobody who can approve yet. It's always there on writes, and empty when there's nothing to say.

More than 120 approvals requests in a minute from one person or key get `429 rate_limit_exceeded`. Wait for the `Retry-After` header, then continue.

## Where to start

- **[List Approval Flows](https://orshot.com/docs/api-reference/approvals-flows-list)**: Find a flow's slug before working with its stages or templates
- **[Add a Template to an Approval Flow](https://orshot.com/docs/api-reference/approvals-items-add)**: Put a template into an approval flow to start its review
- **[Move a Template to Another Stage](https://orshot.com/docs/api-reference/approvals-items-move)**: Submit for review, or move it on with the revision you saw
- **[Get a Template's Approval Status](https://orshot.com/docs/api-reference/approvals-templates-approval-status)**: Check whether API renders of a template are blocked, and why