# Authentication

Every call carries an API token. Its permissions decide what the call may do.

## Send a token

Put the token in the `Authorization` header, after `Bearer`.

```bash
GORUNNER="https://api.gorunner.io"
GORUNNER_TOKEN="grt_…"   # Settings › Workspace › API tokens

curl "$GORUNNER/api/workflows?view=summary&limit=1" \
  -H "Authorization: Bearer $GORUNNER_TOKEN"
```

*Cloud and Self-hosted only:*

A workspace admin makes one in **Settings › Workspace › API tokens**: [Make an API token](https://docs.gorunner.io/build/api-tokens). It is `grt_` and 40 hexadecimal characters, shown once: GoRunner keeps only its hash. It lasts until the date you chose, or for ever if you chose **Never**.

*Embed only:*

In GoRunner inside your provider’s product, an editor makes one with **New token**. It starts with `grt_` and is shown once. It expires after 7 to 365 days, 90 unless you choose, and stops if the person who made it leaves or is no longer an editor. A workspace holds at most 25.

Revoking a token stops it on its next call. A token works in the one workspace it was made in. It can’t manage people, workspaces or tokens, and can’t reach Keel. The calls need no cookie and no CSRF header.

## Permissions

A token holds the permissions it was made with. It can’t hold one its maker doesn’t have. Each endpoint page says which one it needs, and the OpenAPI file names it in `x-permission`.

*Cloud and Self-hosted only:*

A token ends with its maker’s access: when they are removed from its workspace or organization, leave it, or close their account. If their workspace role is lowered, it ends only if it holds a permission the new role lacks. An ended token answers `401`.

| Permission | What it allows | In **It may** |
| --- | --- | --- |
| `workflow:read` | Read workflows, versions, variables, folders and templates. Export a workflow. Check a definition. | All three |
| `workflow:write` | Create, change, import, duplicate and move workflows. Change folders, tags, variables and templates. | Draft and run |
| `workflow:delete` | Delete workflows. | Custom only |
| `workflow:publish` | Publish and unpublish. | Draft, run and publish |
| `workflow:run` | Start runs, in test or production, and run them again. Read a workflow’s webhook token. | Draft and run |
| `connection:read` | Read connections: name, app and state. Never their secrets. | All three |
| `connection:write` | Create, change, test and replace connections. Start a sign-in. | Custom only |
| `connection:delete` | Delete connections. | Custom only |
| `run:read` | Read runs, each step’s data, run streams and run stats. | All three |
| `run:manage` | Cancel and delete runs. Approve or reject a held run. | Custom only |

**All three** means **Read only**, **Draft and run** and **Draft, run and publish**. The rest need **Custom**. The app catalog needs no permission. `workspace:manage` and `org:manage` can never be given to a token.

## When a call is refused

- 401 `UNAUTHORIZED`

  No token, or one that is wrong, expired or revoked. A token sent without `Bearer` gets its own message: “The Authorization header was not recognized”.

- 403 `FORBIDDEN`

  The token lacks the permission. `details.permission` names it.

- 404 `NOT_FOUND`

  Nothing with that ID in the token’s workspace. Another workspace’s workflow answers this too, never 403.

- 401 `TOKEN_ENDED`

  Embed only: the token no longer works. It was revoked or expired, its maker left or is no longer an editor, or the account was deleted. `details.reason` says which.

- 403 `TOKEN_SURFACE_OFF`

  Embed only: the provider turned off what this token is for.

```json
{
  "error": {
    "code": "FORBIDDEN",
    "message": "You don’t have permission to create or change workflows here. Ask an admin of this workspace.",
    "details": { "permission": "workflow:write" }
  }
}
```

## Other keys you may meet

- `gra_…`, `grr_…`: An AI app’s access and refresh tokens, from sign-in. They work only on the MCP servers, never on this API. [Sign-in for developers](https://docs.gorunner.io/mcp/oauth)
- `gre_…`: A partner key, for the Partner API of a product that embeds GoRunner. [Partner API](https://docs.gorunner.io/embed/api)
- `grl_…`, `gri_…`: The embed’s login code and session. [Login codes and sessions](https://docs.gorunner.io/embed/sign-in-and-sessions)

## Rate limits

Calls are counted per minute for each token, or each signed-in person. A call with neither is counted by its address. The AI apps’ servers share the same limit. Every answer says the limit in `X-RateLimit-Limit` and what is left in `X-RateLimit-Remaining`. Over it, the answer is `429 RATE_LIMITED` with `Retry-After`.

One address may also send at most four times one token’s limit, whatever tokens it uses. A self-hosted installation sets the limit with `API_RATE_LIMIT`: 1,200 a minute unless changed. Each API process counts on its own, so two API containers allow twice as much.

Related: [Errors](https://docs.gorunner.io/api/errors) · [Idempotency](https://docs.gorunner.io/api/idempotency) · [Make an API token](https://docs.gorunner.io/build/api-tokens) · [Connect an AI app with a token](https://docs.gorunner.io/mcp/api-token)

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