Skip to main content
MarketplaceProvider Keys — listing your key

Provider Keys — listing your key

The other side of the market: you already pay Fal, Wavespeed or ElevenLabs for capacity you are not fully using, and you list the key so other people's calls run on it at a discount you set.

This page is the end-to-end path through the API. The dashboard does the same thing with a form.

Listing a key, end to end
  1. ProbeIs this key valid? Key used once, never stored
  2. Price itdiscount_percent, 5–80
  3. Scope itCapabilities, refuse rules, caps
  4. Create the offerKey encrypted; listing goes live
The probe is free and the key is thrown away afterwards. Nothing is listed until you create the offer, and the uniqueness claim is taken before the key is stored.

1. Probe the key

Check a key before you commit it. The probe spends nothing — each provider's probe is a free, read-only endpoint — and the key is used once and never stored.

POST /v1/resource/seller/keys/probe
curl -X POST $SURPLUS_BASE_URL/resource/seller/keys/probe \
  -H "Authorization: Bearer $SURPLUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"resource_id":"elevenlabs","api_key":"<the provider key>"}'

A bad key answers 200 with "ok": false rather than an error — it is a verdict about your key, not a failure of the request.

falWhat the probe reads · Unit prices for one endpoint.
wavespeedWhat the probe reads · The account balance endpoint.
elevenlabsWhat the probe reads · The model list.

2. Create the offer

POST /v1/resource/seller/offers
curl -X POST $SURPLUS_BASE_URL/resource/seller/offers \
  -H "Authorization: Bearer $SURPLUS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
        "resource_type": "elevenlabs",
        "api_key": "<the provider key>",
        "label": "Spare ElevenLabs capacity",
        "discount_percent": 50,
        "cap_daily_usd": 25,
        "policy": { "allow": ["elevenlabs.music", "elevenlabs.sound-effects"], "deny": [] },
        "ack_persists_state": true,
        "ack_provider_terms": true
      }'

The response never echoes the key, on any endpoint, including the listing read. It is encrypted under a KMS envelope and decrypted only at call time, in the worker that makes the upstream request.

  • resource_typestringrequired

    fal, wavespeed or elevenlabs.

  • api_keystringrequired

    The provider key. Key listings take api_key only — api_secret is refused with api_secret_unsupported.

  • discount_percentintegerrequired

    A whole number from 5 to 80, off the provider's own list price. pricing_multiplier is refused here with pricing_multiplier_unsupported: a key listing is priced one way only.

  • labelstring

    Yours, and private to you. This is how you tell two listings of the same provider apart — see below.

  • cap_daily_usdnumber

    A spend ceiling in USD. cap_weekly_usd and cap_monthly_usd work the same way. A capable offer that has spent its cap is skipped, and the buyer sees seller_cap_reached.

  • policyobject

    { allow, deny } — the capabilities this key serves and the routes it refuses. Omit it and the listing stays on the provider's default template, resolved when it is read, so a later change to that template reaches every listing that never scoped itself.

  • payout_addressstring

    Where earnings go. Defaults to your organization's payout wallet.

  • ack_persists_statebooleanrequired

    Must be true. You are acknowledging that the provider may keep a record of buyers' calls on your account — on ElevenLabs it certainly will.

  • ack_provider_termsbooleanrequired

    Must be true. Reselling your capacity is between you and the provider; check your plan's terms.

Both acknowledgements are mandatory — omit either and the request is refused acknowledgement_required. They are not a formality: one is about your data, the other about your contract with the provider.

One active listing per key, per model

A credential may back one active offer per model, per base URL — enforced across every workspace and organization, and not behind a feature flag. A second one is refused 409 duplicate_offer_for_credential, naming the offer that already holds the claim.

The key is not the unit of uniqueness; the triple is — credential, base URL, model. Most production credentials legitimately serve many models, so keying on the credential alone would invalidate most of the market. The base URL is in there because the same key against a direct host and against an aggregator are different failure domains.

To change a live listing, update it. Do not create a second one.

bash
curl -X PATCH $SURPLUS_BASE_URL/resource/seller/offers/roff_01JC… \
  -H "Authorization: Bearer $SURPLUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"discount_percent": 40, "cap_daily_usd": 50}'

Labels, not key hints

Listings carry no fragment of the key — not even a last-four hint, and the API does not return one.

That is a deliberate replacement rather than an omission. Several listings of one provider would all draw the same provider name and the same …abcd tail, which makes pausing or repricing the right one guesswork. Your own private label does that job properly, so give each listing one.

What you are agreeing to serve

Three checks decide every call, and the first refusal wins. You control the second and third.

Where your choices sit
1 · The Surplus fenceprovider's own host only; no account, billing or key routesours
2 · Your refuse rulesa match ends the call — beats a ticked capabilityyours
3 · Your capabilitieswhatever survives must fall inside one you tickedyours
The platform fence is ours and cannot be widened. Within it, your refuse rules beat your ticked capabilities — which is what makes a refuse rule a usable escape hatch.

A rule can only ever take away. There is no rule that adds a route, and nothing you write reaches past the fence — so the worst a typo can do is refuse too much. Access rules is the full reference, including the wildcard syntax.

Traits are claims until they are not

You can advertise properties of the account behind your key — zero retention, an EU region, a business plan — and buyers can require them with X-SI-Require.

traitsVerified starts empty on every listing, however much you tick. A claim is not evidence, and the only things that can move a trait to verified are our own prober and an operator reviewing what you supplied. Most traits can only ever be Claimed, because there is nothing about someone else's account configuration we can check from outside — and we would rather say that than imply a check we did not make.

A false claim removes the listing and holds its earnings.

Managing listings

GET /v1/resource/seller/offersWhat it does · Your listings. Never includes the key.
PATCH /v1/resource/seller/offers/{id}What it does · Reprice, re-cap, re-scope, pause.
DELETE /v1/resource/seller/offers/{id}What it does · Delist. Releases the uniqueness claim.
POST /v1/resource/seller/offers/{id}/purgeWhat it does · Delist and destroy the stored credential.
DELETE /v1/resource/seller/offersWhat it does · Delist everything.
GET /v1/resource/seller/analyticsWhat it does · Earnings, and the demand your rules refused.

Your refusals appear on your own analytics as demand your rules refused, priced at your rates, so you can see what a rule costs you before deciding to keep it.