Skip to main content

Migration guide

Migrate one workflow, not a whole catalogue.

Two platforms can offer the same task and still disagree about field names, defaults, terminal states, error meanings and what one unit of billing is. A migration that starts with the base URL finds all of that in production.

The challenge

Same task, different contract

The model is the part that ports cleanly. What does not port is everything around it: which fields are required, what the defaults are, how a reference image gets in, when money is reserved, and what your code should do with a state it has never seen.

01

Field names and defaults rarely line up

Write the mapping down per endpoint instead of inferring it. Input is validated against the published schema with additionalProperties closed, so an unmapped field is a 400 on the first real request rather than a warning in a log.

02

Money is reserved at admission, not at completion

createTask holds the estimate and returns it as cost with settled: false. If your current provider only charges on success, reconciliation needs to understand holds before you move any traffic.

03

The error vocabulary is closed and small

Nine values cover every failure, and content_rejected is one of them: a refusal is a terminal task, not an exception. Map your existing error branches onto those nine before cutover, not after.

The approach

Shadow one endpoint, compare the numbers, then move

Take a single workflow, record its current inputs, outputs, failures and unit cost, and run the same conditions here. Compare the estimate from jobs/quote against what you pay today, keep both paths live behind a flag, and raise the share only while the numbers hold.

Quote first: it runs the same validation and admission checks as create, without reserving funds or starting work.

POST /api/v1/jobs/quote

{
  "model": "<publisher>/<model>/text-to-video",
  "input": {
    "prompt": "A slow aerial drift over a misty pine forest at sunrise",
    "duration_seconds": 5,
    "resolution": "1080p"
  }
}

200 OK — nothing reserved, nothing started

{
  "code": 200,
  "msg": "success",
  "request_id": "req_example_quote",
  "data": {
    "quoteId": "qte_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "model": "<publisher>/<model>/text-to-video",
    "estimatedCost": "0.750000",
    "maxCharge": "0.750000",
    "currency": "USD",
    "quantity": "5",
    "unit": "second",
    "expiresAt": "2026-09-12T12:05:00Z"
  }
}

Contract

The parts worth pinning down

fal alternative: migrate image and video calls — The parts worth pinning down
Field or headerValue or limit
POST /api/v1/jobs/quoteno hold · signed quote valid 5 min
quoteId + expectedCost40901 when the price moved
40240201 balance · 40202 spend cap
published call nameimmutable · renames keep a 308

Swipe horizontally to see all columns.

Integration

From first call to production

  1. 01Pick one endpoint and capture ten representative requests, their outputs and their current cost.
  2. 02Map every field onto the target endpoint's input_schema and store the mapping with a version.
  3. 03Quote each mapped body and compare estimatedCost, quantity and unit against today's bill.
  4. 04Shadow, then canary, then raise the share — with the old path callable at every step.

Fit

When this path is the right one

Choose this when

  • You want one balance, one set of spend caps and one task contract across image, video and text.
  • Your bottleneck is content policy or per-model availability rather than raw model quality.

Look elsewhere when

  • You need a same-day cutover for everything. Contract differences surface per endpoint, and they surface late.
  • Your integration calls the model from the client. Keys belong on the server here.

Evidence

What you can verify yourself

Quote is a real dry run

POST /api/v1/jobs/quote runs the same validation and admission checks as create, returns estimatedCost, maxCharge, quantity and unit, and reserves nothing. The signed quote stays valid for five minutes.

A moved price fails instead of surprising you

Send quoteId and expectedCost with create. If the price changed in between, admission returns business code 40901 before anything is reserved.

Published call names do not move

A released call name stays valid; a rename keeps a 308 and an alias. Your mapping table does not rot silently between releases.

Before launch

Platform behaviour to confirm

  • Version the field map and keep fixtures for one success and one failure per endpoint.
  • Confirm every target endpoint is callable and priced before routing a single request.
  • Rehearse the rollback with traffic on it, not on a staging branch.
  • Reconcile holds against settlements for a full billing cycle before switching the last share.

FAQ

Questions this page gets asked

Not for media. The task contract, field names, state machine and billing semantics all need mapping. Text clients come closest: the OpenAI-compatible surface usually needs only a base URL and a model value.

Keep going

Map one workflow and quote it

Move one workflow at a time: map fields, normalize states and errors, compare the bill, then shift traffic with a rollback path you have actually tested.

Source: fal model APIs ↗

Related guides