---
title: "Issue an API key with a scope"
method: POST
path: "/user/api-keys"
---

# Issue an API key with a scope

`POST /user/api-keys`

## Request

- Base URL: `https://rendry.io/api/v1`
- URL: `https://rendry.io/api/v1/user/api-keys`
- Auth: HTTP bearer

For the signed-in owner, in a browser session (an API key is refused with 401). A connector (MCP/OAuth) token is refused on every account-management door with 403 `browser_session_required`: a connector does MCP work, it does not manage keys. The response carries the whole key once; it is not stored and cannot be shown again. `scope` decides what the key may do (see the APIKey schema). A request that names no scope gets a `full` key, as every key did before scopes could be chosen; an unknown scope is refused and nothing is issued. The resolved account may be selected with X-Rendry-Account; only an owner or admin browser session may administer its keys. The mode and actor are reported in each reply.

## Headers

- `X-End-User` string
- `X-Rendry-Account` string

## Request body

- object
  - `name` string, required: Seen only by the owner, to tell keys apart when revoking. Stored without leading and trailing whitespace; one of whitespace alone is refused.
  - `scope` 'read' | 'render' | 'full'
  - `mode` 'live' | 'test': Immutable execution mode, persisted on the issued key. Test rendering is watermarked and does not debit credits.
  - `actor_id` string: Current member who owns this credential; defaults to the issuing browser actor. Member removal invalidates their keys.

## Response `201`

Issued. An APIKey whose key_value is the whole key.

- APIKey
  - `id` string
  - `name` string
  - `key_value` string: The whole key (sk_live_... or sk_test_...) in the create response, and ONLY there — it is not stored. The list shows the first 12 characters followed by ***.
  - `scope` 'read' | 'render' | 'full': What the key may do. read: list and read templates, presets, documents, usage, and price a batch (POST /batch-tasks/preview parses the table and quotes the run, rendering and charging nothing) — no rendering, no changes. render: everything read may, plus render_image, render_batch, render_document and preview_preset (they spend credits) — no template edits or deletions. full: everything, including creating, editing, sharing and deleting templates and files.
  - `created_at` string
  - `mode` 'live' | 'test'
  - `actor_id` string
  - `issued_by_actor_id` string

## Other responses

- `400`: The body is not JSON, `error` is `api_key_name_required` (the name is empty or only whitespace), `error` is `api_key_name_too_long` (the name is longer than `limit` characters), or `error` is `invalid_api_key_scope`: the scope is not one of read, render, full (they ride in `allowed`); `unknown_field` for a key this door does not read (a near miss is renamed and reported in `autofixes`). Nothing was issued.
- `401`: Not signed in, or an API key was used
- `403`: `error` is `browser_session_required`: a connector (MCP/OAuth) token was used

---

[API](https://skmtc.dev/rendry/apis/rendry-api.md) · [All operations](https://skmtc.dev/rendry/apis/rendry-api/llms.txt) · [OpenAPI document](https://skmtc.dev/rendry/apis/rendry-api/revisions/c4fb572c26c9?raw)
