Happy Pony APIGet an API key →

Agent API

Send reference material and instructions; the video agent decides everything (story, shots, prompt, canvas), renders, optionally reviews the result against your instructions and tries again, and hands back one or more videos. No questions are asked back and nothing needs confirming.

Every call is authenticated with an API key. Create one at www.happypony.io → API Keys; the full key is shown once, so store it somewhere safe. Revoke a leaked key there and create a new one.

Authorization: Bearer hp_…

Session cookies are not accepted on these endpoints. Runs belong to the key's user: another user's run id answers 404.

Base URL below: https://api.happypony.io.

Endpoint What it does
POST /api/v1/agent/runs/uploads Upload a local file; returns a URL to use in a run
POST /api/v1/agent/runs Start a run
GET /api/v1/agent/runs/:id/status Poll for status
GET /api/v1/agent/runs/:id Get the result: videos, credits, parameters, log
PUT /api/v1/agent/runs/:id/cancel Cancel

Upload a file

POST /api/v1/agent/runs/uploads (multipart/form-data)

For media that isn't at a public URL: upload it first, then pass the returned url in any *_url field of Start a run — reference_image_urls, reference_video_urls, reference_audio_urls, first_frame_url or last_frame_url. Upload one file per request.

Form field Required Notes
file yes The image, video or audio file.
kind no image, video or audio. Inferred from the file's content type (or its extension, when the type is application/octet-stream) if left out.
curl -X POST https://api.happypony.io/api/v1/agent/runs/uploads \
  -H "Authorization: Bearer $HP_KEY" \
  -F file=@selfie.png
{ "url": "https://…signed…", "kind": "image", "expires_in": 7200 }

The response is 201.

Status error When
400 invalid_request No multipart file field.
400 invalid_media code is bad_type (unsupported type, or not the kind you named) or too_large; field is file.
401 unauthorized Missing, wrong or revoked API key.
502 media_failed The file could not be stored. Retry.

Two local images into one run:

up() { curl -s -X POST https://api.happypony.io/api/v1/agent/runs/uploads \
         -H "Authorization: Bearer $HP_KEY" -F "file=@$1" | jq -r .url; }
A=$(up selfie3.png); B=$(up selfie4.png)
curl -X POST https://api.happypony.io/api/v1/agent/runs \
  -H "Authorization: Bearer $HP_KEY" -H "Content-Type: application/json" \
  -d "{\"prompt\": \"The two girls have a bar fight\", \"reference_image_urls\": [\"$A\", \"$B\"], \"variants\": 2, \"feedback_loops\": 1}"

Start a run

POST /api/v1/agent/runs

curl -X POST https://api.happypony.io/api/v1/agent/runs \
  -H "Authorization: Bearer $HP_KEY" -H "Content-Type: application/json" \
  -d '{
    "prompt": "A 15 second ad for this trail shoe: a runner at dawn, energetic, ends on the logo.",
    "reference_image_urls": ["https://cdn.example.com/shoe-front.png", "https://cdn.example.com/shoe-side.png"],
    "reference_audio_urls": ["https://cdn.example.com/voice.mp3"],
    "hints": { "duration": 15, "aspect_ratio": "9:16", "short_edge": 768, "tier": "quality", "language": "en" },
    "variants": 2,
    "feedback_loops": 1
  }'
{
  "request_id": "6f1c…",
  "status": "IN_QUEUE",
  "status_url": "https://api.happypony.io/api/v1/agent/runs/6f1c…/status",
  "response_url": "https://api.happypony.io/api/v1/agent/runs/6f1c…",
  "cancel_url": "https://api.happypony.io/api/v1/agent/runs/6f1c…/cancel"
}

The response is 202. Unknown fields are rejected (400), so a typo never silently changes what is rendered.

Field Type Notes
prompt string, required Your instructions, up to 8000 characters.
reference_image_urls string[] ≤ 8 Subjects, products, style. More than 4 are collated into reference sheets by the agent.
reference_video_urls string[] ≤ 1 Motion / identity reference. The render model takes one.
reference_audio_urls string[] ≤ 2 Voice / sound references.
first_frame_url, last_frame_url string Pin the opening / closing frame.
hints.duration int 5–30 Seconds. Must fit the canvas (short_edge, aspect_ratio).
hints.aspect_ratio auto 21:9 16:9 4:3 1:1 3:4 9:16
hints.short_edge 480 768 1080
hints.tier turbo | quality Default quality. Every render in the run uses this tier; the agent never switches it.
hints.language string Language of speech and on-screen text.
variants int 1–4, default 1 Independent takes on the brief, each its own agent.
feedback_loops int 0–3, default 0 How many times a finished render may be reviewed and redone.

A hint you give is binding: the agent cannot override it. Anything you leave out, the agent chooses.

Media URLs

Files you uploaded are passed the same way, by their URL. Each URL is downloaded when the run is created and stored as a private attachment, so a bad URL fails the request (400 invalid_media, naming the field) instead of a run later. The rules:

Credits and limits

Poll for status

GET /api/v1/agent/runs/:id/status

{
  "request_id": "6f1c…",
  "status": "IN_PROGRESS",
  "phase": "rendering",
  "created_at": "2026-09-28T10:00:00.000Z",
  "started_at": "2026-09-28T10:00:03.000Z",
  "completed_at": null,
  "variants": [
    { "variant": 1, "state": "running", "phase": "rendering", "iteration": 1, "max_iterations": 2,
      "renders_finished": 0, "render_status": "pending", "queue_position": 3, "eta_seconds": 240 },
    { "variant": 2, "state": "running", "phase": "analysing", "iteration": 0, "max_iterations": 2, "renders_finished": 0 }
  ],
  "credits_used": 60,
  "quoted_credits": 240,
  "queue_position": 3,
  "eta_seconds": 240,
  "status_url": "…", "response_url": "…", "cancel_url": "…"
}

status uses the batch queue's names:

Status Meaning
IN_QUEUE Accepted, not yet picked up by the agent runner.
IN_PROGRESS The agent is working (see phase).
COMPLETED Every variant that could finish has a final video. A variant that failed is listed in variant_errors; the run is still COMPLETED if at least one variant has a video.
FAILED No variant produced a video. error says why.
CANCELLED Cancelled by you.

phase (run level: the most advanced running variant; null once finished):

Phase Meaning
analysing The agent reads your material and writes a plan.
planning The plan is being priced and submitted.
rendering A render is queued or running. queue_position and eta_seconds come from the render queue.
reviewing Frames of a finished render are being checked against your instructions.
revising The agent is rewriting the plan after a review (or a content-check refusal).

Per variant, iteration counts renders started and max_iterations is feedback_loops + 1. state is running, done or failed. Polling every 10–30 s is plenty; there are no webhooks.

Get the result

GET /api/v1/agent/runs/:id

{
  "request_id": "6f1c…",
  "status": "COMPLETED",
  "videos": [
    { "variant": 1, "iteration": 1, "final": false, "url": "https://…signed…", "activity_id": 812,
      "credits": 120,
      "parameters": { "title": "Trail shoe dawn run", "mode": "from references", "tier": "quality", "duration": 15,
                      "short_edge": 768, "aspect_ratio": "9:16", "inputs": "reference images img1, img2", "seed": 1804289383,
                      "brief": "A runner laces the shoe at dawn, then…" },
      "prompt_used": "…",
      "review": { "satisfied": false, "summary": "The shoe is not visible in the last shot.", "issues": ["logo missing"], "revision": "end on a close-up of the logo" } },
    { "variant": 1, "iteration": 2, "final": true, "url": "https://…signed…", "activity_id": 815,
      "credits": 120, "parameters": { "…": "…" }, "prompt_used": "…" }
  ],
  "total_credits": 240,
  "variant_errors": [{ "variant": 2, "error": "the content check refused every plan (minor): …" }],
  "log": [
    { "at": "2026-09-28T10:00:03.100Z", "kind": "run", "message": "Run started", "data": { "variants": 2, "feedback_loops": 1 } },
    { "at": "2026-09-28T10:00:20.400Z", "variant": 1, "kind": "plan", "message": "Proposed \"Trail shoe dawn run\" (120 credits)", "data": { "credits": 120, "settings": { }, "prompt": "…" } }
  ]
}

While a run is in progress the same endpoint returns the videos finished so far.

Runs also appear in the video agent's chat history on happypony.io, marked API: one read-only chat per run, with your instructions and each video's plan, render and review in order.

Cancel

PUT /api/v1/agent/runs/:id/cancel

{ "request_id": "6f1c…", "status": "CANCELLED", "renders_cancelled": 1, "renders_still_running": 0 }

The run stops within seconds. Renders still waiting in the render queue are cancelled there and refunded. A render a worker has already started cannot be stopped: it finishes, its credits stay spent, and it does not appear in the run's videos. Cancelling a run that already finished is 409 not_cancellable.

Errors

Status error When
400 invalid_request Bad JSON, unknown field, out-of-range value. details lists every problem.
400 invalid_media A URL was refused or could not be downloaded. field names it, code is blocked, invalid_url, unreachable, too_large, bad_type or http_error.
401 unauthorized Missing, wrong or revoked API key.
402 insufficient_credits Balance below the worst case. Has required and available.
404 not_found No such run, or it is not yours.
409 not_cancellable The run is already finished.
429 too_many_runs Three runs are already active.
502 media_failed The media could not be stored. Retry.

How a run works

  1. The request is validated, quoted and its media stored. Each variant gets its own private agent conversation (never shown in the chat history) starting from your instructions.
  2. A runner process claims the run (with a lease it keeps renewing, so a restart or another replica picks it up where it stopped, without submitting a render twice), and drives every variant in parallel: the agent analyses the material and proposes a video; the proposal is confirmed automatically at the quoted price and submitted to the render queue.
  3. When a render finishes and loops remain, sampled frames plus your instructions and the prompt go to the vision model for a structured critique. If it is satisfied the variant is done; otherwise the agent revises and proposes again.
  4. A content-check refusal is fed back to the agent, which revises the plan (up to 3 times per variant). Any other hard failure ends that variant only.