Introduction
Distributors, wholesalers and retailers use Stock2Shop to manage inventory and orders from their ERP/Warehouse.
If you are developer wanting to code some customisation into your existing workflow or someone wanting to integrate with one of our existing merchants, read on!
There are many use cases for connecting to our API, see our tutorials for further examples.
Using the API
Our API is a JSON based RESTful web service.
In order to keep transactions secure requests need to be authenticated. We use token based authentication.
For each request you send, you must include a token=xyz
query parameter.
To obtain a token, first authenticate and then pass this token back on future requests.
The token is ephemeral, meaning it could expire, in which case you should authenticate again. Keep this in mind when designing your application.
Base URLs
Stock2Shop has 2 API’s.
https://app.stock2shop.com/v1/{end point}
https://api.stock2shop.com/v2-queue/{end point}
The former is our original monolith based API that performs mainly CRUD based operations. The later uses our microservices architecture and is normally used for asynchronous operations which involve a queue and worker processing.
Rate Limits
Stock2Shop imposes a rate limit of 3 requests per second for all endpoints, with the exception of unauthenticated endpoints, which have a rate limit of 1 request per second.
If you exceed the rate limit, you’ll receive a 429
response.
Additionally, a HTTP header named x-S2S-RateLimit
will indicate the per-second rate limit for the endpoint you’ve accessed.
User Roles
Users have different roles on Stock2Shop. Roles limit which endpoints you can access.
Our roles are:
Role | Meaning |
---|---|
none | If there is no role assigned you are an admin user |
trade | B2B trade store user |
read_only | Readonly to Customers, Products, Orders and other |
product_editor | Read and write to Products (excluding product meta) |
product_meta_editor | Read and write to Products and Product Meta |
customer_read_only | Read Customers |
When you authenticate you will see your users role returned. If you are an Admin user, you can create additional users (assigned to specific roles) to access the API. It is best to do this through our console, see below.
Console / B2B Trade Store
A good example of how this API can be used is seen in on our Console and B2B Store
You can login to either one of these systems via your browser with the appropriate user role. Once logged in, open your browsers inspector and monitor the xhr requests sent.
Users
POST /v1/users/authenticate
Example
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"system_user_auth": {
"username": "barry",
"password": "*****"
}
}' \
https://app.stock2shop.com/v1/users/authenticate?format=json
Example Response
{
"system_user": {
"id": 1,
"created": "2015-03-03T07:30:06+0000",
"name": "Bob",
"surname": "Seger",
"email": "bob@example.com",
"username": "bob",
"modified": "2020-12-29T13:46:44+0000",
"client_id": 1,
"active": 1,
"admin": 0,
"token": "xxx",
"client_name": "Cleitn Name",
"fulfillment_services": [],
"channels": [],
"sources": [],
"customers": [],
"roles": [
{
"id": 1,
"description": "product_meta_editor"
}
],
"role_description": "product_meta_editor"
}
}
Authenticate a user to retrieve a token.
HTTP Request
POST https://app.stock2shop.com/v1/users/authenticate
Parameters
Parameter | type | Description |
---|---|---|
body | body | system_user_auth json object |
GET /v1/users/valid_token
Example
curl -X GET \
-H 'Content-Type: application/json' \
https://app.stock2shop.com/v1/users/valid_token/xxx?format=json
Example Response
{
"system_user": {
"id": 1,
"created": "2015-03-03T07:30:06+0000",
"name": "Bob",
"surname": "Seager",
"email": "bob@example.com",
"username": "bob",
"modified": "2021-01-05T08:07:57+0000",
"client_id": 1,
"active": 1,
"admin": 0,
"token": "xxx"
}
}
Check to see if a token is valid.
HTTP Request
GET https://app.stock2shop.com/v1/users/valid_token/{token}
Parameters
Parameter | type | Description |
---|---|---|
token | url path | token for authentication |
Sources
GET /v1/sources
Example
curl -X GET \
-H 'Content-Type: application/json' \
https://app.stock2shop.com/v1/sources?format=json$token=xxx
Example Response
{
"system_sources": [
{
"id": 318,
"created": "2017-07-25T10:32:46+0000",
"description": "Example",
"sync_token": "0",
"modified": "2021-08-27T13:57:29+0000",
"client_id": 123,
"type": "apifact_sapone",
"active": 1,
"source_id": null
}
]
}
List all sources.
If source_id
is not null, then this is a linked source.
HTTP Request
POST https://app.stock2shop.com/v1/sources
Parameters
Parameter | type | Description |
---|---|---|
token | query param | token for authentication |
Channels
GET /v1/channels
Example
curl -X GET \
-H 'Content-Type: application/json' \
https://app.stock2shop.com/v1/channels?format=json$token=xxx
Example Response
{
"system_channels": [
{
"id": 123,
"created": "2015-08-08T21:32:46+0000",
"description": "My Sales Channel",
"modified": "2020-05-06T09:53:30+0000",
"client_id": 456,
"active": 1,
"type": "shopify",
"price_tier": null,
"qty_availability": null
}
]
}
List all channels.
HTTP Request
POST https://app.stock2shop.com/v1/channels
Parameters
Parameter | type | Description |
---|---|---|
token | query param | token for authentication |
Products
Products are made up of one or more variants. A variant is the unique SKU (sometimes called barcode or GTIN) found in the ERP.
Products are usually associated with marketing related meta data, such as images, categories, titles and so on. Whereas a variant is associated with warehouses, pricing and is the unit which is ordered, invoiced and shipped. For more information on this relationship see here
We use different identifiers for products and variants.
Identifier | Meaning |
---|---|
product.id |
Stock2Shop’s unique identifier |
product.variant.id |
Stock2Shop’s unique identifier |
product.source_product_code |
Unique identifier for the product on the ERP |
product.variant.source_variant_code |
Unique identifier for the variant on the ERP |
product.variant.sku |
This is normally the same as source_variant_code but can be different depending on your ERP. |
product.channels.channel_product_code |
If the product is linke to a channel, this is the unique identifier on the channel |
If the product/variant has not been synced with channels and sources, the corresponding identifiers will be missing.
POST /v1/products/elastic_search
Example
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"query": {
"bool": {
"filter": [
{
"range": {
"modified": {
"gt": "2021-08-27 08:23:04"
}
}
}
]
}
},
"size": 1,
"from": 0,
"sort": {
"modified": {
"order": "desc"
}
}
}' \
https://app.stock2shop.com/v1/products/elastic_search?token=xxx
Example Response
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 27,
"max_score": null,
"hits": [
{
"_index": "products--------2020-09-02-08-31-08",
"_type": "_doc",
"_id": "62777",
"_score": null,
"_source": {
},
"sort": [
1609835104000
]
}
]
},
"request": {
"url": "https:\/\/vpc-stock2shop-647vemf3m3u22fhny7f7dqjs4m.eu-west-1.es.amazonaws.com\/products\/_search",
"method": "POST",
"body": {
"aggs": {},
"query": {
"bool": {
"filter": [
{
"range": {
"modified": {
"gt": "2018-07-16 08:23:04"
}
}
},
{
"term": {
"client_id": 21
}
},
{
"term": {
"active": true
}
}
]
}
},
"size": 2,
"from": 0,
"sort": {
"modified": {
"unmapped_type": "date",
"order": "desc"
}
}
}
},
"system_products": [
{
"id": 62777,
"created": "2015-03-03 12:37:51",
"active": true,
"source_product_code": "CDROM",
"modified": "2021-01-05 08:25:04",
"client_id": 21,
"source_id": 57,
"body_html": "<p>No one uses CDs in computers anymore. Why is this here? Testing for Wilson.<\/p>",
"collection": "Storage",
"title": "CD ROMs",
"product_type": "Media",
"vendor": "",
"hash": "a22d32a40b598f2fcec0f80e23c06c34",
"channels": [
{
"channel_id": "47",
"channel_product_code": "62777"
},
{
"channel_id": "255",
"channel_product_code": "51"
}
],
"tags": "",
"options": [
{
"name": "Selection",
"position": 1
}
],
"meta": [
{
"id": 244931,
"created": "2016-02-15 10:08:04",
"key": "release_date",
"value": "",
"modified": "2020-10-28 14:10:47",
"template_name": null,
"client_id": 21
},
{
"id": 1068975,
"created": "2017-02-14 07:16:46",
"key": "Dimensions",
"value": "<a href=\"http:\/\/www.stock2shop.com\">test<\/a>",
"modified": "2020-10-28 14:10:47",
"template_name": null,
"client_id": 21
},
{
"id": 3235454,
"created": "2018-10-21 17:49:08",
"key": "channel_b2b",
"value": "TRUE",
"modified": "2020-10-28 14:10:47",
"template_name": null,
"client_id": 21
}
],
"images": [
{
"src_50x50": "https:\/\/s2s-image.s3.amazonaws.com\/thumb\/50x50\/adf8ded854495aedb72edefaaab4209b.jpg",
"src_160x160": "https:\/\/s2s-image.s3.amazonaws.com\/thumb\/160x160\/adf8ded854495aedb72edefaaab4209b.jpg",
"id": 271528,
"created": "2018-06-12 04:55:30",
"source_image_code": null,
"storage_code": "adf8ded854495aedb72edefaaab4209b.jpg",
"src": "https:\/\/s2s-image.s3.amazonaws.com\/adf8ded854495aedb72edefaaab4209b.jpg",
"modified": "2020-01-30 08:32:19",
"client_id": 21,
"product_id": 62777,
"active": true,
"hash": "adf8ded854495aedb72edefaaab4209b"
}
],
"variants": [
{
"id": 67841,
"source_variant_code": "4317647",
"price": 10,
"barcode": "",
"sku": "CD003",
"qty": 38,
"grams": 0,
"inventory_management": true,
"active": true,
"product_id": 62777,
"image_id": 271528,
"client_id": 21,
"option1": "Pack of 25",
"price_tiers": [
{
"tier": "Wholesale",
"price": 28
},
{
"tier": "Was",
"price": 0
}
],
"qty_availability": []
},
{
"id": 67842,
"source_variant_code": "4317716",
"price": 55,
"barcode": "",
"sku": "CD004",
"qty": 39,
"grams": 0,
"inventory_management": true,
"active": true,
"product_id": 62777,
"image_id": 235461,
"client_id": 21,
"option1": "Pack of 50",
"price_tiers": [
{
"tier": "Wholesale",
"price": 50
},
{
"tier": "Was",
"price": 0
}
],
"qty_availability": []
}
]
}
],
"took_s2s": 7
}
Stock2Shop uses Elastic Search to search products.
To build a search query you need to understand how Elastic Search bool queries work
To see the query that is used internally look at the request
response property.
The example query on the right shows all products modified since a certain date. It returns only 1 product and sorts the results by date modified descending. The perfect query if you want to page through products getting most recent updates, see our tutorials for examples of this.
Use the data inside of the system_products
response property has a complete list of product information as stored on Stock2Shop, not on Elastic Search.
This is important, since there may be a delay in indexing product changes to Elastic Search and system_products
will give you a live view.
Elastic search aggregate queries can also be performed. This is a powerful tool if you are looking to group properties.
Kindly note that subject to the above, depending on which user role is assigned, the query param channel_id
would need to be specified to return results.
HTTP Request
POST https://app.stock2shop.com/v1/products/elastic_search
Parameters
Parameter | type | Description |
---|---|---|
token | query param | token for authentication |
channel_id | query param | channel id assigned to customer |
body | body | json elastic search boolean query |
POST /v1/products/queue
Example (minimum required information)
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"system_products": [
{
"source": {
"source_id": "123",
"product_active": "true",
"source_product_code": "ABC",
"sync_token": "0",
"fetch_token": "0"
},
"product": {
"title": "Example",
"variants": {
"source_variant_code": "ABC",
"sku": "ABC"
}
}
}
]
}' \
https://app.stock2shop.com/v1/products/queue?token=xxx&source_id=123
Example Response
{
"system_products": {
"sync_product": 1
}
}
Example (all fields)
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"system_products": [
{
"source": {
"source_id": "{source_id}",
"product_active": "true",
"source_product_code": "ABC",
"sync_token": "0",
"fetch_token": "0"
},
"product": {
"options": [
{
"name": "Size",
"position": "1"
}
],
"body_html": "",
"collection": "",
"product_type": "",
"tags": "",
"title": "Example",
"vendor": "",
"variants": {
"source_variant_code": "ABC-XXL",
"sku": "ABC-XXL",
"barcode": "",
"qty": 9,
"qty_availability": [
{
"description": "Warehouse X",
"qty": 10
}
],
"price": 100.00,
"price_tiers": [
{
"tier": "Price List X",
"price": 80.00
}
],
"inventory_management": "true",
"grams": 0,
"option1": "XXL",
"option2": null,
"option3": null
},
"meta": [
{
"key": "foo",
"value": "bar"
}
]
}
}
]
}' \
https://app.stock2shop.com/v1/products/queue?token=xxx&source_id=123
Example Response
{
"system_products": {
"sync_product": 1
}
}
Queue products for processing.
Products are added to a queue for subsequent processing. The endpoint will return the number of products added to the queue.
HTTP Request
POST https://app.stock2shop.com/v1/products/queue
Parameters
Parameter | type | Description |
---|---|---|
token | query param | token for authentication |
body | body | json elastic search boolean query |
Images
Images are linked to products and can optionally be linked to variants. When an image is created on Stock2Shop, various thumbnail versions are created.
Images are store on AWS S3 with the following url format:-
https://s2s-image.s3.amazonaws.com/{{hash}}.{{extension}}
And for thumbnails:-
https://s2s-image.s3.amazonaws.com/thumb/50x50/{{hash}}.{{extension}}
https://s2s-image.s3.amazonaws.com/thumb/160x160/{{hash}}.{{extension}}
There is a maximum size of 2mb per image. We support .jpg
and .png
image types.
Below are the important ID’s associated with an image.
Identifier | Meaning |
---|---|
image.image_id |
Unique identifier for the image on the ERP (if it originates from the ERP) |
image.product_id |
Stock2Shop internal ID for product. |
source.source_id |
Unique identifier for the source |
source.source_product_code |
Unique identifier for the product on the ERP |
source.source_variant_code |
Unique identifier for the variant on the ERP |
POST /v1/images/queue
The image property should be the base64 encoded text value of the image.
Example Basic
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"system_images": [
{
"source": {
"source_id": 57,
"source_product_code": "keyboard",
"source_variant_code": "4191161"
},
"image": {
"image_id": "a",
"action": "I",
"image": "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4"
}
}
]
}' \
https://app.stock2shop.com/v1/images/queue?token=xxx
Example Response
{
"system_images": {
"sync_image": 1
}
}
Example with Meta
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"system_images": [
{
"source": {
"source_id": 57,
"source_product_code": "keyboard",
"source_variant_code": "4191161"
},
"image": {
"image_id": "a",
"action": "I",
"image": "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4",
"meta": {
"main_image": "true"
}
}
}
]
}' \
https://app.stock2shop.com/v1/images/queue?token=xxx
Example Response
{
"system_images": {
"sync_image": 1
}
}
Queue images for processing.
It is important to understand that the queue in which products and images are processed is FIFO (first in first out).
This means if you are adding a new product with some images, you first need to queue the
product for insertion, followed by the image with the appropriate source_product_code
.
If the product fails to be created for whatever reason, the image will not be added and you will need to requeue the image.
The endpoint will return the number of images added to the queue.
HTTP Request
POST https://app.stock2shop.com/v1/products/queue
Parameters
Parameter | type | Description |
---|---|---|
token | query param | token for authentication |
body | body | json payload, see examples |
Orders
Orders usually originate from a sales channe (ecommerce platform, marketplace or our B2B trade store). They are then saved to Stock2Shop and processed to the ERP, if required.
We use different identifiers for orders.
Identifier | Meaning |
---|---|
order.id |
Stock2Shop’s unique identifier |
order.channel_order_code |
The channels unique identifier |
orders.sources.source_order_code |
Unique identifier for the ERP. It is possible to have multiple sources in the case of split orders. |
If the order has not been synced with channels / sources, the corresponding identifiers will be missing.
POST /v1/orders/elasticsearch
Example
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"query": {
"bool": {
"must": [
{
"multi_match": {
"fields": [
"id.autocomplete",
"channel_order_code",
"sources.source_order_code",
"customer.first_name",
"customer.last_name",
"customer.email",
"customer.channel_customer_code"
],
"query": "abc",
"operator": "or"
}
}
],
"must_not": [],
"filter": []
}
},
"from": 0,
"size": "10",
"sort": {
"_score": {
"order": "desc"
}
}
}' \
https://app.stock2shop.com/v1/orders/elastic_search?token=xxx
Example Response
{
"took": 123,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 10000,
"max_score": 10.32759,
"hits": []
},
"request": {
"url": "https:\/\/vpc-stock2shop-647vemf3m3u22fhny7f7dqjs4m.eu-west-1.es.amazonaws.com\/orders\/_search",
"method": "POST",
"body": {
"aggs": {},
"query": {
"bool": {
"must": [
{
"multi_match": {
"fields": [
"id.autocomplete",
"channel_order_code",
"sources.source_order_code",
"customer.first_name",
"customer.last_name",
"customer.email",
"customer.channel_customer_code"
],
"query": "abc",
"operator": "or"
}
}
],
"must_not": [],
"filter": [
{
"term": {
"client_id": 91
}
}
]
}
},
"from": 0,
"size": "10",
"sort": {
"_score": {
"order": "desc"
}
}
}
},
"system_orders": [
{
"id": 933302,
"created": "2020-06-08 14:03:48",
"channel_order_code": "0027795",
"modified": "2020-06-08 14:04:08",
"channel_id": 191,
"client_id": 91,
"customer_id": 495995,
"notes": "Send Soon",
"status": "ordered",
"total_discount": 0,
"line_item_sub_total": 0,
"line_item_tax": 0,
"shipping_sub_total": 0,
"shipping_tax": 0,
"sub_total": 119.23,
"tax": 17.88,
"total": 137.11,
"customer": {
"channel_customer_code": "5ba71121b3",
"email": "abc@example.com",
"first_name": "Roger",
"last_name": "Waters"
},
"sources": [
{
"source_id": "318",
"source_order_code": "3360213"
}
],
"line_items": [
{
"id": 3715344,
"grams": 0,
"price": 119.23,
"sku": "3397006838",
"total_discount": 0,
"qty": 1,
"variant_id": 620602,
"source_id": 318,
"tax_lines": [
{
"rate": "15"
}
],
"sub_total": 119.23,
"tax": 17.88,
"tax_per_unit": 17.88,
"total": 137.11,
"sub_total_display": "119.23",
"tax_display": "17.88",
"tax_per_unit_display": "17.88",
"total_display": "137.11",
"price_display": "119.23",
"total_discount_display": "0.00"
}
],
"meta": [
{
"key": "customer_reference",
"value": "PO 123"
}
],
"shipping_address": {
"address1": "Sunny Str",
"address2": null,
"city": null,
"company": "Apple",
"country": "ZA",
"country_code": "ZA",
"first_name": "Roger",
"last_name": "Waters",
"phone": "123456789",
"province": null,
"province_code": null,
"zip": "1234"
},
"billing_address": {
"address1": "Sunny Str",
"address2": null,
"city": null,
"company": "Apple",
"country": "ZA",
"country_code": "ZA",
"first_name": "Roger",
"last_name": "Waters",
"phone": "123456789",
"province": null,
"province_code": null,
"zip": "1234"
},
"shipping_total": 0,
"sub_total_display": "119.23",
"shipping_total_display": "0.00",
"shipping_tax_display": "0.00",
"tax_display": "17.88",
"total_display": "137.11",
"total_discount_display": "0.00"
}
],
"took_s2s": 51
}
Stock2Shop uses Elastic Search to search orders.
To build a search query you need to understand how Elastic Search bool queries work
To see the query that is used internally look at the request
response property.
The example query on the right shows a free text search on specific order fields.
Use the data inside of the system_orders
response property has a complete list of order information as stored on Stock2Shop, not on Elastic Search.
This is important, since there may be a delay in indexing order changes to Elastic Search and system_orders
will give you a live view.
Elastic search aggregate queries can also be performed. This is a powerful tool if you are looking to group properties.
HTTP Request
POST https://app.stock2shop.com/v1/orders/elastic_search
Parameters
Parameter | type | Description |
---|---|---|
token | query param | token for authentication |
body | body | json elastic search boolean query |
POST /v2-queue/order
Example (minimum required information)
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"my order payload": {
"foo": "bar"
}
}' \
https://api.stock2shop.com/v2-queue/order?client_id=456&channel_id=123&token=xxx
Example Response
{
"message": "order received",
"queue_messages": [
{
"key": "213-980-0/2021-08/27/17/48/04-213285668-sync_order.json",
"message_id": "3d7b74a0-8b1b-4b06-8d7f-8331cb682705"
}
]
}
Queue orders for processing.
Orders are added to a queue for subsequent processing.
HTTP Request
POST https://api.stock2shop.com/v2-queue/order
Parameters
Parameter | type | Description |
---|---|---|
token | query param | token for authentication |
client_id | query param | Client ID |
channel_id | query param | Channel ID |
body | body | order payload |
Tutorials
Consuming Product Feed
Outlines the steps to consume a JSON feed of product data from a Stock2Shop account.
Product data sets could be large. To practically consume the entire feed, you need to page through products records. If you want to continually keep your system up to date, one approach is to store a modified date on your system and ask for products which have been modified since this date. This will mean you do not need to continually download the entire product feed.
This tutorial will show how to do this.
Your application should store the following information:-
- token (access token to call our api)
- username (to login and get a token)
- password
- modified date (the last modified date you have retrieved data for)
Your application should perform the following logic:
Step 1: Check for a valid token
Checking to see if your token is still valid
If your token is invalid or you don’t have a token then fetch one and save it to your application
Step 2: Fetch products
POST /v1/products/elasticsearch
Your application should set the modified date it stores to the last modified date found in system_products
.
Push products
Outlines steps to send a JSON payload of products to Stock2Shop for processing.
If you want to automatically send product updates to Stock2Shop, follow these instructions.
Step 1: Authenticate
Step 2: Get sources
A “source” is the place your product data comes from.
You need to have a valid source_id
in order to push product data.
There is a distinction between a linked and master source, read here.
Take note of the id
field as this will be used as source_id
in subsequent steps.
Step 3: Post Products to Queue
It is recommended to batch many products per request, instead of individually.
To understand the relationship between products, variants and their options read this. If the relationship between the product and variant is not correctly configure, these products will be ignored.
Post Orders
Outlines the steps to POST an order to a console in Stock2Shop.
You are going to POST a valid JSON payload to a sales channel. Depending on the sales channel, the payload you post will look different. For example, a Shopify order will follow the format of the shopify webhook and a Woocommerce order will follow the Woocommerce format. You are replicating what the channel would normally send Stock2Shop using the HTTP POST method.
If you have a custom channel setup, then it will require whatever format is agreed on with Stock2Shop.
Step 1: Authenticate
Step 2: Get Channel & Client ID
You need to know you client_id
and your channel_id
in order to post the order to a specific channel. Fetch a list of your channels: