Hermoso AI Tools API

The tools API from Hermoso AI — 2 operation(s) for tools.

GitHub OpenAPI

Operations 3

GET /v1/tools List the callable tools #
GET /v1/tools/{name} Retrieve one tool #
POST /v1/tools/{name} Call a tool #

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-tools-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-tools-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Hermoso Ai Tools API
  version: '2026-08-26'
  description: 'Operations tagged tools 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: Tools
paths:
  /v1/tools:
    get:
      operationId: listTools
      summary: List the callable tools
      description: 'Everything this workspace can do over HTTP, grouped by area, each with its human-readable description and its JSON Schema. This is the browsable index: filter by `area`, search by substring, page with a cursor.


        READ IT, DO NOT HARD-CODE IT. The set grows continuously and a tool absent here is not a missing capability — a tool whose connector this workspace has not linked is withheld, because it could only ever answer "not connected". `requires_connector` on every row names what to link.


        CONTRACT TIER: this endpoint''s SHAPE is stable under the same promise as everything else in `/v1`. The TOOLS inside it are not frozen — their arguments improve as the tools do. Treat the roster as live data.'
      tags:
      - Tools
      parameters:
      - name: area
        in: query
        required: false
        schema:
          type: string
        description: Filter to one area. `areas[]` in the response lists them with a description and a count.
      - name: search
        in: query
        required: false
        schema:
          type: string
        description: Case-insensitive substring over name, title and description.
      - name: limit
        in: query
        required: false
        schema:
          type: integer
        description: 1-200, default 25. Each row carries a JSON Schema, so a large page is a large response.
      - name: starting_after
        in: query
        required: false
        schema:
          type: string
        description: A tool name from a previous page; returns the page after it. An unknown cursor is an error, never a silent restart.
      - 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.'
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToolList'
        '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/tools/{name}:
    get:
      operationId: getTool
      summary: Retrieve one tool
      description: 'One tool: what it does, which area it belongs to, whether it is read-only, which connector it needs, and the JSON Schema for its arguments. A tool withheld from the index because its connector is not linked is still described HERE, with `connector_connected: false` — so "what would I have to link to use this?" is answerable without linking anything first.'
      tags:
      - Tools
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      - 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.'
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tool'
        '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: []
    post:
      operationId: callTool
      summary: Call a tool
      description: 'Invoke a tool with a JSON body of its arguments and get its result. The body IS the argument object — there is no envelope to wrap it in.


        UNKNOWN ARGUMENTS are never silently dropped. A key the tool does not declare that looks like a misspelling or synonym of one it does (`pagId`, `platform` for `target`) is refused with `400 invalid_arguments`, and `error.unknown_arguments` lists each one as `{name, did_you_mean}`, the suggestion always being an argument of THIS tool. Any other undeclared key runs the call without it: the result text starts with a line naming it, and the response carries `ignored_arguments: [{name, did_you_mean}]` (the same list is `data.ignoredArguments`). Keys starting with `_` are host metadata and are never named.


        The same handler the MCP server and the CLI run, on the same credential, through the same spend gate: a tool that costs credits costs the same credits here, and one that publishes publishes to the same connected account. Send an `Idempotency-Key` on anything that spends or publishes and a retry is safe.


        The reply carries BOTH halves of the tool''s answer: `text` is the sentence (what an agent reads) and `data` is the structured result (what an integration reads). A tool that refuses answers with an HTTP error in the standard envelope, never a 200 with a failure inside it.


        CONTRACT TIER: the envelope here — the route, the auth, the errors, the rate-limit headers, the idempotency semantics — is stable. A tool''s own arguments are not frozen: they improve with the tool. Read `GET /v1/tools/{name}` rather than pinning a schema.'
      tags:
      - Tools
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      - 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. A callback implies `wait=0` unless you send `wait` yourself.'
      - name: wait
        in: query
        required: false
        schema:
          type: string
        description: 'How long to hold this request while a render finishes. Only tools that render through the job queue are affected: video, avatars, template ads, post-production, and image generation (`generate_image` is queued as an image job whenever `wait` is sent, including `wait=0`). Every other tool answers as it always does. OMIT IT and nothing changes: a render is held for up to 45 seconds and then answered 200 with `data.stillRendering: true` and a `data.jobId`. `wait=0` (or `false`) answers 202 with a job handle the moment the job is queued, which is what a platform with a 30-second step limit wants. `wait=<seconds>` holds at most that long, capped at 45, and answers 202 if the render is not finished by then. Not waiting never changes what a render costs or whether it runs: the job is queued, checked against your balance and billed exactly as it is when you wait. Poll `GET /v1/jobs/{id}`.'
      - 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/ToolCall'
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToolResult'
        '202':
          description: 'Only when `wait` was sent: the render is queued or still running. Poll `poll` (also sent as the `Location` header) until `status` is `done` or `error`. Do NOT call the tool again to check on it, that starts and bills a second render.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToolJob'
        '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:
    ToolList:
      type: object
      properties:
        object:
          type: string
          enum:
          - list
        areas:
          type: array
          description: Every area holding at least one tool available to this workspace, so one call shows the whole map before you page through it.
          items:
            type: object
            properties:
              object:
                type: string
                enum:
                - tool_area
              id:
                type: string
              description:
                type: string
              tool_count:
                type: integer
        data:
          type: array
          items:
            $ref: '#/components/schemas/Tool'
        total:
          type: integer
          description: Tools matching this filter, across all pages.
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
          description: Pass it back as `starting_after`.
        limit:
          type: integer
    ToolJob:
      type: object
      description: A handle to a render that is queued or still running, answered with HTTP 202 when `wait` was sent.
      properties:
        object:
          type: string
          enum:
          - tool_job
        tool:
          type: string
          description: The tool that was called.
        job_id:
          type: string
        status:
          type: string
          description: queued or running at the moment of the reply. This enum MAY GROW.
        poll:
          type: string
          description: 'The path to poll: /v1/jobs/{id}. Also sent as the `Location` header.'
        poll_url:
          type: string
          description: The same, as an absolute URL.
        retry_after_seconds:
          type: integer
          description: A sensible gap between polls. Also sent as `Retry-After`.
        text:
          type: string
          description: The tool’s own sentence about the queued render.
    ToolResult:
      type: object
      properties:
        object:
          type: string
          enum:
          - tool_result
        tool:
          type: string
        text:
          type: string
          description: The tool’s answer as a sentence — what a model would read. Always present.
        data:
          type: object
          nullable: true
          description: The structured result, when the tool produces one; null when its answer is only prose. Its shape is the tool’s own and moves with the tool.
        ignored_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: 'Present when the call ran WITHOUT keys the tool does not take: each one, with the argument it most likely meant. The text starts with the same news, and data.ignoredArguments carries it too.'
    ToolCall:
      type: object
      description: 'The TOOL’S OWN arguments, as a flat object. There is no wrapper: to call a tool that takes `query`, send `{"query":"..."}`. Read the shape from GET /v1/tools/{name}.'
      additionalProperties: true
    Tool:
      type: object
      properties:
        object:
          type: string
          enum:
          - tool
        name:
          type: string
          description: Call it at POST /v1/tools/{name}.
        title:
          type: string
        area:
          type: string
          nullable: true
          description: Which area of the product it belongs to. THIS SET MAY GROW — tolerate a member you have not seen.
        description:
          type: string
          description: 'What it does, written for whoever is choosing: a person reading the index or a model reading the roster.'
        read_only:
          type: boolean
          description: It reads and changes nothing. Read-only tools are metered on the READ rate-limit budget.
        destructive:
          type: boolean
          description: It can remove or overwrite something. Destructive tools carry their own confirmation arguments — read the schema.
        idempotent:
          type: boolean
          description: Calling it twice with the same arguments has the same effect as calling it once. Independent of `Idempotency-Key`, which protects a retry regardless.
        requires_connector:
          type: string
          nullable: true
          description: 'The third-party account this tool needs linked, or null when it needs none. Linking is a browser flow: an OAuth consent screen cannot be completed headlessly.'
        connector_connected:
          type: boolean
          nullable: true
          description: Whether that connector is linked in this workspace. NULL means the connections could not be read, which is NOT the same as "no" — never render it as a refusal.
        rate_class:
          type: string
          enum:
          - read
          - write
          description: Which rate-limit budget a call to it spends.
        invoke:
          type: object
          properties:
            method:
              type: string
            path:
              type: string
        input_schema:
          type: object
          description: JSON Schema for the request body of POST /v1/tools/{name}. The body IS the argument object.
    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