Marketplace Selling

bol.com API Integration Guide: Auth, Versions and Rate Limits

bol.com API integration, checked against the official docs: 299-second tokens, v10 vs v11 headers, rate limits, process statuses and a working Python client.

Ảnh đại diện Long Nguyen

Long Nguyen

Lập trình viên Fullstack · Kỹ sư AI · Nhà nghiên cứu

• • 4 phút đọc •

What a bol.com API integration actually covers

A bol.com API integration connects your store, ERP or warehouse system to bol's Retailer API, so offers (price, stock, delivery promise), orders, shipments and returns move without anyone opening the seller dashboard. You authenticate with OAuth 2 client credentials, send JSON with a versioned media type, and treat every write as asynchronous.

That last sentence is where most integrations go wrong. The API is not hard to call. It is hard to call correctly for months: tokens die after five minutes, versions are retired on a schedule, and a request bol accepted can still fail half a minute later. This guide walks through each of those, checked against bol's own documentation on .

The Retailer API is no longer one block with one version number. It is a family of APIs, and they do not all sit on the same version:

APIWhat you use it forStatus (Oct 2026)
OffersCreate and update what you sell: price, stock, fulfilment method, per EANv11 current, v10 deprecated
Orders & ShipmentsFetch new orders, confirm shipments with carrier and trackingv10 current
ReturnsRead and handle customer returnsv10 current
Shipping LabelsBuy and download labels through bolv10 current
InventoryStock held in bol's warehouses (fulfilment by bol)v10 current
Products / Product ContentCatalogue lookups and content for the product pagev10 current
Commissions, Invoices, Insights, PromotionsFees, billing, performance data, dealsv10 current
SubscriptionsPush notifications instead of pollingv11 current, v10 deprecated
Process StatusThe result of every asynchronous writev10 current
AdvertisingSponsored product campaignsv11 current

Most sellers need four of these to run day to day: Offers, Orders & Shipments, Returns and Process Status. Start there and add the rest when a real need shows up.

How to get bol.com API credentials and authenticate

A seller creates a client ID and client secret in the API settings of the bol seller dashboard. Those two values are all a private integration needs. There is no per-user login and no redirect step for your own account.

You exchange them for a short-lived bearer token:

  • Endpoint: POST https://login.bol.com/token with grant_type=client_credentials.
  • Credentials: HTTP Basic, the base64 of clientId:clientSecret.
  • Accept header: application/json. The login service accepts nothing else.
  • Lifetime: expires_in is 299 seconds. The token is a JWT, so the expiry is also readable inside it.

The mistake that gets your IP banned

The documentation is blunt: do not request a new token for every API call. The login service has far lower limits than the Retailer API itself, and going over them leads to an IP ban, not a polite 429.

In a single script this is easy to respect. It breaks when you scale: ten queue workers, each fetching its own token at the start of each job, is the classic way to lock a whole server out. Keep one token in a shared cache (Redis or a database row), refresh it shortly before expiry, and put a lock around the refresh so two workers do not race.

Here is a client that does the single-process version, written against the conventions in bol's Retailer API conventions:

import time
import requests

TOKEN_URL = 'https://login.bol.com/token'
BASE = 'https://api.bol.com/retailer'  # '/retailer-demo' while testing


class BolClient:
    def __init__(self, client_id, client_secret):
        self._auth = (client_id, client_secret)
        self._token = None
        self._expires_at = 0.0

    def _bearer(self):
        # Reuse the token until 30 s before expiry. Never fetch one per call.
        if self._token and time.time() < self._expires_at - 30:
            return self._token
        r = requests.post(
            TOKEN_URL,
            auth=self._auth,  # HTTP Basic, clientId:clientSecret
            data={'grant_type': 'client_credentials'},
            headers={'Accept': 'application/json'},
            timeout=15,
        )
        r.raise_for_status()
        body = r.json()
        self._token = body['access_token']
        self._expires_at = time.time() + body['expires_in']
        return self._token

    def call(self, method, url, version='v10', **kwargs):
        if not url.startswith('https://'):
            url = BASE + url
        media = 'application/vnd.retailer.' + version + '+json'
        for attempt in range(5):
            headers = {'Authorization': 'Bearer ' + self._bearer(), 'Accept': media}
            if 'json' in kwargs:
                headers['Content-Type'] = media
            r = requests.request(method, url, headers=headers, timeout=30, **kwargs)
            if r.status_code == 429:
                time.sleep(int(r.headers.get('Retry-After', 5)) + 1)
            elif r.status_code in (500, 503, 504):
                time.sleep(2 ** attempt)
            else:
                r.raise_for_status()
                return r
        raise RuntimeError('bol API still failing after 5 attempts: ' + url)

Three details in that code are deliberate. The version is an argument, not a constant. Headers are rebuilt on every attempt, so a retry after a long wait never sends an expired token. And a missing or malformed Authorization header on bol returns 403, not 401, so do not assume 403 always means a permissions problem.

Building for someone else's seller account

bol has a second flow, Code Flow, where a seller logs in at bol and grants an integrator access. It is only available to integration partners that bol lists. Everyone else works with client credentials the seller issues for that integration and can revoke from the seller dashboard. If you hire a developer or agency, that is the model to expect: your account, your credentials, your off switch.

bol.com API versions: why v10 and v11 now run side by side

The version is not in the URL. It lives in the media type you send:

Accept: application/vnd.retailer.v10+json
Content-Type: application/vnd.retailer.v10+json

Only the major version appears, and since the older versions were removed in late 2024, v10 has been the floor. What changed since is that individual APIs now move ahead on their own. Offers, Subscriptions and Advertising are on v11 while Orders, Returns and the rest remain on v10.

The practical consequence: a single global version constant in your code is now a bug waiting to happen. Set the version per call, or per API module, and confirm the exact header for each endpoint in the reference before you ship.

Per bol's API release and lifecycle policy, the rules are:

  • bol uses semantic versioning. Only a major version may break you.
  • When a new major version ships, the previous one is deprecated and scheduled for removal after 12 months. For Advertising resources the window is currently 3 months.
  • Deprecated versions keep full support until removal.
  • Calls to deprecated endpoints come back with a Warning: 299 - Deprecated API response header.

That header is free monitoring. Log it, and raise an alert the first time it appears. You then learn about a retirement from your own logs months ahead, instead of from a failed stock sync. If your integration still updates offers on v10, that migration belongs on this year's plan, not next year's.

A bol.com integration architecture that survives production

Whatever sits on your side (Shopify, WooCommerce, Odoo, a custom ERP), a dependable integration has the same five parts:

  1. Token cache. One shared token, refreshed before its 299 seconds run out.
  2. Change detector. Compares your current price and stock with what was last sent to bol, and queues only real differences.
  3. Outbound queue with a rate limiter. Sends those changes at a pace bol accepts.
  4. Process status tracker. Records the ID of every write and follows it to its end state.
  5. Order importer. Pulls new orders in, pushes shipment confirmations out, and never creates the same order twice.

A few rules of thumb from marketplace work in general apply directly to bol:

  • Pick one source of truth for stock. Your ERP or store owns the number and bol receives it. Two-way stock editing ends in overselling.
  • Make order import idempotent. Use bol's order ID as a unique key on your side. Pagination has no total count and no page size control, so you loop pages until one comes back empty, and you will see the same order more than once.
  • Store times in UTC. bol sends ISO-8601 with an offset (for example 2018-04-20T10:55:37+02:00), and some responses include milliseconds. Parse both forms.
  • Log X-Request-ID. Every response carries it, and bol's Partner Service asks for it when you report a problem.

This is the same shape we use for marketplace integration work on other channels: the marketplace changes, the five parts do not.

Process statuses: why an accepted request has not succeeded yet

Every POST, PUT and DELETE on the Retailer API is asynchronous. bol answers quickly with a process status, which is a receipt, not a result. It contains a processStatusId, an eventType, a status and a links array pointing at the place to check later.

StatusMeaningWhat your code should do
PENDINGReceived, not processed yetWait and check again
SUCCESSApplied. For creates, entityId is now filled inSave the entityId, mark the change as done
FAILURERejected, with an error messageRead the message. Retry only if it can succeed
TIMEOUTbol did not finish in timeRetry the original request

The timing numbers matter for design. Most processes finish within half a minute, but one may take up to 3 hours. Finished statuses are kept for at least 24 hours and then return 404, at which point you stop polling.

So polling inside the web request that triggered the change is the wrong place. Poll briefly, then hand anything still pending to a background job:

def wait_for(client, process_status, max_wait=300):
    # Follow the self link bol returns instead of building the URL yourself.
    url = next(l['href'] for l in process_status['links'] if l['rel'] == 'self')
    delay, waited = 2, 0
    while waited < max_wait:
        status = client.call('GET', url).json()
        if status['status'] != 'PENDING':
            return status  # SUCCESS, FAILURE or TIMEOUT
        time.sleep(delay)
        waited += delay
        delay = min(delay * 2, 30)
    return None  # still PENDING: store the id, let a background job re-check

Note the first line of the function. Follow the self link bol gives you instead of assembling the URL by hand; it keeps working if the path changes between versions. Polling is throttled as well, which is why the delay doubles.

If you would rather not poll at all, look at the Subscriptions API, which pushes events to you. It is on v11 and its event types are version-specific, so read the current list instead of trusting an older tutorial.

bol.com API rate limits and how to stay under them

bol sets limits per endpoint and publishes the live values in a machine-readable list at api.bol.com/retailer/public/ratelimits. The numbers change, so hardcoding them is a mistake. What stays stable is the mechanism:

  • Every response reports your remaining budget and when it resets (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset).
  • Going over returns 429 with a Retry-After header in seconds.
  • Header capitalisation varies per endpoint, so read them case-insensitively.
  • bol logs excessive 429s and contacts sellers whose traffic causes them.

The real fix is sending less, not retrying smarter. bol's own guidance is to send only updates that reflect an actual change and to prefer event-driven updates over batches. In practice the offender is almost always a nightly job that re-sends stock for the whole catalogue. Keep a record of the last price and stock you sent per offer, and compare before sending. The call volume then follows what actually changed in your business, not the size of your catalogue.

bol.com API errors: which ones to retry

Errors follow RFC 7807: a JSON body with title, status, detail and, for validation problems, a violations list naming each bad field and the reason. Store that list. It is what turns a support ticket into a five-minute fix.

CodeUsual cause on bolRetry?
400Invalid payload. Read violationsNo. Fix the data
401Wrong client ID or secret at the token endpointNo
403Missing Authorization header or Bearer prefixOnce, after refreshing the token
404Unknown resource, or a process status older than its retentionNo
406 / 415Wrong version or format in the media typeNo. Fix the header
429Rate limit reachedYes, after Retry-After
500 / 503 / 504Problem on bol's sideYes, with increasing delay

One rule saves a lot of damage: never retry a 400 automatically. A bad EAN stays bad, and a loop retrying it spends the budget your valid updates need.

Testing against the bol.com demo environment

bol provides a demo environment under the path prefix /retailer-demo/, for example https://api.bol.com/retailer-demo/orders. It uses the same credentials and token as production, returns sample data only, and is safe for testing.

Know its limit before you rely on it. Sample data proves that your authentication, headers, parsing and status handling work. It cannot prove your own catalogue is valid, because your EANs, prices and delivery settings are not in it. Plan a second stage: a small batch of real offers in production, watched through their process statuses, before you switch on the full sync.

Custom bol.com integration or a ready-made connector?

Not every seller needs custom code. The honest split looks like this:

Your situationBetter fitWhy
Standard Shopify or WooCommerce store, one warehouse, bol as an extra channelReady-made connectorThe common case is already built. You usually pay a subscription instead of maintaining code
ERP such as Odoo, or a custom back office, as the source of truthCustom integrationConnectors assume the store owns stock and orders. Yours does not
Pricing rules, bundles or several warehouses that differ per channelCustom integrationThis logic is your business. Generic tools rarely model it
Large catalogue with frequent price changesCustom, or a connector you have load-testedChange detection and rate limits decide whether the sync keeps up

Whichever way you go, budget for upkeep. With a 12-month retirement window on each major version, a bol integration is something you maintain, not something you finish.

If you are unsure which side of that table you are on, describe your setup and request a free quote from Netalith. You get a scoped answer on whether a connector covers it or a custom build is worth the cost.

CÂU HỎI THƯỜNG GẶP

Câu hỏi thường gặp

Which version of the bol.com Retailer API should I use in 2026?

It depends on the API. As of October 2026 the Offers, Subscriptions and Advertising APIs are on v11, with Offers and Subscriptions v10 marked deprecated, while Orders and Shipments, Returns, Inventory, Invoices and Process Status are on v10. Set the version per endpoint in the Accept and Content-Type media type, for example application/vnd.retailer.v10+json.

How long does a bol.com API access token last?

The token response returns expires_in of 299 seconds, just under five minutes. Reuse the same token until shortly before it expires. bol explicitly warns against requesting a new token for every call, because the login service has much lower limits than the Retailer API and exceeding them leads to an IP ban.

Does bol.com have a sandbox or test environment for the API?

Yes. The demo environment uses the path prefix /retailer-demo/ on api.bol.com, works with the same client credentials and token as production, and returns sample data only. It is good for testing authentication, headers and response handling, but it does not contain your own products, so validate a small batch of real offers in production as well.

Why does my bol.com API update return success but nothing changes?

Because writes are asynchronous. A POST, PUT or DELETE returns a process status with the state PENDING, which only confirms bol received the request. You have to check that process status until it reaches SUCCESS, FAILURE or TIMEOUT. Most finish within half a minute, some take up to 3 hours, and a FAILURE includes an error message explaining what was rejected.

What are the bol.com API rate limits?

Limits are set per endpoint and bol publishes the current values at api.bol.com/retailer/public/ratelimits. Each response includes headers showing the remaining budget and reset time, and exceeding a limit returns HTTP 429 with a Retry-After header. Read the headers in code instead of hardcoding numbers, and send only offers whose price or stock actually changed.

Can a developer or agency connect to bol.com on my behalf?

Yes. Unless the integrator is a partner listed by bol and uses the Code Flow login, the normal route is that you create client credentials for that integration in your seller dashboard and share them securely. The access stays under your control and you can revoke it from the dashboard at any time.

Cập nhật cùng Netalith

Nhận kiến thức công nghệ, cập nhật sản phẩm và ưu đãi đặc biệt qua email.