Skip to content

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.

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.

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.version is required. When it changes, the new version is released automatically, so bump it whenever the API changes.
  • servers[0].url suggests the upstream base URL: the origin the gateway forwards requests to. Server variables take their default, and a relative URL such as /v1 is resolved against the spec URL. With no servers, 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.

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:

HeaderMeaning
X-Mercado-Key-IdThe consumer’s Key the request was made with.
X-Mercado-Consumer-IdThe consumer organization. Use it for per-customer state.
X-Mercado-Consumer-NameThe consumer organization’s display name.
X-Mercado-Request-IdA 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.

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.

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.

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.

The price of a request comes from two levels; the cap comes from the spec alone:

LevelPrice (what is charged)Cap (what the gate checks and holds)
1. Your response headerX-Mercado-Price-Microsnone: a response cannot change it
2. The spec operationx-mercado-price-microsx-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 declaresPriceCap
a price and a capthe pricethe cap
a price onlythe pricethe price
a cap only$0 basethe cap
nothing, or no operation matches$0 (the proxy fee only)0
keyless operation (security: [])never chargednone

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.

Price an operation by adding these extensions to it in your spec:

ExtensionValueEffect
x-mercado-price-microsnon-negative integerThe operation’s price.
x-mercado-max-price-microsnon-negative integerThe operation’s cap. 0 makes the operation cost only the proxy fee.
x-mercado-settlementdeferredThe 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: 0

The 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:

FindingKind
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 deferrederror
x-mercado-settlement: deferred without an x-mercado-max-price-micros above 0: the hold would be 0, so every settle would failerror
A price above the cap declared on the same operationerror
Extensions on a keyless operation (security: []): it is never charged, so they are ignoredwarning
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 onlywarning

A version that passes is released automatically, prices included (see Price changes), so these checks are what stands between a typo and your consumers.

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/latest matches /jobs/latest before /jobs/{jobId}.
  • A HEAD request uses the GET operation when the spec declares no HEAD.
  • 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 with 400 path_not_routable, because upstreams disagree on how to route it. So is a path outside the API’s listen path.

When requests are not all worth the same (results returned, pages rendered, tokens generated), set headers on your response:

Response headerEffect
X-Mercado-Price-MicrosThe price of this one request, up to the operation’s declared cap.
X-Mercado-Settlement: instantOn a deferred operation: charge this request now and drop the hold. See Deferred settlement.

How the price header is read:

Your price headerWhat is charged
absent, empty, or not an integerthe price the spec declares ($0 without one)
a negative number$0
between 0 and the capexactly that amount
above the capthe 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. 1500 works; 1500.5, $0.0015, and 1.5e3 fall 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: 0 or X-Mercado-Settlement: instant and 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: 10000

Your 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.

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.

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.

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 ” or “Price cap raised on ” on your API page for 30 days. Turning off the Free buffer emails them too. Lowering sends nothing.

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.

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.

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.

  1. 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 402 before your server is called, so you never start work nobody can pay for.
  2. Your response. Answer quickly (for example 202 with 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 sees X-Mercado-Settlement: pending and X-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.
  3. 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:

ResponseResult
5xx, 3xx, 401, 403, 429, or a gateway timeoutNot charged. The hold is released.
Any charged status with X-Mercado-Settlement: instantCharged 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 itPending 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 expiresAt is 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.

Base URL: https://admin-api.apimercado.com.

POST /v1/provider/requests/{requestId}/settle
Authorization: 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>" }:

StatusCodeMeaning
400validation_failedamountMicros is missing or not a non-negative integer.
400settle_amount_exceeds_holdThe amount is above the hold. The detail says the most you can settle.
401missing bearer token, invalid provider keyNo key, or not a live key.
401provider_key_revokedThe key was revoked while the settle ran.
403scope_requiredThe key lacks the scope the endpoint needs.
403banYour organization is banned.
404request_not_foundNo deferred request with this id on your APIs.
409request_in_flightYour response is not recorded yet. Retry after a second.
409request_already_settledAlready settled for a different amount. The detail says which.
410request_expiredThe hold expired (settles close 5 minutes before expiresAt).
410request_releasedThe hold was already released. A settle of 0 answers 200 instead.
429rate_limitedOver 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.

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: 0
const 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 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.

The Provider API lets your servers and CI work with your own APIs without a portal login. Base URL: https://admin-api.apimercado.com.

OperationScopeWhat it does
GET /v1/provider/whoamiany keyReturns { keyId, providerOrgId, scopes }, to check a key before you wire it in.
POST /v1/provider/apis/{apiId}/refreshspecs:refreshRe-reads the spec from its URL now. See Refresh a spec.
POST /v1/provider/requests/{requestId}/settlerequests:settleSets the price of a deferred request. See Settle and read a request.
GET /v1/provider/requests/{requestId}requests:readReturns a deferred request’s hold and status.

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.

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}/refresh
Authorization: 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" }
outcomeMeaning
unchangedThe spec matches what is served.
releasedinfo.version changed, so the new version is live and consumers were emailed.
draft_stagedThe 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.

Every item must pass before the API can go public. The Publish tab shows the same list with a link to fix each one.

  1. An OpenAPI spec version is uploaded.
  2. The spec is imported from a URL.
  3. Your legal name, contact email, and country are set.
  4. The current Provider Agreement is accepted.
  5. Terms of Service and Privacy Policy URLs are set.
  6. The health check is enabled and passing.
  7. 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.

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.