Provider Keys — jobs and settlement
Some provider-key calls answer with a result. Many answer with an id, and the result arrives later. This page is how those behave end to end: what binds a job to a seller, how you follow it, and exactly when money moves.
If you only read one thing: a submit is not the end of the transaction. The charge settles when you fetch the result.
Two shapes of call
What binds a job
When a submit comes back with an id, that id is pinned to the one offer that took it.
The binding lives for 27 hours. After that a follow-up answers 404 job_not_found, so collect long jobs
within a day.
Pinning to one offer is not an optimisation. The provider holds that job against that seller's key, so a poll routed to a different seller would ask the wrong account about a job it has never heard of. Routing happens once, at submit.
If a binding cannot be written, the submit still succeeds — the provider already has the job, so you still get your id — but the follow-ups will 404 rather than reach another seller. The failure is logged on our side. It is the safe direction to fail: a job you cannot collect costs you nothing, whereas a poll on the wrong key would be a correctness bug.
Following a job
This is the part that differs by provider, and it is the one thing you have to get right.
| Can they… | Follow-up URLs | What you do |
|---|---|---|
| Wavespeed, ElevenLabs (path style) | Yesrewritten to our route | Yesfollow them as given |
| Fal (header style) | Noreturned untouched | Partialsend the target in x-fal-target-url |
Path-style: just follow the URL
A Wavespeed submit returns data.urls.get pointing at api.wavespeed.ai. Before that reaches you, we replace
it with the same path on our route:
https://api.wavespeed.ai/api/v3/predictions/{id}/result
↓ rewritten, in the submit response you receive
$SURPLUS_BASE_URL/resources/wavespeed/api/v3/predictions/{id}/resultThree fields are rewritten where a provider returns them — status_url, response_url and cancel_url — plus
data.urls.get. Only URLs on the provider's own hosts are touched; output and CDN links are left exactly as
they are, so a signed artifact URL still works.
Follow the URL you were given and everything stays on your key and your offer. Call the provider's host directly and you have no credential there.
Fal: send the target in the header
Fal's SDK already knows how to build …/requests/{id}/status and proxies each target as a header, so rewriting
would fight it. Keep the path empty and move the target:
curl $SURPLUS_BASE_URL/resources/fal \ -H "Authorization: Bearer $SURPLUS_API_KEY" \ -H "x-fal-target-url: https://queue.fal.run/fal-ai/flux/dev/requests/$ID"
When money moves
- SubmitQuoted; hold = quote + 25%
- PollNo hold, no charge
- Result fetchedProvider's reported usage read
- Chargedmin(reported, quote); hold released
Four rules, and all four are in your favour:
- A failed call costs nothing. Not the quote, not a partial — zero. The hold is released.
- The quote is a ceiling. You are charged the lower of two numbers: the provider's reported usage, or the quote. A report above the quote means our estimator was wrong, and that is not your bill — the seller absorbs the difference.
- No report means the quote. For a bounded unit the quote already is the ceiling.
- Polling is free. A status check places no hold and makes no charge. Only the result settles.
The hold carries a 25% buffer over the quote so a call that prices slightly higher than estimated still has room to capture. The buffer is held, never charged — what you pay is rule 2.
A replay is a second generation
The relay carries no idempotency, deliberately. Idempotency-Key is accepted, but replaying a provider-key
call is a second real generation at the provider, and it is billed as one.
This is the opposite of the money-moving endpoints, where a replay is explicitly safe. The difference is that a topup replayed twice should move money once, whereas a generation replayed twice has genuinely produced two images — and refusing to bill for the second would mean the seller pays for it.
So: retry a failed call freely, since a failure costs nothing. Do not retry a call that succeeded.
Job states are not outcomes
Neither live provider reports failure the way you would expect, so we confirm both the same way you should: on the result, not the status.
| Provider | Status field | Reads done at | Real outcome |
|---|---|---|---|
| Fal | status | COMPLETED | The result: 2xx succeeded, 4xx failed. |
| Wavespeed | data.status | completed | The result read. |
statusReads done at · COMPLETEDReal outcome · The result: 2xx succeeded, 4xx failed.data.statusReads done at · completedReal outcome · The result read.A Fal job with invalid input queues happily, reads COMPLETED, and then answers 422 on its result with
x-fal-billable-units: 0. That is why the result is the settlement trigger: it is the first point where the
truth is knowable, and it is free when the answer is "it failed".
Related
- Your first call — the end-to-end path.
- Fal — the queue route and the header style.
- Wavespeed — tasks and the result read.
- Pricing — how a discount is applied and what it comes off.
- Provider key endpoints — the endpoint reference.