Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Rendry Principal 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: Principal
paths:
/principal:
get:
operationId: getPrincipal
summary: Read the current credential authority
description: Account, member role, credential kind/mode and trusted subject resolved from current persisted authority. Selection cannot widen key or embed authority.
responses:
'200':
description: Read the current credential authority
content:
application/json:
schema:
$ref: '#/components/schemas/Principal'
'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'
parameters:
- $ref: '#/components/parameters/AccountSelector'
tags:
- Principal
components:
schemas:
PrincipalCapabilities:
type: object
properties:
read:
type: boolean
edit:
type: boolean
render:
type: boolean
manage_account:
type: boolean
publish:
type: boolean
required:
- read
- edit
- render
- manage_account
- publish
EmbedBrand:
type: object
additionalProperties: false
description: Authenticated session identity; contains no remote source URL or private logo bytes. Empty brand retains Document editor. Rendry attribution always remains.
properties:
name:
type: string
logo_url:
type: string
enum:
- /api/v1/embed/brand/logo
description: Local authenticated snapshot endpoint. Fetch with bearer authorization into a Blob; never put a bearer token in an image URL.
accent:
type: string
Principal:
type: object
properties:
actor_id:
type: string
account_id:
type: string
role:
type: string
enum:
- owner
- admin
- editor
- viewer
credential_kind:
type: string
enum:
- browser
- oauth
- api_key
- embed
credential_id:
type: string
credential_mode:
type: string
enum:
- live
- test
trusted_subject:
type: string
description: Only for an embed credential; authenticated customer subject, never the X-End-User analytics header.
capabilities:
$ref: '#/components/schemas/PrincipalCapabilities'
brand:
$ref: '#/components/schemas/EmbedBrand'
description: Only on authenticated embed principals.
required:
- actor_id
- account_id
- role
- credential_kind
- credential_mode
- capabilities
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
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