LogoHappyHorse Docs
LogoHappyHorse Docs
Homepage

Start

Quick StartGeneration Workspace

Create

Prompts and ReferencesPrompt PatternsStyle and CameraOutput Quality

Operate

Credits, Plans, and PublishingSafety and Account Limits

Build

Customer APIProduct Overview

Customer API

Create videos, upload references, and poll generation status with the HappyHorse API.

Availability

The HappyHorse Customer API is available at /api/v1 for customer integrations when API keys are enabled for your workspace. If you cannot see the API Keys page, contact your HappyHorse administrator or support team.

When access is enabled, signed-in users can manage keys from Dashboard / Settings / API Keys. Create a key, copy the full secret shown once, store it securely, and delete keys that should no longer be used.

Use every API key as a Bearer token:

Authorization: Bearer hh_live_your_api_key

Base URL

Use your production HappyHorse domain as the base URL:

https://<your-happyhorse-domain>

All examples below assume:

export HAPPYHORSE_API_KEY="hh_live_your_api_key"
export HAPPYHORSE_BASE_URL="https://<your-happyhorse-domain>"

Authentication Check

Every Customer API endpoint requires the Authorization header. Missing, invalid, or rate-limited keys return the stable error shape documented below.

curl "$HAPPYHORSE_BASE_URL/api/v1/videos/example-id" \
  -H "Authorization: Bearer $HAPPYHORSE_API_KEY"

Upload Reference Media

POST /api/v1/uploads/presign creates a short-lived upload URL for reference assets that you want to use in a video request. The API returns a storage key; pass that key into POST /api/v1/videos.

HappyHorse currently supports direct customer uploads for:

KindContent typesMax size
imageimage/png, image/jpeg, image/webp10 MB
videovideo/mp4, video/quicktime, video/webm50 MB
audioaudio/mpeg, audio/mp3, audio/wav, audio/webm15 MB

If your recorder exports audio as audio/mp4, convert it to one of the accepted audio MIME types before uploading. The current API rejects unsupported types with upload_type_not_supported.

Request

{
  "kind": "image",
  "contentType": "image/png",
  "sizeBytes": 5242880
}

kind is optional and defaults to image. sizeBytes must be the exact file size you will upload.

Response

{
  "uploadUrl": "https://storage.example.com/presigned-url",
  "key": "videos/inputs/user-id/asset-id.png",
  "publicUrl": "https://cdn.example.com/videos/inputs/user-id/asset-id.png",
  "kind": "image"
}

You can create the presign, save the response, extract uploadUrl and key, then upload the file with the same content type and size:

curl -X POST "$HAPPYHORSE_BASE_URL/api/v1/uploads/presign" \
  -H "Authorization: Bearer $HAPPYHORSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "image",
    "contentType": "image/png",
    "sizeBytes": 5242880
  }' > presign.json

UPLOAD_URL="$(jq -r '.uploadUrl' presign.json)"
SOURCE_IMAGE_KEY="$(jq -r '.key' presign.json)"

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/png" \
  --data-binary "@reference.png"

Use SOURCE_IMAGE_KEY as sourceImageKey in the video creation request. For reference videos or audio, pass the extracted key as referenceVideoKey or referenceAudioKey.

Common errors are invalid_input, upload_file_too_large, upload_type_not_supported, missing_api_key, invalid_api_key, rate_limited, and internal_error.

Create a Video

POST /api/v1/videos starts an asynchronous video generation job. It charges credits immediately, queues the job, and returns the video ID. Poll GET /api/v1/videos/{id} until the status is ready or failed.

The model is selected by the customer's plan. Do not send a model field in the current API.

Request

{
  "prompt": "A cinematic shot of a chestnut horse running through a sunlit meadow",
  "style": "cinematic",
  "durationSec": 6,
  "aspectRatio": "16:9",
  "resolution": "720p",
  "sourceImageKey": "videos/inputs/user-id/asset-id.png",
  "referenceVideoKey": "videos/reference-video/user-id/clip-id.mp4",
  "referenceAudioKey": "videos/reference-audio/user-id/audio-id.mp3",
  "cameraMotion": "pan-right",
  "motionIntensity": 3,
  "cfgScale": 0.5
}

Supported fields:

FieldTypeNotes
promptstringRequired, 3 to 2000 characters.
stylestringOptional, up to 50 characters.
durationSecintegerRequired, 3 to 12 seconds. Plan limits apply.
aspectRatioenumOptional, default 16:9. Values: 16:9, 9:16, 1:1.
resolutionenumOptional, default 720p. Values: 720p, 1080p.
sourceImageKeystringOptional first-frame/reference image key returned by upload presign.
lastFrameKeystringOptional last-frame image key. Cannot be combined with sourceImageKey.
referenceVideoKeystringOptional reference video key returned by upload presign.
referenceAudioKeystringOptional reference audio key returned by upload presign.
cameraMotionenumOptional, default none. Values: none, static, pan-left, pan-right, tilt-up, tilt-down, zoom-in, zoom-out, orbit.
motionIntensityintegerOptional, 1 to 5.
cfgScalenumberOptional, 0 to 1.

The current API uses HappyHorse storage keys for input media. It does not accept arbitrary inputImageUrl, referenceVideoUrl, or audioUrl fields. Use POST /api/v1/uploads/presign, upload the asset, then pass the returned key.

Response

{
  "id": "018f3f1d-8c2e-7b4d-9db1-8e8f3a0c8f21",
  "status": "queued"
}

Example:

curl -X POST "$HAPPYHORSE_BASE_URL/api/v1/videos" \
  -H "Authorization: Bearer $HAPPYHORSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A cinematic shot of a chestnut horse running through a sunlit meadow",
    "style": "cinematic",
    "durationSec": 6,
    "aspectRatio": "16:9",
    "resolution": "720p"
  }'

Credit and plan errors:

  • insufficient_credits with HTTP 402 means the account does not have enough credits for the requested duration and resolution.
  • plan_gate_exceeded with HTTP 403 means the current plan does not allow a requested setting, such as duration, reference audio, last-frame input, or another gated feature. The response includes field, requiredPlan, and currentPlan.

Get Video Status

GET /api/v1/videos/{id} returns the current state of one video. A key can only read videos owned by the user who created that API key. Videos owned by another account return not_found.

curl "$HAPPYHORSE_BASE_URL/api/v1/videos/018f3f1d-8c2e-7b4d-9db1-8e8f3a0c8f21" \
  -H "Authorization: Bearer $HAPPYHORSE_API_KEY"

Response:

{
  "id": "018f3f1d-8c2e-7b4d-9db1-8e8f3a0c8f21",
  "status": "ready",
  "prompt": "A cinematic shot of a chestnut horse running through a sunlit meadow",
  "style": "cinematic",
  "durationSec": 6,
  "aspectRatio": "16:9",
  "resolution": "720p",
  "videoUrl": "https://cdn.example.com/videos/outputs/018f3f1d.mp4",
  "thumbnailUrl": null,
  "errorMessage": null,
  "readyAt": "2026-04-26T12:30:00.000Z"
}

Typical statuses are queued, processing, ready, and failed. videoUrl and thumbnailUrl are null until the asset exists. If generation fails, status becomes failed and errorMessage describes the failure.

Error Format

Customer API errors use a stable JSON envelope:

{
  "error": {
    "code": "invalid_input",
    "message": "Request body is invalid."
  }
}

Some errors include extra details:

{
  "error": {
    "code": "plan_gate_exceeded",
    "message": "Your plan does not allow this video setting.",
    "field": "referenceAudio",
    "requiredPlan": "pro",
    "currentPlan": "basic"
  }
}

Main error codes:

CodeHTTPMeaning
missing_api_key401No Bearer token was provided.
invalid_api_key401The token is missing, deleted, malformed, or not valid.
rate_limited429The API key limit was exceeded.
invalid_input400The JSON body or route input failed validation.
insufficient_credits402The account does not have enough credits.
plan_gate_exceeded403The current plan does not allow the requested setting.
upload_file_too_large413The upload exceeds the kind-specific max size.
upload_type_not_supported400The MIME type is not accepted for that upload kind.
not_found404The video does not exist or belongs to another account.
internal_error500HappyHorse could not complete the request.

Webhooks

HappyHorse does not yet expose a customer webhook endpoint for video status callbacks. Integrations should poll GET /api/v1/videos/{id} until ready or failed. A 5 to 10 second polling interval is a good default for most jobs.

Table of Contents

Availability
Base URL
Authentication Check
Upload Reference Media
Request
Response
Create a Video
Request
Response
Get Video Status
Error Format
Webhooks