# Partner API

Provision your customers, sign them in, and run their workflows from your server.

## Keys

Every call from your server sends a partner key: `Authorization: Bearer gre_…`. An API token (`grt_…`) is refused. Make keys in [Platform › Partner keys](https://docs.gorunner.io/embed/platform-screens#partner-keys); you see each once, and up to 20 work at once.

A key can be limited to up to 20 addresses or ranges (`203.0.113.0/24`). That limits who may use the key, not [which websites show the embed](https://docs.gorunner.io/embed/set-up#allowed-websites). Rotate a key with a grace period. Never send one to a browser: the page gets a [login code](https://docs.gorunner.io/embed/sign-in-and-sessions) instead.

Each call is checked in this order: the license (`EMBED_NOT_LICENSED`), the key (`UNAUTHORIZED`, `IP_NOT_ALLOWED`), then the limits.

## The address

Every path starts with `/api/embed/v1`, on your installation’s own address: the one your people open, where `/embed.js` is. It is never on `api.gorunner.io`. These pages write it as `https://automations.example.com`.

`/v1` is the Partner API’s version, `gorunner-embed/1`. See [Versions and upgrades](https://docs.gorunner.io/embed/versions).

Bodies are JSON; a write with another `Content-Type` is refused (`415 INVALID_CONTENT_TYPE`). Times are in UTC.

```bash
GORUNNER="https://automations.example.com"
GORUNNER_EMBED_KEY="YOUR_PARTNER_KEY"  # gre_…, from Platform › Partner keys

curl -X POST "$GORUNNER/api/embed/v1/login-codes" \
  -H "Authorization: Bearer $GORUNNER_EMBED_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user": { "id": "ada-123", "name": "Ada Lovelace" },
    "account": { "id": "acct-bakery", "name": "Bakery Ltd" },
    "role": "editor"
  }'
```

## Answers

A success is `{"data": …}`; a list adds `pagination`. A failure is `{"error": …}`. Branch on `code`; `message` is for a person and may change.

`details` says more where there is more: `field` (the input that is wrong), `unknown` (IDs that don’t exist), `reason` (why a session ended) or `switch` (the switch that is off).

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "These switches don't exist: stepData.",
    "details": { "field": "turnOff", "unknown": ["stepData"] }
  }
}
```

## The IDs are yours

Accounts, workspaces, people and managed connections are named by your own IDs: 1 to 255 bytes of UTF-8, no control characters, compared byte for byte. An ID may contain `/`, so URL-encode it in a path: `/accounts/acct%2F42`.

An email is only ever contact information: it never identifies a person or links two. A `PUT` creates or updates by your ID, so sending it twice leaves the same state.

## Idempotency

These `POST`s take an `Idempotency-Key` (up to 255 characters), so a retry after a dropped connection never acts twice: suspend, resume and restore an account; allow a website; install, enable and disable a workflow; cancel its waiting runs; cancel a run and run it again; disconnect an account’s or every AI app.

| When you send the key again | You get |
| --- | --- |
| With the same request, within 24 hours | The first answer again, with `Idempotency-Replayed: true` |
| With a different request | `422 VALIDATION_ERROR` |
| While the first is still running | `409 CONFLICT`: retry shortly |
| After the first failed | A new try: only a success is kept |

Keys belong to the partner key that sent them. A login code and [a token](https://docs.gorunner.io/embed/tokens-and-ai-apps) take none: an unused code runs out in a minute, and a stored answer would keep a token for a day, so a retry makes a second one that you can revoke. Starting a connection sign-in and signing a person in for an AI app take none either.

## Lists

Lists page with a cursor, each in the order its page states. Send `limit` (1 to 100, 50 by default) and, for the next page, the `cursor` the last page gave. `nextCursor` is `null` on the last page; a page that comes back full can carry a cursor with nothing after it, and the next page is then empty. Send a cursor back exactly as it came.

```json
{ "data": [ … ], "pagination": { "limit": 50, "nextCursor": "…" } }
```

## Limits

| Counted for | A minute | Setting |
| --- | --- | --- |
| One partner key | 3,000 requests | [`EMBED_API_RATE_LIMIT`](https://docs.gorunner.io/self-host/settings#EMBED_API_RATE_LIMIT) |
| One customer account: the one in the path, or a login code’s | 300 requests | [`EMBED_ACCOUNT_RATE_LIMIT`](https://docs.gorunner.io/self-host/settings#EMBED_ACCOUNT_RATE_LIMIT) |
| One person of one account | 20 login codes | [`EMBED_PERSON_RATE_LIMIT`](https://docs.gorunner.io/self-host/settings#EMBED_PERSON_RATE_LIMIT) |
| One line of frame sessions | 4 renewals | [`EMBED_RENEW_RATE_LIMIT`](https://docs.gorunner.io/self-host/settings#EMBED_RENEW_RATE_LIMIT) |
| AI app sign-in requests, per key | 600 requests | [`EMBED_OAUTH_REQUEST_RATE_LIMIT`](https://docs.gorunner.io/self-host/settings#EMBED_OAUTH_REQUEST_RATE_LIMIT) |

The windows are fixed minutes, shared by every server of the installation. A request one limit refuses isn’t counted by the others, so one customer over its limit uses up nothing of your key’s. One address may also send at most four times `API_RATE_LIMIT` a minute to each server.

Over a limit, the answer is `429 RATE_LIMITED` with `Retry-After`. `X-RateLimit-Limit` and `X-RateLimit-Remaining` describe the tightest window, and `X-RateLimit-Scope` names it: `key`, `account`, `person` or `session`.

Throttle your own login-code route for each person too. Every frame that opens, and every renewal through your page, is one login code; a frame renews by itself for up to 12 hours, so most renewals never reach your server.

## The record

Every call with your key leaves a row in the audit log of the account it was about, except reading an AI app sign-in request: the route, the status, the request ID, the key, and whether it was a replay. Never a body. The changes themselves, such as an account created or a role changed, leave rows of their own.

## Errors

| Code | Status | When |
| --- | --- | --- |
| `VALIDATION_ERROR` | 400, 422 | An input is wrong; `details` names it. 422 for an idempotency key used with another request. |
| `INVALID_CONTENT_TYPE` | 415 | A `Content-Type` other than `application/json`. |
| `PAYLOAD_TOO_LARGE` | 413 | A body over 5 MiB. |
| `UNAUTHORIZED` | 401 | No key, or one revoked, past its rotation or mistyped. |
| `EMBED_NOT_LICENSED` | 403 | The [license](https://docs.gorunner.io/self-host/license) doesn’t include Embed, or the installation has no valid license. |
| `IP_NOT_ALLOWED` | 403 | The key’s addresses don’t include yours. |
| `FEATURE_OFF` | 403 | A switch the call needs is off for the account; `details.switch` names it. |
| `ORG_LIMIT`, `WORKSPACE_LIMIT` | 403 | A new account or workspace would pass the license’s limit. |
| `PIECE_NOT_ALLOWED` | 403, 422 | An app the account may not use. |
| `NOT_FOUND` | 404 | No account, workspace or person with that ID. |
| `CONFLICT` | 409 | The change clashes with the current state; the message says how. |
| `ACCOUNT_DELETED` | 409 | The account is deleted. Restore it first, within 30 days. |
| `ACCOUNT_NOT_DELETED`, `RESTORE_EXPIRED` | 409 | Restoring an account that isn’t deleted, or whose 30 days are over. |
| `ACCOUNT_SUSPENDED` | 409 | Running a run again in a suspended account. |
| `WORKFLOW_NOT_PUBLISHED` | 409 | Running a run again whose workflow is off. |
| `WEBSITE_EXISTS` | 409 | The website is already on the list. |
| `TOKEN_LIMIT` | 409 | The workspace already has 25 tokens that work. |
| `DEFAULT_WORKSPACE` | 409 | An account’s default workspace can’t be deleted. |
| `INVALID_ORIGIN`, `TOO_MANY_WEBSITES` | 422 | A website can’t be allowed as written, or the list is full: 200 on yours, 20 for one customer. |
| `CONNECTION_MISMATCH` | 422 | A connection given for a template’s slot is for another service. |
| `RATE_LIMITED` | 429 | A limit is used up. See `Retry-After`. |
| `INTERNAL_ERROR` | 500 | Something failed on the installation. Try again. |
| `PIECES_UNAVAILABLE` | 503 | The apps allowed couldn’t be read. Nothing changed. |

Each endpoint lists the codes it answers. For every code, see [Error codes](https://docs.gorunner.io/errors).

## The frame’s own routes

Five routes take no partner key, and the SDK and the frame call them for you. [Check the installation can be embedded](https://docs.gorunner.io/embed/api/sign-in/bootstrap) is public. [Start a session](https://docs.gorunner.io/embed/api/sign-in/start-session) swaps a login code for a session (`gri_…`), which can [renew itself](https://docs.gorunner.io/embed/api/sign-in/renew-session), [sign out](https://docs.gorunner.io/embed/api/sign-in/end-session) or [turn more switches off](https://docs.gorunner.io/embed/api/sign-in/narrow-session).

## Every endpoint 59

### Sign-in

- [Start a session](https://docs.gorunner.io/embed/api/sign-in/start-session): `POST /api/embed/v1/sessions`
- [Make a login code](https://docs.gorunner.io/embed/api/sign-in/issue-login-code): `POST /api/embed/v1/login-codes`
- [Check the installation can be embedded](https://docs.gorunner.io/embed/api/sign-in/bootstrap): `GET /api/embed/v1/bootstrap`
- [End a session](https://docs.gorunner.io/embed/api/sign-in/end-session): `DELETE /api/embed/v1/sessions/current`
- [Turn switches off for a session](https://docs.gorunner.io/embed/api/sign-in/narrow-session): `POST /api/embed/v1/sessions/current/turn-off`
- [Renew a session](https://docs.gorunner.io/embed/api/sign-in/renew-session): `POST /api/embed/v1/sessions/current/renew`

### Accounts

- [List accounts](https://docs.gorunner.io/embed/api/accounts/list-accounts): `GET /api/embed/v1/accounts`
- [Get an account](https://docs.gorunner.io/embed/api/accounts/get-account): `GET /api/embed/v1/accounts/{accountId}`
- [Create or update an account](https://docs.gorunner.io/embed/api/accounts/put-account): `PUT /api/embed/v1/accounts/{accountId}`
- [Suspend an account](https://docs.gorunner.io/embed/api/accounts/suspend-account): `POST /api/embed/v1/accounts/{accountId}/suspend`
- [Resume an account](https://docs.gorunner.io/embed/api/accounts/resume-account): `POST /api/embed/v1/accounts/{accountId}/resume`
- [Restore an account](https://docs.gorunner.io/embed/api/accounts/restore-account): `POST /api/embed/v1/accounts/{accountId}/restore`
- [Delete an account](https://docs.gorunner.io/embed/api/accounts/delete-account): `DELETE /api/embed/v1/accounts/{accountId}`

### Tokens

- [List an account’s tokens](https://docs.gorunner.io/embed/api/tokens/list-tokens): `GET /api/embed/v1/accounts/{accountId}/tokens`
- [Make a token](https://docs.gorunner.io/embed/api/tokens/create-token): `POST /api/embed/v1/accounts/{accountId}/tokens`
- [Revoke a token](https://docs.gorunner.io/embed/api/tokens/revoke-token): `DELETE /api/embed/v1/accounts/{accountId}/tokens/{tokenId}`

### Websites

- [List an account’s websites](https://docs.gorunner.io/embed/api/websites/list-account-websites): `GET /api/embed/v1/accounts/{accountId}/websites`
- [List allowed websites](https://docs.gorunner.io/embed/api/websites/list-websites): `GET /api/embed/v1/websites`
- [Replace an account’s websites](https://docs.gorunner.io/embed/api/websites/put-account-websites): `PUT /api/embed/v1/accounts/{accountId}/websites`
- [Allow a website for one account](https://docs.gorunner.io/embed/api/websites/add-account-website): `POST /api/embed/v1/accounts/{accountId}/websites`
- [Allow a website](https://docs.gorunner.io/embed/api/websites/add-website): `POST /api/embed/v1/websites`
- [Stop allowing a website for one account](https://docs.gorunner.io/embed/api/websites/remove-account-website): `DELETE /api/embed/v1/accounts/{accountId}/websites/{origin}`
- [Stop allowing a website](https://docs.gorunner.io/embed/api/websites/remove-website): `DELETE /api/embed/v1/websites/{origin}`

### Workspaces

- [List workspaces](https://docs.gorunner.io/embed/api/workspaces/list-workspaces): `GET /api/embed/v1/accounts/{accountId}/workspaces`
- [Get a workspace](https://docs.gorunner.io/embed/api/workspaces/get-workspace): `GET /api/embed/v1/accounts/{accountId}/workspaces/{workspaceId}`
- [Create or rename a workspace](https://docs.gorunner.io/embed/api/workspaces/put-workspace): `PUT /api/embed/v1/accounts/{accountId}/workspaces/{workspaceId}`
- [Delete a workspace](https://docs.gorunner.io/embed/api/workspaces/delete-workspace): `DELETE /api/embed/v1/accounts/{accountId}/workspaces/{workspaceId}`

### People

- [List an account’s members](https://docs.gorunner.io/embed/api/people/list-members): `GET /api/embed/v1/accounts/{accountId}/members`
- [Get a person](https://docs.gorunner.io/embed/api/people/get-user): `GET /api/embed/v1/users/{userId}`
- [Give a person a role](https://docs.gorunner.io/embed/api/people/put-member): `PUT /api/embed/v1/accounts/{accountId}/members/{userId}`
- [Create or update a person](https://docs.gorunner.io/embed/api/people/put-user): `PUT /api/embed/v1/users/{userId}`
- [Remove a member](https://docs.gorunner.io/embed/api/people/remove-member): `DELETE /api/embed/v1/accounts/{accountId}/members/{userId}`
- [Delete a person](https://docs.gorunner.io/embed/api/people/delete-user): `DELETE /api/embed/v1/users/{userId}`

### Connections

- [List connections](https://docs.gorunner.io/embed/api/connections/list-connections): `GET /api/embed/v1/accounts/{accountId}/connections`
- [Get a managed connection](https://docs.gorunner.io/embed/api/connections/get-connection): `GET /api/embed/v1/accounts/{accountId}/connections/{connectionKey}`
- [Check how a connection sign-in ended](https://docs.gorunner.io/embed/api/connections/get-connection-sign-in): `GET /api/embed/v1/accounts/{accountId}/connections/oauth2/attempts/{attemptId}`
- [Create or replace a managed connection](https://docs.gorunner.io/embed/api/connections/put-connection): `PUT /api/embed/v1/accounts/{accountId}/connections/{connectionKey}`
- [Start a connection sign-in from your own button](https://docs.gorunner.io/embed/api/connections/start-connection-sign-in): `POST /api/embed/v1/accounts/{accountId}/connections/oauth2/start`
- [Delete a managed connection](https://docs.gorunner.io/embed/api/connections/delete-connection): `DELETE /api/embed/v1/accounts/{accountId}/connections/{connectionKey}`

### Workflows

- [List templates customers can install](https://docs.gorunner.io/embed/api/workflows/list-templates): `GET /api/embed/v1/templates`
- [List workflows](https://docs.gorunner.io/embed/api/workflows/list-workflows): `GET /api/embed/v1/accounts/{accountId}/workflows`
- [Get a workflow](https://docs.gorunner.io/embed/api/workflows/get-workflow): `GET /api/embed/v1/accounts/{accountId}/workflows/{workflowId}`
- [Install a template](https://docs.gorunner.io/embed/api/workflows/install-workflow): `POST /api/embed/v1/accounts/{accountId}/workflows`
- [Enable a workflow](https://docs.gorunner.io/embed/api/workflows/enable-workflow): `POST /api/embed/v1/accounts/{accountId}/workflows/{workflowId}/enable`
- [Disable a workflow](https://docs.gorunner.io/embed/api/workflows/disable-workflow): `POST /api/embed/v1/accounts/{accountId}/workflows/{workflowId}/disable`
- [Cancel a workflow’s waiting runs](https://docs.gorunner.io/embed/api/workflows/cancel-waiting-runs): `POST /api/embed/v1/accounts/{accountId}/workflows/{workflowId}/cancel-waiting`

### Runs

- [List runs](https://docs.gorunner.io/embed/api/runs/list-runs): `GET /api/embed/v1/accounts/{accountId}/runs`
- [Get a run](https://docs.gorunner.io/embed/api/runs/get-run): `GET /api/embed/v1/accounts/{accountId}/runs/{runId}`
- [Cancel a run](https://docs.gorunner.io/embed/api/runs/cancel-run): `POST /api/embed/v1/accounts/{accountId}/runs/{runId}/cancel`
- [Run a run again](https://docs.gorunner.io/embed/api/runs/rerun-run): `POST /api/embed/v1/accounts/{accountId}/runs/{runId}/rerun`

### Usage

- [List every account’s usage](https://docs.gorunner.io/embed/api/usage/list-usage): `GET /api/embed/v1/usage`
- [Get an account’s usage](https://docs.gorunner.io/embed/api/usage/get-account-usage): `GET /api/embed/v1/accounts/{accountId}/usage`

### AI apps

- [List an account’s AI apps](https://docs.gorunner.io/embed/api/ai-apps/list-ai-apps): `GET /api/embed/v1/accounts/{accountId}/ai-apps`
- [Read a sign-in request](https://docs.gorunner.io/embed/api/ai-apps/get-oauth-request): `GET /api/embed/v1/oauth-requests/{requestId}`
- [Sign a person in for an AI app](https://docs.gorunner.io/embed/api/ai-apps/complete-oauth-request): `POST /api/embed/v1/oauth-requests/{requestId}/complete`
- [Turn a sign-in request down](https://docs.gorunner.io/embed/api/ai-apps/deny-oauth-request): `POST /api/embed/v1/oauth-requests/{requestId}/deny`
- [Disconnect an AI app](https://docs.gorunner.io/embed/api/ai-apps/revoke-ai-app): `DELETE /api/embed/v1/accounts/{accountId}/ai-apps/{grantId}`
- [Disconnect an account’s AI apps](https://docs.gorunner.io/embed/api/ai-apps/revoke-account-ai-apps): `POST /api/embed/v1/accounts/{accountId}/ai-apps/revoke`
- [Disconnect every AI app](https://docs.gorunner.io/embed/api/ai-apps/revoke-all-ai-apps): `POST /api/embed/v1/ai-apps/revoke`

Source: https://docs.gorunner.io/embed/api
