Read and maintain articles
With the article addresses of the API you keep your article master data in X-Sitter up to date without maintaining it by hand: create new articles from your shop or ERP, change names, EANs or weights, deactivate articles. You read the stock through a separate address, see Query stock.
Step 1: Create a token with the right permissions
How to create a token is described in the overview. For articles, tick under Articles (German: Artikel) only what your system really does:
- Read only (e.g. fetch the article list into your ERP): read (lesen)
- Transfer articles from your shop: read, create (anlegen) and change (ändern)
X-Sitter only offers these permissions if your user may open the product overview (for read) or edit products (for create and change). Delete is not available to clients: it stays with the logistician who manages your articles.
Step 2: Get to know the fields
An article in X-Sitter consists of a few fixed details and any number of fields (attributes) such as name, EAN, weight or customs tariff number. Your logistician decides which fields exist. Each field has a technical name, the slug (e.g. name, ean, weight). You read and write the value with this slug.
The easiest way to see all fields is to query any single article. You get the ID from the list (step 3):
curl "https://app.x-sitter.com/api/v1/articles/01999a3c-7e41-7b2a-9d0e-3f6c2a1b4e57" \
-H "Authorization: Bearer xst_YOUR_TOKEN"
{
"data": {
"id": "01999a3c-7e41-7b2a-9d0e-3f6c2a1b4e57",
"sku": "SKU-100",
"active": true,
"company_id": 12,
"managing_company_id": null,
"owner_company_id": 27,
"attributes": [
{ "slug": "name", "label": "Name", "type": "text", "value": "Orange juice 1 l" },
{ "slug": "ean", "label": "EAN", "type": "text", "value": "4260000000017" },
{ "slug": "weight", "label": "Gewicht", "type": "number", "value": "1.05" },
{ "slug": "color", "label": "Farbe", "type": "select", "value": null }
],
"categories": [
{ "id": "01999a3c-2222-7b2a-9d0e-3f6c2a1b4e57", "name": "Juices", "path": "Beverages > Juices", "is_main": true }
],
"groups": ["Beverages"],
"added_at": "2026-09-30 10:12:44",
"updated_at": "2026-10-06 08:01:13"
}
}
Note the slugs of the fields your system should fill. If you are missing a field, ask your logistician to create it. Fields cannot be created through the API.
Step 3: List and search articles
curl "https://app.x-sitter.com/api/v1/articles?per_page=100&fields=name,ean" \
-H "Authorization: Bearer xst_YOUR_TOKEN"
In the list, the fields come in compact form as "slug": "value":
{
"data": [
{
"id": "01999a3c-7e41-7b2a-9d0e-3f6c2a1b4e57",
"sku": "SKU-100",
"active": true,
"company_id": 12,
"managing_company_id": null,
"owner_company_id": 27,
"attributes": { "name": "Orange juice 1 l", "ean": "4260000000017" },
"added_at": "2026-09-30 10:12:44",
"updated_at": "2026-10-06 08:01:13"
}
],
"meta": { "page": 1, "per_page": 100, "total": 312, "total_pages": 4 }
}
As a client you only see your own articles, as a logistician all articles you manage. The list comes page by page; how to fetch all pages is shown in Query stock, step 5. Just swap the address.
Find an article by its EAN:
curl -G "https://app.x-sitter.com/api/v1/articles" \
-H "Authorization: Bearer xst_YOUR_TOKEN" \
--data-urlencode "attribute[ean]=4260000000017"
Some tools require the square brackets in attribute[ean] to be URL-encoded (attribute%5Bean%5D=...). With curl -G and --data-urlencode as above this happens automatically.
Step 4: Create an article
Send the details as JSON. Only the article number sku is required, everything else is optional.
curl -X POST "https://app.x-sitter.com/api/v1/articles" \
-H "Authorization: Bearer xst_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sku": "SKU-200",
"active": true,
"attributes": {
"name": "Apple juice 1 l",
"ean": "4260000000024",
"weight": "1.05"
},
"categories": ["01999a3c-2222-7b2a-9d0e-3f6c2a1b4e57"]
}'
<?php
$token = 'xst_YOUR_TOKEN';
$article = [
'sku' => 'SKU-200',
'active' => true,
'attributes' => [
'name' => 'Apple juice 1 l',
'ean' => '4260000000024',
'weight' => '1.05',
],
];
$ch = curl_init('https://app.x-sitter.com/api/v1/articles');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($article),
]);
$result = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo $status === 201 ? 'Created: ' . $result['data']['id'] : 'Error: ' . $result['error']['code'];
import requests
token = "xst_YOUR_TOKEN"
article = {
"sku": "SKU-200",
"active": True,
"attributes": {"name": "Apple juice 1 l", "ean": "4260000000024", "weight": "1.05"},
}
response = requests.post(
"https://app.x-sitter.com/api/v1/articles",
json=article,
headers={"Authorization": f"Bearer {token}"},
timeout=30,
)
result = response.json()
print(result["data"]["id"] if response.status_code == 201 else result["error"])
The response has the status 201 and contains the new article in the same form as in step 2, including its id. Store the ID in your system.
This is how field values are stored:
- Text: as sent, leading and trailing spaces are removed.
- Number: comma and point are both allowed as decimal separator (
"1,05"and1.05). Anything that is not a number is rejected with422 invalid_article_value. - Yes/no:
trueorfalsebecome1and0. - Multiple choice: as a list, e.g.
["red", "blue"]; stored asred;blue. - Selection field: send the stored value of the option, not its display text.
Article number already taken?
An article number exists only once per owner. If it already exists, the API answers with 409 article_number_taken and names the ID of the existing article in details.existing_id. Change that article (step 5) instead of creating a second one.
Step 5: Change an article
With PATCH you change only what you send. Everything else stays as it is.
curl -X PATCH "https://app.x-sitter.com/api/v1/articles/01999a3c-7e41-7b2a-9d0e-3f6c2a1b4e57" \
-H "Authorization: Bearer xst_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"ean": "4260000000031",
"color": null
}
}'
The example sets a new EAN and removes the value of the field color. The response contains the article after the change.
- A field with
null(or an empty text) loses its value. categoriesreplaces the complete list. If you want to add a category, send the existing ones as well.- You deactivate an article with
{"active": false}. It keeps all its data and can be activated again at any time. - The article number can be changed too (
{"sku": "SKU-200-NEW"}), as long as the new one is not taken yet.
Step 6: Sync your system with X-Sitter
For a regular sync from your shop or ERP, this procedure has proven itself:
- For every article from your system: do you already know its X-Sitter
id? Then send aPATCHwith the changed fields. - If you do not know it, send a
POST. - If you get
409 article_number_taken, the article already exists. Take the ID fromdetails.existing_id, store it and send aPATCH.
import requests
BASE = "https://app.x-sitter.com/api/v1/articles"
HEADERS = {"Authorization": "Bearer xst_YOUR_TOKEN"}
def upsert(article: dict, known_id: str | None) -> str:
"""Create or change one article, return its X-Sitter id."""
if known_id is None:
response = requests.post(BASE, json=article, headers=HEADERS, timeout=30)
if response.status_code == 201:
return response.json()["data"]["id"]
error = response.json()["error"]
if error["code"] != "article_number_taken":
raise RuntimeError(f"{error['code']}: {error['message']}")
known_id = error["details"]["existing_id"]
changes = {key: value for key, value in article.items() if key != "sku"}
response = requests.patch(f"{BASE}/{known_id}", json=changes, headers=HEADERS, timeout=30)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
return known_id
Only send articles that have changed since the last sync, not the complete catalogue every time.
Step 7: Delete an article (logisticians only)
curl -X DELETE "https://app.x-sitter.com/api/v1/articles/01999a3c-7e41-7b2a-9d0e-3f6c2a1b4e57" \
-H "Authorization: Bearer xst_YOUR_TOKEN"
The response has the status 204 without content.
Deleting is permanent
The article is deleted immediately together with all field values and categories and cannot be restored. If there is still stock in the warehouse, nothing is deleted: the API answers with 409 article_in_use and names the number of pieces in details.quantity. If you just no longer want to use an article, deactivate it instead (PATCH with {"active": false}).
For logisticians: maintain articles for a client
Send the client's company number in the header X-Company. Then you only see that client's articles, and new articles automatically belong to it:
curl -X POST "https://app.x-sitter.com/api/v1/articles" \
-H "Authorization: Bearer xst_YOUR_TOKEN" \
-H "X-Company: 27" \
-H "Content-Type: application/json" \
-d '{"sku": "SKU-300", "attributes": {"name": "Grape juice 1 l"}}'
Without X-Company you can also name the owner with owner_company_id. The same article number may exist once at the logistician and once at each client.
If something does not work
Details the API does not know (e.g. a typo like "attribute" instead of "attributes") are silently ignored when creating and changing. So after your first test, check in the response whether all values arrived.
You will find all errors and permissions at a glance in the overview.