# Seadanse API v1

Base URL: `https://seadanse.com/api/v1`. Create an API key in **Settings → Security → Developer API**. Keys expire after 90 days by default; the key management endpoint supports 1–365 days. Store keys in n8n/Zapier credentials, never in a browser or public workflow. Requests use `Authorization: Bearer sd_live_…`.

## Text to video

1. `GET /models` lists supported checkpoints and parameter bounds.
2. `GET /credits` returns the spendable wallet balance and download eligibility.
3. `POST /quotes` with `{"model":"seedance-2.0-mini","durationSeconds":4,"resolution":"480p"}` returns a credit estimate.
4. `POST /generations` with an `Idempotency-Key` header (8–128 letters, digits, `.`, `_`, `:`, or `-`) and:

```json
{
  "model": "seedance-2.0-mini",
  "prompt": "A quiet mountain sunrise, slow camera movement",
  "durationSeconds": 4,
  "resolution": "480p",
  "aspectRatio": "16:9",
  "maxCredits": 24
}
```

`maxCredits` is your ceiling, not a fixed price promise. Use the quote response to set it. A changed price above the ceiling returns `409 BUDGET_EXCEEDED` before any hold.

Accepted requests return HTTP 202 and `{ "success": true, "data": { "runId": "…", "status": "queued", "credits": 24, "statusUrl": "/api/v1/generations/…" } }`.

5. Poll `GET /generations/{runId}` about every 10 seconds. Status is `queued`, `processing`, `succeeded`, or `failed`. Terminal success includes `files`, each with `id` and `downloadUrl`.
6. Request the authenticated `downloadUrl`. HTTP 307 redirects to a private download grant valid for 120 seconds. Follow the redirect without forwarding your Seadanse API key to the media host. Request the endpoint again for a fresh grant. Do not persist the temporary URL as a permanent file URL.

## Image to video

`POST /uploads` with `{"contentType":"image/png","contentLength":12345}` returns `assetId`, `uploadUrl`, `token`, and `expiresAt`. Supported types: JPEG, PNG, WebP, maximum 10 MiB.

Send the raw image bytes to `uploadUrl` with method POST, `X-Upload-Token: <upload token>`, the matching `Content-Type`, and exact `Content-Length`. Check the upload response before generating. The upload credential lasts 30 minutes and authorizes one object. Add `imageAssetId` to the generation body. Assets may start new generations for 24 hours. An asset belonging to another account, an expired asset, or an unfinished upload cannot be used. Arbitrary image URLs and video/audio references are not supported in v1.

## Retries and billing

- Use one unique Idempotency-Key per intended generation. Retrying the identical normalized body returns the original HTTP result, even after your balance changes or you rotate API keys. This is account scoped.
- Do not create a new key because a request timed out. Retry the same body and key. `409 SUBMISSION_PENDING` means the original receipt is still being reconciled; respect `Retry-After`.
- Reusing a key with different parameters returns `409 IDEMPOTENCY_CONFLICT`. For an explicitly rejected request, correct the cause and use a new key.
- Credits are reserved before dispatch, captured on success, and released on failure by the existing generation ledger. Quotes do not reserve credits. Paid-account eligibility is required before submission and file download. Free/unlimited generation queues are not part of this API.
- Key limit: 60 requests/minute. Account limit: 120 requests/minute. Both include polling. HTTP 429 includes `Retry-After`. Existing account render concurrency limits also apply.
- Revocation blocks new API requests immediately. Already accepted generations and their completion webhooks continue.

## Completion webhooks

When webhook signing is enabled, key creation also returns a one-time `webhookSecret`. Save it with the API key. Add optional `webhookUrl` to the generation request. It must be a public HTTPS hostname on port 443; credentials, redirects, literal IP addresses, and private network destinations are rejected.

Events contain `{ "id": "…", "type": "generation.succeeded", "data": { "runId": "…", "status": "succeeded", "files": [] } }` or `generation.failed`. File entries use the authenticated download endpoint, never permanent public media URLs.

Verify `Seadanse-Signature: t=<unix seconds>,v1=<hex digest>` by computing HMAC-SHA256 over `<timestamp>.<raw request body>` with your webhook secret. Compare in constant time, reject timestamps outside a five-minute window, and deduplicate using `Seadanse-Event-Id`. Return any 2xx promptly. Delivery is at least once; it retries up to six attempts with exponential delays (1, 2, 4, 8, 16, 32 minutes), driven by the recovery cron. Webhook failures never restart or recharge generation. Polling remains available if delivery is exhausted.

## Errors and diagnostics

Errors use `{ "success": false, "error": { "code": "…", "message": "…" } }`. Common statuses: 400 invalid JSON, 401 invalid/expired/revoked key, 403 missing scope, 402 payment/credits required, 404 resource unavailable to this account, 409 budget/idempotency/asset conflict, 422 invalid parameters, 429 rate/concurrency limit, 503 temporarily unavailable.

Every API response includes `X-Request-Id`. Supply it to support. Request metadata is retained for 30 days; prompts, API key values and signed media URLs are excluded from request logs.

Machine-readable contract: `/developers/openapi.json`. Importable examples: `/developers/postman.json`.
