Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
resolveTurn a domain, URL or GitHub org into the provider it belongs to.
find_cohortsEvery scored population of providers in the catalog.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/hermoso-ai:hermoso-ai-generations-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Hermoso Ai Generations API
version: '2026-08-26'
description: 'Operations tagged generations across 2 of this provider''s published API definitions: app_hermoso_ai.json, app_hermoso_ai_rest.json. Each path carries the servers of the definition it was published in.'
servers:
- url: https://app.hermoso.ai
security:
- bearerAuth: []
tags:
- name: Generations
paths:
/v1/generations/quote:
post:
operationId: quoteGeneration
summary: Price a generation before running it
description: 'The estimated credits a generation with this body would reserve, from the render’s own pricing, with the brand’s product photos attached exactly as the real run attaches them. Free: nothing is queued, reserved or charged. It is an estimate: the render is charged its actual cost when it finishes, usually within a few percent of this figure. `credits: null` with `refused` means the render would not start, and says why.'
tags:
- Generations
parameters:
- name: X-Hermoso-User
in: header
required: false
schema:
type: string
description: 'Which profile to act on, when the account holds more than one: an `id` from `GET /v1/brands`. Defaults to the workspace the key is pinned to. Membership is re-authorised on every request, and an id this key cannot act on is a 404 `brand_not_found`, never an empty result.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GenerationCreate'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/GenerationQuote'
'400':
description: Invalid request.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Missing, invalid or revoked key — or a channel that is not connected, in which case `error.connector` names it and `error.type` is `connector_error`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: '`error.code: brand_not_found`: `X-Hermoso-User` (or the profile this key is pinned to) names a profile this key cannot act on. `GET /v1/brands` lists the valid ids.'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited. `Retry-After` says how long to wait.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- bearerAuth: []
servers:
- url: https://app.hermoso.ai
/v1/generations:
post:
operationId: createGeneration
summary: Generate an image or video
description: 'Queue one image or one video render for this profile and answer 202 with the job. Poll `GET /v1/jobs/{id}` until `status` is `done` (its `url` is the file) or `error`. Billed exactly as the same render is anywhere in Hermoso: the credits are reserved when it starts and settle to the measured cost. A refusal (not enough credits, a prompt the model will not render) is an error in the standard envelope, with the server’s own sentence in `message`. Send an `Idempotency-Key` and a retry never starts a second render.'
tags:
- Generations
parameters:
- name: callback_url
in: query
required: false
schema:
type: string
description: 'The same as the `X-Hermoso-Callback-Url` header, for a client that cannot set headers. Prefer the header: a callback URL is a secret, and query strings are logged.'
- name: X-Hermoso-Callback-Url
in: header
required: false
schema:
type: string
format: uri
description: 'An https:// URL to POST the outcome to, for a platform that cannot poll (for example Zapier `z.generateCallbackUrl()`). When the call queued a job, Hermoso POSTs the job resource — exactly what `GET /v1/jobs/{id}` answers — once the job is `done` or `error`, with headers `Hermoso-Event: job.done | job.error` and `Hermoso-Delivery` (also sent as `Idempotency-Key`; drop a repeat). A 2xx ends delivery; a 4xx other than 408, 425 and 429 is final; anything else is retried for about 16 hours. When the call finished inside the request, its response body is POSTed too (`Hermoso-Event: tool.result`). The response header `Hermoso-Callback` says what happened: `registered`, `sent`, `limit-reached` or `not-registered`. Nothing about what runs or what it costs changes: the URL is attached after the job is queued. A refusal is the HTTP error and nothing is POSTed. Must be https on a public host name; a replay with the same `Idempotency-Key` and a new URL adds that URL to the same job.'
- name: Idempotency-Key
in: header
required: false
schema:
type: string
description: 'Send a unique value to make this call safe to retry. A replay returns the original response with `Idempotency-Replayed: true`, for 24 hours — except a call that queued a job, whose replay is that job as it is now (what `GET /v1/jobs/{id}` answers), so a finished render replays as done with its file.'
- name: X-Hermoso-User
in: header
required: false
schema:
type: string
description: 'Which profile to act on, when the account holds more than one: an `id` from `GET /v1/brands`. Defaults to the workspace the key is pinned to. Membership is re-authorised on every request, and an id this key cannot act on is a 404 `brand_not_found`, never an empty result.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GenerationCreate'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
'202':
description: Queued. Poll `poll` (also sent as `Location`).
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
'400':
description: Invalid request.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Missing, invalid or revoked key — or a channel that is not connected, in which case `error.connector` names it and `error.type` is `connector_error`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: '`error.code: brand_not_found`: `X-Hermoso-User` (or the profile this key is pinned to) names a profile this key cannot act on. `GET /v1/brands` lists the valid ids.'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited. `Retry-After` says how long to wait.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- bearerAuth: []
servers:
- url: https://app.hermoso.ai
components:
schemas:
GenerationQuote:
type: object
properties:
object:
type: string
enum:
- quote
type:
type: string
credits:
type: number
nullable: true
description: What the render reserves. null when it would not start (see `refused`).
likely_credits:
type: number
nullable: true
description: 'Present when the hold covers an optional step (a label repair) that usually does not run: the likely charge, `credits` being the most it can be.'
label:
type: string
nullable: true
description: The model or pipeline that will render it.
refused:
type: string
nullable: true
description: Why the render would not start. Nothing was charged.
reference_images:
type: array
items:
type: string
description: 'Image only: the pictures the render will use, exactly as it attaches them. With `use_brand` on these are the profile’s own saved product photos (up to three) and its logo when the prompt asks for one; empty when none is on file.'
reference_note:
type: string
nullable: true
description: 'Image only: a sentence about those pictures when there is one to say (a photo left out, a lineup, the logo).'
Job:
type: object
properties:
object:
type: string
enum:
- job
id:
type: string
type:
type: string
description: 'What kind of work it is: video, stitch, avatar, templatead, upscale, and so on. This set MAY GROW.'
status:
type: string
description: 'queued, running, done or error. `done` and `error` are final. This enum MAY GROW: keep polling on a value you have not seen.'
progress:
type: number
description: 0 to 1, when the worker reports it.
label:
type: string
nullable: true
brand_id:
type: string
description: The profile that started the job.
created_at:
type: string
nullable: true
description: ISO-8601.
updated_at:
type: string
nullable: true
description: ISO-8601. For a finished job, when it finished.
run_at:
type: string
nullable: true
description: ISO-8601, for work scheduled to start later; null for work that starts immediately.
url:
type: string
nullable: true
description: The finished file, once `status` is `done` and the job produced one. Always an absolute, publicly reachable URL.
poster_url:
type: string
nullable: true
description: A still for a finished video, when there is one.
model:
type: string
nullable: true
description: The model that rendered it.
credits_used:
type: number
nullable: true
error_message:
type: string
nullable: true
description: Why it failed, when `status` is `error`. Named `error_message` so it can never be mistaken for this API’s error envelope.
cancelled:
type: boolean
description: True when the job was stopped by `POST /v1/jobs/{id}/cancel` before it reached a model. Its `status` is `error` and nothing was charged.
result:
type: object
nullable: true
description: 'The worker’s full result. PASSTHROUGH TIER: its shape is the job type’s own and improves with it, like a tool’s `data`. Build on the fields above; read this for detail.'
poll:
type: string
description: This job’s own path.
GenerationCreate:
type: object
required:
- type
- prompt
properties:
type:
type: string
enum:
- image
- video
description: What to make. This enum MAY GROW.
prompt:
type: string
description: What the ad should show, in plain words.
model:
type: string
description: A model id from the live catalog (the `hermoso_capabilities` tool lists them with prices). Omit to let Hermoso pick; the quote then prices the model the server would pick.
aspect_ratio:
type: string
description: e.g. 1:1, 4:5, 9:16, 16:9. Each model lists the ratios it renders.
image_size:
type: string
description: 'Image only: a size tier the model lists (for example 1K, 2K).'
duration_seconds:
type: integer
description: 'Video only: one of the chosen model’s listed lengths.'
ref_image:
type: string
description: 'Video only: a Hermoso-hosted image to animate (image-to-video).'
ref_images:
type: array
items:
type: string
description: 'Image only: up to 3 Hermoso-hosted images (a `POST /v1/media` or Library `url`) the render uses as its reference. When present the brand’s product photos are not attached for you; the quote prices exactly this.'
use_brand:
type: boolean
description: 'Image only, default true: attach the profile’s own product photos (and its logo when the prompt asks for one) so the render shows the real product. The quote prices exactly what this attaches.'
format:
type: string
enum:
- png
- gif
- svg
description: 'Image only, default png. gif = a short looping animated GIF, svg = an editable vector. Both render on gpt-image-2.6 (default) or gpt-image-2.6-flare from the prompt alone: no ref_images, and the profile’s photos are not attached.'
frames:
type: integer
minimum: 2
maximum: 16
description: 'format gif only: frames in the loop (default 9).'
frame_ms:
type: integer
minimum: 20
maximum: 2000
description: 'format gif only: milliseconds per frame, rounded to 10 (default 200).'
complexity:
type: string
enum:
- low
- medium
- high
description: 'gpt-image-2.6 models only: how much the model plans before drawing (default high). No separate charge.'
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- type
- code
- message
properties:
type:
type: string
enum:
- authentication_error
- permission_error
- invalid_request_error
- connector_error
- rate_limit_error
- insufficient_credits
- api_error
description: The coarse family — branch on this. New CODES land inside an existing type wherever possible so that branch keeps working; the enum is published for code generation, and a client should still fall through gracefully on a member it does not recognise.
code:
type: string
description: The specific reason. Log this. New values may appear at any time.
message:
type: string
param:
type: string
description: The offending field, when there is one.
connector:
type: string
description: Present when the reason is a channel that is not connected. Reconnect in the app — an OAuth consent screen cannot be completed headlessly.
credits_needed:
type: number
description: 'On `insufficient_credits` from a recast or an edit: what the request needs.'
balance:
type: number
description: 'On `insufficient_credits` from a recast or an edit: what this workspace has.'
unknown_arguments:
type: array
items:
type: object
properties:
name:
type: string
description: The key that was sent.
did_you_mean:
type: string
nullable: true
description: An argument THIS tool declares that the key most likely meant, or null.
description: 'On `invalid_arguments` from POST /v1/tools/{name}: each key the tool does not take, with the argument it most likely meant. Nothing was run.'
upgrade_url:
type: string
description: 'On `real_face_needs_paid`: where to choose a paid plan.'
alternative:
type: string
description: 'On `real_face_needs_paid`: the field that works on every plan instead (`person.describe`).'
person_url:
type: string
description: 'On a recast with `person.describe` whose recast step was refused after the person was made: the AI person, in your Library. Send it as `person.image_url` to retry without making a new one.'
request_id:
type: string
nullable: true
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: hmk_...
description: A Hermoso API key, or an access token from the OAuth authorization-code flow (which is the same thing).
x-refined-from:
- app_hermoso_ai.json
- app_hermoso_ai_rest.json