Mercura API (1.33.0)
Mercura is an AI-powered platform for processing inquiries — bills of materials, tenders, RFQs — in wholesale distribution, manufacturing, and technical sales. The Mercura API is the integration surface your organisation uses to plug Mercura into the rest of your software landscape.
The API is bidirectional by design:
- Master data flows in — your articles, customers, and suppliers are pushed into Mercura so that incoming inquiries can be processed against them.
- Structured offers flow out — each inquiry, once Mercura has read the LV, captured the positions, and matched each one to an article from your catalogue, is returned as a fully structured offer ready for your downstream system to pick up.
The source system on your side does not have to be a specific ERP. Any system that owns master data — an ERP, a PIM, a custom catalogue, a spreadsheet export pipeline — can be the integration partner.
The end-to-end flow is four steps:
- Push master data.
POST /articles,POST /customers, andPOST /suppliersaccept bulk payloads of up to 100,000 rows each and return aJobAckimmediately. Sending one large request per resource is preferred over many small ones. - Get notified when the jobs finish. Each
POSTreturns ajob_id. The recommended path is a webhook: register a subscription once (admin UI, see Webhooks below) and Mercura delivers a signedjob.finishedevent to your endpoint as soon as the job reaches a terminal state. If a webhook receiver is not an option, fall back to pollingGET /jobs/{job_id}. - Read completed tenders and orders back. This is the payoff. A
tender.completed(ororder.completed) webhook fires the moment a Mercura user releases a record to your system (the Finalize / Export → API action) — your endpoint then callsGET /tenders/{tender_id}(orGET /orders/{order_id}) to pull the full structured record (positions, matched articles, quantities, prices, totals).GET /tenders/GET /ordersare the cursor-paginated list views for catch-up scans; pass?completed_since=…for the completion feed. - Acknowledge the import. Once the record is committed to your ERP/CRM,
POST /tenders/{tender_id}/acknowledgements(or/orders/{order_id}/acknowledgements) withstatus: "SUCCESS"and your ERP document id inexternal_id— Mercura stores it on the record'serp_offer_id(latest-wins) and surfaces the outcome in the app. Reportstatus: "FAILED"with amessagewhen the import is rejected.
A typical integration runs step 1 nightly (delta-sync new and changed master-data rows) and reacts to tender.completed / order.completed webhooks in step 3 to drive new offers into the downstream system in real-time, acknowledging each import in step 4.
Steps 3–4 above can be driven two ways. Webhook (push) is the recommended path — Mercura notifies your endpoint the instant a tender or order completes, so you react in real time. Polling (pull) is the fallback when your side cannot expose an inbound HTTPS endpoint — you scan the completion feed on a schedule. Both end the same way: fetch the full record, write it to your ERP/CRM, and acknowledge the import.
The sequences below show the tender flow. Orders are identical — swap tender.completed → order.completed, GET /tenders/{id} → GET /orders/{id}, and the acknowledgements path.
Poll ?completed_since=… on a schedule (every few minutes is typical). completed_at refreshes on every completion, so a re-exported tender reappears in the feed — key off tender_id and keep your ERP writes idempotent. Keep completed_since on every request; once you hold a next_cursor, send only the cursor.
If you would rather not poll, Mercura can push events to an HTTPS endpoint on your side as soon as something happens — a job.finished event replaces the polling loop above, and a tender.completed / order.completed event tells you the moment a record has been released to your system (see Integration patterns: webhook vs polling above for the full push-vs-pull sequence).
Subscriptions are set up by an org admin in the Mercura web app under Settings → Organisation → Webhooks — there are no /webhook_subscriptions endpoints in this API. Once a subscription exists, the wire format, HMAC-SHA256 signature recipe, retry schedule, and example payloads are documented in the Webhooks chapter further down.
The API uses bearer tokens issued in the Mercura admin UI under Settings → Organisation → API Keys.
Authorization: Bearer mrc_live_<token>Missing, malformed, expired, or revoked tokens return 401 UNAUTHORIZED.
A few conventions hold across every resource:
- Embedded addresses, standalone contacts. Customer and supplier payloads carry their
addresses[]inline — there is no separate/addressesendpoint, and the wire format hides the internal address table so each parent push is a single snapshot. Contacts, by contrast, are managed via the dedicated/contactsresource and carry their ownparent_type(customerorsupplier) andparent_id, so adding or updating a contact does not require re-sending the entire parent — see the Contacts chapter for the full model and upsert rules. - Stable partner-supplied identifiers.
article_number,customer_id, andsupplier_idare the keys you assign in your source system. They must remain stable across sync cycles — Mercura uses them to upsert: same id → existing row updated; new id → new row created. - Timestamps in ISO-8601 UTC. All timestamps on the wire are ISO-8601 with an explicit
Z(or+00:00) suffix. custom_fieldson every master-data resource. Use it for any source-system field that doesn't fit the standard schema. Mercura preserves the value alongside the row; matching uses it where appropriate.
Every non-2xx response uses one envelope:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Request validation failed",
"details": [ { "loc": ["query", "cursor"], "msg": "Invalid cursor", "type": "value_error" } ],
"request_id": "9a3b...e2"
}
}Branch on code — it is stable. message is human-readable and may change between versions. The full list of codes (VALIDATION_FAILED, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, IDEMPOTENCY_KEY_MISMATCH, RATE_LIMITED, INTERNAL_ERROR, …) is documented per endpoint.
The API follows Semantic Versioning (MAJOR.MINOR.PATCH). Only the MAJOR number lives in the URL path (/v1, future /v2, …). The full SemVer string is published in the OpenAPI info.version field and on every response as the X-API-Version header.
| Bump | What it means |
|---|---|
| PATCH | Bugfix / doc fix. OpenAPI shape unchanged. |
| MINOR | Additive only: new optional field, new endpoint, relaxed constraint. Existing clients keep working unchanged. |
| MAJOR | Breaking: removed field, renamed field, narrower constraint. A new URL prefix is published; the previous MAJOR remains available during a migration period. |
Each API key has its own budget. Every request counts as one against the matching limiter — reads draw from the read bucket, writes from the write bucket, and each in-flight bulk-write job holds one concurrency slot. Defaults today:
| Class | Limit |
|---|---|
| Writes (POST) | 60 / minute, burst 10 |
| Reads (GET) | 600 / minute, burst 60 |
| Concurrent in-flight bulk-write jobs | 5 per key |
Every response carries RateLimit-Policy / RateLimit headers (and legacy X-RateLimit-* headers) describing your current budget. On a 429 RATE_LIMITED response, Retry-After tells you how long to wait, and X-RateLimit-Scope tells you whether you hit the per-minute bucket (rate) or the concurrent-jobs cap (concurrency).
Every GET list endpoint — /tenders, /orders, /supplier-requests, /articles, /customers, /suppliers, /projects, and /contacts — is cursor paginated. Pass ?modified_since=<ISO-8601> on the first page to bound the lower edge of the scan by updated_at (useful for delta-sync). Mercura returns a page plus a next_cursor — pass it back as ?cursor=… to get the next page. Once you have a cursor, modified_since is ignored.
On /tenders and /orders you can instead pass ?completed_since=<ISO-8601> to pull the completion feed — only records completed at/after that instant, ordered by completed_at — the reconciliation twin of the tender.completed / order.completed webhooks. Both accept a status filter; /contacts additionally filters by parent_type / parent_id / is_active.
The cursor is keyed on (updated_at, id), so it is stable under concurrent writes: a row updated mid-scan may resurface on a later page, but you will never silently skip or duplicate rows.
The Mercura API documented here is the preferred channel because it is synchronous, low-latency, and operationally light — status comes back immediately, no file-watching is required on either side, and master-data updates can flow event-driven. Where a direct API integration is not feasible (network constraints, security policy, ERP capability), Mercura also supports SFTP-based exchange in both directions for the same payloads. Talk to your Mercura contact if you need that path; the data shapes are identical to the ones documented here.