Providers
The Provider Market's providers, one section each: Fal, ElevenLabs, Wavespeed, Uncensored Higgsfield, OpenRouter, Vercel AI Gateway and Cartesia.
Fal
Fal is the one provider on the Provider Market that does not take its target in the URL. That single difference accounts for most first-call failures, so it is the first thing in this section.
Billing unit: per megapixel. The seller's Fal history never shows your prompt, inputs or output. Fal keeps a queued result for 1 hour (6 minutes if it is 10 KB or larger), so fetch it before then.
The target is a header, and the path is empty
The target must be an absolute https URL on one of Fal's two model hosts. Anything else - Fal's storage,
pricing or account hosts, or any other domain - never leaves our fence.
| Host | What it is |
|---|---|
fal.run | Answers with the result itself. Synchronous. |
queue.fal.run | Starts a job and answers with an id. |
fal.runWhat it is · Answers with the result itself. Synchronous.queue.fal.runWhat it is · Starts a job and answers with an id.So Fal is not a base-URL swap. Both hosts serve the same model paths, and only the host says whether you
want the answer now or a job. Replace https://fal.run (or https://queue.fal.run) with our route and that
choice is lost, so a hand-built URL or a curl that appends Fal's path is refused unexpected_path. Keep
the path empty and send the full Fal URL in x-fal-target-url, or use the SDK below, which does that for you.
Using the SDK
@fal-ai/client is the normal way in. Point proxyUrl at our route and send the target always, not just
when the SDK feels like it:
import { fal } from '@fal-ai/client'; fal.config({ // No Fal key here — the seller's key is resolved on our side, at call time. proxyUrl: { url: '$SURPLUS_BASE_URL/resources/fal', when: 'always', }, requestMiddleware: async (request) => ({ ...request, headers: { ...request.headers, Authorization: `Bearer ${process.env.SURPLUS_API_KEY}` }, }), }); const result = await fal.subscribe('fal-ai/flux/schnell', { input: { prompt: 'a tiny red origami crane', image_size: 'square', num_images: 1 }, }); console.log(result.data.images[0].url);
# The Python client has no proxy option, so call the route directly. import os, requests r = requests.post( "$SURPLUS_BASE_URL/resources/fal", headers={ "Authorization": f"Bearer {os.environ['SURPLUS_API_KEY']}", "x-fal-target-url": "https://fal.run/fal-ai/flux/schnell", "Content-Type": "application/json", }, json={"prompt": "a tiny red origami crane", "image_size": "square", "num_images": 1}, timeout=60, ) print(r.headers["x-si-buyer-cost-micro"], r.headers["x-si-list-cost-micro"]) print(r.json()["images"][0]["url"])
when: 'always' matters. With the default the SDK proxies only some calls and sends the rest straight to Fal,
where your request has no credential and fails - with nothing on our side to explain why.
Fal is the one provider whose follow-up URLs we leave untouched: its SDK builds them itself and sends each target in the header, so it stays on your key without our help. Path-style providers get their follow-up URLs rewritten instead - see jobs and settlement.
A worked queue job
curl -X POST $SURPLUS_BASE_URL/resources/fal \ -H "Authorization: Bearer $SURPLUS_API_KEY" \ -H "x-fal-target-url: https://queue.fal.run/fal-ai/flux/dev" \ -H "Content-Type: application/json" \ -d '{"prompt":"a paper boat","image_size":"square"}' # → { "request_id": "764cabcf-…", "status_url": "…", "response_url": "…" }
# Fal's own URLs are left as they are; send them back through the header. 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/764cabcf-…/status?logs=1" # → { "status": "IN_QUEUE" | "IN_PROGRESS" | "COMPLETED" }
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/764cabcf-…" # → the images, and this is where the charge settles
COMPLETED does not mean it worked. A failed Fal job also reads COMPLETED, with no error on the status, so
the outcome is only confirmed on the result: 2xx succeeded, 4xx failed. We settle on the same evidence you do.
What is reachable
Only Fal's own public models, on three methods:
| Rule | Covers |
|---|---|
POST /fal-ai/+ | Submit or run a model. |
GET /fal-ai/+ | Status and result. |
PUT /fal-ai/+ | Cancel. |
POST /fal-ai/+Covers · Submit or run a model.GET /fal-ai/+Covers · Status and result.PUT /fal-ai/+Covers · Cancel.fal-ai is the only publisher. That is not a preference - a seller's private apps live under
<their-username>/<app> and answer on these same two hosts to the seller's key, so no denylist could cover
them, while nobody else can deploy under fal-ai. An allowlist of one publisher is the only shape that is
safe by construction. Other public publishers are added as data once verified.
?logs=1 is the one query parameter passed through, because the SDK asks for run logs on status polls.
fal_webhook is deliberately not: it would send your result to a URL we do not control.
Two response headers come back untouched, because the SDK reads both: x-fal-request-id and
x-fal-request-timeout-type.
Two things Fal refuses here.
LLMs
fal-ai/any-llm is Fal's unified LLM endpoint - Claude, GPT, Gemini, Llama - and it is denied provider-wide,
not per listing.
An LLM bills on output tokens, which no quote taken before the call can bound. That makes it a platform
pricing gap rather than a seller's choice, which is exactly why it is not left to the access editor. The rule is
unqualified across every method, not POST-only: a family refused on one verb and open on the others reads as
an oversight the next time someone edits it.
LLM resale is the order book's job, not this market's. Use /v1/chat/completions.
Models that can run for minutes
Fal prices most image models per megapixel or per image, and those answer inside the request. Everything else - video, 3D, long audio - can run for minutes.
- Priced per megapixel or imageRuns on fal.run, answers inline
- Priced any other wayRefused on fal.run - 400 job_route_required
- Submit to queue.fal.runSurvives a replaced task
The refusal names itself: job_route_required. Submit the same model to queue.fal.run instead and follow it
as a job. A queue submit answers immediately either way, so this costs you nothing but a different target host.
On the x402 rail there is no queue option - the message says so, and you need a Surplus API key for those models.
Models priced by a reference video
Kling motion control (fal-ai/kling-video/{v3,v2.6}/{standard,pro}/motion-control) bills per second of the
reference video. The hold is the longest clip allowed for character_orientation (10 s for image,
30 s for video), and you are charged the seconds Fal bills. character_orientation is required.
Timeouts
POST /fal-ai/+ is capped at 50 seconds, inside the fleet's 60-second drain, so a deploy never cuts a call
a seller is already billed for. A model that needs longer belongs on the queue.
ElevenLabs
ElevenLabs is a path-style provider: the provider's own path goes straight onto our route, and the seller's
xi-api-key is attached on our side.
Billing unit: per 1 000 characters.
ElevenLabs is the one live provider where the seller's account keeps a record of your call. Text to speech writes a history item with your text and the audio, and speech to text a transcript. We delete it with the seller's key right after you get your result: a transcript at once, a history item within a few seconds (ElevenLabs writes it just after it answers). Until then it is on the seller's account. That is a property of the provider, not of our proxy, and it is why the trait badges on a listing matter here more than anywhere else.
Five routes, and that is the whole surface
ElevenLabs publishes over four hundred operations. Five are reachable (text to speech in two forms).
?output_format= is the one query parameter passed through.
Text to speech and speech to text are off until a seller turns them on, so a listing serves them only when it says so. Each needs the seller's key to carry the permission to delete what the call stores; a key without it is never sent the call.
Calling them
# Answers with audio BYTES, not JSON. The cost headers still ride on the response. curl -X POST $SURPLUS_BASE_URL/resources/elevenlabs/v1/sound-generation \ -H "Authorization: Bearer $SURPLUS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"text":"a short soft chime","duration_seconds":1}' \ --output chime.mp3 --dump-header headers.txt grep x-si- headers.txt # x-si-buyer-cost-micro: 1500 # x-si-list-cost-micro: 3000
# Up to the priced ceiling of five minutes; a 5-minute song answers in about 25 s. curl -X POST $SURPLUS_BASE_URL/resources/elevenlabs/v1/music \ -H "Authorization: Bearer $SURPLUS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"a slow piano loop in A minor","music_length_ms":30000}' \ --output loop.mp3
# A default voice (George here). Priced per character: $0.04 per 1K on Flash and Turbo, $0.08 on the others. curl -X POST $SURPLUS_BASE_URL/resources/elevenlabs/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb \ -H "Authorization: Bearer $SURPLUS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"text":"Hello from Surplus.","model_id":"eleven_flash_v2_5"}' \ --output hello.mp3
# $0.22 per hour of audio, charged for the length ElevenLabs reports.
curl -X POST $SURPLUS_BASE_URL/resources/elevenlabs/v1/speech-to-text \
-H "Authorization: Bearer $SURPLUS_API_KEY" \
-F model_id=scribe_v2 \
-F file=@meeting.mp3# Free and read-only — any valid key may read it, so it doubles as the seller's key probe.
curl $SURPLUS_BASE_URL/resources/elevenlabs/v1/models \
-H "Authorization: Bearer $SURPLUS_API_KEY"Music, text to speech and speech to text may run for up to 110 seconds. A call still running when a server is replaced (a deploy) can be cut, and is then charged its estimate.
What text to speech and speech to text accept
- Voices. Only ElevenLabs' 21 default voices. Any other
voice_idbelongs to an account: a cloned voice, or a Voice Library voice, which ElevenLabs adds to the account that names it. - Refused fields. Text to speech:
pronunciation_dictionary_locators,previous_request_ids,next_request_ids,use_pvc_as_ivc,audio_effects,apply_language_text_normalization. Speech to text:source_url,cloud_storage_url, the webhook fields,use_speaker_libraryand multichannel. Any field not documented above is refused rather than passed on. - Text length. Up to 6,000 characters on Multilingual v2, 2,500 on v3 and v4, 25,000 on Flash and Turbo, so every call finishes inside the timeout. Split longer text into several requests.
- Speech to text pricing. You pay for the length ElevenLabs reports, plus $0.05 per hour with
keytermsand $0.07 per hour withtranscript_edit. Before the call, the file is held at the longest it could play: a WAV by its bytes, an MP3 by counting its frames. Send WAV (PCM), MP3, or raw 16 kHz PCM withfile_format=pcm_s16le_16; convert FLAC, OGG, M4A, MP4, AAC and WebM first, since their length cannot be bounded before ElevenLabs decodes them. Audio over 30 minutes is refused: split it.
Why the other routes are closed
The rule is one sentence: the seller's account may keep a record of your request, but you may read nothing of that account. Each family below fails it for a different reason, and the reasons are worth knowing because they tell you what would have to change.
| Can they… | Reaches account data | Unpriceable up front | Leaves seller history |
|---|---|---|---|
| Speech-to-speech, dialogue | Yesvoice_id may be a private cloned voice | No | Yes |
| The voice lists (/v1/voices, /v2/voices) | Yesthey ARE the seller’s library | No | No |
| Audio isolation | No | Yesbilled by uploaded audio length | Yes |
| Forced alignment | No | Yessame basis as isolation | Yes |
| Account, keys, billing, usage | Yesservice accounts can mint keys | No | No |
| Streaming | No | Yescannot be metered for settlement | No |
Two of those are worth drawing out:
- Voice routes are not a capacity question. A
voice_idcan name one of the seller's private cloned voices. Text to speech is open because it accepts only the default voices; speech-to-speech and dialogue need the same guard before they can open. - Audio isolation and forced alignment are priced by the length of a file you upload. No quote taken before the call can bound that, which is the same property that keeps LLMs off Fal. Opening either is a cost-source change first.
Music is the exception that proves the rule: it leaves no history, and its two fields that would touch the
account - finetune_id and store_for_inpainting - are refused at the point where it is priced.
Two layers, not one
A route can be closed two ways here, and the difference is deliberate.
GET /v1/audio-effects/catalog is deliberately not on the denylist. It lists filter presets - a static
catalogue of the same class as GET /v1/models, which is open - so naming it would imply it reaches the
seller's account, which it does not. It stays refused by the allowlist, which is what "we have not opened this
yet" looks like. A denylist that collects everything unopened stops telling you which entries are load-bearing.
Wavespeed
Wavespeed is a path-style provider, and almost everything on it is a job: you submit a task, you get an id, and you collect the result separately.
Billing unit: per image. Wavespeed keeps no record of a call against the seller's account.
What is reachable
Two rules, and together they are the whole surface:
| Rule | Covers |
|---|---|
POST /api/v3/{publisher}/+ | Submit a task to one of 44 allowed publishers' models. |
GET /api/v3/predictions/*/result | Read one task's result. |
POST /api/v3/{publisher}/+Covers · Submit a task to one of 44 allowed publishers' models.GET /api/v3/predictions/*/resultCovers · Read one task's result.Generation only. No price lookups, model lists, uploads, balance, billing or prediction history - and
POST /api/v3/predictions, the generic submit, is refused precisely because it would sidestep the
per-publisher allowlist.
The 44 publishers are the model owners Wavespeed hosts:
alibaba black-forest-labs bria bytedance character-ai chatterbox clarity-ai decart elevenlabs google heygen hitem3d hyper3d ideogram-ai inworld kwaivgi leonardoai lightricks luma meshy meta microsoft midjourney minimax mirelo-ai mureka-ai nvidia openai pika pixverse pruna-ai recraft-ai runwayml skywork-ai sonilo sourceful sync topaz tripo3d veed vidu wavespeed-ai x-ai z-ai
A model under any other owner is refused policy_refused - the publisher is part of the rule, not a hint.
A listed publisher does not mean every model under it is priced. black-forest-labs/flux-schnell reads like
the obvious first call and is a 404 at Wavespeed's own price endpoint, so it is refused provider_unpriceable
rather than charged at a guess. wavespeed-ai/flux-schnell is the priced equivalent. If a model refuses this
way, try the wavespeed-ai mirror of it.
Submit, then collect
- POST the modelAnswers { data: { id, urls } }
- Bound to that offer27 h, your account only
- GET the status URLFree. created → processing → completed
- GET …/resultThe image, and the charge settles
curl -X POST $SURPLUS_BASE_URL/resources/wavespeed/api/v3/wavespeed-ai/flux-schnell \ -H "Authorization: Bearer $SURPLUS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"prompt":"a tiny blue paper boat on a white table","size":"1024*1024"}'
{
"data": {
"id": "a1b2c3d4e5f6",
"status": "created",
"urls": {
"get": "$SURPLUS_BASE_URL/resources/wavespeed/api/v3/predictions/a1b2c3d4e5f6/result"
}
}
}# Follow the URL the submit gave you — it already points back through us, on your key.
curl $SURPLUS_BASE_URL/resources/wavespeed/api/v3/predictions/a1b2c3d4e5f6/result \
-H "Authorization: Bearer $SURPLUS_API_KEY"Look closely at urls.get in that response. Wavespeed returned its own api.wavespeed.ai URL; we rewrote
it to point back at our route before handing it to you. Follow it as given and your poll stays on your own
Surplus key and on the same seller's offer. Call Wavespeed's URL directly and you have no credential.
That rewriting is why path-style providers need no special handling from you, and it is covered in full under jobs and settlement.
Streaming is refused
Wavespeed publishes streaming variants at /api/v3/{publisher}/{model}/stream, and they are denied
provider-wide rather than left to each seller.
A streamed response has to be metered from the stream, which this rail does not do - it would be billed at the
submit's estimate and settle wrong. That makes it a platform capability gap, not a seller's choice. Both the
/stream and /stream/* forms are denied, because a variant may nest.
Two layers here as well
/api/v3/webhook* is named deliberately: it holds the account's webhook signing secret. It was already
refused by being off the allowlist, but a fence that only works while the allowlist stays narrow is not a fence.
Uncensored
Uncensored AI is a path-style provider, and every generation call on it is a job: you submit, it answers
202 with a job id and a status_url, and you read the job until it finishes. Its images are also a job - there
is no synchronous call, whatever the provider's own page says.
Billing unit: per request for images, per second for video. The seller's Uncensored account keeps every job and the output sits on a public link, so a call does leave state on the seller's side.
Send your Surplus key in Authorization: Bearer or in x-api-key, the header Uncensored's own docs use.
What is reachable
| Rule | Covers |
|---|---|
POST /api/v1/images/generations | Generate an image. |
POST /api/v1/images/studio/edit | Edit an image from one reference image link. |
POST /api/v1/videos/generations | Generate or transform a video. |
GET …/jobs/{id} on each of the three | Read a job you started. |
GET /api/v1/models, GET /api/v1/models/{id}, GET /api/v1/api-keys/scopes, POST /api/v1/billing/estimate, GET /api/v1/health, GET /api/health, GET /health | Free routes that Uncensored answers with no key at all. |
POST /api/v1/images/generationsCovers · Generate an image.POST /api/v1/images/studio/editCovers · Edit an image from one reference image link.POST /api/v1/videos/generationsCovers · Generate or transform a video.GET …/jobs/{id} on each of the threeCovers · Read a job you started.GET /api/v1/models, GET /api/v1/models/{id}, GET /api/v1/api-keys/scopes, POST /api/v1/billing/estimate, GET /api/v1/health, GET /api/health, GET /healthCovers · Free routes that Uncensored answers with no key at all.Chat is not reachable: chat is sold through the order book, and a model that bills on its output cannot be priced before the call. The seller's balance, spend history, limits and API keys are refused by name.
What is priced
The list price of a call is Uncensored's own quote for it, read from its free POST /api/v1/billing/estimate when you
call, not a fixed table. Only the models below are accepted, and a call whose price cannot be read is refused
(provider_unpriceable) before a seller is chosen, rather than charged at a guess.
| Surface | Model | What it takes |
|---|---|---|
| Image | model-dev | A prompt; any size up to 2048×2048. |
| Image edit | standard | One reference image link. |
| Video | wan-2.2-ultra-fast | seconds 5 or 8, 480p or 720p. |
| Video | video-watermark-remover | seconds 1 to 10. |
model-devWhat it takes · A prompt; any size up to 2048×2048.standardWhat it takes · One reference image link.wan-2.2-ultra-fastWhat it takes · seconds 5 or 8, 480p or 720p.video-watermark-removerWhat it takes · seconds 1 to 10.A video call must name seconds. A request that carries any field outside the documented set - a callback_url,
above all, which would send the result and the seller's account to another address - is refused.
Submit, then collect
curl -X POST $SURPLUS_BASE_URL/resources/uncensored/api/v1/images/generations \ -H "Authorization: Bearer $SURPLUS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"model-dev","prompt":"a tiny blue paper boat on a white table"}'
{
"job_id": "f6381cd0-6d70-4ecc-9ff3-c7d375f51ff3",
"status": "processing",
"status_url": "$SURPLUS_BASE_URL/resources/uncensored/api/v1/images/generation/jobs/f6381cd0-6d70-4ecc-9ff3-c7d375f51ff3"
}# Follow the URL the submit gave you — it already points back through us, on your key.
curl $SURPLUS_BASE_URL/resources/uncensored/api/v1/images/generation/jobs/f6381cd0-6d70-4ecc-9ff3-c7d375f51ff3 \
-H "Authorization: Bearer $SURPLUS_API_KEY"
# → { "status": "completed", "image_url": "https://media.…/….jpg", … }An image finishes in a few seconds, an edit in about half a minute, and a video in one to two minutes. You are charged only once the job is confirmed complete; a job that fails is released and costs nothing.
Things to know
- The result is a public link.
image_urlandvideo_urlpoint at Uncensored's CDN and need no key, so anyone who has the link can open it. - Content rules apply to the seller's account. Uncensored scans every input and output and can suspend the account behind a key for violations, so what you send counts against that account. Its acceptable-use policy bars, among other things, realistic human faces without logged consent.
- The model you name is what is priced. Uncensored may substitute another model when the one you asked for is
unavailable (
is_fallback: trueon the job). On edits its docs sayenable_fallbackis accepted only for forward compatibility, so it cannot be turned off there. Check the model on the finished job.
Higgsfield
Higgsfield is a path-style provider and a true base-URL swap: point Higgsfield's own SDK, or curl, at
/v1/resources/higgsfield and keep everything else as it is on Higgsfield. Every generation call is a job: the
submit answers at once with a request_id, a status_url and a cancel_url, and you read the status until it
finishes.
Billing unit: per generation for images, per second for video. The seller's Higgsfield account keeps a record of every job and the output sits on a public link, so a call does leave state on the seller's side.
Your key, and the JS SDK's colon
Send your Surplus key in Authorization: Bearer <key> or in Authorization: Key <key>, the header Higgsfield's own
docs and SDKs use. Higgsfield's JS SDK v2 refuses, on your machine and before it sends anything, any credential
that is not KEY_ID:KEY_SECRET. A Surplus key has no colon, so give it one: <your Surplus key>:x. The :x is filler -
we read only the part before the colon. The Python SDK and curl take the bare key.
import os from higgsfield_client import SyncClient client = SyncClient(base_url="$SURPLUS_BASE_URL/resources/higgsfield", api_key=os.environ["SURPLUS_API_KEY"]) result = client.subscribe("higgsfield-ai/soul/v2/standard", arguments={"prompt": "a logo for Surplus Intelligence"}) print(result["images"][0]["url"])
import { config, higgsfield } from '@higgsfield/client/v2'; // ':x' is filler: the SDK wants KEY_ID:KEY_SECRET, and the route reads only the part before the colon. config({ baseURL: '$SURPLUS_BASE_URL/resources/higgsfield', credentials: `${process.env.SURPLUS_API_KEY}:x`, }); const result = await higgsfield.subscribe('higgsfield-ai/soul/v2/standard', { input: { prompt: 'a logo for Surplus Intelligence' }, withPolling: true }); console.log(result.images?.[0]?.url);
curl $SURPLUS_BASE_URL/resources/higgsfield/higgsfield-ai/soul/v2/standard \ -H "Authorization: Bearer $SURPLUS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"a logo for Surplus Intelligence"}'
Only the v2 JS client works: the older HiggsfieldClient (v1) calls Higgsfield's legacy API, which current models do
not answer.
In Python, follow a job through the handle submit() returns (or subscribe()), never client.status(request_id),
client.result(request_id) or client.cancel(request_id). Those three build Higgsfield's own URL whatever base_url you
set, so through us they would send your Surplus key to Higgsfield and fail.
What is reachable
| Rule | Covers |
|---|---|
POST /{publisher}/… for higgsfield-ai, higgsfield, marketing-studio, kling-video, bytedance, wan, alibaba, minimax, pixverse, lightricks, xai, recraft, ideogram, z-image | Submit to any model, by the model id Higgsfield uses (kling-video/v3.0/std/text-to-video). |
GET /requests/{id}/status | Read a job you started. |
POST /requests/{id}/cancel | Cancel a job you started, while it is still queued. |
GET /v1/text2image/soul-styles, GET /v1/text2image/soul-styles/v2, GET /marketing-studio/image/presets, GET /models/higgsfield/genjutsu/restyle/v1.0/presets | The style and preset lists for style_id and preset_id. The Marketing Studio presets come 50 to a page: pass ?cursor= to read the next. |
POST /{publisher}/… for higgsfield-ai, higgsfield, marketing-studio, kling-video, bytedance, wan, alibaba, minimax, pixverse, lightricks, xai, recraft, ideogram, z-imageCovers · Submit to any model, by the model id Higgsfield uses (kling-video/v3.0/std/text-to-video).GET /requests/{id}/statusCovers · Read a job you started.POST /requests/{id}/cancelCovers · Cancel a job you started, while it is still queued.GET /v1/text2image/soul-styles, GET /v1/text2image/soul-styles/v2, GET /marketing-studio/image/presets, GET /models/higgsfield/genjutsu/restyle/v1.0/presetsCovers · The style and preset lists for style_id and preset_id. The Marketing Studio presets come 50 to a page: pass ?cursor= to read the next.The only query parameter passed through is cursor. Each publisher is a capability a seller can turn off.
What is refused, and why
| Refused | Why |
|---|---|
POST /soul-id, /v1/custom-references… | Soul ID training stores a character on the seller's account, and the list route reads those characters. |
custom_reference_id in a body | It picks a Soul ID character trained on the seller's account - a real person's likeness. Refused provider_unpriceable before a seller is chosen. |
elements in a body (Kling 3.0, O3, Omni) | Kling element IDs must belong to the calling account, so they are the seller's saved characters and objects. Refused provider_unpriceable before a seller is chosen. |
POST /files/generate-upload-url | Uploads would run on the seller's key. Pass your own public https links in image_url, video_url and the like. |
?hf_webhook= | Dropped: it would send the result to an address outside your job. Poll the status_url instead. |
/v1/agent… | Agent chat sessions and their uploads live on the seller's account. |
POST /higgsfield/ai-influencer | Held back until it is known not to store a character on the seller's account. |
/estimate/…, GET /models | The price lookup and catalogue we read on the seller's key. |
POST /soul-id, /v1/custom-references…Why · Soul ID training stores a character on the seller's account, and the list route reads those characters.custom_reference_id in a bodyWhy · It picks a Soul ID character trained on the seller's account - a real person's likeness. Refused provider_unpriceable before a seller is chosen.elements in a body (Kling 3.0, O3, Omni)Why · Kling element IDs must belong to the calling account, so they are the seller's saved characters and objects. Refused provider_unpriceable before a seller is chosen.POST /files/generate-upload-urlWhy · Uploads would run on the seller's key. Pass your own public https links in image_url, video_url and the like.?hf_webhook=Why · Dropped: it would send the result to an address outside your job. Poll the status_url instead./v1/agent…Why · Agent chat sessions and their uploads live on the seller's account.POST /higgsfield/ai-influencerWhy · Held back until it is known not to store a character on the seller's account./estimate/…, GET /modelsWhy · The price lookup and catalogue we read on the seller's key.What is priced
The list price of a call is Higgsfield's own quote for that exact request, read from its estimate on the seller's key when you call, not from a table: the price follows the inputs (in our tests a Soul 2 batch of 4 cost $0.013 against $0.004 for one, and Kling 3.0 at 10 seconds cost twice 5 seconds). It is the price before any discount on the seller's own Higgsfield account; the seller's listing discount then comes off it.
Two kinds of model cannot be priced, and neither is charged at a guess:
- Models Higgsfield prices only by formula - Seedance, Wan 3.0, Genjutsu, Cinema Studio, Ads Studio and Marketing
Studio Flare and Sunburst, among others - answer
provider_unpriceable. - Kling motion control (2.6 and 3.0, standard and pro) and the O3 and Omni video edits answer
price_unavailable(a retryable503): Higgsfield's estimate fails on them. An identical retry is refused for ten minutes without asking Higgsfield again, and they start working when Higgsfield fixes its side.
Submit, then collect
curl -X POST $SURPLUS_BASE_URL/resources/higgsfield/higgsfield-ai/soul/v2/standard \ -H "Authorization: Bearer $SURPLUS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"a small red apple on a white table"}'
{
"status": "queued",
"request_id": "0f0557db-b510-4d57-93d5-ef99162ccbf5",
"status_url": "$SURPLUS_BASE_URL/resources/higgsfield/requests/0f0557db-b510-4d57-93d5-ef99162ccbf5/status",
"cancel_url": "$SURPLUS_BASE_URL/resources/higgsfield/requests/0f0557db-b510-4d57-93d5-ef99162ccbf5/cancel"
}# Follow the URL the submit gave you — it already points back through us, on your key.
curl $SURPLUS_BASE_URL/resources/higgsfield/requests/0f0557db-b510-4d57-93d5-ef99162ccbf5/status \
-H "Authorization: Bearer $SURPLUS_API_KEY"
# → { "status": "completed", "images": [{ "url": "https://….cloudfront.net/….png" }], … }The status reads queued, then in_progress, then completed with images or video; a Soul 2 image took about twenty
seconds in our tests. You are charged only once the job is confirmed complete. A job that ends failed, nsfw or
canceled is released and costs nothing.
Cancelling works only while the job is queued: the cancel answers 202 and the job reads canceled. Once Higgsfield
has started it, the cancel answers 400 ("Request is in progress"), and the job finishes and is charged.
Things to know
- The result is a public link. The image or video URL is on Higgsfield's CDN and needs no key, so anyone who has the link can open it. Higgsfield documents keeping outputs for at least seven days: download what you need to keep.
- A busy seller is skipped. Higgsfield limits how many jobs one account runs at once and, per its docs, answers a
400when that account is full. That call is sent to the next seller instead of failing. - How many jobs you can run at once follows your credit balance, on the same tiers Higgsfield uses for its own
accounts: 2 under $25, 10 from $25, 20 from $100, 40 from $1,000. A job counts until it finishes, fails or is canceled.
A submit past your limit is refused with
429 rate_limitedbefore anything is held or charged: retry once a job finishes, or add credit. - The record stays on the seller's account. Higgsfield has no way to delete a job, so the seller's account keeps it. Nothing on this route can list those records.
Related
- Your first call - the end-to-end path, all five providers.
- Jobs and settlement - bindings, polling and when you are charged.
- Access rules - how a seller narrows a key further.
- Security and privacy - what reaches a seller's account.
- Provider key endpoints - the endpoint reference.
OpenRouter
Use https://api.surplusintelligence.ai/v1/resources/openrouter/api/v1 as the base URL and your Surplus inference key as the bearer token.
Text generation and inline-text embeddings use token pricing. The dedicated image API (POST /images) and video API (POST /videos) support requests whose full cost can be bounded from published provider prices. Unsupported billing dimensions and unbounded options are refused before generation.
Video generation returns a job ID. Follow the rewritten polling and content URLs using the same buyer key. Only that buyer can access the job through Surplus. Failed or canceled jobs release the hold; successful jobs use the reported actual cost, capped at the quote. When actual cost is unavailable, the quoted estimate is charged. The quote can use the highest published price tier.
Older seller listings do not automatically start serving embeddings, images or video. Sellers can enable those capabilities in their listing policy. Account balances, API-key management and unrelated generation history remain unavailable to buyers.
Vercel AI Gateway
For the OpenAI-compatible API use https://api.surplusintelligence.ai/v1/resources/vercel/v1. Image generation uses /images/generations; JSON image edits are available only for models whose reference-image charges are verified and bounded.
For supported AI SDK media protocols, preserve the SDK version suffix when replacing the host. For example, the v4 gateway base URL becomes https://api.surplusintelligence.ai/v1/resources/vercel/v4/ai. The proxy forwards the SDK model selector and image/video specification headers. Image requests support v1, v3 and v4 media protocols; synchronous video requests support v3 and v4 on the Surplus API key credit rail. Video SSE keepalives pass through while generation runs, and the final result determines the charge.
Only published per-image and per-second pricing is supported on these media routes. Video requests require an explicit supported duration. Unknown billing dimensions, unbounded provider options, async SDK start/status operations, and video/audio references are refused. Token-priced or megapixel-priced image models are not treated as flat-price images.
Successful media calls use usable reported actual cost, capped at the quote; otherwise the quote is charged. An HTTP 200 response containing an error is a failure, not a billable generation. Existing seller listings must opt into the new image and video capabilities.
Cartesia
Cartesia supports POST /tts/bytes for speech generation, POST /stt for Ink Whisper file transcription,
and GET /models for the public model catalog. The seller health check displays the live catalog and
identifies which models are supported through Surplus. Catalog discovery is not a paid generation or balance test.
Use https://api.surplusintelligence.ai/v1/resources/cartesia as the base URL and your Surplus key as a Bearer token.
Surplus supplies the pinned Cartesia-Version: 2026-08-14 header upstream.
TTS accepts sonic-3.6, sonic-3.5, sonic-3, and sonic-preview (beta), plus the stable snapshots
sonic-3.6-2026-08-27, sonic-3.5-2026-05-04, and sonic-3-2026-01-12 as model_id.
Use plain-text transcript (1–2,000 characters), the public voice
db6b0ed5-d5d3-463d-ae85-518a07d3c2b4, and output_format with container: "wav",
encoding: "pcm_s16le", sample_rate: 44100. An optional two-letter language is supported.
Normalization is disabled. Markup, private voices, dictionaries and other request fields are refused.
The reference price is $0.065 per 1,000 transcript characters, the published Pro overage rate; the seller's advertised discount applies to that reference. Subscription plans can have different effective rates. Cartesia does not report an actual USD cost on this response, so a successful request is charged its quoted amount. The model catalog is free. A model appearing in that upstream catalog does not mean its generation route is supported here.
For transcription, send multipart fields model=ink-whisper and file containing PCM WAV audio,
at most 60 seconds and a 2 MiB request. Optional fields are language (two-letter code) and
timestamp_granularities[]=word. Remote audio URLs and other formats are not accepted.
The reference rate is $0.00195 per minute (one credit per two seconds at $0.000065 per credit).
Surplus reserves a duration bound with a 5% plus one-second margin, charges the returned duration capped
at that quote, and releases the remainder. If duration is missing, the quote is charged.
Existing listings must opt into the new Speech to text capability; new listings include it by default.
Ink 2 and Ink Preview are listed but unavailable: they require a WebSocket transport. SSE, WebSockets, cloning, agents, account administration, files and private voice-library routes are not enabled. This integration does not promise zero provider retention; sellers acknowledge provider state when listing their key.
Sources: Cartesia TTS API, Cartesia pricing.