Hermoso AI Edits API

The edits API from Hermoso AI — 1 operation(s) for edits.

GitHub OpenAPI

Operations 1

POST /v1/edits Change one thing in a video #

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-edits-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-edits-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Hermoso Ai Edits API
  version: '2026-08-26'
  description: 'Operations tagged edits 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: edits
paths:
  /v1/edits:
    post:
      operationId: createEdit
      summary: Change one thing in a video
      description: 'Edit a clip from a plain instruction: "change only the mug to red", "swap the bottle for our product", "make it night". The motion, timing, framing and cut stay; the named thing changes, and the rest of the frame is held. A `reference` image anchors the material of what changes (a fabric, a finish, the real product). One change per call holds best, on clips of 3 to 15 seconds.


        PRICED UP FRONT. `"dry_run": true` returns the estimated credits the edit reserves when it starts, from the job''s own pricing. The real call reserves the same figure and is charged its actual cost, usually within a few percent of it.


        PREVIEW FIRST (optional). `"preview_first_frame": true` edits ONE still of the clip and returns it with the clip''s price, and nothing else runs. Look at it, then send the same request with `preview_still` set to the still to render the clip matched to it. A preview is one image edit; with `dry_run` it is priced, not made.


        ASYNC. A real clip call answers `202` with the job inside; poll `GET /v1/jobs/{id}` or name a `callback_url`.'
      tags:
      - edits
      parameters:
      - name: callback_url
        in: query
        required: false
        schema:
          type: string
        description: The same as the `X-Hermoso-Callback-Url` header or the body field, for a client that cannot set headers.
      - 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/EditCreate'
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Edit'
        '202':
          description: Queued. Poll `job.poll` (also sent as `Location`) until `status` is `done` (its `url` is the edited clip) or `error`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Edit'
        '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'
        '402':
          description: '`insufficient_credits`: the balance does not cover `credits_needed`. Nothing was started.'
          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'
      x-codeSamples:
      - lang: Shell
        label: curl
        source: "curl -sX POST https://app.hermoso.ai/v1/edits \\\n  -H \"Authorization: Bearer hmk_...\" -H 'content-type: application/json' \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"video\": \"https://example.com/clip.mp4\",\n    \"instruction\": \"change only the mug to red\",\n    \"dry_run\": true\n  }'"
      security:
      - bearerAuth: []
    servers:
    - url: https://app.hermoso.ai
components:
  schemas:
    Edit:
      type: object
      properties:
        object:
          type: string
          enum:
          - edit
        dry_run:
          type: boolean
        mode:
          type: string
          enum:
          - clip
          - preview
          description: '`preview` when `preview_first_frame` was sent. This enum MAY GROW.'
        status:
          type: string
          description: '`quoted`, `refused`, `previewed`, or the job''s own status once queued: `queued`, `running`, `done`, `error`. This enum MAY GROW.'
        quote:
          $ref: '#/components/schemas/EditQuote'
        preview:
          type: object
          nullable: true
          properties:
            still_url:
              type: string
              nullable: true
              description: The edited still (null on a dry run). Send it back as `preview_still`.
            still_credits:
              type: number
              nullable: true
              description: What the still cost, or on a dry run what it reserves.
            still_refused:
              type: string
              nullable: true
            frame_at:
              type: number
              nullable: true
              description: The second of the clip the still was taken from.
            note:
              type: string
              nullable: true
        job_id:
          type: string
          nullable: true
        job:
          allOf:
          - $ref: '#/components/schemas/Job'
          nullable: true
          description: The queued job, as `GET /v1/jobs/{id}` answers it.
        next:
          type: string
          description: What to do now, in one sentence.
    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.
    EditQuote:
      type: object
      properties:
        object:
          type: string
          enum:
          - quote
        credits:
          type: number
          nullable: true
          description: 'What the clip edit reserves when it starts, from its own pricing. An estimate: a real call is charged its actual cost, usually within a few percent. null when it would not start (see `refused`).'
        likely_credits:
          type: number
          nullable: true
        label:
          type: string
          nullable: true
          description: The model or pipeline that will render it.
        refused:
          type: string
          nullable: true
          description: Why it would not start. Nothing was charged.
    EditCreate:
      type: object
      required:
      - video
      - instruction
      properties:
        video:
          type: string
          description: 'The clip to edit: a public https link to a video file or a social post, or a Hermoso file URL. 3 to 15 seconds.'
        instruction:
          type: string
          description: 'The ONE change, in plain words: "change only the mug to red", "swap the bottle for our product", "make it nighttime".'
        reference:
          type: string
          description: 'Optional image (https or a Hermoso file URL) that anchors the material of what changes: a fabric, a finish, a colour swatch, the real product. Only its surface is used, never its framing or light.'
        keep_audio:
          type: boolean
          description: 'Default true: keep the clip''s sound. false = silent.'
        lighting:
          type: string
          enum:
          - auto
          - preserve
          - relight
          description: 'Default auto: keep the light unless the change needs new light (night, a lamp). preserve or relight forces it.'
        preview_first_frame:
          type: boolean
          description: true = edit ONE still of the clip first and return it with the clip's price. Nothing else runs.
        preview_still:
          type: string
          description: 'The `preview.still_url` a preview returned: the clip is then rendered so the change looks the way it does in that still.'
        preview_at:
          type: number
          description: Which second to preview (default the first frame). Send the same value with `preview_still`.
        dry_run:
          type: boolean
          description: true = return the exact price, and run nothing.
        callback_url:
          type: string
          description: Optional https URL to POST the finished job to (the same as the `X-Hermoso-Callback-Url` header).
    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