Publish an API
How to get an existing HTTP API ready to sell on API Mercado. It is written so you can hand it to a coding agent working in your API’s repository: each section says what to change in the code and what to set in the portal.
How a request flows
Section titled “How a request flows”A consumer calls the gateway with their Key:
https://eu-north-1.apimercado.com/v1/@<provider-slug>/<api-slug>/<path>The gateway checks their balance and holds the most the request can cost,
strips their X-API-Key, adds your upstream credential and the platform
headers, and forwards the request to your upstream base URL plus <path>.
When your response comes back, the gateway charges the consumer, removes your
pricing headers, and returns the response with their Cost attached. You never
handle keys, billing, or rate limits yourself.
1. Publish an OpenAPI spec at a URL
Section titled “1. Publish an OpenAPI spec at a URL”The spec is imported from a URL, never pasted, and fetched again every hour.
- OpenAPI 3.x, in JSON or YAML, at most 1 MB.
info.versionis required. When it changes, the new version is released automatically, so bump it whenever the API changes.servers[0].urlsuggests the upstream base URL: the origin the gateway forwards requests to. Server variables take theirdefault, and a relative URL such as/v1is resolved against the spec URL. With noservers, the suggestion is the spec URL’s own host. Consumers never see it: the published spec shows the gateway URL instead, and your own security schemes are removed, since consumers authenticate with their Key.- The URL must be publicly reachable over http or https. Addresses on private networks, loopback, and link-local ranges are rejected, for both the spec URL and the upstream base URL.
Most frameworks can serve a generated spec (for example /openapi.json). Serve
it from the same deployment as the API so it never drifts from the code.
Creating the API in the portal starts from this URL. New API asks for the
spec URL first and reads it straight away, so a spec that cannot be fetched or
is not valid OpenAPI is reported before anything is created. The next step is
prefilled from the spec: the name from info.title, the slug from the title,
the description from info.description, and the upstream base URL as above.
Check each one, change what is wrong (the slug is permanent), and create the
API. It opens with the spec already loaded.
The @<provider-slug> segment is your organization’s slug, set under
Settings > General. Pick it before you publish: once any of your APIs has
left Disabled, it is locked, because it is part of every consumer’s gateway
URL.
2. Accept only gateway traffic
Section titled “2. Accept only gateway traffic”In the portal, open your API’s Authorization tab and add the credential the gateway should send to your upstream: an API key header, a bearer token, basic auth, or a custom header. It is encrypted at rest and never shown to consumers.
In your code, reject any request that does not carry that credential. Answer
401 or 403: consumers are never charged for those statuses, so a
misconfigured credential cannot bill anyone.
The gateway also sends these headers on every request:
| Header | Meaning |
|---|---|
X-Mercado-Key-Id | The consumer’s Key the request was made with. |
X-Mercado-Consumer-Id | The consumer organization. Use it for per-customer state. |
X-Mercado-Consumer-Name | The consumer organization’s display name. |
X-Mercado-Request-Id | A per-request id. The consumer gets the same id on the response. Log it, and store it on deferred work: it is the id you settle with. |
When the consumer set a Max cost for the call, the request also carries
X-Mercado-Max-Cost-Micros, unchanged: the most they agreed to pay for it, the
proxy fee included. The gateway has already checked that your operation’s cap
plus the fee fits under it, so you never need to refuse on it, but you can use
it to scale the work (fewer results, a shorter output). It is absent when the
consumer sent none or an invalid value.
Trust them only on requests that passed your credential check.
3. Add a health endpoint
Section titled “3. Add a health endpoint”Publishing requires a passing health check. Add a cheap endpoint (for example
GET /health) that answers a fixed status without calling paid dependencies.
In Settings you choose the path, the method (GET, HEAD, or POST), the expected
status code, and a timeout between 500 and 30,000 ms.
The gateway also serves that exact path without a Key, free (GET and HEAD for a GET or HEAD check, otherwise only the chosen method), so anyone can monitor your API through it; it need not appear in your spec. The marketplace’s own probe calls your upstream base URL directly, without your credential, so keep the endpoint open in your code.
Keyless operations
Section titled “Keyless operations”To let callers reach an operation without a Key (for example a link token
that is its own credential, called from a browser), declare security: [] on
that operation in your spec. Once that spec is released, the operation is
listed in the Keyless operations card of Settings (Spec section): tick it and
save to serve it keyless. Until you do, it needs a Key like any other.
Keyless calls are free: nothing is charged and you earn nothing for them. They
are limited to 60 per minute per caller IP. The gateway still sends your
upstream credential, but X-Mercado-Key-Id and the X-Mercado-Consumer-*
headers are empty, so authenticate the caller yourself. A security: [] at
the top level of the spec is ignored.
4. Pricing
Section titled “4. Pricing”All amounts are integer micro-dollars: 1,000,000 micro-dollars = $1.00, so
1500 is $0.0015.
Prices live in your spec, one operation at a time. There is no price to type
in the portal: your API’s Settings show what the live spec declares, and
Accept Free buffer (see Free buffer toggle) is the
only pricing choice made there. To change a price, change the spec and bump
info.version.
Consumers pay the price plus a fixed proxy fee per request (the current amount is on the fees overview). You receive the price in full. Your API page shows your price alone, marked ”+ proxy fee”, so a $0 price shows as Free. Which responses are charged is explained in What is charged. Calls your own organization makes to your API are priced and reported like any other call, but only the proxy fee is deducted, and they earn you nothing.
Before forwarding a request, the gateway checks that the consumer can pay the operation’s cap plus the proxy fee, and holds that amount. A request is never forwarded on money the consumer does not have.
Two levels
Section titled “Two levels”The price of a request comes from two levels; the cap comes from the spec alone:
| Level | Price (what is charged) | Cap (what the gate checks and holds) |
|---|---|---|
| 1. Your response header | X-Mercado-Price-Micros | none: a response cannot change it |
| 2. The spec operation | x-mercado-price-micros | x-mercado-max-price-micros |
- A price header on your response sets the price of that one request; without one, the price the spec declares applies.
- The charged price is clamped to the cap. A price above it charges the cap.
- The gate holds the declared cap before your server is called, so no response changes the cap. A deferred request always holds the declared cap; to charge less, settle less.
What an operation’s extensions come to:
| The spec operation declares | Price | Cap |
|---|---|---|
| a price and a cap | the price | the cap |
| a price only | the price | the price |
| a cap only | $0 base | the cap |
| nothing, or no operation matches | $0 (the proxy fee only) | 0 |
keyless operation (security: []) | never charged | none |
A price declared alone is also the cap, so a price header can lower it but never raise it. An operation with nothing declared has a cap of 0, so a price header on it charges nothing: declare a cap on every operation whose code sends one.
Pricing in the spec
Section titled “Pricing in the spec”Price an operation by adding these extensions to it in your spec:
| Extension | Value | Effect |
|---|---|---|
x-mercado-price-micros | non-negative integer | The operation’s price. |
x-mercado-max-price-micros | non-negative integer | The operation’s cap. 0 makes the operation cost only the proxy fee. |
x-mercado-settlement | deferred | The price is set after the response, by a settle call. See Deferred settlement. |
paths: /search: get: x-mercado-price-micros: 1000 x-mercado-max-price-micros: 10000 /videos: post: x-mercado-settlement: deferred x-mercado-max-price-micros: 250000 /jobs/{jobId}: get: x-mercado-max-price-micros: 0The import checks the extensions. Errors block the import (the spec is
reported as pricing_invalid); warnings let it through and come back with
the import result:
| Finding | Kind |
|---|---|
A price or cap that is not a non-negative integer (1500.5, "1500", -1) | error |
| A price or cap above 1,000,000,000 micro-dollars ($1,000) | error |
x-mercado-settlement with any value other than deferred | error |
x-mercado-settlement: deferred without an x-mercado-max-price-micros above 0: the hold would be 0, so every settle would fail | error |
| A price above the cap declared on the same operation | error |
Extensions on a keyless operation (security: []): it is never charged, so they are ignored | warning |
A path segment that mixes a {param} with text, such as /files/{id}.json: the gateway never matches it, so calls to it are charged the proxy fee only | warning |
A version that passes is released automatically, prices included (see Price changes), so these checks are what stands between a typo and your consumers.
How a request finds its operation
Section titled “How a request finds its operation”The gateway takes the path after /v1/@<provider-slug>/<api-slug> and matches
it, with the method, against the path templates of your released spec:
- Whole segments only. A
{param}segment matches any one segment; a literal segment matches the same text, compared with exact case first, then ignoring case. - When several templates match, the first segment where one is literal and
the other a
{param}decides, and the literal wins:GET /jobs/latestmatches/jobs/latestbefore/jobs/{jobId}. - A
HEADrequest uses theGEToperation when the spec declares noHEAD. - Segments are percent-decoded, empty segments and
.are dropped, and..removes the segment before it, the way the gateway routes the request. - A request that matches no operation, or an operation without extensions, is charged the proxy fee only.
- On an API whose spec declares pricing, a segment holding an encoded slash
(
%2F) or a backslash is refused with400 path_not_routable, because upstreams disagree on how to route it. So is a path outside the API’s listen path.
Charging a different amount per request
Section titled “Charging a different amount per request”When requests are not all worth the same (results returned, pages rendered, tokens generated), set headers on your response:
| Response header | Effect |
|---|---|
X-Mercado-Price-Micros | The price of this one request, up to the operation’s declared cap. |
X-Mercado-Settlement: instant | On a deferred operation: charge this request now and drop the hold. See Deferred settlement. |
How the price header is read:
| Your price header | What is charged |
|---|---|
| absent, empty, or not an integer | the price the spec declares ($0 without one) |
| a negative number | $0 |
| between 0 and the cap | exactly that amount |
| above the cap | the cap |
There is no response header for the cap: an X-Mercado-Max-Price-Micros on
your response is removed and ignored.
The gateway removes these headers before the response reaches the consumer,
and adds X-Mercado-Cost-Micros with what they actually paid (your amount
plus the proxy fee). It also removes any X-Mercado-Cost-Micros,
X-Mercado-Balance-Micros, X-Mercado-Hold-Micros or similar money header
you send, and replaces X-Mercado-Request-Id with its own. The headers only
matter on responses that are charged at all.
Rules for your code:
- Send whole numbers only.
1500works;1500.5,$0.0015, and1.5e3fall back to the declared price. - Compute it from the work the request actually did, after the work is done.
- Raise the cap in the spec, and release it, before your code starts sending higher values, or they are silently capped.
- If every request costs the same, do not send the header: declare only
x-mercado-price-micros.
Never copy request input into response headers. If your server echoes request headers back (some proxies and debug middlewares do), a caller can send
X-Mercado-Price-Micros: 0orX-Mercado-Settlement: instantand set their own price.
Example: $0.0005 per result returned, at least $0.001 and at most $0.01. The
spec declares the floor as the price and the most as the cap, as /search
above:
paths: /search: get: x-mercado-price-micros: 1000 x-mercado-max-price-micros: 10000Your code then sends the price of each request. Node (Express):
const PRICE_PER_RESULT_MICROS = 500;const MIN_PRICE_MICROS = 1000;
app.get('/search', async (req, res) => { const results = await search(req.query.q); const price = Math.max(MIN_PRICE_MICROS, results.length * PRICE_PER_RESULT_MICROS); res.set('X-Mercado-Price-Micros', String(price)); res.json({ results });});Hono:
app.get('/search', async (c) => { const results = await search(c.req.query('q')); const price = Math.max(1000, results.length * 500); c.header('X-Mercado-Price-Micros', String(price)); return c.json({ results });});Python (FastAPI):
@app.get("/search")async def search_route(q: str, response: Response): results = await search(q) price = max(1000, len(results) * 500) response.headers["X-Mercado-Price-Micros"] = str(price) return {"results": results}Describe the pricing rule in your API’s description or in each operation’s description, so consumers know what a call will cost before they make it.
Organizations that may not charge yet
Section titled “Organizations that may not charge yet”Paid pricing opens once provider payouts are live. Until then your spec
imports and releases normally, prices included, but a priced operation (one
with a cap above 0) is not available to consumers: a call to it gets
503 paid_pricing_unavailable before your server is called, and nothing is
charged or held. Operations without a price serve as usual, at the proxy fee
only. Calls from your own organization, including the test call the publish
checklist asks for, are still served at the proxy fee only. Your API page marks the priced operations “Not available yet”, and the
Pricing box in your API’s Settings says why.
When paid pricing opens for your organization, the prices your spec declares apply from the next call, without a price-raise email.
Free buffer toggle
Section titled “Free buffer toggle”Every consumer organization gets a Free buffer each Month (the current amount is on the fees overview). Accept Free buffer (Settings > Pricing, on by default) decides whether it may pay your Price:
- On: consumers can try your API before they top up. Requests paid from the Free buffer earn you nothing.
- Off: the Price of every call must come from the consumer’s Top-up
balance, so every charged request earns you the full Price. The proxy fee
can still come from the Free buffer, so a $0 operation stays callable
without topping up. A consumer whose Top-up balance cannot cover the price
cap gets
402 topup_required.
Leave it on unless you have a reason: consumers are more willing to buy an API they could try first. Your own organization is never affected (it pays no Price). Deferred holds are always paid from Top-up, whatever the toggle says.
Price changes
Section titled “Price changes”A new spec version is released automatically, and its prices apply from the
next call. Raising a price or a cap on a Public or Unlisted API emails every
organization with your API enabled and shows “Price raised on
Each operation is compared with its own previous declaration, or with the proxy fee alone when it had none, so putting a price on an existing operation that had none is a raise. A path that was not in the live spec is never a raise: it has no callers yet. Renaming a path makes it a new one.
Consumers see the price range your API actually charged (5th to 95th percentile over 30 days, once there are 20 charged requests) and the cap.
What you earn
Section titled “What you earn”You earn the Price of every charged request once the consumer pays from purchased dollars. Requests paid from a consumer’s monthly Free buffer are a trial funded by the Marketplace and earn nothing. A settled deferred request is always paid from Top-up, so it earns the settled amount in full. Calls from your own organization are priced and reported normally, pay only the proxy fee, and never earn.
5. Deferred settlement
Section titled “5. Deferred settlement”Some work only has a price once it is done: a video render, a transcript, an
LLM job that runs longer than the gateway’s 30 seconds.
Declare those operations x-mercado-settlement: deferred, with an
x-mercado-max-price-micros above 0. The gate holds that cap, your server
answers at once, and you settle the real price later.
How it works
Section titled “How it works”- Gate. The gateway holds the operation’s declared cap from the
consumer’s Top-up balance, minus their other open holds. The Free buffer
never funds a hold. A consumer who cannot cover it gets
402before your server is called, so you never start work nobody can pay for. - Your response. Answer quickly (for example
202with a job id). A response the consumer is charged for (2xx or 4xx, except 401, 403 and 429) leaves the request pending: the consumer pays the proxy fee now and seesX-Mercado-Settlement: pendingandX-Mercado-Hold-Micros. The hold is always the declared cap: no response header changes it, and a price header on a pending response is ignored. To charge less, settle less. - Settle. When the work ends, settle the request once with an amount from 0 up to the hold. The consumer pays that amount and the rest is released. Settle 0 for a job that failed.
Other outcomes:
| Response | Result |
|---|---|
| 5xx, 3xx, 401, 403, 429, or a gateway timeout | Not charged. The hold is released. |
Any charged status with X-Mercado-Settlement: instant | Charged now at the price header, else the price the spec declares, up to the hold. Nothing to settle. Use it for a cached result, a rejected input, or a repeat request for work already held. |
| Any charged status without it | Pending until you settle. |
A 400 for bad input is a charged status, so it stays pending unless you
answer it with X-Mercado-Settlement: instant (add X-Mercado-Price-Micros: 0
to charge only the proxy fee) or settle it for 0.
Rules:
- Settle once. Repeating the same amount returns the same response; a
different amount is refused with
409. There are no partial or repeated captures and no amounts above the hold. - A hold’s
expiresAtis 48 hours after the request. Settles close 5 minutes before it, and holds not settled by then are released: the consumer pays nothing beyond the proxy fee and you earn nothing. - The proxy fee is charged when the request is made and is never returned, whatever you settle.
- The hold must be confirmed by your response within 60 seconds of the request. A response that reaches the gateway later holds nothing and can only be settled for 0.
- Calls your own organization makes hold nothing beyond the proxy fee. You may settle them for any amount: the settled price is recorded in full on the request, marked internal, and nothing more is deducted.
- In rare failures (the hold’s record is lost before it is stored) the
consumer pays only the proxy fee and your settle answers
404.
Settling needs a provider API key, see Provider API.
Settle and read a request
Section titled “Settle and read a request”Base URL: https://admin-api.apimercado.com.
POST /v1/provider/requests/{requestId}/settleAuthorization: Bearer mpk_...Content-Type: application/json
{ "amountMicros": 182000 }requestId is the X-Mercado-Request-Id the gateway sent with the request.
amountMicros is an integer from 0 to the hold. Both endpoints answer the
request’s state:
{ "requestId": "4f1c2b9e-0d7a-4b8e-9a51-2f3c6d7e8a90", "apiId": "b6a1e3f0-5c2d-4e8f-9b7a-1d2c3e4f5a6b", "operation": "POST /videos", "status": "settled", "heldMicros": 250000, "settledMicros": 182000, "expiresAt": "2026-10-31T09:12:44.000Z"}GET /v1/provider/requests/{requestId}returns the same shape. status is pending, settled or released;
settledMicros is null while pending and 0 once released.
Errors are { "error": "<code>: <detail>" }:
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_failed | amountMicros is missing or not a non-negative integer. |
| 400 | settle_amount_exceeds_hold | The amount is above the hold. The detail says the most you can settle. |
| 401 | missing bearer token, invalid provider key | No key, or not a live key. |
| 401 | provider_key_revoked | The key was revoked while the settle ran. |
| 403 | scope_required | The key lacks the scope the endpoint needs. |
| 403 | ban | Your organization is banned. |
| 404 | request_not_found | No deferred request with this id on your APIs. |
| 409 | request_in_flight | Your response is not recorded yet. Retry after a second. |
| 409 | request_already_settled | Already settled for a different amount. The detail says which. |
| 410 | request_expired | The hold expired (settles close 5 minutes before expiresAt). |
| 410 | request_released | The hold was already released. A settle of 0 answers 200 instead. |
| 429 | rate_limited | Over 600 calls a minute for your organization, or 1,200 from one IP. See Retry-After. |
A settle is safe to retry with the same amount after a network error or a
5xx.
Example: submit and poll
Section titled “Example: submit and poll”A job API in Hono. The spec declares the submit deferred with a $0.25 cap and the poll free of any Price:
paths: /videos: post: x-mercado-settlement: deferred x-mercado-max-price-micros: 250000 /jobs/{jobId}: get: x-mercado-max-price-micros: 0const SETTLE = 'https://admin-api.apimercado.com/v1/provider/requests';
app.post('/videos', async (c) => { const job = await jobs.create({ input: await c.req.json(), consumerId: c.req.header('X-Mercado-Consumer-Id'), requestId: c.req.header('X-Mercado-Request-Id'), }); c.header('Location', `/jobs/${job.id}`); return c.json({ jobId: job.id }, 202); // pending: the fee now, the price on settle});
app.get('/jobs/:jobId', async (c) => { const job = await jobs.get(c.req.param('jobId')); if (!job || job.consumerId !== c.req.header('X-Mercado-Consumer-Id')) { return c.json({ error: 'not_found' }, 404); } return c.json({ status: job.status, result: job.result ?? null });});
// Called by the worker when the job ends, for good.async function settle(job: Job) { const amountMicros = job.failed ? 0 : Math.min(250000, priceFor(job.cost)); for (let attempt = 0; attempt < 10; attempt++) { const res = await fetch(`${SETTLE}/${job.requestId}/settle`, { method: 'POST', headers: { Authorization: `Bearer ${env.MERCADO_PROVIDER_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ amountMicros }), }); if (res.status !== 409 && res.status < 500) return res.json(); const { error } = await res.json().catch(() => ({ error: '' })); if (res.status === 409 && !String(error).startsWith('request_in_flight')) return; await new Promise((r) => setTimeout(r, 2000)); } throw new Error(`settle ${job.requestId} failed; retry it later`);}Each poll costs the consumer only the proxy fee. Settle 0 only when the job has failed for good: a job you retry keeps its request id and settles when it finally ends.
The 30-second gateway limit
Section titled “The 30-second gateway limit”The gateway waits at most 30 seconds for your response headers. A slower
response gets the consumer a 504, is not charged, and its hold is released.
For anything that can run longer, answer at once and let the consumer poll,
as above.
6. Provider API
Section titled “6. Provider API”The Provider API lets your servers and CI work with your own APIs without a
portal login. Base URL: https://admin-api.apimercado.com.
| Operation | Scope | What it does |
|---|---|---|
GET /v1/provider/whoami | any key | Returns { keyId, providerOrgId, scopes }, to check a key before you wire it in. |
POST /v1/provider/apis/{apiId}/refresh | specs:refresh | Re-reads the spec from its URL now. See Refresh a spec. |
POST /v1/provider/requests/{requestId}/settle | requests:settle | Sets the price of a deferred request. See Settle and read a request. |
GET /v1/provider/requests/{requestId} | requests:read | Returns a deferred request’s hold and status. |
Provider API keys
Section titled “Provider API keys”Create a key in Settings > Provider API (org admins only; the tab appears
once your organization has an API, even a draft). The key starts with mpk_
and is shown once. Revoking it in the same place takes effect immediately.
- Send it as
Authorization: Bearer mpk_.... - Every key has every scope above. It cannot call APIs or sign in to the portal, and it only sees your own APIs and their requests.
- At most 10 live keys per organization.
From TypeScript, createProviderClient({ providerKey }) in
@apimercado/client types every
operation above.
Refresh a spec
Section titled “Refresh a spec”We re-read your spec URL every hour. To pick up a change at once, for example as the last step of a deploy, call:
POST /v1/provider/apis/{apiId}/refreshAuthorization: Bearer mpk_...apiId is the id in your API’s dashboard URL (/dashboard/my-apis/{apiId}).
The answer is the same as Check now in the portal:
{ "outcome": "released", "fromVersion": "1.2.0", "toVersion": "1.3.0" }outcome | Meaning |
|---|---|
unchanged | The spec matches what is served. |
released | info.version changed, so the new version is live and consumers were emailed. |
draft_staged | The spec changed but info.version did not. Raise it to release the change. |
It answers 400 no_source_url when the API has no spec URL, 422 with the
reason when the spec cannot be fetched or is invalid, and 404 for an API
that is not yours. At most 10 refreshes a minute per organization.
7. Go-live checklist
Section titled “7. Go-live checklist”Every item must pass before the API can go public. The Publish tab shows the same list with a link to fix each one.
- An OpenAPI spec version is uploaded.
- The spec is imported from a URL.
- Your legal name, contact email, and country are set.
- The current Provider Agreement is accepted.
- Terms of Service and Privacy Policy URLs are set.
- The health check is enabled and passing.
- At least one successful call went through the gateway with your own Key while the API is Private. Enable your own API, create a Key, and call it.
A summary and a category are recommended, so consumers can find the API.
Prompt for your coding agent
Section titled “Prompt for your coding agent”Paste this into a coding agent opened in your API’s repository:
Read https://docs.apimercado.com/publish-an-api/index.md and prepare this repo to be published on API Mercado, including per-request pricing.