Skip to content

API Conventions

Both APIs follow the same rules for the things every client has to get right. The examples are real answers of the public demo backend, https://demo-api.entirius.com, trimmed where marked. Storefront reads need no credential; admin reads use the read-only staff login viewer / viewer on POST /api/token/ (Demo Credentials).

A sales channel is a path segment, channel_idx: /api/matrix/v2/<channel_idx>/products/ on the storefront API, /api/pim/v2/admin/<channel_idx>/products/ on the admin API. Admin routes that are not about one channel have no such segment. The translator routes call the same identifier shop_idx (/api/pim-translator/v2/admin/<shop_idx>/…).

GET /api/pim/v2/admin/channels/ lists the channels with their idx, default language, default currency and languages. The demo has three: sloik-pl (PLN; pl, en), sloik-eu (EUR; en, es, de, pl) and sloik-firmy (PLN; pl, en); the seeded local stack of the Quickstart has default-europe and default-local (Your First Request). An unknown channel answers 404 NOT_FOUND. What a channel is and how modules share it: Channel Access Patterns.

Admin payloads carry every language. A translatable field comes twice: the plain field in the channel’s default language and a _t9n dictionary keyed by language code. One feature from GET /api/pim/v2/admin/sloik-pl/features/?page_size=1 (sloik-pl defaults to Polish; the dictionary lists every language of the installation, not only the channel’s two; cut: other fields):

{"idx": "name", "name": "Nazwa", "name_t9n": {"de": "Name", "en": "Name", "es": "Nombre", "pl": "Nazwa"}}

Storefront answers carry one language. The language, currency and country query parameters pick it (?language=pl&currency=EUR); a missing or unknown value falls back to the channel’s default. The formats and the fallback rule: Response Contract. How languages are stored: Languages.

The same product list in three requests (page_size=2, only name and the gross price shown):

GET /api/matrix/v2/sloik-pl/products/?language=en
SC-POD-011 Stars over the desert 119.99 PLN
GET /api/matrix/v2/sloik-eu/products/?language=de&currency=EUR
SC-POD-011 Sterne über der Wüste 27.90 EUR
GET /api/matrix/v2/sloik-pl/products/?language=xx&currency=ZZZ
SC-POD-011 Gwiazdy nad pustynią 119.99 PLN

The last one is the fallback: unknown values answer in the channel’s defaults, with status 200.

A paginated list takes page (from 1) and page_size, and answers in one of two envelopes.

Counted, on admin lists. GET /api/pim/v2/admin/sloik-pl/products/?page_size=3:

{
"count": 212,
"next": "https://demo-api.entirius.com/api/pim/v2/admin/sloik-pl/products/?page=2&page_size=3",
"previous": null,
"results": []
}

Uncounted, on storefront catalog lists, where counting a filtered catalog costs more than the page itself. GET /api/matrix/v2/sloik-pl/products/?page_size=2:

{
"has_next_page": true,
"page": 1,
"page_size": 2,
"results": [],
"meta": {"status": "ok", "warnings": []}
}

results is cut to [] in both examples. Follow next on a counted list; on an uncounted one ask for page + 1 while has_next_page is true. The envelope follows the kind of list, not the API: the storefront FAQ items (GET /api/faq/v2/sloik-pl/items/) are counted.

The default and the largest page_size differ per endpoint, and the explorer states them on each operation. Two examples: PIM admin lists default to 20 and stop at 100; the storefront product list defaults to 12 and stops at 100. A larger page_size is cut to the maximum, not refused: ?page_size=500 on the storefront list answers "page_size": 100.

Admin lists take ordering with a field name, and a leading - for descending: GET /api/pim/v2/admin/sloik-pl/products/?ordering=-sku answers SC-ZIM-010, SC-ZIM-009, SC-ZIM-008. The fields are per endpoint and named in the explorer; the PIM product list knows sku, visibility, gap_worst_severity and gap_count. An unknown value is not an error there: ?ordering=-name answers the list in ascending SKU order (SC-AKC-001, SC-AKC-002, …).

A few lists name the parameter sort instead, with the same - prefix, for example the FAQ items of the storefront API.

The storefront product list sorts with s_{field} and the direction as the value: GET /api/matrix/v2/sloik-pl/products/?s_price=asc. Fields are price, name, created, sku, on_stock, relevance and any feature the channel marks as sortable (GET /api/matrix/v2/<channel_idx>/options/ lists them under sort). Price sorts by the price the customer pays: on the demo SC-AKC-012 has a gross price of 12.99 and a final price of 6.99, and sits between 5.99 and 6.99.

search is the free-text parameter on both APIs: GET /api/pim/v2/admin/sloik-pl/products/?search=zorza answers the 2 demo products that match (SC-POD-008, SC-ZIM-009); the storefront list takes the same parameter. Which fields it reads is part of each operation’s description.

The storefront product list filters on product features:

ParameterDoesExample
q_{feature_idx}value filter on a feature; repeat it for several values (OR within one feature)?q_<feature_idx>=<value>
r_{feature_idx}range filter, two values, wired for price?r_price=100&r_price=500
category, on_stock, product_type, sku, url_keyfixed filters?category=zorza&on_stock=false

GET /api/matrix/v2/sloik-pl/products/count/ takes the same filters and answers only the number. On the demo:

?q_nastroj=spokoj -> {"count": 105, ...}
?q_nastroj=spokoj&q_nastroj=radosc -> {"count": 160, ...}
?r_price=100&r_price=125 -> {"count": 14, ...}

GET /api/matrix/v2/<channel_idx>/options/ lists the feature indexes, their values and counts for the filter sidebar.

Every other filter belongs to one endpoint and is listed with it in the CMS Admin API and Storefront API explorers.

The v2 error handler has one envelope. GET /api/pim/v2/admin/channels/ without a token answers 401:

{"error": "AUTHENTICATION_REQUIRED", "message": "Authentication credentials were not provided or are invalid.", "debug_id": "93ccc863", "details": []}

A bad filter answers 400 with the field named. GET /api/agreements/v2/admin/clause-sets/?channel_idx=sloik-pl&current=maybe with the viewer token (location says body even though the value came in the query string):

{"error": "VALIDATION_ERROR", "message": "Request validation failed.", "debug_id": "43d73ce5", "details": [{"field": "current", "location": "body", "issue": "INVALID", "description": "Input should be a valid boolean, unable to interpret input"}]}

An unknown channel, GET /api/matrix/v2/nope/products/, answers 404:

{"error": "NOT_FOUND", "message": "The requested resource was not found.", "debug_id": "aa247f93", "details": []}

error is the code to branch on, message a fixed sentence per status, details a list of field / location / issue / description entries (one per invalid field on a validation error), and debug_id the id of the matching log line. Quote it when you report a problem.

The codes are VALIDATION_ERROR and INVALID_REQUEST (400), AUTHENTICATION_REQUIRED (401), PERMISSION_DENIED (403), NOT_FOUND (404), CONFLICT or the conflict’s own code (409), RATE_LIMITED (429) and INTERNAL_ERROR (500). The table with what raises each one: Error Handling.

Not every route emits it yet. Measured on the demo:

  • Unknown admin object ids answer 404 {"detail": "Product with SKU 'NOPE-123' not found in channel 'sloik-pl'"}.
  • GET /api/deliverypoints/v2/sloik-pl/points/nearby/?lat=abc&lng=1 answers 400 {"detail": "lat and lng must be valid numbers."}.
  • Storefront catalog operations wrap their answers: an unknown SKU on /api/matrix/v2/sloik-pl/products/NOPE-123/ answers 404 {"meta": {"status": "NOT_FOUND", ...}, "data": "Product NOPE-123 does not exist"}.

Branch on the status code first, on error only when the body has it.

A 500 never explains itself. It answers INTERNAL_ERROR with a debug_id, or {"detail": "Internal server error [<error_id>]"} from endpoints that carry an id of their own. Either id finds the cause in the server log.

No limit applies to the API as a whole. Limits sit on the endpoints that create state without a login or that start expensive work, each with a scope and a default rate. The modules are listed in the Module Catalog. A refused request answers 429 with the error code RATE_LIMITED.

ModuleEndpointsScopeDefault
AgreementsPOST …/newsletter/subscribe/agreements_subscribe5/min
AgreementsPOST …/consents/, …/consents/withdraw/agreements_consent20/min
Agreements…/consents/confirm/, …/consents/unsubscribe/agreements_token10/min
AgreementsPOST …/orders/{order_id}/agreements/agreements_order20/min
AgreementsPOST …/cookie-consents/agreements_cookie_consent30/min
CheckoutPATCH …/carts/{cart_id}/payment/checkout_cart_payment30/minute
Contact formsPOST …/submit/contact_forms_submit30/hour
Contact formsGET …/form-types/contact_forms_form_types120/hour
Contact forms…/bookings/contact_forms_booking10/hour
AtlasGET …/admin/pim-sku/has-changes/atlas_change_log_bulk120/min
SuppliersGET …/admin/pim-sku/has-changes/suppliers_change_log_bulk120/min
EnrichmentPOST …/admin/proposals/, …/proposals/upload-media/enrichment_intake100/min
EnrichmentPOST …/admin/tasks/, …/tasks/import-csv/, …/spawn-rules/{key}/run/enrichment_spawn120/min
EnrichmentPOST …/admin/proposals/bulk-accept/, bulk-reject/, bulk-undo/enrichment_bulk30/min
LookupPOST …/admin/search/, …/admin/check/lookup_check; lookup_image when the request carries a picture60/min; 30/min
PricefighterPOST …/admin/apply/pricefighter_apply30/hour

Storefront limits count per client address (the checkout one per address and cart; the agreements ones apply to callers without a customer token), admin limits per user. Each contact-forms limit also has a ceiling per application token (scope <scope>_token, 20 times the default) that holds when one token is replayed from many addresses.

An installation changes a rate by its scope in the service settings: REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["contact_forms_booking"] = "30/hour". The default holds when the scope is not configured. The Atlas, Suppliers and Enrichment rates are fixed in the module and take no override.

Failed logins have their own counters on POST /api/token/ and on the customer token route. Only wrong passwords count, a success is never counted, and a blocked login answers 429 with Retry-After before the password is checked:

CounterDefaultSetting
per username and address10 in 900 sAUTH_TOKEN_MAX_FAILURES_PER_USER_IP, AUTH_TOKEN_FAILURE_WINDOW_S
per address100 in 900 sAUTH_TOKEN_MAX_FAILURES_PER_IP, AUTH_TOKEN_FAILURE_WINDOW_S
per username, any address50 in 3600 sAUTH_TOKEN_MAX_FAILURES_PER_USER, AUTH_TOKEN_USER_FAILURE_WINDOW_S

Next: Versioning, what the v2 in these URLs promises.