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-oauth-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 MCP server and (not the REST API) OAuth API
version: 1.0.0
summary: 'The Hermoso MCP endpoint and its OAuth documents. The REST API at /v1 is described by its own spec, generated from the router: https://app.hermoso.ai/openapi.json'
description: 'Hermoso is an AI marketing studio built to be driven by an agent. The **public programmatic interface is a Model Context Protocol (MCP) server** at `POST https://app.hermoso.ai/mcp`, speaking JSON-RPC 2.0 over the MCP Streamable HTTP transport. One connection exposes the whole loop as tools: research the ads running in a market, generate on-brand image and video ads, publish or schedule them to the brand''s own channels, and build and read paid campaigns.
**This document describes the MCP endpoint and the OAuth documents around it, and nothing else.** It is deliberately small, because the MCP surface genuinely is: the tools are not separate REST paths, they are `tools/call` methods on the single MCP endpoint. Call `tools/list` after `initialize` to enumerate them, or read the generated roster at .
**There is also a public REST API, and it has its own document.** `/v1` covers publishing, scheduling, media, channels and credits on the same `hmk_` bearer token, and an agent with no account can provision one on a paid plan with `POST /v1/signup`. It is not described here, deliberately: its OpenAPI document is GENERATED from the same operations table the router is mounted from, so a second hand-written copy would be a second thing to go stale. Read it at (live, authoritative) or (static copy), and the prose at . `/v1` sends no CORS headers either, on purpose: an API key carries full account authority.
**What genuinely has no contract are the routes under `/api` on the app.** They are shaped for our own web client, they authenticate with a browser session, and they change frequently. They are not documented anywhere because building against them will break. Use `/v1`, MCP or the npm CLI (`npm install -g hermoso`).
## Authenticating
Two methods, both bearer tokens on the `Authorization` header:
- **OAuth 2.1 authorization code + PKCE** — the path an MCP client takes. Dynamic client registration is open (RFC 7591), so a client can register itself with no pre-arranged credentials. Discovery follows RFC 9728 and RFC 8414: the `401` from `/mcp` carries a `WWW-Authenticate: Bearer resource_metadata="…"` header pointing at the protected-resource document.
- **Agent key** — a long-lived `hmk_…` token minted at app.hermoso.ai → MCP & CLI → Terminal & API keys, for scripts and headless agents.
`initialize` answers without a token so a client can discover the server before authenticating. `tools/call` requires one.
## Errors
Every failure has the same JSON shape — `error` (a human-safe string), an optional `connector` key naming a provider that is not linked, and an optional `meta` object of machine-readable hints. The one rule worth memorising: **a 401 without a `connector` field means your key is bad; a 401 with one means that provider is not connected.** Branch on the field''s presence, never on the message text. Full contract: .
## Costs
Generation is metered in credits with exact published per-render prices. Call the `hermoso_capabilities` tool — free and read-only — before rendering: it returns the valid model ids with their exact credit costs and what this workspace has connected. Machine-readable price list: .'
termsOfService: https://hermoso.ai/terms/
contact:
name: Hermoso support
email: admin@hermoso.ai
url: https://hermoso.ai/contact/
license:
name: Proprietary
url: https://hermoso.ai/terms/
servers:
- url: https://app.hermoso.ai
description: Production
security:
- bearerAuth: []
- oauth2:
- hermoso.research
- hermoso.generate
tags:
- name: OAuth
description: OAuth 2.1 authorization code flow with PKCE and open dynamic client registration.
paths:
/.well-known/oauth-protected-resource:
get:
operationId: getProtectedResourceMetadata
tags:
- OAuth
summary: Get OAuth protected-resource metadata (RFC 9728)
description: Names the resource an access token must be audience-bound to, the authorization servers that may issue one, and the scopes this resource understands. An unauthenticated request to `/mcp` points here from its `WWW-Authenticate` header, which is how a client bootstraps the whole flow knowing only the MCP URL.
security:
- {}
responses:
'200':
description: Protected-resource metadata.
content:
application/json:
schema:
$ref: '#/components/schemas/ProtectedResourceMetadata'
/.well-known/oauth-authorization-server:
get:
operationId: getAuthorizationServerMetadata
tags:
- OAuth
summary: Get OAuth authorization server metadata (RFC 8414)
description: The authorization, token and dynamic-registration endpoints, the grant and response types supported, the PKCE methods accepted, and the scopes on offer. Everything a client needs to run the flow without being configured by hand.
security:
- {}
responses:
'200':
description: Authorization server metadata.
content:
application/json:
schema:
$ref: '#/components/schemas/AuthorizationServerMetadata'
/oauth/register:
post:
operationId: registerOAuthClient
tags:
- OAuth
summary: Register an OAuth client dynamically (RFC 7591)
description: 'Registration is open: any MCP client can register itself and receive a `client_id` with no pre-arranged credentials and no approval step. Clients are public — `token_endpoint_auth_method` is `none` — so PKCE is what protects the exchange, and no client secret is issued.'
security:
- {}
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ClientRegistrationRequest'
example:
client_name: My agent
redirect_uris:
- https://example.com/oauth/callback
responses:
'201':
description: The registered client.
content:
application/json:
schema:
$ref: '#/components/schemas/ClientRegistrationResponse'
'200':
description: The registered client.
content:
application/json:
schema:
$ref: '#/components/schemas/ClientRegistrationResponse'
'400':
$ref: '#/components/responses/OAuthError'
/oauth/authorize:
get:
operationId: startAuthorization
tags:
- OAuth
summary: Start the authorization code flow
description: 'A browser endpoint: it renders Hermoso''s sign-in and consent screen and then redirects back to `redirect_uri` with a `code`. **An agent cannot complete this step headlessly** — it needs a human at a browser. A headless agent should use an `hmk_…` agent key instead, minted at app.hermoso.ai → MCP & CLI → Terminal & API keys.'
security:
- {}
parameters:
- name: response_type
in: query
required: true
description: Must be `code`.
schema:
type: string
enum:
- code
- name: client_id
in: query
required: true
description: The `client_id` returned by dynamic registration.
schema:
type: string
- name: redirect_uri
in: query
required: true
description: One of the URIs registered for this client.
schema:
type: string
format: uri
- name: code_challenge
in: query
required: true
description: PKCE challenge derived from the client's verifier.
schema:
type: string
- name: code_challenge_method
in: query
required: true
description: Must be `S256`; `plain` is not accepted.
schema:
type: string
enum:
- S256
- name: scope
in: query
required: false
description: Space-separated scopes.
schema:
type: string
examples:
- hermoso.research hermoso.generate
- name: state
in: query
required: false
description: Opaque value echoed back on the redirect, for CSRF protection.
schema:
type: string
- name: resource
in: query
required: false
description: The resource the token should be bound to (RFC 8707).
schema:
type: string
format: uri
responses:
'302':
description: Redirect back to `redirect_uri` carrying `code` and `state`.
headers:
Location:
description: The callback URL with the authorization code.
schema:
type: string
format: uri
'200':
description: The sign-in and consent page, as HTML.
content:
text/html:
schema:
type: string
'400':
description: A required parameter is missing or invalid.
content:
text/html:
schema:
type: string
/oauth/token:
post:
operationId: exchangeAuthorizationCode
tags:
- OAuth
summary: Exchange an authorization code for an access token
description: 'Exchanges the `code` from the redirect for a bearer access token, proving possession of the PKCE verifier. `authorization_code` is the only grant type supported: there is no client-credentials grant, because every token is bound to a person''s workspace.'
security:
- {}
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TokenRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/TokenRequest'
responses:
'200':
description: The access token.
content:
application/json:
schema:
$ref: '#/components/schemas/TokenResponse'
'400':
$ref: '#/components/responses/OAuthError'
components:
responses:
OAuthError:
description: A standard OAuth 2.0 error response (RFC 6749 §5.2).
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthErrorBody'
schemas:
ClientRegistrationRequest:
type: object
title: ClientRegistrationRequest
description: An RFC 7591 dynamic client registration request.
required:
- redirect_uris
properties:
redirect_uris:
type: array
minItems: 1
description: The callback URIs this client will use.
items:
type: string
format: uri
client_name:
type: string
description: A human-readable name, shown on the consent screen.
grant_types:
type: array
description: Requested grant types. Only `authorization_code` is supported.
items:
type: string
enum:
- authorization_code
response_types:
type: array
description: Requested response types. Only `code` is supported.
items:
type: string
enum:
- code
token_endpoint_auth_method:
type: string
description: Clients are public; this is `none`.
enum:
- none
scope:
type: string
description: Space-separated scopes the client intends to request.
ClientRegistrationResponse:
type: object
title: ClientRegistrationResponse
description: The registered client. No client secret is issued — PKCE protects the exchange instead.
required:
- client_id
properties:
client_id:
type: string
description: The identifier to use on `/oauth/authorize`.
examples:
- cl_Md83zaYY16gulR6YxfxFKg
client_name:
type: string
description: The name as registered.
redirect_uris:
type: array
description: The callback URIs as registered.
items:
type: string
format: uri
grant_types:
type: array
description: The grant types granted.
items:
type: string
response_types:
type: array
description: The response types granted.
items:
type: string
token_endpoint_auth_method:
type: string
description: Always `none` — clients are public.
TokenRequest:
type: object
title: TokenRequest
description: An authorization code exchange.
required:
- grant_type
- code
- redirect_uri
- client_id
- code_verifier
properties:
grant_type:
type: string
description: The only grant type supported.
enum:
- authorization_code
code:
type: string
description: The authorization code from the redirect.
redirect_uri:
type: string
format: uri
description: Must match the one used on `/oauth/authorize`.
client_id:
type: string
description: The registered client id.
code_verifier:
type: string
description: The PKCE verifier whose S256 hash was sent as `code_challenge`.
resource:
type: string
format: uri
description: The resource to bind the token to (RFC 8707).
ProtectedResourceMetadata:
type: object
title: ProtectedResourceMetadata
description: RFC 9728 protected-resource metadata.
required:
- resource
- authorization_servers
properties:
resource:
type: string
format: uri
description: The resource identifier an access token must be bound to.
examples:
- https://app.hermoso.ai/mcp
authorization_servers:
type: array
description: Issuers permitted to mint tokens for this resource.
items:
type: string
format: uri
scopes_supported:
type: array
description: The scopes this resource understands.
items:
type: string
enum:
- hermoso.research
- hermoso.generate
bearer_methods_supported:
type: array
description: How a token may be presented.
items:
type: string
enum:
- header
AuthorizationServerMetadata:
type: object
title: AuthorizationServerMetadata
description: RFC 8414 authorization server metadata.
required:
- issuer
- authorization_endpoint
- token_endpoint
properties:
issuer:
type: string
format: uri
description: The issuer identifier.
authorization_endpoint:
type: string
format: uri
description: Where a browser starts the flow.
token_endpoint:
type: string
format: uri
description: Where a code is exchanged for a token.
registration_endpoint:
type: string
format: uri
description: Where a client registers itself (RFC 7591).
response_types_supported:
type: array
description: Supported response types.
items:
type: string
enum:
- code
grant_types_supported:
type: array
description: Supported grant types.
items:
type: string
enum:
- authorization_code
code_challenge_methods_supported:
type: array
description: Supported PKCE methods. `plain` is deliberately not offered.
items:
type: string
enum:
- S256
token_endpoint_auth_methods_supported:
type: array
description: Clients are public, so no secret is issued and none is accepted.
items:
type: string
enum:
- none
scopes_supported:
type: array
description: The scopes on offer.
items:
type: string
enum:
- hermoso.research
- hermoso.generate
TokenResponse:
type: object
title: TokenResponse
description: A successful token exchange.
required:
- access_token
- token_type
properties:
access_token:
type: string
description: 'The bearer token. Send it as `Authorization: Bearer …` on every MCP request.'
token_type:
type: string
description: Always `Bearer`.
enum:
- Bearer
expires_in:
type: integer
description: Lifetime in seconds.
scope:
type: string
description: The scopes actually granted, which may be narrower than those requested.
OAuthErrorBody:
type: object
title: OAuthErrorBody
description: A standard OAuth 2.0 error (RFC 6749 §5.2). The OAuth endpoints use this shape rather than the Hermoso `Error` shape, because OAuth clients parse it.
required:
- error
properties:
error:
type: string
description: The OAuth error code.
examples:
- invalid_request
- invalid_grant
- unsupported_grant_type
- invalid_client
error_description:
type: string
description: A human-readable explanation.
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'A Hermoso agent key (`hmk_…`), minted at app.hermoso.ai → MCP & CLI → Terminal & API keys. Sent as `Authorization: Bearer hmk_…`. A key is pinned to one brand workspace, and that pin is re-authorised on the server for every request — it is never read off a header the caller controls.'
oauth2:
type: oauth2
description: OAuth 2.1 authorization code flow with PKCE (S256). Dynamic client registration is open, so no credentials need to be arranged in advance.
flows:
authorizationCode:
authorizationUrl: https://app.hermoso.ai/oauth/authorize
tokenUrl: https://app.hermoso.ai/oauth/token
scopes:
hermoso.research: 'Names the research surface: the Meta, Google and LinkedIn ad libraries, organic social search, competitor teardowns, and reads of the brand, workspace, credit balance and job status.'
hermoso.generate: 'Names the acting surface: generating creative, publishing to connected channels, and building and reading ad campaigns. Renders consume credits, and any call that arms real ad spend requires an explicit per-call confirmation regardless of the token presented.'
externalDocs:
description: Hermoso developer documentation
url: https://hermoso.ai/docs/