Martini Generation API

Generate media from JavaScript or TypeScript, bill in olives, and place each request on a Martini canvas.

Set this up with a coding agent

Paste this into Claude, Cursor, or another coding agent working in your backend — it covers credentials, supported models, a complete route implementation, and an acceptance checklist.

Overview

Martini provides a typed client for asynchronous image and video generation. Requests are authenticated, validated, billed in olives, added to Generation History, and returned with durable output URLs.

Every request produces exactly one output: one generated video or image.

Quick start

1. Install the Martini client

npm install --save @martini-film/client

Use Node.js 18 or later. Call Martini from trusted server code, never from the browser.

2. Create an API key

Generation API access is currently enabled for selected accounts. Create a Martini API key or request access in your Martini API settings. The full key is shown once. Store it in a server-side secret such as MARTINI_API_KEY; never expose it in browser code or a NEXT_PUBLIC_* variable.

Sign in with a full Martini account. Organization admins can create and revoke keys; billing members can view existing key metadata but cannot manage keys.

The client sends the key as Authorization: Key <key>. Revoking the key blocks submit, status, and result calls.

3. Create the client

import { createMartiniClient } from '@martini-film/client' const martini = createMartiniClient({ apiKey: process.env.MARTINI_API_KEY, })

Supported endpoints

Endpoint aliasPurpose
bytedance/seedance-2.0/text-to-videoGenerate one video from a prompt.
bytedance/seedance-2.0/image-to-videoGenerate one video from a start image, with an optional end image.
bytedance/seedance-2.0/reference-to-videoGenerate one video using supported image, MP4, and audio references.
bytedance/seedance-2.0/fast/text-to-videoGenerate one 480p or 720p video with the faster Seedance tier.
bytedance/seedance-2.0/fast/image-to-videoGenerate one 480p or 720p video from a start image with the faster Seedance tier.
bytedance/seedance-2.0/fast/reference-to-videoGenerate one 480p or 720p video from reference media with the faster Seedance tier.
bytedance/seedance-2.0/mini/text-to-videoGenerate one 480p or 720p video with the most affordable Seedance tier.
bytedance/seedance-2.0/mini/image-to-videoGenerate one 480p or 720p video from a start image with the most affordable Seedance tier.
bytedance/seedance-2.0/mini/reference-to-videoGenerate one 480p or 720p video from reference media with the most affordable Seedance tier.
xai/grok-imagine-video/v1.5/text-to-videoGenerate one video from a prompt at up to 1080p with native audio.
xai/grok-imagine-video/v1.5/image-to-videoGenerate one video from a start image at up to 1080p with native audio.
xai/grok-imagine-video/v1.5/reference-to-videoGenerate one 480p or 720p video from up to seven images and one tagged audio reference.
bytedance/seedance-2.5/text-to-videoGenerate one video with up to 30 seconds of native audio and video.
bytedance/seedance-2.5/image-to-videoGenerate one video from a start image, with an optional end image.
bytedance/seedance-2.5/reference-to-videoGenerate one video from up to 50 image, video, and audio references.
nano-banana-2Generate one image from a prompt.
nano-banana-2/editGenerate one edited image from a prompt and image references.

Supported input subset

Seedance 2.0 supports prompts, 4–15 second durations, resolution, aspect ratio, native audio, bitrate mode, start and end images, supported image/MP4/audio references, and an end-user ID. If duration is omitted or set to auto, Martini uses 5 seconds.

Seedance 2.5 supports the same three generation modes at 480p, 720p, or 1080p, with 4–30 second durations and no bitrate mode. Its auto duration lets the model select a 4–30 second take.

Grok Imagine 1.5 supports text-to-video and image-to-video at 480p, 720p, or 1080p. Its reference mode accepts up to seven images and one optional tagged audio reference at 480p or 720p. Duration is an integer from 1–15 seconds, and native audio is always enabled.

Nano Banana 2 supports a prompt, seed, aspect ratio, output format, safety tolerance, resolution, limit-generations, web search, thinking level, and system prompt. Image references are accepted only on nano-banana-2/edit.

num_images must be 1.

Reference media and prompt binding

Seedance reference arrays are positional, and array order determines each 1-based token. Bind every reference meant to influence the generation. Multiple video references and every audio reference must be bound.

Seedance input fieldPrompt tokenExample
image_urls@ImageN@Image1 is the lead character.
video_urls@VideoNFollow the camera movement from @Video1.
audio_urls@AudioNTime the movement to @Audio1.
Bare labels such as NORMAN or “the first character image” do not bind media. Write @Image1 is Norman and use @Image1 wherever the prompt needs that identity.
const result = await martini.subscribe( 'bytedance/seedance-2.0/reference-to-video', { input: { prompt: '@Image1 is the lead character. @Image2 is the laboratory. Keep exactly one instance of @Image1. Inside @Image2, @Image1 dodges the attack. Follow the camera movement from @Video1 and time the impact to @Audio1.', image_urls: [characterUrl, locationUrl], video_urls: [cameraGuideUrl], audio_urls: [impactAudioUrl], duration: '8', resolution: '720p', aspect_ratio: '16:9', generate_audio: true, }, }, )

Grok reference-to-video uses reference_image_urls and reference_audio_urls. It requires 1–7 images and accepts at most one audio file; address them with the same @ImageN and @Audio1 tokens.

const result = await martini.subscribe( 'xai/grok-imagine-video/v1.5/reference-to-video', { input: { prompt: '@Image1 crosses the frame to the footsteps in @Audio1.', reference_image_urls: [characterUrl], reference_audio_urls: [footstepsUrl], duration: 8, resolution: '720p', aspect_ratio: '16:9', }, }, )

Grok reference-to-video has no video-reference array, accepts at most one audio reference, and supports 480p or 720p. Preserve array order and keep every token within the supplied image or audio array.

The following reference rules and limits apply to Seedance 2.0:

  • Each media type has its own numbering: image 1, video 1, and audio 1 are separate references.
  • One URL is one reference. A contact sheet is one image; Martini does not split or name its panels.
  • Use separate files when individual people, locations, or storyboard panels need distinct prompt tokens. If one sheet repeats a person across views, state that every panel is the same single character.
  • Only tagged audio references are used. This is separate from generated audio.
  • Unbound images do not fail validation. They are still submitted, with an advisory in martini.warnings, because Seedance may not use them. Treat that advisory as a prompt bug unless the reference was meant to be unbound.
  • Multiple video references and every audio reference must have matching tokens.
  • Out-of-range tokens and duplicate URLs fail validation.

Seedance 2.0 accepts up to 9 images, 3 videos, and 3 audio files, with 12 files total. Seedance 2.5 accepts up to 30 images, 10 videos, and 10 audio files, with 50 files total. Reference videos must total 2–15 seconds for Seedance 2.0 or 2–30 seconds for Seedance 2.5. Use the image-to-video endpoint for start and end frames; use the reference-to-video endpoint for reference arrays.

Keep the action achievable within the selected duration. Split prompts with many cuts, dialogue lines, entrances, and impacts into separate generations when their order must remain clear.

Unsupported features and values

The following fail before olives are charged or a model provider is contacted:

  • Synchronous run(), streaming status or output, and realtime/WebSocket inference
  • Provider BYOK
  • Webhooks or callback URLs
  • sync_mode: true, unregistered endpoints, unknown fields, and multiple outputs

Queue methods

MethodMartini behavior
martini.subscribe()Submit, poll status, and return the completed result.
martini.queue.submit()Generate immediately by default, or create an editable draft with mode: draft.
martini.queue.generate()Start generation for an API-created draft using its latest Martini settings.
martini.queue.status()Return the current queue state.
martini.queue.result()Return the completed output with a durable Martini URL.
martini.queue.cancel()Cancel a queued or running request when the model provider still allows it.

subscribe() uses polling. A failed or cancelled request ends polling with an error; it does not remain in a polling loop. It always generates immediately; draft mode is available only on queue.submit().

await martini.queue.cancel(endpoint, { requestId: request_id, })

Each API key may have up to 300 active generations. Submit traffic is limited to 300 requests per 10 seconds and 600 requests per minute; excess requests return RATE_LIMITED.

Submit and status responses include olive_cost once pricing is available, and result responses always include it. An overlapping retry of the same idempotent submission can briefly omit the field while the original request is still being priced. This is the snapshotted charge in olives, so it does not change when model pricing changes. Martini applies any negotiated billing-organization, model, and resolution multiplier before rounding the charge to four decimal places. The same negotiated multipliers apply to generations in the Martini interface; MCP generations keep standard pricing.

const { request_id, martini: destination, olive_cost } = await martini.queue.submit( 'nano-banana-2', { input: { prompt: 'A practical miniature moon base, photographed on 35mm.' } }, ) console.log(destination.preview_url) const status = await martini.queue.status('nano-banana-2', { requestId: request_id, }) const result = await martini.queue.result('nano-banana-2', { requestId: request_id, }) console.log(olive_cost, status.olive_cost, result.data.olive_cost) console.log(result.data.martini.preview_url)

Create a draft without generating

Set mode: 'draft' to author an editable canvas node without reserving olives, consuming generation capacity, or contacting a model provider. Generate it later using its latest Martini draft settings.

const draft = await martini.queue.submit('nano-banana-2', { input: { prompt: 'A practical miniature moon base, photographed on 35mm.' }, mode: 'draft', projectId, }) console.log(draft.martini?.state) // "draft" console.log(draft.martini?.preview_url) const queued = await martini.queue.generate({ requestId: draft.request_id, }) console.log(queued.martini?.state) // "generating"

Destinations and Martini options

OptionBehavior
projectIdOverride the key’s default project. Without a canvas override, Martini uses the project’s first canvas.
canvasIdOverride the destination canvas. It must belong to the selected project.
mode: 'draft'Create an editable draft without starting or charging for generation. Omit it to generate immediately.
placement: 'auto'Request automatic placement. This is the only supported placement value.
idempotencyKeyOptional key for replaying the same submit without another charge or provider job.

Without destination options, Martini uses the defaults bound to the API key. The first key for an organization creates or reuses the private API Generations project and its Canvas 1.

Overrides require project write access and must resolve to the key’s billing organization. Projects without a canvas return CANVAS_NOT_FOUND.

const result = await martini.subscribe('nano-banana-2', { input: { prompt: 'A graphite storyboard frame of a desert observatory.' }, projectId, idempotencyKey: 'storyboard-frame-42', })

Media URLs and base64

Base64 works in the normal URL fields

Use a data:<mime>;base64,<bytes> URL directly in fields such as image_url, image_urls, video_urls, and audio_urls. There is no separate base64 field or upload call.

// Image-to-video image_url: `data:image/png;base64,${imageBase64}` // Reference-to-video video_urls: [`data:video/mp4;base64,${videoBase64}`]

Media URL fields accept:

  • Public HTTPS URLs
  • Martini-hosted HTTPS URLs
  • Base64 data URLs in the form data:<mime>;base64,<bytes>
MediaLimit
Images10 MB decoded or fetched bytes per image
MP4 video references30 MB decoded or fetched bytes per video
MP3 or WAV references15 MB decoded or fetched bytes per audio file
Complete request body72 MiB, including base64 and JSON

Martini validates type and content, then uploads data URLs to durable storage before billing, provider submission, or canvas persistence. Binary File and Blob values are not accepted directly in generation media fields; upload them as reusable assets first. Completed results contain durable Martini URLs, not expiring provider URLs.

Base64 adds roughly 33% to the media size. One 30 MB video becomes about 40 MiB in the request; two maximum-sized inline videos exceed the request-body limit. Use HTTPS URLs for large or multiple references.

Asset uploads

Upload reusable project media before generation when you have a binary file, a large reference, or media you want to use across multiple requests. Uploads create visible canvas assets in the API key's default project and canvas, then return a stable Martini URL.

To upload into another accessible project, provide both projectId and canvasId. Upload destination overrides must include both values.

const reference = await martini.assets.upload(file, { filename: 'reference.mp4', contentType: 'video/mp4', }) const result = await martini.subscribe('bytedance/seedance-2.0/reference-to-video', { input: { prompt: 'Follow the camera movement from @Video1 for a nighttime city shot.', video_urls: [reference.url!], }, })

Direct upload flow

martini.assets.upload() wraps the full flow: prepare a create-only upload URL, PUT bytes directly to Martini storage, complete the upload, then poll asset status with jittered backoff until processing finishes.

StepMethodBehavior
1assets.prepareUpload()Creates an uploading canvas placeholder and returns a presigned PUT URL.
2PUT uploadUrlUploads bytes directly to storage with the returned headers.
3assets.completeUpload()Verifies the object, claims the upload, and queues thumbnail, proxy, waveform, or metadata processing.
4assets.get()Polls for completed or failed status and returns the durable asset URL.

Upload limits

MediaDirect upload limitBase64 fallback limit
Images10 MB JPEG, PNG, or WebP10 MB
Video30 MB MP430 MB MP4
Audio15 MB MP3 or WAV15 MB
Batch prepare/complete50 files or asset IDsOne file per call

The client uses direct upload for normal file transfers. assets.uploadBase64() exists for small server-side fallbacks and local development paths where direct storage is unavailable. At high volume, keep base64 low-concurrency and prefer direct uploads.

Upload completion can return queued while Martini waits for a bounded processing worker. Keep the default SDK polling backoff unless you are coordinating your own queue.

Automatic placement

Visible automatic placement

After validation and a successful charge, Martini creates a visible pending generation on the selected canvas, placed to the right of the rightmost root node. Completion replaces the pending state with the generated asset; failure remains visible on the canvas.

If inference completes but final canvas placement fails, the generation remains completed and billed. The durable result is still available, and placement can be retried without another provider call or olive charge.

Submit, status, and result responses include a martini object with preview_url, project_id, canvas_id, asset_id, and any non-blocking input advisories in warnings.

Queue lifecycle

Generation stateQueue response
PendingIN_QUEUE
RunningIN_PROGRESS
CompletedCOMPLETED
FailedHTTP error

queue.submit() returns a Martini-owned public request ID. Provider operation IDs remain private. Returned response, status, and cancellation URLs use Martini API URLs. Status, result, and cancel calls must use the same API key and endpoint alias used at submission.

Error contract

Errors use JSON with a stable Martini code. Provider details are sanitized before they are returned.

HTTP statusStable codes
400INVALID_TARGET_URL, UNSUPPORTED_OPERATION, WEBHOOKS_UNSUPPORTED, UNSUPPORTED_MARTINI_MODE
401INVALID_API_KEY, API_KEY_REVOKED
402INSUFFICIENT_OLIVES, SPEND_CAP_EXCEEDED
403PROJECT_ACCESS_DENIED, BILLING_ORGANIZATION_MISMATCH, GENERATION_API_DISABLED
404ENDPOINT_NOT_REGISTERED, REQUEST_NOT_FOUND, CANVAS_NOT_FOUND
409API_KEY_DEFAULTS_MISSING, DRAFT_ALREADY_GENERATING, REQUEST_ALREADY_TERMINAL, RESULT_NOT_READY, CANCELLED
422INVALID_INPUT, UNSUPPORTED_INPUT, INVALID_MEDIA, MEDIA_TOO_LARGE, GENERATION_FAILED
429RATE_LIMITED
500GENERATION_SUBMISSION_FAILED, PLACEMENT_FAILED
503GENERATION_API_ACCESS_UNAVAILABLE

Seedance 2.5 example

This example uses duration: 'auto', allowing the model to select a 4–30 second take.

import { createMartiniClient } from '@martini-film/client' const martini = createMartiniClient({ apiKey: process.env.MARTINI_API_KEY, }) const result = await martini.subscribe( 'bytedance/seedance-2.5/text-to-video', { input: { prompt: 'An octopus football game under the sea, photographed as a wide cinematic shot.', duration: 'auto', resolution: '720p', aspect_ratio: '16:9', generate_audio: true, }, onQueueUpdate(update) { console.log(update.status) }, }, ) console.log(result.requestId) console.log(result.data.video.url)

Nano Banana 2 example

Nano Banana returns one image. Keep num_images: 1. Use the /edit endpoint for image references; video, audio, and PDF context are unsupported.

import { createMartiniClient } from '@martini-film/client' const martini = createMartiniClient({ apiKey: process.env.MARTINI_API_KEY, }) const result = await martini.subscribe('nano-banana-2', { input: { prompt: 'A practical miniature moon base at blue hour, photographed on 35mm film.', num_images: 1, aspect_ratio: '16:9', output_format: 'png', resolution: '1K', }, onQueueUpdate(update) { console.log(update.status) }, }) console.log(result.requestId) console.log(result.data.images[0].url)