# Make a token

**POST** `/api/embed/v1/accounts/{accountId}/tokens`

Makes an API token for one of an account’s workspaces, for your automation or for the customer.

Needs a partner key: `Authorization: Bearer gre_…`, from [Platform › Partner keys](https://docs.gorunner.io/embed/platform-screens#partner-keys). Call it from your server only.

Embed · Self-hosted with an Embed license

## Parameters

### Path

- `accountId` · Required · string: Your ID for the customer’s account: 1 to 255 bytes of UTF-8, case-sensitive. URL-encode it: it may contain `/`.

### Body

Send `access` or `scopes`, not both: a token never gets rights by default.

- `name` · Required · string: Up to 100 characters, to recognise it later.
- `access` · Optional · string: `read`, `run` or `build`, each including the one before. Narrowed by the account’s switches.
- `workspaceId` · Optional · string: Your workspace ID. Left out: the default workspace.
- `surfaces` · Optional · array of strings: `["api"]` (the default), or with `mcp` for AI apps, which needs the MCP switch.
- `expiresInDays` · Optional · integer: 7 to 365. The default is 90.

**What are the rarely used parameters?**

- `scopes` · Optional · array of strings: Exact permissions instead, such as `workflow:read` and `run:read`.
- `turnOff` · Optional · array of strings: Switches turned off for this token alone.

## What it may do

No more than an editor of the account, and never in another account. It is checked on every request: it stops while the Tokens switch is off, only reads while the account is suspended, and is revoked when the account is deleted or the partner key that made it is revoked. Rotating that key doesn’t stop it.

With `mcp`, AI apps use it at `/mcp/tools`, and `/mcp/build` while MCP building is on. It takes no `Idempotency-Key`, so a stored answer never keeps the token: a retry makes a second one. See [Tokens and AI apps](https://docs.gorunner.io/embed/tokens-and-ai-apps).

## Responses

- 201 Created

  Made. `raw` is the token, `grt_` and 40 hex characters, shown this once.

- 400 `VALIDATION_ERROR`

  A field isn’t valid, or neither `access` nor `scopes` was sent.

- 403 `FEATURE_OFF`

  The Tokens switch is off for this account, or `surfaces` has `mcp` while the MCP switch is off (`details.switch`).

- 403 `FORBIDDEN`

  The partner key that sent it can no longer make tokens.

- 404 `NOT_FOUND`

  No such account, or no such workspace in it.

- 409 `ACCOUNT_DELETED`

  The account is deleted. Restore it first, within 30 days.

- 409 `TOKEN_LIMIT`

  The workspace already has 25 tokens that work. Revoke one first.

- 500 `INTERNAL_ERROR`

  Something failed on the installation’s side. Try again.

Any call can also answer: [`401 UNAUTHORIZED`](https://docs.gorunner.io/errors#UNAUTHORIZED) · [`403 EMBED_NOT_LICENSED`](https://docs.gorunner.io/errors#EMBED_NOT_LICENSED) · [`403 IP_NOT_ALLOWED`](https://docs.gorunner.io/errors#IP_NOT_ALLOWED) · [`413 PAYLOAD_TOO_LARGE`](https://docs.gorunner.io/errors#PAYLOAD_TOO_LARGE) · [`415 INVALID_CONTENT_TYPE`](https://docs.gorunner.io/errors#INVALID_CONTENT_TYPE) · [`429 RATE_LIMITED`](https://docs.gorunner.io/errors#RATE_LIMITED) · [`503 BUSY`](https://docs.gorunner.io/errors#BUSY). See [Errors](https://docs.gorunner.io/embed/api#errors).

Request

**curl**

```bash
GORUNNER="https://automations.example.com"
GORUNNER_EMBED_KEY="YOUR_PARTNER_KEY"  # gre_…
ACCOUNT_ID="acct-bakery"

URL="$GORUNNER/api/embed/v1/accounts/$ACCOUNT_ID/"
URL="${URL}tokens"
curl -X POST "$URL" \
  -H "Authorization: Bearer $GORUNNER_EMBED_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Nightly sync",
    "access": "run",
    "expiresInDays": 90
  }'
```

**JavaScript**

```js
const GORUNNER = "https://automations.example.com";
const KEY = process.env.GORUNNER_EMBED_KEY; // gre_…
const ACCOUNT_ID = encodeURIComponent("acct-bakery");

const res = await fetch(
  `${GORUNNER}/api/embed/v1/accounts/` +
    `${ACCOUNT_ID}/tokens`,
  {
    method: "POST",
    headers: {
        Authorization: `Bearer ${KEY}`,
        "Content-Type": "application/json",
      },
    body: JSON.stringify({
        name: "Nightly sync",
        access: "run",
        expiresInDays: 90,
      }),
  },
);
const { data } = await res.json();
```

**Python**

```python
import os, requests
from urllib.parse import quote

GORUNNER = "https://automations.example.com"
KEY = os.environ["GORUNNER_EMBED_KEY"]  # gre_…
ACCOUNT_ID = quote("acct-bakery", safe="")

res = requests.post(
    f"{GORUNNER}/api/embed/v1/accounts/"
    f"{ACCOUNT_ID}/tokens",
    headers={"Authorization": f"Bearer {KEY}"},
    json={
      "name": "Nightly sync",
      "access": "run",
      "expiresInDays": 90,
    },
)
data = res.json()["data"]
```

Response · 201 Created

```json
{
  "data": {
    "token": {
      "id": "6e9a1c3f-8b2d-4a57-9e0c-3f7b5d1a8c42",
      "name": "Nightly sync",
      "prefix": "grt_3f9a1c2b",
      "access": "run",
      "scopes": [
        "workflow:read",
        "connection:read",
        "run:read",
        "workflow:run",
        "run:manage"
      ],
      "surfaces": [
        "api"
      ],
      "workspaceId": null,
      "createdBy": {
        "kind": "partner"
      },
      "createdAt": "2026-10-25T09:00:00Z",
      "expiresAt": "2027-01-23T09:00:00Z",
      "lastUsedAt": null,
      "expiring": false,
      "turnedOff": []
    },
    "raw": "grt_3f9a1c2b…"
  }
}
```

**Every field**

Body: any.

`name` and one of `access` or `scopes` are required; send `access` or `scopes`, not both. Neither is 400 `VALIDATION_ERROR` with `details.field` `access`: a token is given what it can do on purpose, never by default.

The body takes one of 2 forms.

### Response · 201 Created

- `data` · object
  - `token` · object: An API token made in an embed: on the Tokens page by an editor, or by your server. Never the token itself.
    - `id` · UUID
    - `name` · string
    - `prefix` · string: The token’s first characters (`grt_` and 8 more), to tell tokens apart.
    - `access` · string: The widest level its scopes reach: read; run (also start, cancel and re-run runs); build (also create and change workflows and connections). `custom`: it was made with `scopes` that are not exactly one of those levels. One of `read`, `run`, `build`, `custom`.
    - `scopes` · array of strings
    - `surfaces` · array of strings: Where it works: `api` (the App API) and `mcp` (AI apps, over MCP). One of `api`, `mcp`.
    - `workspaceId` · string or null: The partner’s id of its workspace; null for the account’s default workspace.
    - `createdBy` · object
      - `kind` · string: One of `person`, `partner`.
      - `id` · string: The partner’s user id of the editor who made it (kind person).
      - `name` · string
    - `createdAt` · time
    - `expiresAt` · time
    - `lastUsedAt` · time or null
    - `expiring` · boolean: It stops within seven days.
    - `turnedOff` · array of strings: Switches turned off for this token alone.
  - `raw` · string: The token, `grt_` and 40 hex characters. Shown once.

Related: [List an account’s tokens](https://docs.gorunner.io/embed/api/tokens/list-tokens) · [Revoke a token](https://docs.gorunner.io/embed/api/tokens/revoke-token)

Source: https://docs.gorunner.io/embed/api/tokens/create-token
