Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Rendry Accounts API
version: 1.6.0
description: 'Template-based image generation API. Design a template in the visual editor (or via POST /presets), then render on-brand images with one call. Every capability the MCP server exposes as a tool is reachable over REST as well — same implementation behind both, so the two never drift. For AI agents a remote MCP endpoint is available at /mcp (streamable HTTP) — see https://rendry.io/llms-full.txt. Every refusal, on every door, is one JSON object {error: machine code, message: sentence, …fields} with the HTTP status of its class — see the Refusal schema; an MCP tool error carries the same object as its text.
Authentication: four bearer credentials open the render and template doors — a persisted live or test API key (sk_live_… / sk_test_…), the browser session of the signed-in owner (the JWT POST /auth/login returns) a connector token (the JWT the OAuth flow issues to an MCP client), and an ephemeral editor session (em_…). Browser/OAuth credentials may select a current team membership with X-Rendry-Account; API keys and embed sessions are bound to their persisted account and cannot switch accounts. GET /principal returns the resolved authority. Roles and scopes intersect; a removed member immediately loses access. Test mode is persisted on the credential: renders retain all admission limits, cost zero credits and carry a RENDRY TEST watermark; test credentials cannot publish public links. Operations that list the `session` scheme alone are the account cabinet: an API key is refused there with 401, a connector token with 403 `browser_session_required` (account deletion: `account_deletion_needs_session`). Operations with an empty `security` are public.
Also served, not described operation by operation: GET /templates/{id}/preview.png and GET /template-assets/{name} (public; the gallery''s pictures), GET /assets/library (public; the built-in image stock), GET /plans, GET /billing/config and GET /version (public; prices and the running build), GET /openapi.json (public; this document), POST /feedback (public, rate-limited by IP) and POST /feedback/agent (render credentials), the OAuth 2.1 doors under /oauth (the three /.well-known discovery documents, /register, /authorize, /client-info, /token — public; POST /oauth/decision takes the owner''s session), the guest batch under /batch-tasks/guest (public, rate-limited by IP: POST /preview, POST, GET /{token}, GET /{token}/archive), the sign-in, sign-up, e-mail verification and password doors under /auth (public; POST /auth/verify/resend takes the owner''s session), the cabinet''s POST /user/password, POST /checkout, GET /billing/history, GET /billing/subscription, GET /photos/search and POST /photos/used (owner''s session only), the payment webhooks POST /billing/webhook and POST /billing/webhook/{provider} (signed by the provider), the /internal/* doors of the render nodes (internal token) and the /admin group (administrators, session only).'
contact:
url: https://rendry.io
servers:
- url: https://rendry.io/api/v1
description: Production
security:
- apiKey: []
- session: []
- connector: []
- embed: []
tags:
- name: Accounts
paths:
/accounts:
get:
operationId: listAccounts
summary: List current browser memberships
description: ''
responses:
'200':
description: List current browser memberships
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Account'
'400':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'401':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'403':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'404':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'409':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
security:
- session: []
parameters:
- $ref: '#/components/parameters/AccountSelector'
tags:
- Accounts
post:
operationId: createAccount
summary: Create a team account
description: ''
responses:
'201':
description: Create a team account
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'400':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'401':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'403':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'404':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'409':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 255
required:
- name
security:
- session: []
parameters:
- $ref: '#/components/parameters/AccountSelector'
tags:
- Accounts
/accounts/{account}/members:
get:
operationId: listAccountMembers
summary: List team members
description: Owner or admin browser authority for this account; member identifiers are an existing actor UUID or a registered email address.
responses:
'200':
description: List team members
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AccountMember'
'400':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'401':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'403':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'404':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'409':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
security:
- session: []
parameters:
- name: account
in: path
required: true
schema:
type: string
- $ref: '#/components/parameters/AccountSelector'
tags:
- Accounts
/accounts/{account}/members/{actor}:
put:
operationId: setAccountMember
summary: Add or change a registered member role
description: Owner or admin required. The last owner cannot be removed or demoted. The actor path accepts an existing actor UUID or registered email address. This endpoint does not send an invitation.
responses:
'204':
description: Add or change a registered member role
'400':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'401':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'403':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'404':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'409':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
role:
type: string
enum:
- owner
- admin
- editor
- viewer
required:
- role
security:
- session: []
parameters:
- name: account
in: path
required: true
schema:
type: string
- name: actor
in: path
required: true
schema:
type: string
- $ref: '#/components/parameters/AccountSelector'
tags:
- Accounts
delete:
operationId: removeAccountMember
summary: Remove a member without deleting account resources
description: Owner or admin required. Templates and balance remain in the account; the removed member and their keys immediately lose authority.
responses:
'204':
description: Remove a member without deleting account resources
'400':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'401':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'403':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'404':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'409':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Typed refusal with a stable error code.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
security:
- session: []
parameters:
- name: account
in: path
required: true
schema:
type: string
- name: actor
in: path
required: true
schema:
type: string
- $ref: '#/components/parameters/AccountSelector'
tags:
- Accounts
components:
schemas:
AccountMember:
type: object
properties:
actor_id:
type: string
email:
type: string
role:
type: string
enum:
- owner
- admin
- editor
- viewer
required:
- actor_id
- email
- role
PrepareArtRowsRequest:
type: object
properties:
preset_id:
type: string
rows:
type: array
maxItems: 10
items:
type: object
properties:
dynamic_data:
type: object
additionalProperties:
type: string
filename:
type: string
rows:
type: object
additionalProperties:
type: array
items:
type: object
additionalProperties:
type: string
description: This item's rows for its repeat groups and tables (rows_key)
additionalProperties: false
minItems: 1
dry_run:
type: boolean
default: false
required:
- preset_id
- rows
additionalProperties: false
VariableDiagnosticCode:
type: string
description: The stable codes of variable diagnostics (specs/007-nested-variables/contracts/language.md). A diagnostic names the variable, the code, a reason sub-code, the referenced name, a cycle path and a code-point span in the source — never a source string, a supplied value or a result. A schema problem refuses the save (invalid_name, invalid_mode, reserved_name, unsupported_row_scope, source_limit) and nothing is written; a definition that does not resolve from the saved values is saved and named in the save's warnings; at render every unresolvable definition refuses the request before any charge (400, nothing rendered, nothing charged) with the same codes.
enum:
- invalid_name
- invalid_mode
- invalid_format
- invalid_format_value
- reserved_name
- unsupported_row_scope
- unsupported_reference_scope
- template_syntax
- formula_syntax
- unknown_reference
- cycle
- nonnumeric_operand
- division_by_zero
- numeric_limit
- source_limit
- node_limit
- depth_limit
- output_limit
- circular_layout_dependency
PrepareArtJobRequest:
type: object
properties:
job_id:
type: string
format: uuid
required:
- job_id
additionalProperties: false
PrepareArtRequest:
type: object
properties:
preset_id:
type: string
rows:
type: array
maxItems: 10
items:
type: object
properties:
dynamic_data:
type: object
additionalProperties:
type: string
filename:
type: string
rows:
type: object
additionalProperties:
type: array
items:
type: object
additionalProperties:
type: string
description: This item's rows for its repeat groups and tables (rows_key)
additionalProperties: false
minItems: 1
dry_run:
type: boolean
default: false
job_id:
type: string
format: uuid
additionalProperties: false
oneOf:
- $ref: '#/components/schemas/PrepareArtRowsRequest'
- $ref: '#/components/schemas/PrepareArtJobRequest'
description: Rows are the same objects as render_batch items. Send preset_id and rows to quote or start; job_id alone reads durable progress. Row indexes start at zero. Dry runs create no image/job rows, charge nothing and call no provider.
VariableDiagnostic:
type: object
description: One reason a variable does not resolve (spec 007). Names, codes, positions — never a source string, a supplied value or a resolved value.
required:
- code
- severity
properties:
code:
$ref: '#/components/schemas/VariableDiagnosticCode'
severity:
type: string
enum:
- error
- warning
variable:
type: string
description: The definition or supplied input the problem belongs to; empty for a context-wide limit.
reference:
type: string
description: The referenced name (unknown_reference, nonnumeric_operand).
path:
type: array
items:
type: string
description: A cycle, its first name repeated at the end.
reason:
type: string
description: 'A stable sub-code: which syntax rule or which limit.'
span:
type: object
description: Half-open range of Unicode code points in the definition's source.
properties:
start:
type: integer
end:
type: integer
Account:
type: object
properties:
id:
type: string
kind:
type: string
enum:
- personal
- team
name:
type: string
role:
type: string
enum:
- owner
- admin
- editor
- viewer
created_at:
type: string
format: date-time
required:
- id
- kind
- name
- created_at
Refusal:
type: object
description: 'Every refusal of every door (status 400 and above), on REST and as the JSON text of an MCP tool error. `error` is a stable machine code to branch on; `message` is an English sentence for the log or the agent. A door that names its condition uses its own code (preset_not_found, unknown_field, variables_unresolved, insufficient_credits, api_key_scope_insufficient, …) and adds its fields beside the two; any other refusal carries the code of its HTTP status class: 400 invalid_input (invalid_request when the body is not the JSON the door reads), 401 unauthorized / invalid_token / invalid_api_key, 403 forbidden, 404 not_found, 405 method_not_allowed (with `allow`), 408/504 timeout, 409 conflict, 413 request_too_large (with `limit_bytes`), 422 unprocessable, 429 rate_limit_exceeded, 500 internal_error, 502/503 service_unavailable. A path that does not exist answers 404 not_found and a wrong method 405 before any credential is read. The message never carries our infrastructure''s text (hosts, drivers, Go type names).'
required:
- error
- message
properties:
error:
type: string
description: Machine code, snake_case.
message:
type: string
description: What was refused and what to do, in English.
fields:
type: array
description: 'unknown_field: each key the door does not read, with the nearest known key. Nothing was run or charged. A near miss (a typo of one or two letters, a known alias) is not refused: it is renamed and reported in the reply: `autofixes` on /render/sync, /render/async, /batch-tasks, /assets/import and /user/api-keys; `autofixed_fields` on the doors that run an MCP tool (/render/batch, /render/preview, PATCH /presets/{id}, components, theme, clone, palette).'
items:
type: object
properties:
field:
type: string
description: 'The key as sent, with its path: `items[0].filenme`.'
did_you_mean:
type: string
diagnostics:
type: array
description: 'variables_unresolved: why the template''s variables do not resolve for this call. Never a source, a supplied value or a result. Absent on the public /u/ link, whose visitor is not the template''s author.'
items:
$ref: '#/components/schemas/VariableDiagnostic'
prompt:
type: string
description: 'art_missing: the resolved art description.'
prepare_art:
$ref: '#/components/schemas/PrepareArtRequest'
style_id:
type: string
description: An unavailable or unowned art style.
generate_asset:
type: object
description: Exact cache-sharing repair for inline layouts, drafts, document masters and document-specific variable or page facts. Generate the supplied resolved prompt, then retry the render.
properties:
style_id:
type: string
prompt:
type: string
aspect:
type: string
additionalProperties: true
parameters:
AccountSelector:
name: X-Rendry-Account
in: header
schema:
type: string
description: Browser/OAuth current member account selector. API keys/embed credentials are bound to their persisted account and reject another account. Omitting this header selects the browser actor personal account.
securitySchemes:
apiKey:
type: http
scheme: bearer
description: 'API key sk_live_... or sk_test_... — create at Settings → API Keys. Every key has a scope (read, render or full — see the APIKey schema); a call the scope does not cover answers 403 with `error` = `api_key_scope_insufficient`, the refused `tool`, the key''s `scope`, and a `message` naming what the key can call. A key limited to a list of presets answers a preset outside it with 403 `api_key_preset_not_allowed` and the refused `preset_id`; such a key renders and edits only its listed presets, and an inline design (canvas + layers, a document page or master of its own), a draft or a new template is refused with 403 `api_key_limited_to_presets` and a `reason` (inline_design, draft, create_template, inline_page). RESERVED: no door issues a key limited to presets yet — POST /user/api-keys hands out keys for the whole account — so `api_key_preset_not_allowed` and `api_key_limited_to_presets` are documented for clients built ahead of that door and are not answered by any key issued today.'
session:
type: http
scheme: bearer
bearerFormat: JWT
description: 'The browser session of the signed-in owner: the JWT POST /auth/login (or the SSO door) returns, sent as `Authorization: Bearer <jwt>`. Opens every render and template door and, alone, the account cabinet (API keys, account deletion, password, billing).'
connector:
type: http
scheme: bearer
bearerFormat: JWT
description: 'The token the OAuth 2.1 flow under /oauth issues to an MCP client (claude.ai connectors): the same JWT shape as a session, tagged as issued to a connector. Opens the render and template doors and /mcp; every cabinet door refuses it with 403 `browser_session_required`, account deletion with 403 `account_deletion_needs_session`.'
embed:
type: http
scheme: bearer
description: Ephemeral em_... token redeemed by the origin-restricted embedded editor. Its account, trusted subject, role and live/test mode are persisted server-side. It cannot access account administration, billing, account-wide batch services or public links. Never persist or put it in a URL.
x-rendry-service-policy:
rates:
rest_render:
requests: 10
window_seconds: 1
burst: 10
scope: account
consistency: api_process
mcp_request:
requests: 10
window_seconds: 1
burst: 10
scope: account
consistency: api_process
ui_request:
requests: 10
window_seconds: 1
burst: 10
scope: account
consistency: api_process
public_link:
requests: 60
window_seconds: 60
burst: 60
scope: ip
consistency: api_process
embed_frame:
requests: 10
window_seconds: 60
burst: 10
scope: ip
consistency: api_process
embed_redeem:
requests: 10
window_seconds: 60
burst: 10
scope: ip
consistency: api_process
sync_concurrency:
account: 4
subject: 2
consistency: api_process
scope: synchronous_render
document_queue:
pending_per_account: 4
long_pending_per_account: 1
long_above_pages: 20
consistency: postgres_transaction
scope: account; queued and processing documents
retention:
authenticated_days: 365
guest_hours: 72
url_cache_days: 30
starts_at: 'authenticated: completion; guest: batch creation'
end_user:
header: X-End-User
max_bytes: 64
pattern: ^[A-Za-z0-9_.:@-]{1,64}$
authority: analytics only; trusted embedded subject takes precedence
notes:
- REST, MCP and UI request buckets are separate; each account bucket is shared by members, keys and embedded subjects
- API keys bypass only the UI bucket; authenticated embedded sessions use it
- Sync concurrency is per API process; queued document admission is separately reserved in PostgreSQL
- Retention is the source policy; deployment of storage lifecycle rules requires a separate operator action