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.

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: the agent prefers quality.
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

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, "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, "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.

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.

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.