Bol.com API Conventions

For the retailer API we laid down a couple of conventions that we strive to follow each and every time we add a new endpoint. There are a few reasons for this:

  • Stability

  • Predictability

  • Ease of implementation

The conventions are also publicly available on our developer resources page. Users can read the details and use this information to build proper API clients. Here we provide the same conventions, but also add technical context on how to build the API implementation to uphold these conventions.

Authentication

The retailer API is secured with OAuth2. For all secured endpoints, users need to log into the SSO server at https://login.bol.com and retrieve a token. This token can then be used to authorize users for secured endpoints. The authentication process is described in detail in a separate documentation, which can be found here.

More information on the headers to set is available in the headers section.

Rate Limiting

The retailer API enforces strict limits when calling the API to prevent overloading of the retailer API and/or Backend services. Endpoints are throttled based on a set of properties, similar to the list below:

Table 1. Rate Limiting
Rule Method Throttle Match TimeUnit TTL Limit Description

1

GET

/retailer/orders

MINUTES

3

7

Any GET to /retailers/orders will be limited to 7 requests every 3 minutes

2

GET

/retailer/orders/*

MINUTES

1

8

Any GET to /retailers/orders/* will be limited to 8 requests per minute

3

POST

/retailer/orders/*

MINUTES

1

25

Any POST to /retailer/orders/*/cancellation will be limited to 25 requests every minute

There are three time units available, SECONDS, MINUTES and HOURS. The list above is an example of how throttling limits are configured in our landscape and not representative of current production settings. The most specific rule will have highest precedence:

Table 2. Rate Limiting Matching
Method Path Matched Rule

GET

/retailers/orders

Retrieves order list, matches rule 1

GET

/retailers/orders/12345

Retrieves order 12354, matches rule 2

POST

/retailers/orders/12345/cancellation

Cancels order with id 12354, matches rule 3

Rate Limiting Headers

Whenever a call is executed to the API, headers are set in the response to give the caller information on the current throttling limits that are applied. This way, the caller can see what limits are currently affecting their API usage. There are two sets of throttling headers:

Table 3. Rate Limiting Headers (when rate limit not exceeded)
Header Header Value Description

X-RateLimit-Limit

20

The total limit currently active for this endpoint, 20 requests can be made in total

X-RateLimit-Reset

1539261914

The time in milliseconds since the EPOCH in GMT after which the current limit will be reset

X-RateLimit-Remaining

19

The total number of remaining requests within the given time range

When the throttling limit has been reached for a specific user, the following header will be returned:

Table 4. Rate Limiting Headers (when rate limit exceeded)
Header Header Value Description

Retry-After

4

The number of seconds the user has to wait before being able to retry the request

The body for the request will also change to reflect this, see below for an example (response will be accompanied by status code 429 too many requests):

Server response after exceeding rate limits
{
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Too many requests, retry in 4 seconds."
}

Rate Limits Resource

We also provide a rate limit resource which contains the list of rate limits for all resources provided by the retailer API. This information is available in our rate limits section.

No rights can be derived from this resource, as rate limits may be adjusted over time. For current, live insights in the rate limits, please inspect the headers provided in every call or use the rate limits API resource that exposes the rate limits in JSON format.

Response Codes

The retailer API provides HTTP response codes for each request that correspond with the body and action the user has attempted. Here are the most common response codes used by the retailer API:

Table 5. Retailer API response codes
Code Label Description Retryable

200

OK

The HTTP 200 OK success status response code indicates that the request has succeeded.

No

201

CREATED

The request has been fulfilled, resulting in the creation of a new resource.

No

202

ACCEPTED

The request has been accepted for processing, but the processing has not been completed.

No

400

BAD REQUEST

The server cannot or will not process the request due to an apparent client error (validation, wrong resource url, etc).

No

401

UNAUTHORIZED

Used when authentication is required and has failed or has not yet been provided.

No

403

FORBIDDEN

The request was valid, but the server is refusing action. No permission to use resource.

Yes

404

NOT FOUND

The requested resource could not be found but may be available in the future.

No

405

METHOD NOT ALLOWED

A request method is not supported for the requested resource.

No

406

NOT ACCEPTABLE

The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.

No

410

GONE

Indicates that the resource requested is no longer available and will not be available again.

No

415

UNSUPPORTED MEDIA TYPE

The request entity has a media type which the server or resource does not support.

No

500

INTERNAL SERVER ERROR

A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.

Yes

503

SERVICE UNAVAILABLE

The server is currently unavailable (because it is overloaded or down for maintenance).

Yes

504

GATEWAY TIMEOUT

The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.

Yes

Problems

As with any application, things can go wrong. The retailer API employs a standardized way to relay these situations, and follows the Problem RFC 7807 as a guide. In this RFC, a common model for displaying errors is defined, which gives all the information needed to resolve common errors while interacting with the API, be they recoverable user errors (4xx range) or technical errors (5xx range). The type of error should also define whether or not a call to technical support is warranted.

Example Problem response
{
    "type": "https://api.bol.com/problems",
    "title": "Error validating request body. Consult the bol.com API documentation for more information.",
    "status": 400,
    "detail": "Bad request",
    "host": "Instance-001",
    "instance": "https://api.bol.com/retailer/offers",
    "violations": [
        {
            "name": "RetailerOffers[0].condition",
            "reason": "Request contains invalid value(s): 'UNKNOWN'. Allowed values: NEW, AS_NEW, GOOD, REASONABLE, MODERATE."
        },
        {
            "name": "RetailerOffers[0].fulfilmentMethod",
            "reason": "Request contains invalid value(s): 'FBS'. Allowed values: FBR, FBB."
        },
        {
            "name": "RetailerOffers[0].price",
            "reason": "Request contains invalid value(s): '10.000'. Prices should have a precision of two decimals."
        }
    ]
}

The structure of the Problem response is included in the Redoc documentation and the Swagger/OpenAPI specification. You can find the version of the documentation corresponding to the version of the retailer API you are using in the menu on the left.

Message Formats

The retailer API supports various message formats in which to supply requests and receive responses. We only support JSON and XML as request formats, for responses we support additional types such as CSV, PDF and EXCEL (openxmlformat).

Table 6. Retailer API response codes
Request format Response format Information

JSON

JSON

JSON payload - schemaless - there’s no schema definition other than the OpenAPI specification and Redoc documentation. You can find the documentation for the version you are using in the menu on the left.

XML

XML

XML payload - schemaless - there’s no schema definition other than the OpenAPI specification and Redoc documentation. You can find the documentation for the version you are using in the menu on the left.

N/A

CSV

CSV document (comma separated values)

N/A

PDF

PDF document (binary data, can be opened by PDF readers)

N/A

openxmlformats-officedocument.spreadsheetml.sheet

Excel document (XML format, can be opened by Excel)

Note: There are no examples for the XML payloads, as Redoc and OpenAPI Specification do not support this properly. The XML payloads follow the same structure as JSON, and can be deduced from these payloads. Unlike JSON, XML requires a defined root element. For requests, the root element can have any value to be accepted, for responses we define a value for this. This is in most cases similar to:

Example arbitrary xml root node
<orderResponse><order></order></orderResponse>

or

Example arbitrary xml root node
<orders><order></order></orders>

JSON/XML Property Naming

All properties and entities in the responses returned from the retailer API are in lowerCamelCase. This means the first character is lowercase, and each following word starts with an uppercase Character. Some examples:

  • orders

  • orderId

  • fulfilmentMethod

  • trackAndTrace

This applies to both XML elements and JSON properties. In addition, XML supports the usage of attributes within XML tags (tagValue). This is not compatible with the JSON model, which is why we do not use this functionality. All XML returned is attribute free. In addition, as our JSON is not namespaced, the same applies to our XML. No namespace information is applied.

Date and Time fields

The date and time fields are presented in ISO-8601 standard truncated to seconds with an offset from UTC.

The date and time format used for the retailer API is listed below (example in Java, may vary in other programming languages. Please refer to your language documentation for more information).

ISO 8601 date/time format
yyyy-MM-dd'T'HH:mm:ssXXX

This will result in the following example date format returned from the API.

Example date/time output
{
  "latestDeliveryDateTime": "2018-04-20T10:55:37+02:00"
}

Null Values

The current version of the API does not marshal fields that are null. This means that we will omit all properties in XML and JSON that do not have a value at a given time. The response below in incorrect and will not be returned from the API in this way:

Response with explicit null values — not supporter by the retailer API
{
    "id": 100000175,
    "product": null
}

The correct representation for the data listed above is:

{
    "id": 100000175
}

It is important to implement your client code to handle this properly. To find out which fields are available for consumption, please consult the Redoc documentation.

Empty fields

In case a field is not to be used, you should omit the field and not send an empty string. An empty string still is a value and will be validated which can lead to a 400 (bad request).

Executing requests

The retailer API currently supports the following HTTP methods. Others are available, but not actively supported by the API.

HTTP Methods

Table 7. Supported HTTP methods
Method Intent Example Result

GET

Retrieve information from the API. No request body.

/retailer/offers

Retrieves all offers

POST

Create an entity. Mandatory request body.

/retailer/offers

Creates a new offer based on the information in the request body, returns a response body that will point to the created offer after creation

PUT

Modify an entity. Mandatory request body.

/retailer/offers/

Modifies an offer with the given id to the information in the request body, returns a response body that will allow user to track if the modification was processed successfully

DELETE

Delete an entity. No request body.

/retailer/offers/

Deletes an offer with the given id, returns a response body that will allow user to track if the deletion was processed successfully

Resource Paths

Resource paths follow path naming conventions where the prefix /retailer/ is followed by the resource name in plural. All paths are defined as lowercase.

  • /retailer/transports

  • /retailer/offers

  • /retailer/orders

When requesting, modifying or deleting specific entities, the id for the entity will be part of the path as a path parameter. When part of a resource path, these path parameters are non-optional, so failure to pass them to endpoints that define them will lead to a 400 bad request or a 404 not found.

  • /retailer/transports/<transport-id>

  • /retailer/offers/<offer-id>

  • /retailer/orders/<order-id>

Sometimes endpoints support specific actions. These can potentially be expressed through using a PATCH method, but at current we choose to express them as follows.

  • /retailer/offers/<offer-id>/stock

  • /retailer/offers/<offer-id>/price

  • /retailer/orders/<order-id>/confirm

Query Parameters (Filtering)

Many requests have additional options that can be used to shape the response. Generally speaking, one can consider a resource such as /retailer/orders as a resource that returns a list of all orders. With the use of query parameters, users can define additional filters that can limit the number of entities returned in the response. Users can optionally specify the query parameters and combine them at will. Query parameters are added to the end of a resource path. The first query parameter is prefixed by ?, all additional query parameters are separated by the & symbol. Some examples:

  • /retailer/transports?fulfilment-method=FBB → Return all transports for FBB

  • /retailer/offers?ean=1234&condition=NEW → Return all offers with a given EAN and condition

  • /retailer/orders?fulfilment-method=FBB → Return all orders for FBB

Query parameters are always expressed in kebab-case, where words are separated by a hyphen -. Please note: all property names are lowercase, the values however are case-sensitive.

  • fulfilment-method

  • condition

  • some-longer-parameter

Pagination

A common example for query parameters is pagination. Resources that return lists of entities are all paginated. To use pagination, the user specifies a query parameter page with a given page number, a non-negative integer. When omitted, the default value for page is 1.

The number of items returned depends on the resource and the backing services, but can not be specified by API users. In general we do not return total counts for resources or the number of pages available; it’s up to the user to request subsequent pages until the responses are empty (meaning the end of the list was reached).

  • /retailer/orders → Page 1 - implicit

  • /retailer/orders?page=1 → Page 1 - explicit

  • /retailer/orders?page=3 → Page 3 - explicit

Headers

When interacting with the API, meta-information for your requests can be added by way of headers. There are a few headers that are of particular importance.

Accept Header and Content-Type Headers

The "Accept" header specifies the format of the request the caller is expecting to receive. In contrast, the "Content-Type" header defines the format the caller is sending to the API. Depending on the endpoint, the retailer API supports a set of different message formats. The value of the header should always be a valid media type.

The retailer API makes use of vendor specific media types – types that are specific for use with the retailer API and will generally not be accepted by other APIs. The reason for this is that we’ve chosen an approach where we will version the API based on header values. For more on this, please see the "Versioning" part of this document.

Users can specify which version of content they want to receive/send, and in addition they can specify the format the response will be sent in (such as xml, json, csv or pdf). In the redoc documentation, the accepted Content-Type and Accept headers are listed for each endpoint.

Table 8. Retailer API supported media types
Type Compatible MediaType Retailer API Equivalent

XML

application/xml

application/vnd.retailer.v4+xml

JSON

application/json

application/vnd.retailer.v4+json

PDF

application/pdf

application/vnd.retailer.v4+pdf

CSV

application/csv

application/vnd.retailer.v4+csv

The retailer API Vendor Specific MediaType Format

The structure for the retailer API media types follows the same structure every time, the prefix of "application/vnd.retailer." followed by version and format, show below.

Vendor specific media type example
application/vnd.retailer.<VERSION>+<FORMAT>

User-Agent header

The retailer API supports usage of the "User-Agent" header. The common use for a "User-Agent" header is described here. Generally speaking, the user-agent will contain information on the client that is being used to connect to the API. The values can be set by the user directly (for instance, if they have created their own tools to interact with the API, they might want to set a custom value containing the name of the tool), but in general these values will be set by the client software. For example, when using postman, the "User-Agent" header will contain a value similar to this.

PostmanRuntime/7.3.0

Note: The "User-Agent" header should never contain information about the user, but information on the tool/software that’s making the connection to the API.

Authorization Header

The retailer API uses OAuth2 for authentication. This is a departure from older implementations of the API, where we used a custom API-Key based authentication mechanism. OAuth2 requires the user to retrieve a token from an OAuth2 server, using any of the supported authentication methods. After retrieving a token from the OAuth2 server, the user can start making calls to the API. For all endpoints that require authentication, it is mandatory to specify an "Authorization" header, which will contain the String "Bearer ", followed by the token retrieved from the OAuth2 server.

For example:

Bearer eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJlMmViZW<rest of token omitted>

Failures to set the "Authorization" header in a request it will result in a response with status 403 (forbidden) immediately, which blocks access to the API. It’s key to follow the specification, not prefixing the token with "Bearer " will also result in a status 403 response.

Versioning

There are several ways to request a specific version of an API, among which are the following approaches:

Table 9. Common API versioning strategies
Type Example

Path Versioning

/retailer/orders/v4

Query Parameter Versioning

/retailer/orders?version=4

Header Versioning

Accept=application/vnd.retailer.v4+xml / Content-Type=application/vnd.retailer.v4+xml

For the retailer API we have chosen Header Versioning. There are a few reasons for choosing this approach.

As we are specifically versioning the model going in and out of the API - rather than the endpoints themselves - we can use the "Content-Type" and "Accept" header for this. As described earlier, we use vendor specific media types which allow us to express version and format in a single parameter, and we can use separate headers to define the version and types of the models going in and out. We can now in theory support a call to the API with a request body in JSON and version 3 representation, while requesting a response in XML and v4.

Content-Type=application/vnd.retailer.v4+json
Accept=application/vnd.retailer.v4+xml

Versioning scheme

The intention is to keep the versioning scheme as simple as possible. The retailer API uses a minor/major versioning scheme, depicted below.

<major-verison>.<minor-version>

We only use the minor version in our release notes, so we can keep track of the (non-breaking) changes we’ve made to the API and define to the users what effects they might have on their interaction with the API. Because all minor changes are supposed to be backwards compatible, users do not need to upgrade to keep using the API.

Note: In MediaTypes the major version is the only version used, the minor is omitted. Users can keep specifying the major version without losing functionality. In addition, they can choose to opt-in to use the newly added features of a minor release (i.e. filter params, extra fields, etc.)

Major version changes are generally considered breaking changes, where models lose compatibility to the older endpoints. When this happens, users are required to change their implementations to upgrade as the older major version will be deprecated somewhere in the future.

Note: We can support different versions at any given time for the API. For instance, we are currently supporting v3, v4 and v5. When adding v6, v3 is dropped, and v4 is marked as deprecated.

To achieve this we apply the following rules.

Change Effect MediaType before changes MediaType after change

Adding an optional field to the request body

Minor version (i.e. update 4.0 to 4.1)

application/vnd.order.v4+

No change

Adding a new field to the response body

Minor version (i.e. update 4.0 to 4.1)

application/vnd.order.v4+

No change

Adding a new optional query parameter (or with a default value)

Minor version (i.e. update 4.0 to 4.1)

application/vnd.order.v4+

No change

Adding a new supported MediaType

Minor version (i.e. update 4.0 to 4.1)

application/vnd.order.v4+

No change

Removing a field from the request

Minor version (i.e. update 4.0 to 4.1)

application/vnd.order.v4+

No change

Removing a field from the response

Major version (i.e. update 4.0 to 5.0)

application/vnd.order.v4+

application/vnd.order.v5+

Adding a new required field to the request

Major version (i.e. update 4.0 to 5.0)

application/vnd.order.v4+

application/vnd.order.v5+

As you can see, most of the actions can be done without having to bump a version. Some changes where we break backwards compatibility require a bump in major version.

Deprecation Headers

Whenever we deprecate a version of the API we will set a warning header for the given endpoint. When implementing the calls to these endpoints, API users can check for these headers to be present and take the proper action by moving to the new endpoint.

We are following a standard described in RFC-7234. Read the specification for more information.

If you are using deprecated endpoints, please make sure you migrate to the new version in time. Deprecation and sunset dates will be published on the developers website.

Table 10. Example warning header for deprecated APIs
Name Value

Warning

299 - Deprecated API

Asynchronous Processing

The retailer API is a fully synchronous service when it comes to GET requests; users can wait for an answer to arrive. For POST/PUT/DELETE operations we follow a different strategy, we handle these requests asynchronously. For these operations we return an instance of type ProcessStatus, which will contain the processing status of the original request.

{
    "id": 100000,
    "entityId": "123456789",
    "eventType": "CONFIRM_SHIPMENT",
    "description": "Confirm shipment for order item 123456789.",
    "status": "PENDING",
    "createTimestamp": "2019-03-01T10:52:57.106+02:00",
    "links": [
        {
            "rel": "self",
            "href": "https://api.bol.com/retailer/process-status/100000",
            "method": "GET"
        }
    ]
}

For a description of the fields, please check the API documentation. The most important fields to look at in this response are the 'id' and 'status' fields. At present, the process in the example is listed as PENDING, which means a background process is still busy processing the request. In time, the process should reach an end state, which is one of the following: "SUCCESS, FAILURE, TIMEOUT". In case of success, no further action is required, in case of "FAILURE" or "TIMEOUT" the user can attempt to execute the action again, unless the error message clearly specifies that this will not succeed.

Users should poll the process status returned to them using the aforementioned endpoint in the API documentation. Initially, process status instance will start their lifecycle "PENDING", they will change to an end state at an unspecified time after being submitted. To protect the services in our landscape from overloading, we throttle these actions, which means we cannot guarantee immediate execution. Generally speaking, most process status entities will change state within half a minute of submission. The number of times a user can poll the process status is limited by throttling. The limit is quite high, so it should not interfere, but to be safe, allow some time between individual polls to make sure these limits are not hit.

In the case shown above, the executed action is the shipment confirmation of an order, so the entity-id (order id in this case) is known. Some actions (such as creating an inbound shipment for FBB) will return an idea upon completion of the process. For these actions, the entityId field will be null while in "PENDING" state, and only after the process transitions to "SUCCESS" the entityId field will be populated by the resulting value. For all other processes (update/delete type operations) the action can’t be initiated without the id of the entity to update/delete.

Supported features

The approach for handling PUT/POST/DELETE operations was implemented to support:

  • Transparent handling of unavailable services; the request can still be delivered to the API and will be scheduled for execution. Whenever services downstream are unavailable, we can delay the message to 'hide' this outage from the user; This is especially useful in deployment situations, several services the API depends on have to be deployed with downtime, we can hide this from our users;

  • Automatic retries; some services can run into locking issues or return other (temporary) functional or technical errors. We can automatically retry the message for a certain number of times until it finally succeeds (or times out) - without the user having to implement this;

  • Convenient retrieval of process status; when initiating an operation, no direct feedback is available. For instance, when confirming an order, users can start polling the GET shipments to see if the order transitions in the shipments list. This is an extremely expensive operation, so we want to discourage this. The process status itself is cheap to query (it has been optimized for this goal), so users can poll this entity until the status change, and then retrieve the resulting information from the relevant endpoints.

Please note that there is limited retention of process status instances after completion. A process status can take up to 3 hours to reach SUCCESS, FAILURE or TIMEOUT state, after which we will keep it available for polling for at least 24 hours. Whenever a process status is deleted, the API will return a 404 response for the GET single, or no list item in the bulk process status calls. Please make sure to stop polling process statuses once they are removed.

All endpoints that execute PUT/POST/DELETE operations will function in this manner, but only if the operation updates or creates an resource. Bulk requests to retrieve data similar to a single GET are not returning process status entities.

Demo Environment

For testing during development without having to create test data or test with production data users can use the demo environment. The same authentication validations apply as the actual API so you need to use the token as you would accessing the retailer API.

Demo resource paths follow path naming conventions with the prefix /retailer-demo/.

Documentation on the demo environment can be found in the menu on the left, grouped by version. This page documents on how to use the demo environment endpoints with the predefined requests and expected responses for all calls. The documentation is generated and tested and kept up to date after each deployment.

If the request does not match exactly with the documented requests a problem with status 400 bad request is returned to inform you the requested resource is not available.

{
    "type": "https://api.bol.com/problems",
    "title": "Bad Request",
    "status": 400,
    "detail": "Requested demo environment resource is not available. Consult the bol.com API demo documentation for more information.",
    "host": "Instance-002",
    "instance": "https://api.bol.com/retailer-demo/orders?page=WRONG_PAGE"
}

This demo environment is NOT linked to any production data and uses only fictional data.

Support and tracing

Whenever the API isn’t responding the way you would expect it to or is returning errors, there’s the opportunity to get in touch with our support organisation, partnerservice.

We’ve added headers to each request that can be provided in your query with partnerservice to make it easier to find all trace information for your request. This will help in determining the root cause of any issues.

The header to uniquely identify your request is named X-Request-ID header, make sure you log this together with any errors that might have occurred processing your requests so you can add it to your support queries with bol.com.