Skip to content

Mercura API (1.33.0)

Getting Started

What this API is

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.

Main use case

The end-to-end flow is four steps:

  1. Push master data. POST /articles, POST /customers, and POST /suppliers accept bulk payloads of up to 100,000 rows each and return a JobAck immediately. Sending one large request per resource is preferred over many small ones.
  2. Get notified when the jobs finish. Each POST returns a job_id. The recommended path is a webhook: register a subscription once (admin UI, see Webhooks below) and Mercura delivers a signed job.finished event to your endpoint as soon as the job reaches a terminal state. If a webhook receiver is not an option, fall back to polling GET /jobs/{job_id}.
  3. Read completed tenders and orders back. This is the payoff. A tender.completed (or order.completed) webhook fires the moment a Mercura user releases a record to your system (the Finalize / Export → API action) — your endpoint then calls GET /tenders/{tender_id} (or GET /orders/{order_id}) to pull the full structured record (positions, matched articles, quantities, prices, totals). GET /tenders / GET /orders are the cursor-paginated list views for catch-up scans; pass ?completed_since=… for the completion feed.
  4. Acknowledge the import. Once the record is committed to your ERP/CRM, POST /tenders/{tender_id}/acknowledgements (or /orders/{order_id}/acknowledgements) with status: "SUCCESS" and your ERP document id in external_id — Mercura stores it on the record's erp_offer_id (latest-wins) and surfaces the outcome in the app. Report status: "FAILED" with a message when 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.

Integration patterns: webhook vs polling

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.completedorder.completed, GET /tenders/{id} GET /orders/{id}, and the acknowledgements path.

Webhook (push)

Webhook (push) sequence — Mercura user finalizes a record; Mercura POSTs an HMAC-signed tender.completed to your endpoint; you GET /tenders/{tender_id}, write the offer to your ERP, then POST the acknowledgement.

The webhook payload is intentionally small — it identifies which tender changed and when; you always fetch the current state with GET /tenders/{tender_id}. See the Webhooks chapter for the wire format, the HMAC-SHA256 signature recipe, and the retry schedule.

Polling (pull)

Polling (pull) sequence — your poller GETs /tenders?completed_since=… and pages via cursor; each item is a full tender; you write the offer to your ERP, then POST the acknowledgement.

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.

Webhooks

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.

Authentication

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.

Operations

Data conventions

A few conventions hold across every resource:

  • Embedded addresses, standalone contacts. Customer and supplier payloads carry their addresses[] inline — there is no separate /addresses endpoint, 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 /contacts resource and carry their own parent_type (customer or supplier) and parent_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, and supplier_id are 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_fields on 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.

Errors

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.

Request IDs

Every response carries an X-Request-Id header (echoed if you supply one, generated otherwise) and the same id is embedded in every error envelope. Quote it in support tickets — it is the fastest way for Mercura's team to look up the exact request in our logs.

Versioning

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.

BumpWhat it means
PATCHBugfix / doc fix. OpenAPI shape unchanged.
MINORAdditive only: new optional field, new endpoint, relaxed constraint. Existing clients keep working unchanged.
MAJORBreaking: removed field, renamed field, narrower constraint. A new URL prefix is published; the previous MAJOR remains available during a migration period.

Rate limits

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:

ClassLimit
Writes (POST)60 / minute, burst 10
Reads (GET)600 / minute, burst 60
Concurrent in-flight bulk-write jobs5 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).

Read-endpoint pagination

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.

Alternative integration

SFTP-based integration

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.

Download OpenAPI description
Languages
Servers
Mock server
https://docs.mercura.ai/_mock/openapi
https://docs.mercura.ai/api/public/v1
Production
https://prod-euapi.mercura.ai/api/public/v1