API reference
Last updatedAPI reference#
POST products
This returns a list of products. You send a JSON body to search and filter the results. Described below.
GET products/32245
This returns a single product.
POST uri
This returns a list of products in a category, or single product, depending on what the URI leads to.
GET categories
Returns a list of categories.
Products Endpoint#
If you use POST products
without a JSON body, you get all products back. You use the JSON body to filter and search the products, so you get fewer back.
The response object contains these:
1
2
3
4
5
6
{
"token": "esf1p3tgchfg5ggtpqdpgqjtt6",
"products": [...],
"productCount": 344,
"filter": [...]
}
- token: the session token that you always have in this API
- products: an array of products
- productCount: the total number of products without paging. So you can show "page 1 of 7" even if you only fetch 50 at a time.
- filter: is the filter values of the products you are viewing now, also without paging. So you know there are 35 red ones and 12 blue for example.
The request you send can contain these fields, everything is optional:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"skipFirst": 5,
"limit": 10,
"categories": [1,2,3],
"collections": [1,2,3],
"silkProduct": 123,
"search": "hello world",
"products": [1,2,3],
"relatedProducts": true,
"brands": [1,2,3],
"swatch.desc": ["Red", "Blue", "Green"],
"items.name":["W24\/L30"],
"onlyAvailable":true,
"uri":{
"uri":"jeans\/black",
"for":["product", "category"]
},
"sortOrder": [
{
"field": "priceAsNumber",
"order": "desc"
}
]
}
- skipFirst + limit: for paging
- categories: you want products in these categories
- search: free text search
- relatedProducts: when a product has relatedProducts and this is true, you get the complete data for those releated products. Otherwise you will get a small subset of the data back: only the media and product id.
- swatch.desc: filtering based on the color swatch (This is a client specific field, and not all Centra instances will have this field)
- items.name: filtering on specific item names
- onlyAvailable: true means you only get back products that are in stock or available for preorder. If you also specify items.name, those items must be available
- uri: filter on a product or category with a specific URI
- sortOrder: Sort returned products based on the specified field, ascending or descending. Currently you can filter on:
uri
,categoryItemSort
,collectionUri
,priceAsNumber
,createdAt
andmodifiedAt
, in eitherasc
ordesc
order
Remember that there is a special case for related products: If a product relation is a variant, it will appear regardless of the relatedProducts parameter. However, if the relation is standard, it will only appear if relatedProducts is set to true.
If you select more "Product Filter Fields" in the Centra plugin settings, you can send them in the request and also get them back in the response in the "filter".
Example#
1
2
3
4
5
6
7
8
POST products?pretty
{
"limit": 2,
"skipFirst": 5,
"search": "som",
"categories": [709],
"swatch.desc": ["Red","Blue"]
}
(notice ?pretty
in the URL, this will return indented JSON)
This means products must match:
Free text search for "som" AND category = 709 AND swatch.desc = (Red OR Blue)
Paging is return 2 products, and skip the first 5.
So how do you know about category 709? Or that swatch.desc can be Red or Blue? This is what the "filter" in the response is for. It contains all possible filtering values, and a count of how many products matches each filtering value in the current set of filtered products and for all products.
Response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
{
"token": "esf1p3tgchfg5ggtpqdpgqjtt6",
"products": [
{
"product": "22068",
"variant": "30372",
"name": "Avery solid mohair beanie ice blue",
"uri": "avery-solid-mohair-beanie-ice-blue",
"sku": "SKU1SKU1",
"productSku": "SKU1SKU1",
"brand": "1",
"brandName": "Some Brand",
"brandUri": "some-brand",
"collection": "51",
"collectionName": "Last Season",
"collectionUri": "last-season",
"variantName": "smoke",
"countryOrigin": "CN",
"excerpt": "",
"excerptHtml": "",
"description": "Oversized beanie in a mohair blend",
"descriptionHtml": "<p>Oversized beanie in a mohair blend.",
"metaTitle": "Solid Mohair beanie | Some Brand ",
"metaDescription": "An oversized beanie in a mohair blend.",
"metaKeywords": "Women\\'s knitted beanie hat",
"stockUnit": "",
"category": "709",
"categoryName": [
"Women\\'s",
"Sale"
],
"categoryUri": "womens\/sale",
"centraProduct": "26453",
"centraVariant": "22068",
"itemQuantityMinimum": 1,
"itemQuantityMultipleOf": 1,
"price": "150\u00a0SEK",
"priceAsNumber": 150,
"discountPercent": 70,
"priceBeforeDiscount": "500\u00a0SEK",
"priceBeforeDiscountAsNumber": 500,
"showAsOnSale": true,
"itemTable": {
"x": [
"onesize"
],
"y": [
""
],
"dividerSymbol": "x"
},
"items": [
{
"item": "22208-126748",
"itemTableX": 0,
"itemTableY": 0,
"name": "onesize",
"ean": "123123123",
"sku": "SKU1SKU1"
}
],
"media": {
"standard": [
"https:\/\/example.net\/client\/dynamic\/images\/26453_c80b4e751c-5.jpg"
]
},
"swatch": {
"desc": "Blue",
"hex": "Blue"
},
"categories": {
"709": {
"category": "709",
"name": [
"Women\\'s",
"Sale"
],
"uri": "womens\/sale"
}
},
"relatedProducts": [
]
},
{
"product": "22069",
"name": "Snapback baseball cap winter white",
"uri": "snapback-baseball-cap-winter-white",
"sku": "SKU2SKU2",
"productSku": "SKU2SKU2",
"brand": "1",
"brandName": "Some Brand",
"brandUri": "some-brand",
"collection": "51",
"collectionName": "Last Season",
"collectionUri": "last-season",
"variantName": "winter white",
"countryOrigin": "CN",
"excerpt": "",
"excerptHtml": "",
"description": "ladies\\' baseball cap\n[b]QUALITY:[\/b]\nfurpile synthetic",
"descriptionHtml": "<p>ladies' baseball cap<br \/>\n<strong>QUALITY:<\/strong><br \/>\nfurpile synthetic<\/p>",
"metaTitle": "Snapback baseball cap | Some Brand ",
"metaDescription": "",
"metaKeywords": "",
"stockUnit": "",
"category": "709",
"categoryName": [
"Women\\'s",
"Sale"
],
"categoryUri": "womens\/sale",
"centraProduct": "26589",
"centraVariant": "22204",
"itemQuantityMinimum": 1,
"itemQuantityMultipleOf": 1,
"price": "120\u00a0SEK",
"priceAsNumber": 120,
"discountPercent": 70,
"priceBeforeDiscount": "400\u00a0SEK",
"priceBeforeDiscountAsNumber": 400,
"showAsOnSale": true,
"itemTable": {
"x": [
"onesize"
],
"y": [
""
],
"dividerSymbol": "x"
},
"items": [
{
"item": "22069-127296",
"itemTableX": 0,
"itemTableY": 0,
"name": "onesize",
"ean": "234234234",
"sku": "SKU2SKU2"
}
],
"media": {
"standard": [
"https:\/\/example.net\/client\/dynamic\/images\/26589_e31b009b90-g409647021.jpg"
]
},
"swatch": {
"desc": "Red",
"hex": "ff0000"
},
"categories": {
"709": {
"category": "709",
"name": [
"Women\\'s",
"Sale"
],
"uri": "womens\/sale"
}
},
"relatedProducts": [
]
}
],
"productCount": 7,
"filter": [
{
"field": "brands",
"values": [
{
"value": "1",
"count": 7,
"totalCount": 344,
"data": {
"brand": "1",
"brandName": "Some Brand"
}
}
]
},
{
"field": "categories",
"values": [
{
"value": "599",
"count": 0,
"totalCount": 49,
"data": {
"category": "599",
"name": [
"Jeans"
],
"uri": "jeans"
}
},
{
"value": "62",
"count": 0,
"totalCount": 23,
"data": {
"category": "62",
"name": [
"Jeans",
"T-shirts"
],
"uri": "jeans\/t-shirts",
"inCategory": "599"
}
},
{
"value": "14",
"count": 0,
"totalCount": 17,
"data": {
"category": "14",
"name": [
"Jeans",
"Shirts "
],
"uri": "jeans\/shirts",
"inCategory": "599"
}
},
{
"value": "3",
"count": 7,
"totalCount": 69,
"data": {
"category": "3",
"name": [
"Women\\'s"
],
"uri": "womens"
}
},
{
"value": "320",
"count": 0,
"totalCount": 3,
"data": {
"category": "320",
"name": [
"Women\\'s",
"Tops"
],
"uri": "womens\/tops",
"inCategory": "3"
}
},
{
"value": "709",
"count": 7,
"totalCount": 38,
"data": {
"category": "709",
"name": [
"Women\\'s",
"Sale"
],
"uri": "womens\/sale",
"inCategory": "3"
}
}
]
},
{
"field": "collections",
"values": [
{
"value": "27",
"count": 0,
"totalCount": 37,
"data": {
"collection": "27",
"collectionName": "Spring"
}
},
{
"value": "51",
"count": 7,
"totalCount": 95,
"data": {
"collection": "51",
"collectionName": "Last Season"
}
}
]
},
{
"field": "swatch.desc",
"values": [
{
"value": "Red",
"count": 1,
"totalCount": 35,
"data": {
"desc": "Red",
"hex": "ff0000"
}
},
{
"value": "Green",
"count": 0,
"totalCount": 14,
"data": {
"desc": "Green",
"hex": "00ff00"
}
},
{
"value": "Blue",
"count": 6,
"totalCount": 12,
"data": {
"desc": "Blue",
"hex": "Blue"
}
}
]
}
]
}
The "filter" object has values for the field "swatch.desc" at the end of this JSON blob. And the last thing is value "Blue". "count":6 means there are 6 blue products in the current filtered set of products, "totalCount":12 means there are 12 blue products in total without filtering. The "data" object contains the data the frontend should use to display "Blue", it is the same data as the "swatch" on the product itself.
In the filter object, the only thing that changes depending on what you filter on is the "count". If you do not filter on anything count = totalCount.
Searching improvements since v2.92#
Using the free-text "search" field in POST /products endpoint now has the following improvements:
- Handling of non-Latin characters (e.g. “grön”, “små”),
- Matching Latin to non-Latin characters (searching “grön” will also match “gron”),
- Proximity (fuzzy) matching of phrases (searching “prdct” should match “product”).
For example, if you have a Product named “Test Product” which contains “Grön” in its description, you should be able to find it using phrases like: "test product", "test grön", "product gron", or even "prdct gron".
Fuzzy search only works on words longer than 3 characters.
Routing#
You can post a uri to the POST products endpoint to filter out products that have a specific uri or that are in a category with a specific uri:
1
2
3
4
5
6
7
POST products?pretty
{
"uri": {
"uri": "jeans/slim-5-pocket-jeans-white",
"for": ["category", "product"]
}
}
You will get 1 product back for this, if a product exists with the uri "jeans/slim-5-pocket-jeans-white". The "uri" object is the URI filtering:
- uri.uri is the uri
- uri.for is what the uri is for. It can be "category" and/or "product".
There is a more generic endpoint for this:
POST uri
You post a uri, and what the uri can be for. Just like POST products:
1
2
3
4
5
6
7
POST uri
{
"uri": "jeans/slim-5-pocket-jeans-white",
"for": ["category", "product"],
"limit": 2,
"skipFirst": 0
}
- uri is the uri
- for is what it is for. It can be "product" or "category".
- limit + skipFirst is paging, used when you get an array of products in a category or back to limit the number of products
The response changes depending on what was found. The plan is that we will add new things to this endpoint in the future, maybe CMS articles. You would then be able to POST "for":["category", "cms"]
and get back CMS articles or a category of products. But you have to explicitly ask for it, you will not get back unknown results.
Example: URI leads to a product#
The response has "found": "product"
and a "product"
object:
1
2
3
4
5
6
7
POST uri
{
"uri": "jeans/slim-5-pocket-jeans-white",
"for": ["category", "product"],
"limit": 2,
"skipFirst": 0
}
Response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
{
"token": "esf1p3tgchfg5ggtpqdpgqjtt6",
"found": "product",
"product": {
"product": "24379",
"name": "Slim 5-pocket jeans white",
"uri": "slim-5-pocket-jeans-white",
"sku": "",
"productSku": "",
"brand": "1",
"brandName": "Some Brand",
"brandUri": "some-brand",
"collection": "55",
"collectionName": "Summer",
"collectionUri": "summer",
"variantName": "white",
"countryOrigin": "TR",
"excerpt": "",
"excerptHtml": "",
"description": "Classic 5-pocket jeans",
"descriptionHtml": "<p>Classic 5-pocket jeans<\/p>",
"metaTitle": "Eddy 5-pocket jeans | Some Brand ",
"metaDescription": "",
"metaKeywords": "",
"stockUnit": "",
"category": "599",
"categoryName": [
"Jeans"
],
"categoryUri": "jeans",
"centraProduct": "26954",
"centraVariant": "22775",
"itemQuantityMinimum": 1,
"itemQuantityMultipleOf": 1,
"price": "900\u00a0SEK",
"priceAsNumber": 900,
"discountPercent": 0,
"priceBeforeDiscount": "900\u00a0SEK",
"priceBeforeDiscountAsNumber": 900,
"showAsOnSale": false,
"itemTable": {
"x": [
"W28L32",
"W29L32",
"W30L32",
"W31L32",
"W31L34",
"W32L32",
"W32L34",
"W33L32",
"W33L34",
"W34L32",
"W34L34",
"W36L32",
"W36L34",
"W38L34"
],
"y": [
""
],
"dividerSymbol": "x"
},
"items": [
{
"item": "24379-130563",
"itemTableX": 0,
"itemTableY": 0,
"name": "W28L32",
"ean": "",
"sku": "",
"stock":"yes"
},
{
"item": "24379-130564",
"itemTableX": 1,
"itemTableY": 0,
"name": "W29L32",
"ean": "",
"sku": "",
"stock":"yes"
},
{
"item": "24379-130565",
"itemTableX": 2,
"itemTableY": 0,
"name": "W30L32",
"ean": "",
"sku": "",
"stock":"yes"
},
{
"item": "24379-130566",
"itemTableX": 3,
"itemTableY": 0,
"name": "W31L32",
"ean": "",
"sku": "",
"stock":"yes"
}
],
"media": {
"standard": [
"https:\/\/example.com\/client\/dynamic\/images\/26954_09e21e4415-banner-dress-shirts_1.jpg",
"https:\/\/example.com\/client\/dynamic\/images\/26954_7e68107548-27295_821558126c-h109916999-original5.jpg",
"https:\/\/example.com\/client\/dynamic\/images\/26954_a506c5a76b-h105127001_d2.jpg",
"https:\/\/example.com\/client\/dynamic\/images\/26954_a7a7ad85b3-h105127001_d1.jpg",
"https:\/\/example.com\/client\/dynamic\/images\/26954_ebfa438d2f-h105127001_d3.jpg"
]
},
"categories": {
"599": {
"category": "599",
"name": [
"Jeans"
],
"uri": "jeans"
}
}
}
}
Example: URI leads nowhere, API returns 404#
The only difference from the previous example is the request has "for":["category"]
but we know this uri leads to a product. So no category with that URI is found. The API returns status code 404, and the response has an "errors" object (the rest of the API follows this convention, if there are errors then the response contains an "errors" object).
1
2
3
4
5
6
7
POST uri
{
"uri": "jeans/slim-5-pocket-jeans-white",
"for": ["category"],
"limit": 2,
"skipFirst": 0
}
Response header:
HTTP/1.1 404 Not Found
Response:
1
2
3
4
5
6
{
"token": "esf1p3tgchfg5ggtpqdpgqjtt6",
"errors": {
"uri": "not found"
}
}
Example: URI leads to a category, API returns a category and list of products#
The response contains "found": "category"
, the "category"
object with the category name, and just like POST products
it has "products"
, "productCount"
and "filter"
. The content of these is exactly the same as POST products
.
1
2
3
4
5
6
7
POST uri
{
"uri": "jeans",
"for": ["category", "product"],
"limit": 2,
"skipFirst": 0
}
Response (edited to make it shorter):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"token": "esf1p3tgchfg5ggtpqdpgqjtt6",
"found": "category",
"category": {
"category": "599",
"name": [
"Jeans"
],
"uri": "jeans"
},
"products": [
{
"product": "32240",
...
},
{
"product": "32245",
...
}
],
"productCount": 49,
"filter": [...]
}
Categories#
GET categories returns an array of categories like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"token": "esf1p3tgchfg5ggtpqdpgqjtt6",
"categories": [
{
"category": "5",
"name": [
"Some category"
],
"uri": "some_category"
},
{
"category": "3",
"name": [
"V\u00e4xt"
],
"uri": "vaxt"
},
{
"category": "4",
"name": [
"V\u00e4xt",
"Buske"
],
"uri": "vaxt\/buske",
"inCategory": "3"
},
{
"category": "2",
"name": [
"V\u00e4xt",
"Buske",
"Nypon"
],
"uri": "vaxt\/buske\/nypon",
"inCategory": "4"
}
]
}
This array is sorted in the order you set in the Centra admin. Notice that some categories in the array are under-categories of another category. You see this on last two, they have the field inCategory
with the category id of the category they are a subcategory of. Also notice the name
array and uri
of these, they contain the full name and uri, of the main category and under-categories.
Products and categories#
A product has an array of items. An item is the thing you buy, what you add to a selection (also called cart, basket). For clothes, the items correspond to sizes of the product, and you would buy a sweater (product) in size M (item).
Products are organized into categories. Each category contains a list of products. A category can also contain categories. For example, one category could be "Clothes" and that could contain a category called "Sweaters".
A product object can contain a relatedProducts array, this array contains other product objects. These related products are related to the main product in some way, for example, it might be the same product in different colors.
Products and categories have a URI. A site normally has routes leading to specific products and categories based on the URIs. A category page should usually display the products in that category. You can ask the API what a specific URI points to, and also request all products in a specific category.
In a product listing (a category page), you would usually list only the main products and maybe indicate that a product has more colors when relatedProducts is not empty. When you view a single product, you would usually display all the related products along with it.
When you filter and search in the API, you are doing that on the main product plus all the related products. For example, if you filter on blue products, you can get back a product that is red but has a blue one in relatedProducts.
Example product#
With one related product
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
{
"product": "3",
"name": "Tv\u00e5",
"uri": "tva",
"sku": "p1v2",
"productSku": "p1",
"brand": "1",
"brandName": "Brand",
"brandUri": "brand",
"collection": "1",
"collectionName": "Min kollektion",
"collectionUri": "minkollektion",
"variantName": "V2",
"countryOrigin": "",
"excerpt": "Min korta display description",
"excerptHtml": "<p>Min korta display description<\/p>",
"description": "Min l\u00e5nga display description!\n\n\nhej\n\n\nhej\n\n\n\nhej",
"descriptionHtml": "<p>Min l\u00e5nga display description!<\/p>\n<p>hej<\/p>\n<p>hej<\/p>\n<p>hej<\/p>",
"metaTitle": "min display meta title!",
"metaDescription": "Min display meta description!",
"metaKeywords": "Min display meta keywords!",
"stockUnit": "",
"category": "4",
"categoryName": [
"V\u00e4xt",
"Buske"
],
"categoryUri": "vaxt\/buske",
"centraProduct": "1",
"centraVariant": "2",
"itemQuantityMinimum": 1,
"itemQuantityMultipleOf": 1,
"price": "88.89\u00a0GBP",
"priceAsNumber": 88.89,
"priceBeforeDiscount": "88.89\u00a0GBP",
"discountPercent": 0,
"showAsOnSale": false,
"priceBeforeDiscountAsNumber": 88.89,
"itemTable": {
"x": [
""
],
"y": [
""
],
"dividerSymbol": "x"
},
"items": [
{
"item": "3-2",
"itemTableX": 0,
"itemTableY": 0,
"name": "",
"ean": "23143657879",
"sku": "p1v2size",
"stock": "yes"
}
],
"categories": {
"4": {
"category": "4",
"name": [
"V\u00e4xt",
"Buske"
],
"uri": "vaxt\/buske"
},
"3": {
"category": "3",
"name": [
"V\u00e4xt"
],
"uri": "vaxt"
}
},
"available": true,
"relatedProducts": [
{
"product": "38",
"name": "Tv\u00e5",
"uri": "tva",
"sku": "p1v1",
"productSku": "p1",
"brand": "1",
"brandName": "Brand",
"brandUri": "brand",
"collection": "1",
"collectionName": "Min kollektion",
"collectionUri": "minkollektion",
"variantName": "V1",
"countryOrigin": "",
"excerpt": "Min korta display description",
"excerptHtml": "<p>Min korta display description<\/p>",
"description": "Min l\u00e5nga display description!\n\n\nhej\n\n\nhej\n\n\n\nhej",
"descriptionHtml": "<p>Min l\u00e5nga display description!<\/p>\n<p>hej<\/p>\n<p>hej<\/p>\n<p>hej<\/p>",
"metaTitle": "min display meta title!",
"metaDescription": "Min display meta description!",
"metaKeywords": "Min display meta keywords!",
"stockUnit": "",
"category": "4",
"categoryName": [
"V\u00e4xt",
"Buske"
],
"categoryUri": "vaxt\/buske",
"centraProduct": "1",
"centraVariant": "1",
"itemQuantityMinimum": 1,
"itemQuantityMultipleOf": 1,
"price": "199.99\u00a0GBP",
"priceAsNumber": 199.99,
"priceBeforeDiscount": "199.99\u00a0GBP",
"discountPercent": 0,
"showAsOnSale": false,
"priceBeforeDiscountAsNumber": 199.99,
"itemTable": {
"x": [
""
],
"y": [
""
],
"dividerSymbol": "x"
},
"items": [
{
"item": "38-1",
"itemTableX": 0,
"itemTableY": 0,
"name": "",
"ean": "asd",
"sku": "p1v1",
"stock": "yes"
}
],
"media": {
"s_big": [
"https:\/\/example.com\/client\/dynamic\/images\/1_aa17ecb525-nypon_frukter-s_big.jpg",
"https:\/\/example.com\/client\/dynamic\/images\/1_97b96240d4-nypon1-s_big.jpg"
]
},
"categories": {
"4": {
"category": "4",
"name": [
"V\u00e4xt",
"Buske"
],
"uri": "vaxt\/buske"
},
"3": {
"category": "3",
"name": [
"V\u00e4xt"
],
"uri": "vaxt"
}
},
"available": true
}
]
}
Item Table#
The product data has a itemTable
with x
and y
arrays. This is the x and y axis of a table. And each item in the array of items
has itemTableX
and itemTableX
integer fields.
You can use this to sort and display the items in the correct order in a table.
In the example above, the products have only a single item, so it does not illustrate how this works. This example, with jeans, has more. I have removed most data, only the itemTable and a few items are left:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
"product": "3530",
...
"itemTable": {
"x": [
"W24",
"W25",
"W26",
"W27",
"W28",
"W29",
"W30",
"W31",
"W32",
"W33",
"W34",
"W36",
"W38"
],
"y": [
"L30",
"L32",
"L34",
"L36"
],
"dividerSymbol": "\/"
},
"items": [
{
"item": "4165-32694",
"itemTableX": 0,
"itemTableY": 0,
"name": "W24\/L30",
"ean": "123123123123",
"sku": "12345-L30-W24",
"stock": "yes"
},
{
"item": "4165-32707",
"itemTableX": 2,
"itemTableY": 1,
"name": "W26\/L32",
"ean": "123123123124",
"sku": "12345-L32-W26",
"stock": "yes"
}
]
}
You can display this as a table, where 4165-32694 is at a
and 4165-32707
at b
1
2
3
4
5
W24 W25 W26 W27 W28 W29 W30 W31 W32 W33 W34 W36 W38
L30 a
L32 b
L34
L36
Selections and orders#
A selection is an open order, or a shopping cart. You add items to the selection, the product items to buy.
If you have found an item 1313-19728
using the products endpoint, you can add it to your selection:
POST items/1313-19728 HTTP/1.1
You get back the selection
. The response also has the session token
. Use the token for the API-token header, like API-Token 297qi56173p6sm0l5bii30ois3
API Errors#
The API reports errors by returning response code 400 or above.
The response body will contain an "errors" object when there is an error. So there are no errors if there is no "errors".
Example:#
POST https://example.com/api/checkout/items/123-456
Response:
1
2
3
4
5
6
{
"token": "0ms3rnl09a4i4brtbitt1o0cu1",
"errors": {
"item": "product item not found"
}
}
Payment flow#
1: Add something to the selection:#
POST https://example.com/api/checkout/items/1313-19728
In the response object you get back, there is
- token: the token to use for the "API-Token" header, session cookie
- selection: the selection (aka cart) with prices, currency
- products: the product data
- location: the customers country, we detect this with Geo-IP
- paymentMethods: the payment methods you can select
- paymentFields: the fields for the checkout.
- countries: all countries that we ship to. includes states for example for USA
2: (optional) change country#
PUT https://example.com/api/checkout/countries/SE
You get back the same structure as in 1. This can change prices/currency and in some cases can remove items that are not for sale in the selected country.
3: (optional) select a specific payment method#
PUT https://example.com/api/checkout/payment-methods/paypal
paypal
here is from the paymentMethods
in the previous responses. You get back the same structure as in #1.
4: Begin payment#
POST https://example.com/api/checkout/payment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"paymentReturnPage":"https://mywebshop.example.com/payment-return-page",
"paymentFailedPage":"https://mywebshop.example.com/payment-failed-page",
"termsAndConditions":true,
"address":{
"firstName":"asd",
"lastName":"asd",
"email":"hello@example.com",
"address1":"asd",
"city":"asd",
"country":"dk",
"phoneNumber":"123123",
"zipCode":"123"
}
}
Some integrations, like DHL shipping, require that you format the zip code (postal code) in a format that is commonly used in the shipping country. If you pass the zip code in a different format, creating a shipment can fail. It is therefore important that you follow the zip code formatting recommendation for every country you intend to ship to. For example, Swedish codes are formatted as NNN NN
(with a space), in Germany you have: NNNNN
, in Poland: NN-NNN
, in Denmark: NNNN
. A full list of postal codes formats by country can be found on Wikipedia: https://en.wikipedia.org/wiki/List_of_postal_codes. If you encounter any problems after following these guidelines, we recommend to contact DHL support.
Response:
1
2
3
4
5
6
{
"token": "be37e53c31dc3d0e66933560e187ef72",
"action": "redirect",
"url": "https://www.sandbox.paypal.com/checkoutnow?token=20E81578H46162301",
"orderId": "20E81578H46162301"
}
This means you should redirect the visitor to the URL, to the payment page of paypal
.
The paymentReturnPage
and paymentFailedPage
in the request is where the visitor will return after the payment at paypal
. These must on your frontend.
When the customer ends up on paymentFailedPage
, you know that payment failed.
When the customer ends up on paymentReturnPage
, you MUST ask the API if the payment was a success. It can still fail. You do this by forwarding the GET and POST variables that the visitor had when it accessed the paymentReturnPage
to the API:
5: Get the result of the payment#
POST https://example.com/api/checkout/payment-result
1
2
3
4
5
6
7
{
"paymentMethodFields": {
"orderNum": "1114",
"paymentMethod": "paypal",
"orderRef": "ad0eccd6a1e9402facf09f6ac49e848f"
}
}
You take the GET and POST variables that visitor had when it returned to the "paymentReturnPage" and send them to the API inside "paymentMethodFields".
Be mindful to keep the original formatting of the parameters you receive from payment provider and pass on to Centra. Depending on the payment method they may be written in camelCase (like orderRef
in PayPal) or in snake_case (like klarna_order
in Klarna). Sending wrong parameter names to Centra may cause problems with receiving order confirmation and prevent you from displaying a proper receipt.
Response (cut down):
1
2
3
4
5
6
7
HTTP/1.1 200 OK
{
"token": "dacdi99cb9q3vv5gl5lac6gmj6",
"order": "1114",
"status": "untouched",
"..."
}
This response is a success. But i will change the format of this to match the other responses better.
Payment flow, with klarna checkout#
Similar to the above, with the following changes:
3: select klarna checkout as payment method#
PUT https://example.com/api/checkout/payment-methods/klarna-checkout
4: Begin payment#
POST https://example.com/api/checkout/payment
1
2
3
4
5
6
7
8
{
"paymentReturnPage":"https://example.com/payment-return-page",
"paymentFailedPage":"https://example.com/payment-failed-page",
"termsAndConditions":true,
"address":{
"country":"se"
}
}
Response:
1
2
3
4
5
{
"token":"0ms3rnl09a4i4brtbitt1o0cu1",
"action":"form",
"formHtml":"<div id=\"klarna-checkout-container\" style=\"overflow-x: hidden;\">\n <script type=\"text\/javascript\">\n \/* <![CDATA[ *\/\n (function(w,k,i,d,n,c,l,p){\n w[k]=w[k]||function(){(w[k].q=w[k].q||[]).push(arguments)};\n w[k].config={\n container:w.document.getElementById(i),\n ORDER_URL:'https:\/\/checkout.testdrive.klarna.com\/checkout\/orders\/FZKBVVGD7PIIFMOOOE5N61Y5TKY',\n AUTH_HEADER:'KlarnaCheckout MsmS7sUBsXVCIzo80FlZ',\n LAYOUT:'desktop',\n LOCALE:'sv-se',\n ORDER_STATUS:'checkout_incomplete',\n MERCHANT_TAC_URI:'http:\/\/example.com\/terms.html',\n MERCHANT_TAC_TITLE:'Young Skilled',\n MERCHANT_NAME:'Young Skilled',\n MERCHANT_COLOR:'',\n GUI_OPTIONS:[],\n ALLOW_SEPARATE_SHIPPING_ADDRESS:\n false,\n PURCHASE_COUNTRY:'swe',\n PURCHASE_CURRENCY:'sek',\n NATIONAL_IDENTIFICATION_NUMBER_MANDATORY:\n false,\n ANALYTICS:'UA-36053137-1',\n TESTDRIVE:true,\n PHONE_MANDATORY:true,\n PACKSTATION_ENABLED:false,\n BOOTSTRAP_SRC:'https:\/\/checkout.testdrive.klarna.com\/170312-6cde26c\/checkout.bootstrap.js',\n PREFILLED: false\n };\n n=d.createElement('script');\n c=d.getElementById(i);\n n.async=!0;\n n.src=w[k].config.BOOTSTRAP_SRC;\n c.appendChild(n);\n try{\n p = w[k].config.BOOTSTRAP_SRC.split('\/');\n p = p.slice(0, p.length - 1);\n l = p.join('\/') +\n '\/api\/_t\/v1\/snippet\/load?order_url=' +\n w.encodeURIComponent(w[k].config.ORDER_URL) + '&order_status=' +\n w.encodeURIComponent(w[k].config.ORDER_STATUS) + '×tamp=' +\n (new Date).getTime();\n ((w.Image && (new w.Image))||(d.createElement&&d.createElement('img'))||{}).src=l;\n }catch(e){}\n })(this,'_klarnaCheckout','klarna-checkout-container',document);\n \/* ]]> *\/\n <\/script>\n <noscript>\n Please <a href=\"http:\/\/enable-javascript.com\">enable JavaScript<\/a>.\n <\/noscript>\n<\/div>\n"
}
This response has a different action
called form
, and a formHtml
. You need to display this formHtml
in the frontend. This thing from klarna should redirect the visitor to the paymentReturnPage
or paymentFailedPage
after payment.
Klarna checkout gives us the customers address after the payment is done, so you only need to send the country to the API. We need the country to calculate prices correctly.
5: Get the result of the payment#
This should be exactly like for PayPal. You will get different data from klarna, but just pass it on to the API and it will tell you if the payment was successful
Payment response cases:#
POST https://example.com/api/checkout/payment
can respond with an error, or one of 4 different actions:
"action":"redirect"
like PayPal above, redirect the client to theurl
in the response."action":"form"
like klarna-checkout above, display thishtmlForm
HTML-snippet. The HTML you get for klarna checkout i unusual, for other payment methods that have this response it is a HTML form that you need to POST in the visitors browser. This requires a server side page on the frontend since you cannot POST a form like this with Javascript (as far as i know)."action":"success"
this means the payment succeeded at once (no need for step 5 from above). It happens with some invoice payments or when the credit card number is integrated in our checkout page. The response contains the order data that you would otherwise get from step 5."action":"failed"
this means the payment failed at once.
So far, these 4 cases have covered all payment integrations we have. If we implement these, the checkout should work with lots of payment providers. Klarna checkout is the most odd one, that probably needs specific treatment in the frontend.
Out of stock errors#
The API does a stock check at POST https://example.com/api/checkout/payment
If something is unavailable, you get back an error in errors
, response code 410, and:
unavailable
contains a list of the items that were unavailable. These have been removed from the selection.- the rest of the response looks like
GET selection
Example, the selection.items has item=4-5, but quantity=5. the unavailable response looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HTTP/1.1 410 Gone
Content-type: application/json
{
"token": "atqsqi9m7llsp35hv5689m6tu5",
"unavailable": [
{
"item": "4-5",
"product": "4",
"originalQuantity": 11,
"unavailable": 6,
"available": 5
}
],
"selection": {
"currency": "SEK",
"paymentMethod": "adyen-256",
"..."
Selection lines bulk update#
Checkout API selection bulk update allows efficiently make multiple changes to a selection in a single request.
Features list:
- Decreasing item quantity: reduce the quantity of an existing item in the selection.
- Removing item from selection: remove an item from the selection by setting its quantity to 0.
- Adding item quantity: increase the quantity of an existing item in the selection.
- Adding item to selection: you can also add new items to the selection with a specified quantity.
By consolidating these functionalities into a single request, the API endpoint fosters efficient and user-friendly management of items in a selection, accommodating a wide range of use cases and requirements.
The new API endpoint built for that purpose is PUT /items.
Request body#
The request body is split into 2 parts:
lines
- array of line objects representing existing selection lines that will be updated. Each object consists of:- line - [String] Selection line identifier.
- quantity - [Integer] Could be 0.
items
- array of item objects representing items that will be added to the selection. Each object consists of:- item - [String] Shop item identifier (pdi_id-psi_id).
- quantity [Integer] Must be greater than 0.
"items" cannot contain items that are already part of the existing selection line - in such case, line quantity should be increased using "lines"
Example request body
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"lines": [
{
"line": "501272086e441ac629c578f5dea8d7b79f03b4c1",
"quantity": 1
},
{
"line": "7a5e4bd0d41684a550c772eb681299104b06c1d4",
"quantity": 0
}
],
"items": [
{
"item": "5-6",
"quantity": 1
},
{
"item": "12345-6789",
"quantity": 2
}
]
}
Response codes#
200 OK
- when the request is successful and items have been updated successfully, even partially.400 Bad Request
- when the request body is malformed or missing required fields, such as the items array or itemId and quantity fields in each item.404 Not Found
- when one or more items in the request body cannot be found in the basket.406 Not Acceptable
- when validations fail, e.g. an attempt to add an item that is already part of the selection and line update should be used.
Partial success is possible if request payload validation passes but for some reason line update or selection item addition fails. In such case selection response will contain additional field errors that will contain a list of failed operations.
Note that we don’t have a detailed reason of failure of either line update or item addition, because our service returns only boolean result.
For more information about error handling and responses, please refer to our Checkout API documentation.
Location#
The response from the api contains a location
, the visitors country. This is detected from the IP address with Geo-IP. This usually works, but sometimes it does not. For example if you are shopping on a satellite phone. You cannot buy anything until you set a country. Then it will look like this:
1
2
3
4
5
6
7
8
"location": {
"country": null,
"name": "",
"state": null,
"stateName": "",
"eu": false,
"shipTo": false
}
The store may not ship to all countries. In this case, shipTo
is false
. And you cannot buy anything until you set a different country.
1
2
3
4
5
6
7
8
"location": {
"country": "AX",
"name": "Aland Islands",
"eu": false,
"state": null,
"stateName": "",
"shipTo": false
}
We do not detect state from Geo-IP. But we need it for the countries that have states (in the JSON datas "countries") because some countries that have states use state-specific tax. This sets country
+ state
:
PUT https://example.com/api/checkout/countries/us/states/ca
1
2
3
4
5
6
7
8
"location": {
"country": "US",
"name": "United States",
"eu": false,
"state": "CA",
"stateName": "California",
"shipTo": true
}
PUT https://example.com/api/checkout/countries/PL
This is probably the most common, no states but shipTo
is true
:
1
2
3
4
5
6
7
8
"location": {
"country": "PL",
"name": "Poland",
"eu": true,
"state": null,
"stateName": "",
"shipTo": true
}
If your code are connecting to the API from a server, instead of from the visitors browser, our Geo-IP lookup will always use the IP of your server. You need to have Geo-IP or a similar solution on your end, and tell the API what country the visitor is from.
Address search for Klarna Invoice payments#
POST https://example.com/api/checkout/address-search
1
2
3
4
{
"identityNumber": "410321-9202",
"paymentMethod": "klarna-invoice"
}
Response
1
2
3
4
5
6
7
8
9
10
11
{
"token": "3uqv8uq1ubltkppcmv4862e9j4",
"address": {
"firstName": "Testperson-se",
"lastName": "Approved",
"address1": "St\u00e5rgatan 1",
"zipCode": "12345",
"city": "Ankeborg",
"country": "SE"
}
}
This is specifically for klarna invoice, in sweden. 410321-9202 is a personal idenitity number for a klarna test customer.
Localization#
The language is set automatically using GEO IP. The API returns data localized to the selected language.
You can change the language with:
PUT /languages/de
Or with the optional language
field when you change country:
1
2
3
4
PUT /countries/se
{
"language": "sv"
}
The response JSON contains a language
object, and also a list of languages
, and language
fields on the location
and countries
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
"location": {
"country": "DE",
"name": "Germany",
"eu": true,
"language": "de",
"state": null,
"stateName": "",
"shipTo": true
},
"language": {
"language": "de",
"name": "Deutch"
},
...
"countries": [
{
"country": "AF",
"name": "Afghanistan",
"eu": false,
"language": "en"
},
"..."
{
"country": "DE",
"name": "Germany",
"eu": true,
"language": "de"
},
"..."
{
"country": "SE",
"name": "Sweden",
"eu": true,
"language": "sv"
},
{
"country": "CH",
"name": "Switzerland",
"eu": false,
"language": "en"
},
"..."
],
"languages": [
{
"language": "de",
"name": "Deutch"
},
{
"language": "en",
"name": "English"
},
{
"language": "sv",
"name": "Svenska"
}
]
}
The languages
array is the possible languages, sorted by name
. Use the language
as the ID for selecting a language with the PUT /languages/{language}
. If Centra is not localized, there will be only one language.
countries
have a new language
field, the default language for that country. Not sure if you have any use for this.
The language
object is the selected language. This is what changes with PUT /languages/{language}
The location.language
is just copied from countries
. It is not the selected language.
Centra setup:#
In Centra you add new languages with System -> Languages. The "Language code" in Centra is the language
id in the API.
The main Centra language (for not localized data) is called en
and English
by default, this is set up in the API plugin. For example, on a Swedish webshop the Centra data is probably in Swedish so it could make sense to set it as sv
Swedish
there.
User accounts#
Register:
- POST register
- POST login/{email}
- POST logout
Change the registered address, email, password:
- PUT address
- PUT email
- PUT password
Get pervious orders:
- POST orders
Password reset email sending and use:
- POST password-reset-email/{email}
- POST password-reset
Reminder of how the errors work in the API: If the response does not contain an "errors" object, there are no errors, the operation was successful. This is important here, for example the register operation does not return "register=success". (you also get a return code over 400 for errors)
When you are logged in, the response JSON contains a "loggedIn" object with the customers address. This is the data we have in Centra today, we have 1 address and no separate shipping addresses. The login is connected to the token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"token": "cstvqilgkdkoijj8ac9fm9o0g0",
...
"loggedIn": {
"email": "hello@example.com",
"firstName": "Hello",
"lastName": "World",
"address1": "Address One",
"address2": "Address Two",
"zipCode": "12345",
"city": "add",
"state": "",
"country": "SE",
"phoneNumber": "123456789"
}
}
3 operations will login:
- POST login/{email}
- POST register
- POST password-reset (this is used when you click on the link in the password reset email)
When you are logged in, the items you add to your selection are saved to your account. If you log in on a different computer you have the same items.
If you have items in the selection before you login, they are added to your saved selection. So it will contain your previously saved items plus these new ones.
If you log out, the login is removed from the token, and the selection is empty.
Please see the swagger for the input fields.
Some operations are only allowed if you are logged in. If you are not, they will respond with http code 403 Forbidden and
1
2
3
4
5
{
"errors": {
"accessRight": "denied"
}
}
POST register#
This will register a new customer and login. If all goes well, you get back the regular JSON data with a "loggedIn" object.
Possible errors (there are more than this):
- response code 409: "email":"already registered"
- response code 406: "password":"not valid" if Centra thinks the password is not good enough. I dont know what criteria it has.
POST login/{email}#
This will login (yes, really). You get the same regular JSON back as with register.
If it fails you get response code 406 and errors with "password":"incorrect". This happens even if the email is not for a registered customer, this call will not reveal if a customer exists or not. You will find out if you use register or password-reset-email
POST logout#
This just logs out. You get the regular JSON response back, now the "loggedIn" is gone
POST password-reset-email/{email} + POST password-reset#
The first will send an email with a password reset link. You can specify the URI of the link, but the base URL must be set in the Centra checkout API plugin. So you cannot send emails to people that point to other domains.
The second is used when the customer clicks the link in the email. You need to pass on the hash from the link to the API.
Example:
1
2
3
4
POST /password-reset-email/same@example.com
{
"linkUri": "my-uri/with/slashes"
}
The "linkUri" is optional, but Centra MUST be setup with a frontend URL.
The email contains this link:
https://mywebshop.example.com/my-uri/with/slashes?id=48&i=8a87a9e9fea3c3cfdea183b281ec43f3
When the customer clicks on it, you need to send the "id" and "i" and "newPassword" to the API:
1
2
3
4
5
6
POST /password-reset
{
"newPassword":"secret!",
"i":"8a87a9e9fea3c3cfdea183b281ec43f3",
"id":"48"
}
After this the password is changed and you are logged in.
PUT address, PUT email, PUT password#
These just change the address, email and password. I hope the swagger is enough explanation.
POST orders#
This returns a list of the customers previous orders, with shipments and tracking links.
POST "from" and "size" paramters for paging. Without them all orders will be returned. If a customer has a lot of orders it could be slow to get all of them.
The response looks similar to the regular JSON response, except it contains an "orders" object that is an array of orders. And a "ordersPaging" object that keeps track of paging.
Example:
1
2
3
4
5
POST orders
{
"from":3,
"size":2
}
Response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
{
"token": "cstvqilgkdkoijj8ac9fm9o0g0",
"orders": [
{
"order": "575",
"status": "untouched",
"statusDescription": "Pending",
"message": "Thank you for your order!",
"date": "2015-10-20 16:01:34",
"currency": "USD",
"paymentMethod": "paypal",
"paymentMethodName": "P\u00e4jP\u00e4l",
"shippingMethod": "",
"shippingMethodName": "",
"items": [
{
"item": "2-1",
"product": "2",
"brandName": "Brand",
"productName": "\u221aEtt",
"size": "",
"sku": "p1v1",
"ean": "",
"quantity": 1,
"storePickup": false,
"line": "17d0eaf6e6a165676cf39222ffc81bff",
"priceEach": "$\u00a04\u00a0USD",
"priceEachAsNumber": 4,
"totalPrice": "$\u00a04\u00a0USD",
"totalPriceAsNumber": 4,
"priceEachBeforeDiscount": "$\u00a04\u00a0USD",
"priceEachBeforeDiscountAsNumber": 4,
"anyDiscount": false,
"taxPercent": 25,
"priceEachWithoutTax": "$\u00a03\u00a0USD",
"priceEachWithoutTaxAsNumber": 3.2
}
],
"discounts": {
"anyDiscount": false,
"discount": "$\u00a00\u00a0USD",
"discountAsNumber": 0,
"automaticDiscounts": [
],
"vouchers": [
]
},
"totals": {
"itemsTotalPrice": "$\u00a04\u00a0USD",
"itemsTotalPriceAsNumber": 4,
"totalDiscountPrice": false,
"totalDiscountPriceAsNumber": false,
"shippingPrice": "$\u00a099\u00a0USD",
"shippingPriceAsNumber": 99,
"handlingCostPrice": "$\u00a00\u00a0USD",
"handlingCostPriceAsNumber": 0,
"totalQuantity": 1,
"taxDeducted": "$\u00a0-21\u00a0USD",
"taxDeductedAsNumber": -20.6,
"taxAdded": false,
"taxAddedAsNumber": false,
"taxPercent": 0,
"grandTotalPrice": "$\u00a082\u00a0USD",
"grandTotalPriceAsNumber": 82.4,
"grandTotalPriceTax": "$\u00a00\u00a0USD",
"grandTotalPriceTaxAsNumber": 0
},
"vatExempt": true,
"address": {
"email": "fr@example.com",
"firstName": "First",
"lastName": "Last",
"company": "",
"address1": "Rue",
"address2": "",
"zipCode": "75000",
"city": "Paris",
"state": "Alsace",
"country": "FR",
"countryName": "France",
"phoneNumber": "1234556"
},
"shippingAddress": {
"email": "fr@example.com",
"firstName": "First",
"lastName": "Last",
"company": "",
"address1": "Rue",
"address2": "",
"zipCode": "75000",
"city": "Paris",
"state": "Alsace",
"country": "FR",
"countryName": "France",
"phoneNumber": "12345678"
},
"giftMessage": null,
"shipments": [
],
"currencyFormat": {
"currency": "USD",
"name": "USD",
"prefix": "$ ",
"suffix": " USD",
"decimalPoint": ".",
"decimalDigits": "0",
"uri": "usd"
}
},
{
"order": "574",
"status": "untouched",
"statusDescription": "Pending",
"message": "Thank you for your order!",
"date": "2015-10-20 15:58:21",
"currency": "USD",
"paymentMethod": "paypal",
"paymentMethodName": "P\u00e4jP\u00e4l",
"shippingMethod": "",
"shippingMethodName": "",
"items": [
{
"item": "2-1",
"product": "2",
"brandName": "Brand",
"productName": "\u221aEtt",
"size": "",
"sku": "p1v1",
"ean": "",
"quantity": 1,
"storePickup": false,
"line": "58b36250237a1d67e67fc95ce18c8f7d",
"priceEach": "$\u00a04\u00a0USD",
"priceEachAsNumber": 4,
"totalPrice": "$\u00a04\u00a0USD",
"totalPriceAsNumber": 4,
"priceEachBeforeDiscount": "$\u00a04\u00a0USD",
"priceEachBeforeDiscountAsNumber": 4,
"anyDiscount": false,
"taxPercent": 25,
"priceEachWithoutTax": "$\u00a03\u00a0USD",
"priceEachWithoutTaxAsNumber": 3.2
}
],
"discounts": {
"anyDiscount": false,
"discount": "$\u00a00\u00a0USD",
"discountAsNumber": 0,
"automaticDiscounts": [
],
"vouchers": [
]
},
"totals": {
"itemsTotalPrice": "$\u00a04\u00a0USD",
"itemsTotalPriceAsNumber": 4,
"totalDiscountPrice": false,
"totalDiscountPriceAsNumber": false,
"shippingPrice": "$\u00a099\u00a0USD",
"shippingPriceAsNumber": 99,
"handlingCostPrice": "$\u00a00\u00a0USD",
"handlingCostPriceAsNumber": 0,
"totalQuantity": 1,
"taxDeducted": false,
"taxDeductedAsNumber": false,
"taxAdded": false,
"taxAddedAsNumber": false,
"taxPercent": 25,
"grandTotalPrice": "$\u00a0103\u00a0USD",
"grandTotalPriceAsNumber": 103,
"grandTotalPriceTax": "$\u00a021\u00a0USD",
"grandTotalPriceTaxAsNumber": 20.6
},
"vatExempt": false,
"address": {
"email": "fr@example.com",
"firstName": "First",
"lastName": "Last",
"company": "",
"address1": "Rue",
"address2": "",
"zipCode": "75000",
"city": "Paris",
"state": "Alsace",
"country": "FR",
"countryName": "France",
"phoneNumber": "1234556"
},
"shippingAddress": {
"email": "fr@example.com",
"firstName": "First",
"lastName": "Last",
"company": "",
"address1": "Rue",
"address2": "",
"zipCode": "75000",
"city": "Paris",
"state": "Alsace",
"country": "FR",
"countryName": "France",
"phoneNumber": "12345678"
},
"giftMessage": null,
"shipments": [
],
"currencyFormat": {
"currency": "USD",
"name": "USD",
"prefix": "$ ",
"suffix": " USD",
"decimalPoint": ".",
"decimalDigits": "0",
"uri": "usd"
}
}
],
"ordersPaging": {
"from": 3,
"size": 2,
"totalSize": 38
},
"products": [
{
"product": "2",
"name": "\u221aEtt1",
"uri": "ett-v1",
"sku": "p1v1",
"productSku": "p1",
"brandName": "Brand",
"silkProduct": "1",
"silkVariant": "1",
"variantName": "V1",
"primaryVariant": true,
"excerpt": "Min korta display description",
"description": "Min l\u00e5nga display description!",
"metaTitle": "min display meta title!",
"metaDescription": "Min display meta description!",
"metaKeywords": "Min display meta keywords!",
"stockUnit": "",
"harmCode": "",
"harmCodeDescription": "",
"twilfitMultiSwatch": {
"primary": {
"color_text": "xzcx",
"color_hex": "eqwe",
"parent": "asdads",
"parent_hex": "adsasd"
},
"additional": {
"8": {
"color_text": "Hejsan",
"color_hex": "deadbeef",
"color_img": {
"type": "image",
"url": "http:\/\/7c965611.ngrok.io\/client\/dynamic\/attributes\/concorde32_7663_png.jpg",
"width": "24",
"height": "24",
"mimeType": "image\/jpeg"
},
"parent": "parenten",
"parent_hex": "cafe"
}
}
},
"price": "$\u00a04\u00a0USD",
"priceBeforeDiscount": "$\u00a04\u00a0USD",
"priceReduction": "$\u00a00\u00a0USD",
"priceAsNumber": 4,
"priceBeforeDiscountAsNumber": 4,
"priceReductionAsNumber": 0,
"discountPercent": 0,
"showAsOnSale": false,
"newProduct": false,
"inStock": true,
"items": [
{
"item": "2-1",
"name": "",
"ean": "",
"sku": "p1v1",
"inStock": true
}
],
"canonicalUri": "vaxt\/buske\/nypon\/ett-v1",
"media": {
"s_big": [
"https:\/\/7c965611.ngrok.io\/client\/dynamic\/images\/1_7c19db6361-hagebutten_am_strauch-s_big.jpg",
"https:\/\/7c965611.ngrok.io\/client\/dynamic\/images\/1_c1d15594e8-nypon_994760a-s_big.jpg",
"https:\/\/7c965611.ngrok.io\/client\/dynamic\/images\/1_c1a16a4381-casio_exilim_ex-z1050_nypon-s_big.jpg"
]
},
"relatedProducts": [
{
"product": "5",
"relation": "standard"
},
{
"product": "4",
"relation": "variant"
},
{
"product": "3",
"relation": "silkProduct"
},
{
"product": "37",
"relation": "silkProduct"
},
{
"product": "37",
"relation": "display"
}
]
}
],
"location": {
"country": "US",
"name": "United States",
"eu": false,
"language": "en",
"state": null,
"stateName": "",
"shipTo": true
},
"language": {
"language": "en",
"name": "English"
},
"loggedIn": {
"email": "same@example.com",
"firstName": "H\u00e9ll\u00f6 \u30d7\u30ec\u30a4\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3",
"lastName": "W\u00f6\u00aeld \u0625\u0646 \u0634\u0627\u0621 \u0627\u0644\u0644\u0647\u200e",
"address1": "Address One",
"address2": "Address Two",
"zipCode": "12345",
"city": "add",
"state": "",
"country": "US",
"phoneNumber": "123456789"
}
}
The orders do have shipments on them, although not in the example abve. If an order is shipped, it has an array of shipments in "shipments" like this:
1
2
3
4
5
6
7
8
9
10
"shipments": [
{
"shippedDate": "2017-08-04 16:22:20",
"carrier": "UPS",
"service": "Ground",
"trackingId": "1Z123123123123",
"trackingUrl": "http://example.com/tracking?1Z123123123123"
}
],