Mirakl API Integration: Endpoints, Order Flow and Rate Limits
Mirakl API integration for sellers: authentication, product and offer imports, stock and price sync, the order workflow and the official rate limits.
Long Nguyen
Développeur fullstack · Ingénieur IA · Chercheur
What a Mirakl API integration connects
A Mirakl API integration is the code that links your own systems (ERP, online store, warehouse or PIM) to a marketplace that runs on Mirakl, so that products, offers, stock, prices and orders move without anyone exporting spreadsheets. Retailers such as B&Q, La Redoute, Catch and PcComponentes run their seller marketplaces on Mirakl, and each one exposes the same seller API.
In practice the integration does three jobs: it publishes your catalog, it keeps stock and prices current, and it pulls orders in and pushes acceptance and tracking back. Everything below was checked against Mirakl's seller API reference on .
The first thing to get right is which API you are reading about, because "Mirakl API" covers three different surfaces:
| API surface | Who calls it | Authentication | Base URL |
|---|---|---|---|
| Seller APIs (shop role) | A seller, or the agency or connector acting for the seller | API key sent in the Authorization header | https://your-instance.mirakl.net/api |
| Operator APIs | The retailer that owns the marketplace | Operator credentials, separate reference | The operator's own instance |
| Connect Channel Platform APIs | A sales channel plugging itself into Mirakl Connect | OAuth 2.0 bearer token (JWT) from a client ID and secret issued by Mirakl's Partner team | https://miraklconnect.com/api/channel-platform |
If you sell on a Mirakl marketplace, you want the first row. The rest of this guide covers the seller APIs only.
Mirakl API authentication: API key, shop_id and one instance per marketplace
Seller authentication is deliberately plain. You generate an API key in the marketplace's seller back office and send it, as is, in the Authorization header. There is no Bearer prefix and no token exchange, and every request must use HTTPS.
curl -i 'https://your-instance.mirakl.net/api/orders?max=100' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Accept: application/json'Three details cause most of the early failures:
- One instance per marketplace. The host is the operator's Mirakl instance, so three marketplaces means three base URLs and three API keys. The endpoints are identical; the credentials and the catalog rules are not.
shop_idmatters for multi-shop users. When a user is attached to several shops, the API uses the default shop unless you passshop_id. Agencies should always pass it explicitly, otherwise a stock file can land in the wrong shop.- Roles are per endpoint. Each operation in the reference lists the roles that may call it. Updating offers with OF24, for example, lists
ROLE_SHOP_ADMINandROLE_PARTNER_OFFER_WRITE. When a key works on one endpoint and returns 403 on another, check the role before you check your code.
Mirakl publishes an official PHP SDK for the seller role. From Python, Node or Go the API is plain HTTPS and JSON, which is what the examples in this guide use.
Products vs offers: the split that shapes the whole integration
Mirakl separates the product (the catalog record: title, category, attributes, images) from the offer (your price, quantity, condition and lead time on that product). They travel through different endpoints, at different speeds, with different validation.
| Step | API | Endpoint | What you get back |
|---|---|---|---|
| Read the category tree | H11 | GET /api/hierarchies | The operator's categories |
| Read required attributes | PM11 | Product attribute configuration | Which fields each category needs |
| Read allowed values | VL11 | GET /api/values_lists | The operator's value lists (colors, sizes, brands) |
| Send products | P41 | POST /api/products/imports | An import ID (CSV, XML or XLSX file) |
| Track the import | P42 | GET /api/products/imports/{import} | Import status |
| Read what failed | P44, P47 | .../error_report and the transformation error report | Rejected products and source-file errors |
| Read what was added | P45 | .../new_product_report | Products the operator integrated |
The reference explains each call well. What it does not spell out is where the effort goes: H11, PM11 and VL11 return a different answer on every marketplace, because each operator defines its own categories, mandatory attributes and value lists. Your code is reusable across Mirakl marketplaces; your catalog mapping is not.
That mapping is the same job we do for Amazon SP-API, eBay and Walmart in our marketplace API integration service, and it is usually the largest line in a Mirakl project too.
Two practical rules follow from the split:
- Offers depend on products. An offer points at a product through
product_idandproduct_id_type(an EAN in Mirakl's own example). If the product is not in the operator's catalog yet, the offer has nothing to attach to, so sequence product imports first and treat offer creation as a later step. - Cache the catalog configuration. H11, PM11 and VL11 are each capped at one call per hour. Pull them on a schedule and store them; do not call them inside a per-product loop.
Syncing offers, stock and prices: OF01, OF24, STO01 and PRI01
There are four ways to write offer data, and choosing the wrong one is the most common source of silent data loss.
| API | Endpoint | Use it for | The trap |
|---|---|---|---|
| OF24 | POST /api/offers (JSON) | Creating, updating or deleting offers from code | You must send all offer fields. Fields you leave out are reset to their default value. |
| OF01 | POST /api/offers/imports (CSV, XML, XLSX) | Bulk offer files, optionally with product data (with_products) | import_mode is required. The current values are NORMAL and REPLACE; REPLACE replaces the shop's whole offer set. |
| STO01 | POST /api/offers/stock/imports (CSV) | Quantity-only updates, global or per warehouse | A row without warehouse-code is treated as the global quantity for that SKU. |
| PRI01 | POST /api/offers/pricing/imports (CSV) | Price-only updates, including channel and volume pricing | The mode is delete and replace: any existing price you do not resend is deleted. Up to 50 prices per offer. |
The practitioner rule: use STO01 for stock and PRI01 for prices, and keep OF24 for genuine offer changes. Teams that push a stock-only payload through OF24 wipe descriptions, lead times and discounts, because every unsent field falls back to its default.
All four are asynchronous. The call returns an import ID immediately, and the real result arrives later through a status endpoint and an error report. A 201 response means "file received", not "stock updated".
import csv, io, time
import requests
BASE = 'https://your-instance.mirakl.net/api' # one base URL per marketplace
session = requests.Session()
session.headers.update({'Authorization': 'YOUR_API_KEY', 'Accept': 'application/json'})
def call(method, path, **kwargs):
for _ in range(5):
r = session.request(method, BASE + path, timeout=30, **kwargs)
if r.status_code == 429:
# Mirakl tells you how many seconds to wait
time.sleep(int(r.headers.get('Retry-After', '60')))
continue
r.raise_for_status()
return r
raise RuntimeError('Still rate limited after 5 attempts')
def push_stock(rows):
# rows: [('SKU-001', 12), ('SKU-002', 0)] -> one batch, not one call per SKU
buf = io.StringIO()
writer = csv.writer(buf, delimiter=';', quoting=csv.QUOTE_ALL)
writer.writerow(['offer-sku', 'quantity', 'warehouse-code', 'update-delete'])
for sku, qty in rows:
writer.writerow([sku, qty, '', ''])
files = {'file': ('stock.csv', buf.getvalue(), 'text/csv')}
return call('POST', '/offers/stock/imports', files=files).json()['import_id']
def stock_import_result(import_id):
# STO02: poll this until the import finishes, then read the error report (STO03)
status = call('GET', f'/offers/stock/imports/{import_id}/status').json()
if status.get('has_error_report'):
report = call('GET', f'/offers/stock/imports/{import_id}/error_report',
headers={'Accept': '*/*'})
return status, report.text
return status, NoneStore every import ID with the batch that produced it. When a seller asks why a SKU oversold, the error report for that import is the only place the answer lives.
The Mirakl order API workflow: OR11, OR21, OR23, OR24
Orders on Mirakl are not "paid, then ship". The seller accepts first, the customer is debited second, and only then can the order be shipped. Your integration has to follow the order state, not assume it.
| Order state | Meaning | Your call |
|---|---|---|
WAITING_ACCEPTANCE | The order is waiting for the shop's answer | OR21 PUT /api/orders/{order_id}/accept, accepting or refusing each line |
WAITING_DEBIT | The shop accepted the order | None, wait |
WAITING_DEBIT_PAYMENT | The operator's system was asked to debit the customer | None, wait |
SHIPPING | Paid, waiting for the shop to ship | OR23 PUT .../tracking, then OR24 PUT .../ship |
SHIPPED | Sent by the shop | None |
RECEIVED, CLOSED | Delivered, then finalized | None |
REFUSED, CANCELED | Stopped before fulfillment | Release the reserved stock |
Poll OR11 (GET /api/orders) with start_update_date so you only fetch what changed. The reference notes that Mirakl subtracts a time delta from that date so no order is missed. The consequence is that you will receive the same order more than once, so make processing idempotent on order ID plus state.
def sync_orders(since_iso):
# since_iso example: '2026-10-10T01:00:00Z' (dates are UTC, ISO 8601)
offset = 0
while True:
page = call('GET', '/orders', params={
'start_update_date': since_iso, 'max': 100, 'offset': offset,
}).json()
for order in page['orders']:
handle(order)
offset += 100
if offset >= page['total_count']:
break
def handle(order):
order_id, state = order['order_id'], order['order_state']
if state == 'WAITING_ACCEPTANCE':
lines = [{'id': line['order_line_id'], 'accepted': in_stock(line)}
for line in order['order_lines']]
call('PUT', f'/orders/{order_id}/accept', json={'order_lines': lines})
elif state == 'SHIPPING':
parcel = tracking_for(order_id) # from your warehouse or carrier
if parcel:
call('PUT', f'/orders/{order_id}/tracking', json={
'carrier_code': parcel['carrier_code'],
'tracking_number': parcel['tracking_number'],
})
call('PUT', f'/orders/{order_id}/ship') # no bodyNotes from the reference that are easy to miss:
- OR24 needs
Content-Length: 0. The ship call has no body, and Mirakl requires the header anyway. Most HTTP libraries add it for a bodiless PUT, but hand-built requests and some proxies do not, and the call fails. - Unregistered carriers are allowed. If the carrier is not in the operator's list, OR23 accepts a complete tracking URL instead of a carrier code.
- Several parcels need a different group. Orders that ship in more than one parcel go through the Multiple Shipments APIs: ST01 (
POST /api/shipments), ST23 for tracking and ST24 to ship. - Large backlogs should not use OR11. Mirakl points high-volume callers to the asynchronous export: OR13 to request it, OR14 to poll, OR15 to download.
Mirakl API rate limits and call frequency
Mirakl does not publish one global quota. Every operation has its own "Call Frequency" block with a recommended and a maximum usage, and exceeding it returns 429 Too Many Requests with a Retry-After header giving the number of seconds to wait.
| API | Purpose | Recommended | Maximum |
|---|---|---|---|
| OF24 | Create, update, delete offers | Every 5 minutes | Once per minute |
| OF01 | Offer file import | Every 5 minutes (offers only); every hour with products | Once per minute (offers only); once every 15 minutes with products |
| STO01 | Stock file import | Every 5 minutes when stock needs updating | Once per minute |
| STO02 | Stock import status | After each STO01 call, every 5 minutes | Once every 15 seconds |
| PRI01 | Price file import | Every 5 minutes | Once per minute |
| P41 | Product import | Every hour, per seller | Every 15 minutes, per seller |
| P42 | Product import status | Once per minute until the final status | Once per minute |
| H11, PM11, VL11 | Categories, attributes, value lists | Every hour | Every hour |
| OR11 | List orders (background sync) | Every 5 minutes | Once per minute |
| OR11 as "External Partner" | List orders (background sync) | Every 15 minutes | Every 15 minutes |
| OR13 | Asynchronous order export | Differential every 15 minutes | Differential every 5 minutes; full every 24 hours |
| OF52 | Asynchronous offer export | Differential every 5 minutes; full once per day | Differential once per minute; full once per day |
These numbers decide the architecture more than any framework choice does:
- Stock sync is batch, not event-per-SKU. With STO01 capped at once per minute, you collect every stock change into a queue and flush one file per interval. A webhook from your store that fires one Mirakl call per sale will be throttled on a busy day.
- Your oversell window is your flush interval plus Mirakl's import processing time. At the recommended five minutes, keep a safety buffer on fast-moving SKUs instead of publishing your last unit.
- Limits apply per seller. A connector serving many shops needs one queue per shop, not one global queue.
- Order sync is polling. The seller reference lists no webhook endpoints as of October 2026, so new orders reach you on your OR11 cycle. Agencies calling as an external partner get the slower 15-minute limit, which is worth knowing before you promise a client "instant" order import.
Pagination, dates and compatibility rules that break integrations later
These rarely show up in a demo and regularly show up in production:
- Two pagination styles. Offset pagination uses
max(default 10, maximum 100) andoffset, with next and previous URLs in theLinkheader; OR11 works this way. Seek pagination useslimitand anext_page_tokenyou pass back aspage_token; shipments (ST11) work this way. With apage_token, every other parameter is ignored. - The default page is 10 items. An integration that forgets
max=100and never paginates looks fine in testing and drops orders the first day volume passes ten. - Dates are UTC. Responses use ISO 8601 with a trailing
Z. In GET parameters the date must be URL-encoded, which matters when you send an offset such as+02:00. - New fields and new enum values can appear at any time. Mirakl ships continuously and guarantees backward compatibility only if your code tolerates added fields, reordered fields and unknown enumeration values. Deserialize order states as strings, not as a closed enum that throws on a new value.
- Prefer JSON. Mirakl advises JSON because its newest APIs are JSON-only.
- Skip deprecated endpoints. OR12, OR26, OR41, OF26 and OF51 are marked deprecated in the reference. Older tutorials and plugins still use them; a new build should start on OR11, OR28 and the asynchronous exports.
- Read status codes, not reason phrases. Mirakl says the text after the code is optional and may be missing.
Sources for everything above: Mirakl's seller REST API reference and the official Mirakl PHP SDK for sellers, whose changelog (v1.27.0, ) is a quick way to see which operations were added recently.
Custom Mirakl integration or a ready-made connector?
Both are valid. The decision depends on where your complexity lives, not on which is "better".
| Your situation | Better fit | Why |
|---|---|---|
| One or two Mirakl marketplaces, a standard store platform, a small catalog | Connector or feed tool | The mapping is light and a subscription costs less than maintaining code |
| Custom ERP or in-house order system | Custom integration | Connectors expect a known platform on your side; an ERP needs its own adapter either way |
| Multi-warehouse stock, channel pricing, volume pricing, pricing rules | Custom integration | STO01 per warehouse and PRI01 channel prices need logic that generic tools flatten |
| Many Mirakl marketplaces with strict category attributes | Custom, or a connector plus custom mapping | The API code is shared, but the per-operator mapping has to live somewhere you control |
| You are an agency or SaaS acting for many sellers | Custom integration | Per-seller queues, shop_id handling and partner rate limits are your core product |
Whichever route you choose, scope it in this order: catalog mapping first, then stock and price sync, then the order workflow, then returns and messaging. The order endpoints are the smallest part of the work; teams that estimate from the order flow alone underestimate the project.
If you want that scope written down against your own ERP or store, send your marketplace list and SKU count through our free quote form. It costs nothing, needs no account, and the build itself is priced to scope.
FAQ
Questions fréquentes
What is a Mirakl API integration?
It is a software connection between your own systems (ERP, online store, warehouse or PIM) and a marketplace that runs on Mirakl. It uses Mirakl's seller REST APIs to import products, keep offers, stock and prices up to date, retrieve orders, and send back order acceptance and tracking.
How does Mirakl API authentication work for sellers?
Sellers generate an API key in the marketplace's seller back office and send it directly in the Authorization header of every HTTPS request, with no Bearer prefix. If the user belongs to several shops, add the shop_id parameter to choose the shop. Mirakl Connect's Channel Platform APIs are different: they use an OAuth 2.0 bearer token.
What are the Mirakl API rate limits?
There is no single global quota. Each operation documents its own call frequency. For example, OF24 (update offers), STO01 (stock import) and PRI01 (price import) are recommended every 5 minutes with a maximum of once per minute, P41 (product import) is recommended hourly with a maximum of every 15 minutes per seller, and OR11 (list orders) is recommended every 5 minutes with a maximum of once per minute. Exceeding a limit returns HTTP 429 with a Retry-After header.
What is the difference between OF01 and OF24 in the Mirakl API?
OF01 imports an offer file (CSV, XML or XLSX) and requires an import_mode, while OF24 creates, updates or deletes offers from a JSON body. Both are asynchronous and return an import ID. With OF24 you must send every offer field, because fields that are not sent are reset to their default value. For stock-only or price-only changes, STO01 and PRI01 are the safer endpoints.
Does the Mirakl seller API send webhooks for new orders?
The seller REST API reference lists no webhook endpoints as of October 2026. Order sync is done by polling OR11 with the start_update_date filter, or by using the asynchronous order export (OR13, OR14, OR15) for large volumes.
Can one integration work with every Mirakl marketplace?
The code can, because every Mirakl marketplace exposes the same seller API. The configuration cannot: each marketplace has its own instance URL, its own API key, and its own categories, required attributes and value lists. Plan for shared API code and a separate catalog mapping per marketplace.