Bol.com API Conventions

For the Retailer API we have established a set of conventions that we try to adhere with every new endpoint.This helps us to maintain our focus on:

  • 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 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

/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

There are three time units available: * SECONDS * MINUTES * HOURS

The list above is an example of how throttling limits are configured in our landscape and is not representative of current production settings.The most specific rule will have the 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; one if the rate limit was not exceeded, and one if it was exceeded.

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 seconds from this moment until 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.

Http Methods

The retailer API is able to support all available HTTP methods, although some may not be supported for all resources.

Table 5. Retailer API supported methods
Method Description

GET

Retrieve data from a resource, in most cases (but depending on the response code), this method will return a body.

POST

Creates a new resource, will in most cases return a body containing a link to the process status resource, where clients can track the result of the operation.

PUT

Updates an existing resource, will in most cases return a body containing a link to the process status resource, where clients can track the result of the operation.

DELETE

Deletes a new resource, will in most cases return a body containing a link to the process status resource, where clients can track the result of the operation.

HEAD

Will execute the request as normal but only return the headers that normally would be returned, the body is omitted. Typically only available everywhere GET is supported.

OPTIONS

Retrieves the supported HTTP method for the requested path, by listing them in the 'Allow' header.

Whenever a specific method is not supported by the retailer API, an error will be returned. For HEAD and OPTIONS requests, the body will be omitted, even in case of errors. The status codes will be returned as per usual.

Server response when supplying an unsupported HTTP method.
{
    "type": "https://api.bol.com/problems",
    "title": "Bad Request",
    "status": 400,
    "detail": "HTTP method not supported for this endpoint.",
    "host": "Instance-001"
}

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 6. 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. This could be a validation, wrong resource URL, or other error.

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 requested 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

The Retailer API employs a standardized way to relay problems that occur, and follows the Problem RFC 7807 as a guide. The RFC defines a common model for displaying errors is defined, which gives all the information needed to resolve common errors while interacting with the API. User errors can be recoverable (4xx range) or technical (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 support JSON and XML as request formats in V4, and only JSON as a request format in V5. For responses we support additional types such as CSV, PDF and Excel (openxmlformat).

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

JSON

JSON

JSON payload - schemaless - 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 (V4 only)

XML

XML payload - schemaless - 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)

There are no examples for the XML payloads, as the 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 will be accepted with any value, and responses will define a value. In most cases this will be 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.

XML will no longer be supported from V5 of the Retailer API.We recommend migrating to JSON as soon as possible.For more information see here.

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 with a Java example.Refer to your own programming language documentation in case it differs:

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

This will return the following example date format 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 null fields. This means that we will omit all properties that do not have a value at a given time. The response below is incorrect and will not be returned from the API in this way:

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

The correct representation for the data listed above is:

{
    "id": 100000175
}

If the list is empty or null we omit the element.

Example 1. Example response
{}

We will not return an element with an empty list:

{
  "orders" : []
}

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.Other methods are available, but not actively supported by the API.

HTTP methods

Table 8. 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, and 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, and returns a response body that will allow a 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, and 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 they are 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 currently 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, a resource such as /retailer/orders is likely to return 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 ?, and all additional query parameters are separated by the & symbol.Some examples:

  • /retailer/transports?fulfilment-method=FBB: returns all transports for FBB

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

  • /retailer/orders?fulfilment-method=FBB: returns all orders for FBB

Query parameters are always expressed in kebab-case, where words are separated by a hyphen -.

All property names are lowercase, but the values are case-sensitive.
  • fulfilment-method

  • condition

  • some-longer-parameter

Pagination

Pagination is a common example for query parameters. Resources that return lists of entities are all paginated. To use pagination, the user must specify 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 cannot be specified by API users. In general we do not return total counts for resources or the number of pages available; the user can 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 and Content-Type headers

The Accept header specifies the format of the request the caller is expecting to receive, while 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 Versioning.

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 9. 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.v5+json

PDF

application/pdf

application/vnd.retailer.v5+pdf

CSV

application/csv

application/vnd.retailer.v5+csv

The Retailer API vendor-specific media type format

The structure for the Retailer API media types follows the same structure every time, with a 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 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
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 containing the string Bearer, followed by the token retrieved from the OAuth2 server.

For example:

Bearer eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJlMmViZW<rest of token omitted>

It’s crucial to follow the specification, since failing to set the Authorization header in a request will result in a response with status 403: (forbidden) immediately, which blocks access to the API. 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 10. Common API versioning strategies
Type Example

Path Versioning

/retailer/orders/v5

Query Parameter Versioning

/retailer/orders?version=5

Header Versioning

Accept=application/vnd.retailer.v5+json / `Content-Type=application/vnd.retailer.v5+json

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.v5+json
Accept=application/vnd.retailer.v5+json

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.

In MediaTypes the major version is the only version used, while 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, for example filter params and extra fields.

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.

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 will be dropped, and V4 will be 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 5.0 to 5.1)

application/vnd.order.v5+

No change

Adding a new field to the response body

Minor version (for instance updating 5.0 to 5.1)

application/vnd.order.v5+

No change

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

Minor version (for instance updating 5.0 to 5.1)

application/vnd.order.v5+

No change

Adding a new supported MediaType

Minor version (for instance updating 5.0 to 5.1)

application/vnd.order.v5+

No change

Removing a field from the request

Minor version (for instance updating 5.0 to 5.1)

application/vnd.order.v5+

No change

Removing a field from the response

Major version (for instance updating 4.0 to 5.0)

application/vnd.order.v4+

application/vnd.order.v5+

Adding a new required field to the request

Major version (for instance updating 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 11. 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, where 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 refer to the relevant API reference 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 a 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.

In the example 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 ID 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 will the field 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.

Users should poll the process status returned to them using the relevant endpoint specified in the API documentation. Initially, the process status instance will start their lifecycle with the status PENDING, and 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.

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, and 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 since it has been optimized for that, so users can poll this entity until the status change, and then retrieve the resulting information from the relevant endpoints.

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 a 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.

This demo environment only uses sample data and is safe for testing.

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

Documentation on the demo environment can be found here, grouped by version. This page documents 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-001",
    "instance": "https://api.bol.com/retailer-demo/orders?page=WRONG_PAGE"
}

Using list and single calls

The resources in the Retailer API were designed to be used differently when calling lists versus single items. Specifically, list calls such as GET Orders intentionally retrieve much less information per order than single calls such as GET order. This is because detailed calls are frequently accessing multiple backend systems, and providing the bulk of information in a list at high frequencies will slowdown our services.

We ask that you use the API calls in the way they were designed to minimize the impact on the Retailer API. List calls should be used to provide the information on the next steps to take, and the single calls allow you to process each case.

For example, with Orders you may feel that it would be beneficial to poll the Orders list regularly for changes to order items. However, doing so would incur a cost in the capacity of our service, and the information you need would probably not be helpful until the time when the list of order items was ready to be fulfilled, since new cancellations can occur at any time. In this case you could poll the service at a normal frequency to track your orders list, and then use single calls at the time of fulfilment to see if for example any items had been cancelled.

Support and tracing

Whenever the API isn’t responding the way you would expect it to or is returning errors, you can get in touch with Partner Service for assistance.

We’ve added headers to each request that can be provided in your query with Partner Service 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.