Hermoso AI OAuth API

OAuth 2.1 authorization code flow with PKCE and open dynamic client registration.

GitHub OpenAPI

Operations 5

GET /.well-known/oauth-protected-resource Get OAuth protected-resource metadata (RFC 9728) #
GET /.well-known/oauth-authorization-server Get OAuth authorization server metadata (RFC 8414) #
POST /oauth/register Register an OAuth client dynamically (RFC 7591) #
GET /oauth/authorize Start the authorization code flow #
POST /oauth/token Exchange an authorization code for an access token #

Work with this as data

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 Specification

hermoso-ai-oauth-api-openapi.yml Raw ↑
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/