API overview
This is the documentation for the v2.0 System-1 API which provides access to the System-1 back-end system for retrieving and ordering products.
Major changes include an overhaul to most car related routes and models.
General
The API is based on the principals of REST which makes it a RESTful webservice. This means:
-
There is base Uri
- PRODUCTION environment: https://s1-api.fource.nl/api/
- TEST environment: https://s1-api-tst.fource.nl/api/
- All standard HTTP methods are used. These are: GET, POST, PUT and DELETE
- An internet media type is used for communicating data. This API supports JSON and XML.
Authentication
You need to be authenticated if you wish to make any requests to the API. Each application (either website, mobile application, desktop application) requires an API-key to identify the application against the API. The API key is unique per application and shouldn't be shared.
You need 3 things to authenticate the user with the API:
- API key
- Username
- Password
The API key needs to be sent with in a header named X-ApiKey
. The username and password are in the message body in the preferred media type format. There is a 3rd optional body parameter named RememberMe. This will determine if the session is temporary or permanent. The default value is false. This only applies to browser based clients.
Note that the Host
header is required with every request.
Example request
POST https://s1-api-tst.fource.nl/api/auth/credentials HTTP/1.1 X-ApiKey: [ApiKey] Host: https://s1-api-tst.fource.nl.nl Content-Length: 38 Content-Type: application/json Accept: application/json {"UserName":"[UserName]", "Password":"[Password]", "RememberMe": [true|false] }
Example response
HTTP/1.1 200 OK Cache-Control: private Content-Type: application/json; charset=utf-8 Server: Microsoft-IIS/8.5 X-Powered-By: ServiceStack/3.971 Win32NT/.NET Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: X-ApiKey, Content-Type, Authorization Access-Control-Expose-Headers: Location Access-Control-Allow-Credentials: true X-AspNet-Version: 4.0.30319 Set-Cookie: ss-id=P7/JJm75PKnIDw+THfzS; path=/; HttpOnly Set-Cookie: ss-pid=IwEIOR4NqRKqKHVV8PaG; expires=Sat, 27-May-2034 13:16:04 GMT; path=/; HttpOnly Date: Tue, 27 May 2014 13:16:06 GMT Content-Length: 71 {"sessionId":"P7/JJm75PKnIDw+THfzS","authToken": "5e6d7248-668d-4197-819a-fe330b0579de","userName":"[username]","responseStatus":{}}
You will receive a session id when you successfully authenticate. You will need to send this session id as the ss-id cookie with every request. Note that the server also returns several Set-Cookie headers. This will instruct any browser to automatically set those cookies for the current host. Also note that they are HttpOnly
which means you cannot access these cookies using javascript.
Session ID cookies are automatically set when the authentication is done within a browser. There will be a ss-id cookie when RememberMe
was false
and a ss-pid when RememberMe
was true
Together with a session id, an authentication token is provided that will allow to open the B2B website without the need to log in. The token can only be used once and is valid for 10 minutes.
Single-sign-on with B2B
We highly recommend building your application on top of the API. However, We are aware this is not viable for all integration partner. The B2B portal can be used in order to communicate with the API.
Start B2B with token
After obtaining an Authentication Token, the B2B web portal should be started using the url: https://s1-tst.fource.nl?auth_token=[tokenId]
.
The B2B website will then use the same session as the one the initial authentication request started.
Be aware that this token expires after 10 minutes, and can only be used once. Also note that some browsers/web components will overwrite cookies across all instances, therefor only 1 session can be active at a given time and using another token will discard the previous session.
Code examples
C# API example
A simple example authenticating and getting the user info. It uses the RestSharp nuget package.
The flow from the code example:
- Authenticate the web browser control with a POST authenticate request using credentials and API key
- The response with the cookie will be set in the web browser control
- Read the sessionid from the response
- Load https://s1-tst.fource.nl and be logged in
- Communicate with the API with the sessionid in the same session as the user in the B2B website
class Program { private static RestClient _client; static void Main(string[] args) { _client = new RestClient("https://s1-api-tst.fource.nl/api"); var sessionId = Authenticate(); _client.AddDefaultParameter("ss-id", sessionId, ParameterType.Cookie); var account = GetAccount(); Console.WriteLine(account.DisplayName); Console.ReadLine(); } static string Authenticate() { var request = new RestRequest("auth/credentials", Method.POST); request.AddHeader("X-ApiKey", "[ApiKey]"); request.AddParameter("UserName", "[username]"); request.AddParameter("Password", "[password]"); return _client.Execute<AuthenticationResult>(request).Data.SessionId; } static Account GetAccount() { var request = new RestRequest("account", Method.GET); return _client.Execute<Account>(request).Data; } } public class AuthenticationResult { public string SessionId { get; set; } } public class Account { public string DisplayName { get; set; } }
C# iframe example
A simple example authenticating and getting the cart from a windows forms application while letting the user use the B2B website.
download ZipJavascript example
This example uses the jQuery library to make the ajax calls.
$(function () { $.ajax({ url: 'http://s1-api-tst.fource.nl/api/auth/credentials', type: 'POST', accept: 'application/json', contentType: 'application/json', dataType: 'json', cache: false, data: JSON.stringify({ UserName: '[username]', Password: '[password]', RememberMe: [true|false] }), beforeSend: function (xhr) { // you only need to do this when authenticating xhr.setRequestHeader('X-ApiKey', '[api key]'); }, success: function (result) { // authentication was succesful and the browser should have handeld the ss-(p)id cookies. }, error: function (xhr) { // an error occured } }); });
Sessions
Every user that authenticates gets assigned a new session. The session contains the information about the products in the cart and the products the user has placed in the RMA order. There are two scenario's where the session id might change:
- The users places an (RMA) order. Everything about an order is tied to a session. So you will get a new session id in case the user would like to place another (RMA) order.
- A session expires. The current session lifetime scope is about twenty minutes. Reason for this is that products which are placed in the cart, will be reserved (based on the quantity) in order to guarantee the product is still in stock when the order is placed. These reservations are released when the session expires. When a request is made with an expired session id, The API will provide a new session id.
Set-Cookie
header with the new session id. Other clients will need to look for the Set-Cookie
headers manually in every response.
There are some rare cases where the session will become corrupted. This will result in a forced logout. You will get a HTTP response with status code 401
and broken-session
as the message.
Dates
All dates have the following format: /Date(1400000000000)/
. The number within the parentheses is an integer value representing the number of milliseconds since 1 January 1970 00:00:00 UTC (Unix Epoch). This representation can be parsed with JavaScript using var date = eval('new Date(1400000000000)')
or for example using the moment.js library like: moment('/Date(1400000000000)/')
. One could use a regex to extract the milliseconds (\d+
).
CORS
CORS is enabled, browser based clients will be able to make requests to the API. However, there are some limitations. For example; older browsers will not be able to send PUT or DELETE request in a CORS context.
Error handling
Errors that are being thrown by the application result in a response with a HTTP status code and optionally an error message. Here you can find a general list with HTTP status codes.
Every invalid request will result in HTTP status code with 400
and a message describing why the request is invalid. These error-messages should be handeld by your application to enhance user experience.
All requests that are being issued without a session id or with an invalid session id will result in HTTP status code 401
. You will need to retreive a new session id by authenticating again.
Any internal problems will result in HTTP status code 500
. This usually means something is seriously wrong on our site and you should contact us.
We use alot of 3rd party online services to complement our own service. The 504
HTTP status code will be returned when one of these services are offline or they are performing unexpected behaviour.
It's possible to create a permanent session when logging in. After a few days of inactivity the session will be reset. The API will send the 205
HTTP status code to notify that this has happend. When this occurs the cart will be emptied. The request can be retried in order to receive it's actual result.
Error messages
Name | Description |
api-key-not-found | Cannot create application for the api. Input was invalid. |
auth-invalid-api-key | The provided Api key is unknown or invalid. |
auth-invalid-username-password | The username and/or password do not match. |
backorder-routeid-invalid | A backorder delivery route needs to be provided. |
bad-action-accu-retour | This product should be added to the retour order instead of the cart. |
bad-action-diesel-retour | This product should be added to the retour order instead of the cart. |
bad-action-tire-retour | This product should be added to the retour order instead of the cart. |
broken-session | The session was corrupted. A forced logout has been performed. |
carid-is-required | The car id is required. |
cart-is-locked | The cart is locked for changes. |
cart-invalid-quantity | The provided quantity is invalid. The quantity should 1 or higher. |
cart-unknown-product | The provided product id is unknown or invalid. Product id's should be 9 characters long. |
could-not-set-account-setting | Unable to set property, could not parse value or unknown setting name. |
cart-customerorderreferencenumber-empty | The custom order reference number should not be empty. |
debtor-not-found | The requested debtor was not found |
defered-routeid-invalid | A defered delivery route needs to be provided. |
cart-deliveryroute-empty | A delivery route needs to be provided. |
direct-routeid-invalid | A direct delivery route needs to be provided. |
file-to-large | The file is to large. |
insufficient-permissions-accu-retour | The user is not allowed to return car batteries. |
insufficient-permissions-auto-data | The user is not allowed to use autodata. |
insufficient-permissions-create-news | The user is not allowed to create news items. |
insufficient-permissions-create-notification | The user is not allowed to create notifications. |
insufficient-permissions-delete-news | The user is not allowed to delete news items. |
insufficient-permissions-delete-notification | The user is not allowed to delete notifications. |
insufficient-permissions-diesel-retour | The user is not allowed to request a 'diesel injector test' |
insufficient-permissions-retours | The user is not allowed to use retours. |
insufficient-permissions-tire-center | The user is not allowed to perform this tire center action. |
insufficient-permissions-tire-retour | The user is not allowed to return tires. |
invalid-carid | The provided car id is not valid. |
invalid-categoryid | The provided category id is invalid. |
invalid-date | The given date is not valid |
invalid-input-date-format | The provided date was is invalid. |
invalid-delivery-address-id | The selected delivery address is invalid. |
invalid-end-date | The provided date was is invalid. |
invalid-file-extension | Invalid file extension |
invalid-gaik-submission | The gaik submission is not complete. The order cannot be processed. |
invalid-input-on-create-application | Cannot create application for the api. Input was invalid. |
invalid-interval-ids | The provided internal idid'ss are invalid |
required-length-for-{0}-is-{1} | Indicates that the given parameter does not match it's length requirement. |
invalid-license-plate | The provided licenseplate is valid. This wikipedia page lists all valid formats |
invalid-parameter-value | An invalid parameter value. Parameter types can be found in the api reference. |
invalid-productid | The provided product id is invalid. Product id's should be 9 characters long. |
invalid-retourid | The provided route id is invalid. |
invalid-vin | Invalid VIN |
{0}-is-invalid | Indicates that the given parameter value is invalid. |
{0}-is-required | Indicates that the given parameter is required. |
{0}-is-unknown | Indicates that the given parameter value is unknown. |
items-on-route-do-not-match | The quantity of items do not match the quantity of items in the cart |
max-quantity-per-route-overflow | Cannot update the retour item. The maximum quantity per route has been reached. |
missing-required-parameter | Missing required parameter. Refer to the api reference for a full overview of parameters. |
mpm-down | The mpm service we are depending on is responding in an unexpected manner |
news-id-already-exists | The provided news item id already exists. |
no-catalogue-found | |
no-productid | A product id has to be provided. |
no-productids | One or more product id's have to be provided |
not-found | The requested resource was not found |
notificationid-already-exists | The provided notification id already exists. |
cart-paymentMethod-empty | The payment method should not be empty. |
cart-orderwithoutitemsincart-invalid | The order could not be placed because the cart was empty. |
reset-session | The session was reset. Retry the request for actual results. |
retour-invalid-deposit-amount | The deposit amount needs to be larger then 0 when it's a deposit retour. |
retour-invalid-quantity | Adding a retour with negative quantity is not allowed. |
retour-no-retourid | The retour item id is required. |
retour-no-orderid | Only products that belong to a certain order id and order line id can be returned. |
retour-no-orderlineid | Only products order-line-id can be returned. |
retour-no-productid | The product id has to be provided in order to add it to the retours |
retour-not-retourable | The product cannot be retoured |
routeid-expired | The chosen route is expired. Make sure to update the routes before trying to place an order. |
routeid-invalid | The chosen route is expired. Make sure to update the routes before trying to place an order. |
products-search-invalid-categoryid-or-genericarticleids | No category id or generic article ids were provided while searching for products for a car |
invalid-searchterm-null-empty-or-whitespace | The searchterm cannot be null or empty. |
shipping-unknown | The chosen route is expired. Make sure to update the routes before trying to place an order. |
tire-center-process-error | Could not procces tireCenter request, relogging might fix this |
too-many-results-refine-the-search | The search resulted with more then 200 results. The user should refine his search. |
unknown-carid | The provided car id does not exist. |
cartitem-unknown | The provided cart item id does currently not exist. |
History
April 30 2014
WithoutPriceStock
is now deprecated
The WithoutPriceStock
request parameter is now deprecated. From now on will the prices and stock quantities always be retrieved because the performance impact is minimal. We've replaced the flag with another flag named WithoutRouteInfo
. Route info will not be loaded if this flag is set to true
. This will save you about 3 seconds for each product in the response. We will remove this flag on June 30 2014.
July 15 2014
withoutAutodataInfo
is now deprecated
The withoutAutodataInfo
request parameter (used by the Get Carinfo
requests)is now deprecated. From now on the AutodataCarInfos will always be returned; based on the account permissions. AutodataCarInfo will no longer contain TechnicalInfo
, this information can be retrieved by calling car/autodata/{autodataid}/technicalinfo
. The flag will be completely removed after April 15 2014.
September 11 2015
cart/placeorder
is now deprecated
Replaced by cart/place-order
and cart/place-simple-order
Products
When searching products or retrieving products based on cars and categories, you can receive more than just the products. This chapter describes how to interpertate this additional information. For example; a duration property is returned when searching for products, the duration property describes how long the call took, and how long calls to external services took
Filters
Each request for products will return a property called filterOptions
. This is an array of filters that can be applied to the list of products. Each filter options consists of
-
A human readable
name
-
A unique
key
- A
filterType
which specifies the type of filter (Multiselect, NumberRange, DateRange) isAttribute
which specifies whether the filter is on the properties of a product or applies to the attributes list of the product-
Values
is an array of all the possible values for the filter option. Each value consists of-
an unique
Id
-
human readable
value
-
a list of conforming
productIds
-
an unique
productsWithoutValue
specifies how many product have not specified value for this filter optionpriority
specifies the sorting regarding the filter options
Assets
Some request will provide assets to provide the user with additional context, for example, where retrieving products of the category "10000000028" for a car will provide an image with the layout of different parts belonging to that car.
Assets
consists of an array of different assets. Each asset consists of:
-
Human readable
name
-
An image link
imageUrl
-
productAssetMaps
, an array defining clickable area's on the image. They can be used to apply filters to the product list. each productAssetMap consists of:groupId
, a corresponding value can be found within filter optionscoordinates
, an array defining the corners of a polygon, used to draw an area over the provided image.
Paging
Requesting large amount of products (100+) can cause long response times, therefor it's advisable to make use of the pagination provided in the API. Requests that retrieve a list of products have a skip and take query parameter. Skip 100 and take 10 will return the products #101 to #110.
Backorder Info
Some products are possible to be backorders. When Fource does not have the product in stock. Backorder information can be requested in order for Fource to check external parties and request stock information there. For example, the user needs 5 of product X, Fource has only 3 products in stock. An external party has 4 in stock. When the user tries to put 5 in his cart. Backorder information can be requested for the remaining 2 products.
When requesting backorder information, you'll need to calculate the quantity needed for the backorder yourself. (Total needed quantity - quantity in stock)
Placing an order
You can add products to the cart using POST /cart/items
with the specified product and quantity. The quantity can be changed using PUT /cart/items/{id}
or be deleted using DELETE /cart/items/{id}
. You can clear the cart in it's entirety using DELETE /cart/items
.
There are several things the user needs to select so he or she can place an order. A user has to select his preferred payment method. The available payment methods for a user can be received with GET /account
.
This can return multiple options depending on the user:
Id | Value |
0 | Op rekening |
1 | Rembours |
2 | Contant |
A user also has to specify delivery routes. There are a three types of delivery routes: direct, deferred and backorder. The type of delivery depends on the product and the requested quantity. The possible delivery routes can be received with GET /cart/shipping
. Be aware that delivery routes can expire and so should be checked again when the user wants to place the order.
There are two additional options available for all route types. The user always has the option to collect the products himself. To send this option when placing an order send "A" as the route id. The user also has the option to let the order be sent later with another delivery. Note you can only send this option when you have received delivery routes. To send this option when placing an order send "N" as the route id.
The user also needs to provide an order reference. This is something that the client can use for it's own history system(s). This is a required field.
There are two ways to place an order. The ‘simple’ version allows you to provide an option indication whether all products should be shipped together, or the products should be shipped individually as soon as possible. The regular version allows for more flexibility in route selection.
Using the ‘simple’ version will require less work on your part. The regular version will require some additional logic on your part.
Simple version
Placing a simple order requires you to provide us with a Delivery option, we will select the appropriate routes for the given delivery option.
Given a Cart with one product with quantity 2 and shipping information on 2 different routes (R24572180800 & R24572181030).
Placing an order with option:
- AsSoonAsPossible: This will set the routes in such a way that the items will be delivered as soon as possible. So 1 will be delivered on route R24572180800 and the other one on route R24572181030
- AsSingleDelivery: This will set the routes in such a way that the items will be delivered as one package, since one of the items is not available on route R24572180800, both items will be delivered on route R24572181030
- AsNotUrgent: This will let us decide when it is most convenient to ship.
- AsPickUp: Will not be shipped.
The boolean ComplyWithDeliveryTimePreference can be used to specify whether delivery time preference should be honoured when selecting the routes. A user can configure the times they would (not) like to receive shipments. If for example a user has set that no routes should be selected on Saturdays and Sundays, then we will not select routes on Saturday or Sunday, but instead select the first route on Monday.
Regular version
Placing a regular order requires you to build a model representing the cartitems on a specific route. To do this you’ll need to know which routes are available. And which cart items can be placed on what route.
Generally the flow can be described as:
- Add product(s) to cart.
- Get the shipping information for each cart item (included in the create cart item response).
- Request cart routes from first to last time within the shipping information.
- Create a model for items on selected routes taking into account the shipping information
- Place order.
- If the request fails, the provided routes might have expired, so go back to step 2.
For example: This request places an order containing one product with quantity 1 on route R24572180800.
{ "CustomerOrderReferenceNumber": "32-HL-BK", "PaymentMethod": "1", "ItemsOnRoutes": [ { "CartItemId": "001", "RouteId": "R24572180800", "Quantity": "1" } ] }
This request places an order containing one product with quantity 2 on routes R24572180800 and R24572181030
{ "CustomerOrderReferenceNumber": "32-HL-BK", "PaymentMethod": "1", "ItemsOnRoutes": [ { "CartItemId": "001", "RouteId": "R24572180800", "Quantity": "1" }, { "CartItemId": "001", "RouteId": "R24572181030", "Quantity": "1" } ] }
Each modification to the cart will also provide you with shipping information regarding the modified cart item. Note the delivery date, this cart item could be shipped on every cart route equal to or later than the specified delivery date.
"shipments": [ { "shipmentId": "Direct", "shipmentLocation": "HOOFDMAGAZIJN HOENSBROEK", "deliveryDate": "/Date(1436853600000-0000)/", "quantity": 1 } ]
To get a list of possible routes the resource GET /cart/routes should be called. This will return a list of possible routes for the logged in user. For this example is the first route equal to the delivery date of the cart item shipping information, therefore it’s a valid route for this cart item.
{ "routes": [ { "id": "R24572180800", "when": "/Date(1436853600000-0000)/" }, { "id": "R24572180915", "when": "/Date(1436858100000-0000)/" }, { "id": "R24572181045", "when": "/Date(1436863500000-0000)/" }, { "id": "R24572181200", "when": "/Date(1436868000000-0000)/" }, { "id": "R24572181330", "when": "/Date(1436873400000-0000)/" }, { "id": "R24572181500", "when": "/Date(1436878800000-0000)/" }, { "id": "R24572190800", "when": "/Date(1436940000000-0000)/" }, { "id": "R24572190915", "when": "/Date(1436944500000-0000)/" }, { "id": "R24572191045", "when": "/Date(1436949900000-0000)/" }, { "id": "R24572191200", "when": "/Date(1436954400000-0000)/" }, { "id": "R24572191330", "when": "/Date(1436959800000-0000)/" }, { "id": "R24572191500", "when": "/Date(1436965200000-0000)/" } ], "urgentMessages": [] }
Urgent messages
The admins can place an urgent message to notify the users. We provide these messages in every request. Even if the actual request fails. Every request contains a deserialized message containing it's actual result and the collection with urgent messages. Example:
{ "products":[ ... ], "urgentMessages":[ { "id":1, "startDate":"\/Date(1403853780000-0000)\/", "level":"Error", "title":"Problemen bij TecDoc", "message":"Beste klant, <br>TecDoc Heeft momenteel een technische storing. Onze excuses voor de overlast." } ] }
The level
property can have one of these 3 values: Info, Warning or Error. This reflects the importance of that message and allows a developer to style the message accordingly. The message
can contain html elements.