Seedance 2.5 now available for improved lip-sync Try now

The API is in closed beta. Access is granted per account — book a demo with us, or reach out via the in-app support chat to request access.

Developers · Beta

One More Shot API

Bring lyric transcription, lyric videos and narrative music videos into your own product. The API is a paid feature of your One More Shot account: calls are charged in tokens from your regular balance, with no separate contract or plan needed.

Base URL https://api.onemoreshot.ai/v1
Docs for AI agents .md OpenAPI specification

Getting started

  1. Create a One More Shot account and make your first purchase. Beta API access must also be enabled for your account.
  2. Open Settings → API in the app and create an API key.
  3. Copy the key immediately: it is shown only once at creation.
Keep keys server-side. Never embed an API key in websites, mobile apps or any client-side code — anyone with the key can spend your tokens. You can revoke a key at any time from the same settings page.

Authentication

Authenticate every request by passing your API key as a Bearer token in the Authorization header. Keys look like oms_live_….

Example request
curl https://api.onemoreshot.ai/v1/account \
  -H "Authorization: Bearer oms_live_..."

Rate limits

Requests are rate limited per key — 60 requests per minute by default. When you exceed the limit the API responds with 429 rate_limit_exceeded; wait a full Retry-After interval (currently 60 seconds) before retrying. Reads and cancellation requests count too; the limit uses calendar-minute windows. If you need higher limits for production traffic, contact us.

Idempotency & queued work

Send an Idempotency-Key on every new POST: 1–128 printable non-space ASCII characters. Reuse the same key and identical body after a timeout. Keys are scoped to the account and operation for 24 hours. Replays return the existing job with 202 and Idempotency-Replayed: true; the current status and charge may have changed. Different input with the same key returns 409. Without a key, retries can create separate jobs and charges. The optional idempotency_key body field is also accepted; if both forms are provided, they must match.

A new 202 response means queued admission, not successful media validation or a completed charge. Inspect GET and signed webhooks for asynchronous failures such as insufficient_credits, invalid media or provider errors. Keep the job ID when a client times out; do not submit a new job to check progress. API responses specify Cache-Control: private, no-store. Poll every 15–30 seconds and honor Retry-After.

Errors

Errors always use the same JSON envelope, with a machine-readable code and a human-readable message:

Error response
{
  "error": {
    "code": "invalid_request",
    "message": "song_url is required."
  }
}
StatusCodeDescription
400 invalid_request A parameter is missing or invalid — the message explains which one.
401 missing_api_key No Bearer API key in the Authorization header.
401 invalid_api_key Invalid or revoked API key.
403 insufficient_scope The key doesn't have the scope required by this endpoint.
404 not_found Unknown resource, or a job that belongs to another account.
429 rate_limit_exceeded Honor the Retry-After header (60 seconds).
409 idempotency_conflict The same idempotency key was used for different input.
409 not_cancellable_yet | not_cancellable Cancellation is too early (honor Retry-After) or the job is already terminal.
503 api_unavailable | rate_limit_unavailable New jobs are disabled, or request limits cannot be checked. Retry later.
5xx internal_error Server-side failure. Retry POST with the same idempotency key and body.

Pricing

API calls spend tokens from your One More Shot balance — the same tokens you buy in the app. New jobs are queued with zero initial charge. Workers validate media and balance, then charge before provider execution. Invalid media or insufficient tokens fails the job asynchronously without a charge. Charged failures and confirmed cancellations are refunded. Rates below are defaults; configured rates may differ. Video cost is measured seconds × rate, rounded up: 180 seconds of basic lyric video costs 5,940 tokens.

FeatureModelCost
Transcription flat 5 tokens per song.
Lyric video (basic) per second 33 tokens per second of audio.
Lyric video (pro) per second 40 tokens per second of audio.
Narrative video (basic) per second 50 tokens per second of audio.
Narrative video (pro) per second 80 tokens per second of audio.
GET

/account

Returns your current token balance and the limits of the key making the request. Useful for checking available credits before dispatching work.

200 OK
{
  "credits": 4200,
  "scopes": ["lyric_videos", "narrative_videos"],
  "rate_limit_rpm": 60,
  "webhook_secret": "3f1a…9c"
}
FieldTypeDescription
credits number Current token balance of the account.
scopes string[] Scopes granted to this API key.
rate_limit_rpm number Requests per minute allowed for this key.
webhook_secret string | null Secret for verifying webhook signatures. Null until your first job with a webhook_url; available after its initial 202 admission.
POST

/lyric-videos/transcriptions

Transcribe a song into time-coded lyric segments — the same segments used to drive lyric video generation. Present them to your users for review and editing before generating the final video.

Cost: 5 tokens per song, refunded automatically if the transcription fails.

Request body

FieldTypeDescription
song_url required string Public http(s) audio URL; up to 100 MB and 420 seconds. Trim bounds must fit the original file.
trim_start optional number Start of the section to transcribe, in seconds. Requires trim_end.
trim_end optional number End of the section to transcribe, in seconds. Must be greater than trim_start.
webhook_url optional string Public https URL notified on terminal outcomes. See Webhooks.
Example request
curl -X POST https://api.onemoreshot.ai/v1/lyric-videos/transcriptions \
  -H "Authorization: Bearer oms_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_UNIQUE_REQUEST_ID" \
  -d '{
    "song_url": "https://your-cdn.com/song.mp3",
    "trim_start": 0,
    "trim_end": 60
  }'
202 Accepted
{
  "id": "aBc123XyZ",
  "status": "processing",
  "credits_charged": 0
}

Transcription is queued; timing depends on queue and provider load. Poll GET /lyric-videos/transcriptions/{id} until status becomes completed or failed.

GET

/lyric-videos/transcriptions/{id}

Returns the status of a transcription job. When completed, the response includes the detected language and the time-coded segments with per-word timings.

200 OK — completed
{
  "id": "aBc123XyZ",
  "status": "completed",
  "credits_charged": 5,
  "credits_refunded": 0,
  "language": "en",
  "segments": [
    {
      "text": "First line of the song",
      "start": 8.5,
      "end": 10.6,
      "duration": 2.1,
      "wordCount": 5,
      "timedWords": [
        { "text": "First", "start": 8.5, "end": 8.9 }
      ]
    }
  ],
  "error": null,
  "created_at": "2026-01-01T12:00:00.000Z",
  "completed_at": "2026-01-01T12:00:14.000Z"
}
FieldTypeDescription
status string One of 'processing', 'completed' or 'failed'.
language string Detected language code (completed jobs only).
segments object[] Lyric lines with start/end times, word count and per-word timings.
error object | null Code/message object on failure, otherwise null. Inspect credits_refunded for returned tokens.
POST

/lyric-videos

Turn a song into a full lyric video. Optionally pass segments from the transcription endpoint (edited or not) to control the displayed lyrics; without them the song is transcribed automatically as part of the generation.

Cost: priced per second of audio — we probe the song's real duration server-side, so the charge always matches the audio you send. Refunded automatically if the generation fails.

Request body

FieldTypeDescription
song_url required string Public http(s) audio URL; up to 100 MB and 10–420 seconds.
quality optional string 'basic' (720p) or 'pro' (1080p). Default 'basic'.
aspect_ratio optional string '9:16' (default), '16:9' or '1:1'.
style_preset optional string Visual style for the generated scenes. Must be one of the supported preset keys listed below; omit for the default look.
song_genre optional string Genre hint, at most 500 characters.
singer_image_url optional string Public http(s) photo URL; at most 10 MB and 25 megapixels.
segments optional object[] 1–1000 ordered, non-overlapping segments within the audio duration. Each includes text, start, end, duration, wordCount and timedWords; word times must fit their parent segment.
webhook_url optional string Public https URL notified on terminal outcomes. See Webhooks.

Segment text is limited to 10,000 characters. Duration must equal end minus start within 0.05 seconds; wordCount is a non-negative integer and timedWords has at most 1,000 entries per segment. Media-dependent validation happens after admission. Request JSON is limited to 1 MB.

Style presets

style_preset accepts exactly one of these keys — any other value is rejected with a 400 invalid_request.

80S_ILLUSTRATION 80s Illustration ART_BRUT Art Brut ART_POSTER Art Poster BAUHAUS Bauhaus BLUEPRINT Blueprint C4D_CARTOON C4D Cartoon CHILDRENS_BOOK Children's Book COLLAGE Collage DOODLE Doodle EMOTIONAL_MINIMAL Emotional Minimal FLAT_ART Flat Art FLAT_VECTOR Flat Vector GRAFFITI_I Graffiti GRAFFITI_II Graffiti II MINIMAL_ILLUSTRATION Minimal MIXED_MEDIA Mixed Media PAINT_GESTURE Paint Gesture POP_ART Pop Art RETRO_ETCHING Retro Etching WATERCOLOR Watercolor WEIRD Weird WOODBLOCK_PRINT Woodblock Print
Example request
curl -X POST https://api.onemoreshot.ai/v1/lyric-videos \
  -H "Authorization: Bearer oms_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_UNIQUE_REQUEST_ID" \
  -d '{
    "song_url": "https://your-cdn.com/song.mp3",
    "quality": "basic",
    "aspect_ratio": "9:16",
    "song_genre": "pop"
  }'
202 Accepted
{
  "id": "dEf456UvW",
  "status": "processing",
  "credits_charged": 0
}

Rendering is queued. Initial admission has no measured duration or charge; these appear after successful preparation. Poll every 15–30 seconds or use webhooks; queue/provider delays vary and a client timeout does not cancel the job. Poll GET /lyric-videos/{id} until status becomes completed or failed or canceled.

GET

/lyric-videos/{id}

Returns the status of a lyric video job. When completed, the response includes the final video and thumbnail URLs.

200 OK — completed
{
  "id": "dEf456UvW",
  "status": "completed",
  "credits_charged": 5940,
  "credits_refunded": 0,
  "duration_seconds": 180,
  "video_url": "https://storage.googleapis.com/.../final.mp4",
  "thumbnail_url": "https://storage.googleapis.com/.../thumb.jpg",
  "error": null,
  "created_at": "2026-01-01T12:00:00.000Z",
  "completed_at": "2026-01-01T12:07:41.000Z"
}
FieldTypeDescription
status string One of 'processing', 'completed', 'failed' or 'canceled'.
credits_refunded number Tokens returned after failure or confirmed cancellation.
cancellation_requested boolean Present as true after a cancellation request; it is not terminal confirmation.
duration_seconds number Probed duration of the song, which the price was based on.
video_url string Final rendered video (completed jobs only).
thumbnail_url string Video thumbnail (completed jobs only).
error object | null Code/message object on failure, otherwise null. Inspect credits_refunded for returned tokens.
DELETE

/lyric-videos/{id}

Cancel a queued video immediately, without charge, before a worker first claims it for media preparation. Cancellation and that claim are checked atomically; if cancellation wins, the job cannot start or charge. Once preparation has started (including retries and waiting for the next generation task), the configured waiting period from job creation applies (60-minute fallback), independently of the displayed ETA. Earlier requests return 409 not_cancellable_yet with Retry-After in seconds. An eligible request is queued and returns 202 with cancellation_requested: true. Poll GET or wait for job.canceled to confirm the canceled status and full refund of charged tokens; completion can win the race. Repeating DELETE after confirmed cancellation returns 204 with no body. Other terminal states return 409 not_cancellable. Transcription has no public cancellation endpoint.

Cancel a video
curl -X DELETE https://api.onemoreshot.ai/v1/lyric-videos/dEf456UvW \
  -H "Authorization: Bearer oms_live_..."
202 Accepted — cancellation requested
{ "id": "dEf456UvW", "status": "processing", "cancellation_requested": true }

POST /music-videos

Create a complete narrative music video from a song, prompt and cast. Existing API keys work here too. Creative assets are generated internally; no separate preview approval is needed.

FieldTypeDescription
song_url required string Public audio URL; 10–420 seconds and at most 100 MB.
prompt required string Nonblank creative direction, at most 10,000 characters.
characters required array 1–4 characters. Each needs name (1–100 characters), image_url and role (singer or character). Exactly one singer. Optional description: 1–2,000 characters. Each image: at most 10 MB / 25 megapixels. No duets.
quality string basic (default): 720p, 50 tokens/second. pro: 1080p, 80 tokens/second.
aspect_ratio string 9:16 (default), 16:9 or 1:1.
style_preset string 90s-anime, ghibli, yellow-family, pixar-3d, ps2-low-poly, disney-90s, lego, cartoon-network, pixel-art. Omit for realistic styling.
webhook_url string Optional public HTTPS URL for signed terminal notifications.
idempotency_key string Optional alternative to the Idempotency-Key header; both must match when provided.
POST /music-videos — JSON body
{
  "song_url": "https://example.com/song.mp3",
  "prompt": "Two friends reunite in a neon-lit city.",
  "quality": "basic",
  "aspect_ratio": "9:16",
  "style_preset": "90s-anime",
  "characters": [
    {
      "name": "Alex",
      "role": "singer",
      "image_url": "https://example.com/alex.jpg"
    },
    {
      "name": "Sam",
      "role": "character",
      "image_url": "https://example.com/sam.jpg",
      "description": "Alex’s childhood friend"
    }
  ],
  "webhook_url": "https://example.com/webhooks/oms"
}

Send to https://api.onemoreshot.ai/v1/music-videos with Bearer authentication, Content-Type: application/json and an Idempotency-Key. Unknown fields, creative seeds, preview assets and provider settings are rejected. Returns 202 with a job ID and initially zero charged tokens. Workers snapshot media before charging ceil(measured seconds × rate). Retries retain the job’s chosen renderer configuration.

GET /music-videos/{id}

Uses the same status, duration, charge/refund, timestamps, error and completed video_url/thumbnail_url fields as lyric-video jobs. Signed webhooks use type: narrative_video with the existing delivery IDs and terminal events. Poll every 15–30 seconds.

DELETE /music-videos/{id}

Cancel immediately before the first media-preparation claim. After preparation starts, the configured delay from job creation applies (60-minute fallback). Returns 202 while queued; poll or wait for job.canceled to confirm. Confirmed repeat cancellations return 204. Charged failures and confirmed cancellations receive one full refund. All narrative endpoints share existing API rate limits and queues.

Webhooks

Instead of polling, pass a webhook_url (https only) in the body of POST /lyric-videos or POST /lyric-videos/transcriptions. When the job completes, fails or is canceled, we POST its terminal status and result, charges/refunds, error and timestamps, plus a type field.

Events & headers

HeaderValueDescription
X-OMS-Event job.completed | job.failed | job.canceled What happened to the job.
X-OMS-Delivery string Equals the job id — use it to deduplicate deliveries.
X-OMS-Signature sha256=<hex> HMAC-SHA256 of the raw request body, keyed with your webhook secret.
Example delivery — job.completed
POST https://your-server.com/oms-webhook
Content-Type: application/json
X-OMS-Event: job.completed
X-OMS-Delivery: dEf456UvW
X-OMS-Signature: sha256=6ac1…e2

{
  "id": "dEf456UvW",
  "type": "lyric_video",
  "status": "completed",
  "credits_charged": 5940,
  "credits_refunded": 0,
  "duration_seconds": 180,
  "video_url": "https://storage.googleapis.com/.../final.mp4",
  "thumbnail_url": "https://storage.googleapis.com/.../thumb.jpg",
  "error": null,
  "created_at": "2026-01-01T12:00:00.000Z",
  "completed_at": "2026-01-01T12:07:41.000Z"
}

Verifying signatures

Your webhook secret is created automatically with your first webhook job and returned by GET /account as webhook_secret. Compute an HMAC-SHA256 of the raw request body and compare it to X-OMS-Signature — reject the request if they differ.

Node.js / Express
const crypto = require('crypto');
const express = require('express');
const app = express();

// Verify against the RAW request body — the exact bytes we signed.
app.post('/oms-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.OMS_WEBHOOK_SECRET) // from GET /account
    .update(req.body)
    .digest('hex');
  const signature = req.headers['x-oms-signature'] || '';

  const actualBytes = Buffer.from(signature, 'utf8');
  const expectedBytes = Buffer.from(expected, 'utf8');
  const valid = actualBytes.length === expectedBytes.length &&
    crypto.timingSafeEqual(actualBytes, expectedBytes);
  if (!valid) return res.status(401).end();

  const job = JSON.parse(req.body);
  // Dedupe on req.headers['x-oms-delivery'] (equals job.id).
  console.log(job.status, job.video_url);
  res.status(200).end();
});

Delivery & retries

Respond with any 2xx within 10 seconds. Normally there are up to three total HTTP attempts (initial delivery plus two retries). Webhooks are best-effort; keep polling available as a fallback. Retries and crash recovery can produce duplicates: persistently deduplicate by X-OMS-Delivery before applying side effects, and acknowledge duplicates with 2xx.

Support

The API is in beta and evolving quickly. For questions, higher rate limits or volume pricing, reach out via the in-app support chat or our help center.

Create your video