Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Rendry Images 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: Images
paths:
/images/extract-object:
post:
summary: Cut one object out of a photo
description: Targets a single object by text prompt or by a point on it. Costs 1 credit, like /images/remove-background, refunded when the cutout fails. Hidden from the MCP tools/list on a build where the service is not configured; this door then answers that the feature is off.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
url:
type: string
storage_key:
type: string
prompt:
type: string
description: 'Short English concept: "coffee cup", "the dog"'
point:
type: object
properties:
x:
type: integer
y:
type: integer
filename:
type: string
responses:
'200':
description: Cutout saved to assets
content:
application/json:
schema:
$ref: '#/components/schemas/Cutout'
'401':
description: Missing/invalid API key
'402':
description: 'Not enough credits: the body carries error, balance, required and packages_url.'
'429':
description: Too many renders at once — concurrency limit, not a monthly quota. Retry in a moment.
headers:
Retry-After:
$ref: '#/components/headers/RetryAfter'
'400':
description: '`invalid_input`, `url_rejected`, `download_failed`, `file_too_large`; 422 `unprocessable` when nothing matched the prompt or point (the credit is refunded).'
operationId: extractObject
parameters:
- $ref: '#/components/parameters/AccountSelector'
tags:
- Images
/images/palette:
post:
summary: Dominant colours of a photo
description: Six dominant colours plus a role suggestion (background / support / accent) — use it to theme a layout around an image.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
url:
type: string
storage_key:
type: string
responses:
'200':
description: Palette
content:
application/json:
schema:
$ref: '#/components/schemas/PhotoPalette'
'401':
description: Missing/invalid API key
'400':
description: '`unknown_field`, `invalid_input`, `url_rejected` (the url is not a public http(s) address that resolves), `download_failed` (with `upstream_status` when the address answered), `file_too_large`. A storage_key outside this account is 404 `not_found`.'
operationId: getImagePalette
parameters:
- $ref: '#/components/parameters/AccountSelector'
tags:
- Images
/images/remove-background:
post:
summary: Cut the background out of a photo
description: Returns a transparent PNG saved to your assets (an asset slot; when every slot is taken the oldest previous cutout is evicted to make room). Costs 1 credit, refunded when the cutout fails.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
url:
type: string
description: Public https image URL
storage_key:
type: string
description: storage_key of your own uploaded image (from GET /assets)
filename:
type: string
responses:
'200':
description: Cutout saved to assets
content:
application/json:
schema:
$ref: '#/components/schemas/Cutout'
'401':
description: Missing/invalid API key
'402':
description: 'Not enough credits: the body carries error, balance, required and packages_url.'
'429':
description: Too many renders at once — concurrency limit, not a monthly quota. Retry in a moment.
headers:
Retry-After:
$ref: '#/components/headers/RetryAfter'
'400':
description: '`unknown_field`, `invalid_input`, `url_rejected` (the url is not a public http(s) address that resolves), `download_failed` (with `upstream_status` when the address answered), `file_too_large`. A storage_key outside this account is 404 `not_found`.'
operationId: removeBackground
parameters:
- $ref: '#/components/parameters/AccountSelector'
tags:
- Images
/images/search:
get:
summary: Search licensed stock photos
description: 'Returns image_url, dimensions and author per photo, all distinct. Costs no credits, but IS rationed: a daily ration per account, shared by every agent and session on it, because the provider pool is shared by every user of this server — the reply carries searches_left and the MCP tool description states the number. Repeating a query you already ran today NORMALLY spends no ration, whatever detail and start you pass: the day''s list is kept in memory per process, so a repeat after a deploy or on another replica does spend one, and looking closer at a photo of a kept list is free. Hotlink the returned URL directly in a layer.'
parameters:
- name: query
in: query
required: true
schema:
type: string
description: 'English, 2-4 broad nouns. Words are AND-matched: 5+ usually returns nothing'
- name: per_page
in: query
schema:
type: integer
minimum: 1
maximum: 10
- name: orientation
in: query
schema:
type: string
enum:
- landscape
- portrait
- squarish
- name: detail
in: query
schema:
type: string
enum:
- thumb
- large
description: Inline preview size. thumb (default) is a ~200px thumbnail; large returns fewer photos at a size where a logo on a shirt is readable — use it before a photo becomes the sample of a brand template
- name: start
in: query
schema:
type: integer
minimum: 1
maximum: 10
description: 'Photo number the page starts at (default 1), in the same numbering the thumbnails carry: start=7 with detail=large shows photo 7 big. A number past the end of the list is refused and the refusal says how many photos there are'
- $ref: '#/components/parameters/AccountSelector'
responses:
'200':
description: Photos
'401':
description: Missing/invalid API key
'400':
description: Missing query
operationId: searchImages
tags:
- Images
/images/generate:
post:
operationId: generateImage
summary: Make AI images and save them to owned assets
description: Mirrors generate_asset. Price and current feature availability come from get_capabilities.ai_images.price_credits. Each new image has its own charge and failure refund. Owned cached images are free; variation asks for new art. Full-scope live keys and browser/OAuth sessions may generate; embed sessions are refused. Test keys get validated, free placeholders even when the paid service is disabled.
parameters:
- $ref: '#/components/parameters/AccountSelector'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateImageRequest'
responses:
'200':
description: Owned AI images or free test-key placeholders; see failed/refused for partial outcomes.
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateImageReply'
'400':
description: Invalid input, unknown fields or a failed reference download; nothing was charged.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'401':
description: Missing or invalid credential.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'402':
description: Insufficient credits; required, balance, packages_url and money explain the refusal.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'403':
description: Full scope required, account authority refused, unverified email or asset slots exhausted.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'404':
description: A style reference is not owned by the account.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'413':
description: Request exceeds the route body limit.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'429':
description: Account or subject concurrency or daily limit; resets_at says when to retry.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'500':
description: Storage, billing or recovery unavailable; money describes any uncertain charge.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'503':
description: Feature disabled or daily server provider budget exhausted; nothing was charged.
content:
application/json:
schema:
$ref: '#/components/schemas/Refusal'
'502':
description: No images delivered. Generation failures carry images, failed/refused and money; infrastructure failures use the common refusal envelope.
content:
application/json:
schema:
anyOf:
- $ref: '#/components/schemas/GenerateImageReply'
- $ref: '#/components/schemas/Refusal'
tags:
- Images
components:
schemas:
AIImageFailure:
type: object
required:
- index
- reason
- message
properties:
index:
type: integer
minimum: 0
reason:
type: string
message:
type: string
description: Image index and safe failure or refusal details; money explains whether refunds are complete.
PhotoPalette:
type: object
description: Dominant colours of a photo and the roles suggested for a layout around it.
properties:
colors:
type: array
items:
type: object
properties:
hex:
type: string
share:
type: number
description: Fraction of the measured pixels
suggestion:
type: object
properties:
background:
type: string
support:
type: string
accent:
type: string
measured_over:
type: string
description: 'What was measured: the whole photo, or the frame passed'
roles:
type: object
additionalProperties:
type: string
description: bg, support, accent, on and the ranked colours 0..5 — the names a derived colour is addressed by
hint:
type: string
GenerateImageReply:
type: object
required:
- images
- credits_charged
- balance
- rules_version
- cached
description: Delivered images, actual charge and current balance. Partial success may include failed or refused entries. An identical retry returns saved assets without a new charge. Test keys return free placeholders without storing or calling the provider.
properties:
images:
type: array
items:
$ref: '#/components/schemas/AIImageOutput'
credits_charged:
type: number
balance:
type:
- number
- 'null'
description: Null when the current balance could not be read.
rules_version:
type: integer
cached:
type: boolean
placeholder:
type: boolean
money:
type: string
failed:
type: array
items:
$ref: '#/components/schemas/AIImageFailure'
refused:
type: array
items:
$ref: '#/components/schemas/AIImageFailure'
error:
type: string
message:
type: string
image_base64:
type: string
description: Inline preview bytes, base64 encoded; present when a preview is available.
mime_type:
type: string
autofixed_fields:
type: array
items:
type: string
autofix_note:
type: string
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
Cutout:
type: object
description: A cutout saved to the account's files (asset_type image, created_via derived).
required:
- url
- storage_key
- file_name
properties:
url:
type: string
description: Absolute URL of the PNG; served to the owner only, like every user asset
storage_key:
type: string
file_name:
type: string
hint:
type: string
evicted_note:
type: string
description: Sent when the asset slots were full and the OLDEST previous cutout was removed to make room
image_base64:
type: string
description: A JPEG preview of the cutout
mime_type:
type: string
AIImageOutput:
type: object
required:
- asset_id
- url
- storage_key
- width
- height
- cached
properties:
asset_id:
type: string
url:
type: string
storage_key:
type: string
width:
type: integer
height:
type: integer
cached:
type: boolean
description: An owned AI asset. Test placeholders have empty asset_id and storage_key and a public placeholder URL.
GenerateImageRequest:
type: object
additionalProperties: false
required:
- prompt
description: Describe the subject; Rendry adds versioned style, composition and background rules. Identical requests reuse owned assets free. Limits mirror generate_asset and the shared AI image constants.
properties:
prompt:
type: string
description: Subject description. Rendry normalizes NFC and whitespace before enforcing x-normalized-max-length; a raw JSON string can be longer when normalization shortens it.
x-normalized-max-length: 1000
style_refs:
type: array
maxItems: 4
items:
type: string
description: Owned storage_keys or public HTTPS image URLs whose style to follow; images are sent privately to the provider.
aspect:
type: string
enum:
- '1:1'
- '3:2'
- '2:3'
- '4:3'
- '3:4'
- '16:9'
- '9:16'
default: '1:1'
background:
type: string
enum:
- as_prompted
- white
- transparent
description: Omit to use the saved background when style_id is provided, otherwise as_prompted. An explicit value overrides the saved background.
n:
type: integer
minimum: 1
maximum: 4
default: 1
variation:
type: integer
minimum: 0
default: 0
description: Change this value to request new art for otherwise identical inputs.
filename:
type: string
description: Optional asset filename; each delivered image gets an indexed PNG name.
style_id:
type: string
description: Saved ai_style; excludes style_refs. Uses the saved references and pinned prompt rules; omitted background uses the saved background.
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
headers:
RetryAfter:
description: 'Seconds to wait before retrying. Set on every 429 the render doors answer: the per-account request limit, the concurrency limit of live renders and the public link''s daily budget.'
schema:
type: integer
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
# --- truncated at 32 KB (32 KB total) ---
# Full source: https://artifacts.apis.io/rendry-io/openapi/rendry-io-images-api-openapi.yml