# Swap API > HTTP API for character replacement in video, image-to-video and text-to-video, paid with Swap credits. Docs: https://api.tryaswap.com/docs (Portuguese). Machine-readable spec: https://api.tryaswap.com/openapi.json (OpenAPI 3.1). - Base URL: https://api.tryaswap.com/v1 - Auth: `Authorization: Bearer swap_sk_key_…`. Keys are created by the account owner at https://app.tryaswap.com/desenvolvedores, shown once, server-side only. Browser requests from other origins get 403. - Envelope: success `{"data": …}`; error `{"error":{"code","message"},"requestId"}`. Branch on `error.code`, not on the message. Timestamps are Unix milliseconds. - Scopes: jobs:write, jobs:read, assets:write, assets:read, balance:read, usage:read, personas:read. - Before generating, call `GET /v1/capabilities`. If `generation.available` is false, quote and job creation return 503 `engine_unavailable` and reserve nothing. ## Flow (character replacement) 1. `POST /v1/assets` with the raw video bytes; headers `Content-Type: video/mp4`, `X-Swap-Filename: `, `Idempotency-Key: `. Response `data.asset.id`. Max 20 MiB; JPEG/PNG/WebP/MP4/WebM. 2. Same for the character image (`image/png`), or use a studio character id from `GET /v1/personas` as `personaId`. 3. `POST /v1/jobs/quote` JSON `{"mode":"character_replacement","sourceAssetId":"asset_…","referenceAssetId":"asset_…","durationSeconds":5,"aspectRatio":"9:16"}` → `reservedCredits`, `sufficient`. Reserves nothing. 4. `POST /v1/jobs` same JSON + `Idempotency-Key`. 202 with the job. Reuse the same key on retries; a different body with the same key is 409. 5. Poll `GET /v1/jobs/{id}` every 5–10 s until `state` is `succeeded` or `failed`. 6. `GET /v1/jobs/{id}/output` streams the MP4 (Range supported). Other modes: `image_to_video` (image in `sourceAssetId` + `prompt`), `text_to_video` (`prompt` only, no asset). `durationSeconds` 1–30; `aspectRatio` 9:16, 16:9 or 1:1. ## Credits Price: character swap 10 credits per started 5 s at 720p (10 s = 20); image-to-video and text-to-video 8 per 5 s; also in `GET /v1/capabilities` → `generation.pricing`. One balance shared by the studio, the apps and the API (`GET /v1/balance`, ledger at `GET /v1/usage`). Creating a job reserves the quote; success charges the real cost up to the reservation and releases the rest; failure releases everything. 402 `insufficient_credits` = account balance; 402 `key_credit_limit` = the key's lifetime ceiling. `frozen: true` blocks new jobs (409 `billing_financial_review`). Credits are bought in the dashboard, never via API keys. ## Limits Per-key requests per minute (default 60, max 120) with `X-RateLimit-Limit/Remaining/Reset`; 429 `rate_limited` or `ip_rate_limited` with `Retry-After`. Uploads: 256 MiB and 100 files per account, 20 uploads per hour. Pagination: `limit` ≤ 100, follow `nextCursor`.