Read and maintain articles

Step by step guide to listing, creating, changing and deleting articles through the X-Sitter API – fields, categories, variants, syncing with your own system

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.

What Method and address Token permission
List and search articles GET /api/v1/articles Articles: read
One article with all fields GET /api/v1/articles/{id} Articles: read
Create an article POST /api/v1/articles Articles: create
Change an article PATCH /api/v1/articles/{id} Articles: change
Delete an article DELETE /api/v1/articles/{id} Articles: delete (logisticians only)

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"
  }
}
Field Meaning
id Unique ID of the article in X-Sitter. Store it in your system so you can change the article directly later.
sku The article number.
active Whether the article is active.
company_id Company number of the logistician who manages the article.
owner_company_id Company number of the client that owns the article. null for articles of the logistician itself.
managing_company_id Technical detail, not needed for an integration.
attributes All fields of the logistician with slug, label, type and the value of this article (null = no value). The label is the one your logistician gave the field.
categories The categories of the article with ID and path. is_main marks the main category.
groups The names of the article groups the article belongs to.
added_at, updated_at When the article was created and last changed.

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.

Parameter Example Effect
fields fields=name,ean,weight Which fields each entry carries (slugs separated by commas). Without the parameter you get the default fields your logistician has set.
q q=Orange Searches the text in the article number and in all field values (partial match).
attribute[slug] attribute[ean]=4260000000017 Only articles where the field has exactly this value. Several filters are combined.
active active=true Only active (true) or only inactive (false) articles.
category category=01999a3c-2222-… Only articles of this category and its subcategories.
sort sort=added Sort order: sku (default), added (creation date) or the slug of a field.
direction direction=desc asc (default) or desc.
page, per_page per_page=500 Page and entries per page (default 25, at most 500).

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"

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.

Detail Required Meaning
sku yes Article number, at most 255 characters.
active no true (default) or false.
attributes no Fields as "slug": "value". Only slugs that exist at the logistician are accepted.
categories no List of category IDs. The first one is the main category. You can see the IDs in step 2 under categories[].id.
parent_id no For variants: the number of the parent article as your system names it (at most 52 characters).
owner_company_id no Logisticians only: company number of the client that owns the article. Empty or null = article of the logistician. As a client, leave the field out; your articles automatically belong to you.

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" and 1.05). Anything that is not a number is rejected with 422 invalid_article_value.
  • Yes/no: true or false become 1 and 0.
  • Multiple choice: as a list, e.g. ["red", "blue"]; stored as red;blue.
  • Selection field: send the stored value of the option, not its display text.

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.
  • categories replaces 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:

  1. For every article from your system: do you already know its X-Sitter id? Then send a PATCH with the changed fields.
  2. If you do not know it, send a POST.
  3. If you get 409 article_number_taken, the article already exists. Take the ID from details.existing_id, store it and send a PATCH.
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.

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

Response Cause Solution
403 scope_missing The token lacks the permission (read, create, change or delete). Create a new token with the required permission.
400 invalid_json The body is not a valid JSON object. Check the JSON (quotes, commas) and send Content-Type: application/json.
400 invalid_parameter A list parameter has the wrong format, e.g. active=maybe. See the table in step 3.
404 article_not_found The ID does not exist or the article does not belong to your company. Take the ID from a current response.
409 article_number_taken The article number already exists. Change the existing article from details.existing_id (step 6).
409 article_in_use The article still has stock and cannot be deleted. Deactivate it instead of deleting it.
422 invalid_article_value A value does not fit, e.g. a field that does not exist, no number in a number field, an unknown category or a missing sku. The message names the field. Compare the slugs with step 2.

You will find all errors and permissions at a glance in the overview.