API Reference

Integrate HappyHorse AI video and image generation into your applications via REST API.

Base URL

https://happy-horse.ai/api/v1

Authentication

All API requests require an API key sent via the Authorization header. Get your key from the API Keys page.

curl https://happy-horse.ai/api/v1/credits \
  -H "Authorization: Bearer sk-sd_your_api_key_here"

Rate Limiting

Default: 60 requests per minute per API key. Rate limit info is returned in response headers:

HeaderDescription
X-RateLimit-LimitMax requests per minute
X-RateLimit-RemainingRemaining requests in window
X-RateLimit-ResetUnix timestamp when window resets

Response Format

Success

{
  "data": { ... },
  "error": null
}

Error

{
  "data": null,
  "error": {
    "message": "...",
    "code": "..."
  }
}

Endpoints

POST/api/v1/generate

Start a video generation task. Supports text-to-video and image-to-video modes.

ParameterTypeRequiredDescription
promptstringYesText description of the video to generate
modelstringNoModel ID. Default: seedance-1-5-pro
generation_typestringNotext_to_video (default) or image_to_video
image_urlstringNoReference image URL (required for image_to_video)
aspect_ratiostringNoAspect ratio. Default: 16:9
durationnumberNoVideo duration in seconds. Default: 5
resolutionstringNoOutput resolution. Default: 720p
curl -X POST https://happy-horse.ai/api/v1/generate \
  -H "Authorization: Bearer sk-sd_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A cat playing piano in a jazz club, cinematic lighting",
    "duration": 5,
    "aspect_ratio": "16:9"
  }'
POST/api/v1/generate-image

Start an image generation task using Nano Banana 2.0 model. Requires paid access.

ParameterTypeRequiredDescription
promptstringYesText description of the image to generate
modelstringNoModel ID. Default: nano-banana-2
resolutionstringNoOutput resolution: 1k, 2k, or 4k
aspect_ratiostringNoAspect ratio. Default: 1:1
output_formatstringNopng (default), jpg, or webp
imagesstring[]NoReference image URLs for edit mode
GET/api/v1/videos

List your video generation history with pagination.

ParameterTypeRequiredDescription
pagenumberNoPage number. Default: 1
limitnumberNoItems per page (1-50). Default: 20
GET/api/v1/videos/:video_id

Get the status of a specific video generation. If the video is still processing, this endpoint will poll the upstream API for the latest result.

Polling: Call this endpoint periodically (e.g., every 5 seconds) until status is completed or failed.
POST/api/v1/upload

Upload an image for use with image-to-video generation. Returns a hosted URL.

ParameterTypeRequiredDescription
imageFileYesImage file (JPG, PNG, WebP). Max 10MB.
Content-Type: multipart/form-data
GET/api/v1/credits

Check your remaining credits, paid access, and subscription status.

Error Codes

HTTPCodeDescription
401unauthorizedMissing or invalid API key
403insufficient_creditsNot enough credits
403paid_access_requiredModel requires paid access
400validation_errorMissing or invalid parameters
429rate_limit_exceededToo many requests
500internal_errorServer-side failure
502upstream_errorUpstream API failure

Models & Credits

ModelIDCredits/secSubscription
HappyHorse Litedoubao-seedance-1-5-pro5Not required
HappyHorse 1.0doubao-seedance-2-07.5 equivalent (rounded up per video)Required
HappyHorse 1.0 Fastdoubao-seedance-2-0-fast5Required
Nano Banana 2.0nano-banana-25 per imageRequired

Full Example: Generate & Poll

import requests
import time

API_KEY = "sk-sd_your_key"
BASE = "https://happy-horse.ai/api/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}

# 1. Start generation
resp = requests.post(f"{BASE}/generate", headers=headers, json={
    "prompt": "A golden retriever surfing a wave at sunset",
    "duration": 5,
})
video_id = resp.json()["data"]["video_id"]
print(f"Started: {video_id}")

# 2. Poll for completion
while True:
    status_resp = requests.get(f"{BASE}/videos/{video_id}", headers=headers)
    result = status_resp.json()["data"]
    print(f"Status: {result['status']}")

    if result["status"] == "completed":
        print(f"Video URL: {result['video_url']}")
        break
    elif result["status"] == "failed":
        print(f"Error: {result.get('error')}")
        break

    time.sleep(5)