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.
To run a multi-step workflow saved from a canvas with the same key, see the Workflows API.
Quick start
1. Install a Martini client
Use Node.js 18 or later, or Python 3.10 or later. Call Martini from trusted server code, never from the browser. This page describes @martini-film/client 0.10.0 and martini-client 0.3.0.
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
In Python the queue methods below are martini.generations.* — the Martini-native routes described further down — plus martini.models(), martini.workflows.*, martini.runs.*, and martini.assets.*. The fal-compatible queue is JavaScript-only.
Supported endpoints
| Endpoint alias | Purpose |
|---|---|
bytedance/seedance-2.0/text-to-video | Generate one video from a prompt. |
bytedance/seedance-2.0/image-to-video | Generate one video from a start image, with an optional end image. |
bytedance/seedance-2.0/reference-to-video | Generate one video using supported image, MP4, and audio references. |
bytedance/seedance-2.0/fast/text-to-video | Generate one 480p or 720p video with the faster Seedance tier. |
bytedance/seedance-2.0/fast/image-to-video | Generate one 480p or 720p video from a start image with the faster Seedance tier. |
bytedance/seedance-2.0/fast/reference-to-video | Generate one 480p or 720p video from reference media with the faster Seedance tier. |
bytedance/seedance-2.0/mini/text-to-video | Generate one 480p or 720p video with the most affordable Seedance tier. |
bytedance/seedance-2.0/mini/image-to-video | Generate one 480p or 720p video from a start image with the most affordable Seedance tier. |
bytedance/seedance-2.0/mini/reference-to-video | Generate one 480p or 720p video from reference media with the most affordable Seedance tier. |
xai/grok-imagine-video/v1.5/text-to-video | Generate one video from a prompt at up to 1080p with native audio. |
xai/grok-imagine-video/v1.5/image-to-video | Generate one video from a start image at up to 1080p with native audio. |
xai/grok-imagine-video/v1.5/reference-to-video | Generate one 480p or 720p video from up to seven images and one tagged audio reference. |
alibaba/wan-3.0/text-to-video | Generate one 2-30 second video from a prompt at 480p, 720p, or 1080p. |
alibaba/wan-3.0/image-to-video | Generate one 2-30 second video from a start image. |
alibaba/wan-3.0/reference-to-video | Generate one video from up to 10 image, 5 video, and 5 audio references. |
alibaba/wan-3.0-prime/text-to-video | Generate one 2-30 second video with the premium Wan 3.0 tier. |
alibaba/wan-3.0-prime/image-to-video | Generate one 2-30 second video from a start image with the premium Wan 3.0 tier. |
alibaba/wan-3.0-prime/reference-to-video | Generate one video from reference media with the premium Wan 3.0 tier. |
bytedance/seedance-2.5/text-to-video | Generate one video with up to 30 seconds of native audio and video. |
bytedance/seedance-2.5/image-to-video | Generate one video from a start image, with an optional end image. |
bytedance/seedance-2.5/reference-to-video | Generate one video from up to 50 image, video, and audio references. |
minimax/h3/text-to-video | Generate one 2K video from a prompt. |
minimax/h3/image-to-video | Generate one 2K video from a start image, with an optional end image. |
minimax/h3/reference-to-video | Generate one 2K video from up to nine image, three video, and three audio references. |
fal-ai/minimax/hailuo-03/text-to-video | Legacy alias for minimax/h3/text-to-video (fal's early-access id). |
fal-ai/minimax/hailuo-03/image-to-video | Legacy alias for minimax/h3/image-to-video (fal's early-access id). |
fal-ai/minimax/hailuo-03/reference-to-video | Legacy alias for minimax/h3/reference-to-video (fal's early-access id). |
minimax/h3-max/text-to-video | Generate one 480p, 768p, or 1080p video with native audio, near-instantly. |
minimax/h3-max/image-to-video | Generate one 480p, 768p, or 1080p video from a start image, with an optional end image. |
minimax/h3-max/reference-to-video | Generate one 480p, 768p, or 1080p video from up to nine image, three video, and three audio references (12 files total). |
minimax/h3-max-turbo/text-to-video | Generate one 480p, 768p, or 1080p video with native audio at half the H3 Max cost (preview). |
minimax/h3-max-turbo/image-to-video | Generate one 480p, 768p, or 1080p video from a start image, with an optional end image, at half the H3 Max cost (preview). |
nano-banana-2 | Generate one image from a prompt. |
nano-banana-2/edit | Generate one edited image from a prompt and image references. |
Discovery
The same table is available to your key at runtime, with your organization’s negotiated pricing attached, so you do not have to hard-code this list:
Each entry carries the endpoint alias, its model summary, the billable unit (second or image), any negotiated price multipliers, and a deprecation block when an alias is scheduled for removal. Endpoints your key cannot invoke are not listed. Where an endpoint carries a reference-image surcharge (Hailuo 03’s reference mode), its model.pricing.reference_images block states the included count and per-image price. Input fields are not published there yet — use the sections below.
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.
Hailuo 03 renders 5–15 second takes at a fixed 2K with native stereo audio, and its reference mode takes up to nine image, three video, and three audio references. H3 Max — fal’s post-trained Hailuo 03 — renders 5–15 seconds at 480P, 768P (default), or 1080P with native audio, an optional end frame, a seed, and a prompt_expansion_mode of disabled or balanced (default); its reference mode takes the same nine image, three video, and three audio references, capped at 12 files combined. On Hailuo 03, the first five reference images are included; each additional distinct image adds 0.8 olives, and the submit response carries a warning with the exact amount. H3 Max Turbo (minimax/h3-max-turbo, preview) is the fastest video model on the API: the same text-to-video and image-to-video inputs as H3 Max at half the cost and roughly 2.5x lower latency. It has no reference route — reference requests are rejected, not downgraded.
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 field | Prompt token | Example |
|---|---|---|
image_urls | @ImageN | @Image1 is the lead character. |
video_urls | @VideoN | Follow the camera movement from @Video1. |
audio_urls | @AudioN | Time the movement to @Audio1. |
NORMAN or “the first character image” do not bind media. Write @Image1 is Norman and use @Image1 wherever the prompt needs that identity.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.
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
| Method | Martini 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().
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.
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.
Destinations and Martini options
| Option | Behavior |
|---|---|
projectId | Override the key’s default project. Without a canvas override, Martini uses the project’s first canvas. GET /v1/projects lists the projects the key can name here, with canEdit on each. |
canvasId | Override the destination canvas. It must belong to the selected project; GET /v1/projects/{projectId}/canvases lists them. |
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. |
idempotencyKey | Optional 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.
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.
Media URL fields accept:
- Public HTTPS URLs
- Martini-hosted HTTPS URLs
- Base64 data URLs in the form
data:<mime>;base64,<bytes>
| Media | Limit |
|---|---|
| Images | 10 MB decoded or fetched bytes per image |
| MP4 video references | 30 MB decoded or fetched bytes per video |
| MP3 or WAV references | 15 MB decoded or fetched bytes per audio file |
| Complete request body | 72 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.
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.
| Step | Method | Behavior |
|---|---|---|
| 1 | assets.prepareUpload() | Creates an uploading canvas placeholder and returns a presigned PUT URL. |
| 2 | PUT uploadUrl | Uploads bytes directly to storage with the returned headers. |
| 3 | assets.completeUpload() | Verifies the object, claims the upload, and queues thumbnail, proxy, waveform, or metadata processing. |
| 4 | assets.get() | Polls for completed or failed status and returns the durable asset URL. |
Upload limits
| Media | Direct upload limit | Base64 fallback limit |
|---|---|---|
| Images | 10 MB JPEG, PNG, or WebP | 10 MB |
| Video | 30 MB MP4 | 30 MB MP4 |
| Audio | 15 MB MP3 or WAV | 15 MB |
| Batch prepare/complete | 50 files or asset IDs | One 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 state | Queue response |
|---|---|
| Pending | IN_QUEUE |
| Running | IN_PROGRESS |
| Completed | COMPLETED |
| Failed | HTTP 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.
Martini-native routes
The same generations are also available as plain REST under /v1, the namespace the Workflows API uses: one resource describes a generation from draft to terminal state and carries its output once completed, so a caller polls one URL and never needs the endpoint alias to poll. Same key, same models, same billing and canvas placement as the queue methods above.
The whole /v1 surface, including the upload routes and every model’s input schema, is described by an OpenAPI 3.1 document at api.martini.film/v1/openapi.json (no authentication). Point a client generator or a coding agent at it for any language the SDKs do not cover.
| Method | Path | Purpose |
|---|---|---|
POST | /v1/generations | Submit a generation, or a draft with mode: "draft". 201 on create, 200 on an idempotent replay. |
GET | /v1/generations/{id} | The generation; output is on this object once status is completed. |
POST | /v1/generations/{id}/cancel | Cancel a queued or running generation. |
POST | /v1/generations/{id}/generate | Start a draft using its latest Martini settings. |
GET | /v1/models | The aliases your key can invoke, with your organization’s negotiated pricing. |
Authenticate with Authorization: Bearer $MARTINI_API_KEY (or Key). The submit body carries the model input in its fal field names plus the Martini options that the queue methods send as headers:
Optional body fields: projectId, canvasId (requires projectId), placement: "auto", idempotencyKey, and mode: "draft". Poll GET /v1/generations/{id} until status is completed, failed, or cancelled. A draft never settles until POST /v1/generations/{id}/generate starts it.
Both clients wrap the five routes as generations.submit(), status(), cancel(), generate(), wait(), and subscribe(), plus models(). subscribe() and wait() return a failed or cancelled generation with its error rather than throwing; check output or status. In Python, generation.result() returns the typed output or raises GenerationFailedError.
The generation object
| Field | Meaning |
|---|---|
id | The public request id. It is also the canvas asset the generation lands on. |
endpoint | The registered alias the request was submitted against. |
status | draft, pending, running, completed, failed, or cancelled. |
origin | Always "api" for generations started here. |
createdAt, startedAt, completedAt | ISO timestamps; startedAt and completedAt are null until reached. |
oliveCost | The snapshotted charge in olives, or null while a concurrent idempotent submit is still being priced. |
output | The model output in its fal field names once completed (for example output.video.url); otherwise null. |
error | { code, message } when failed or cancelled; otherwise null. |
warnings[] | Non-blocking input advisories. The generation is still accepted. |
martini | { previewUrl, projectId, canvasId, assetId } once a canvas asset exists; otherwise null. |
links | self and cancel paths, plus generate while the request is a draft. |
Errors on these routes are { "error": "<copy for a human>", "code": "<STABLE_CODE>" } with the same stable codes as the table below; a malformed body is 400 INVALID_REQUEST, and 429 RATE_LIMITED carries a Retry-After header in seconds. The queue methods above answer the same codes with message in place of error.
Error contract
Errors use JSON with a stable Martini code. Provider details are sanitized before they are returned.
| HTTP status | Stable codes |
|---|---|
400 | INVALID_TARGET_URL, UNSUPPORTED_OPERATION, WEBHOOKS_UNSUPPORTED, UNSUPPORTED_MARTINI_MODE, UNSUPPORTED_API_VERSION, INVALID_REQUEST |
401 | INVALID_API_KEY, API_KEY_REVOKED, API_KEY_EXPIRED |
402 | INSUFFICIENT_OLIVES, SPEND_CAP_EXCEEDED |
403 | PROJECT_ACCESS_DENIED, BILLING_ORGANIZATION_MISMATCH, GENERATION_API_DISABLED, RUN_KEY_NOT_ALLOWED |
404 | ENDPOINT_NOT_REGISTERED, REQUEST_NOT_FOUND, CANVAS_NOT_FOUND |
409 | API_KEY_DEFAULTS_MISSING, DRAFT_ALREADY_GENERATING, REQUEST_ALREADY_TERMINAL, RESULT_NOT_READY, PROJECT_DOCUMENT_TOO_LARGE, CANCELLED |
422 | INVALID_INPUT, UNSUPPORTED_INPUT, INVALID_MEDIA, MEDIA_TOO_LARGE, GENERATION_FAILED |
429 | RATE_LIMITED |
500 | GENERATION_SUBMISSION_FAILED, PLACEMENT_FAILED |
503 | GENERATION_API_ACCESS_UNAVAILABLE |
Seedance 2.5 example
This example uses duration: 'auto', allowing the model to select a 4–30 second take.
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.