Changelog

A record of all notable changes to the Kataio API.

v2.19.0

patch

September 22, 2026

  • FixedThe product media tab showed several labels and toast messages in French regardless of the active locale ("Fichiers", "Importer", "fichier(s) ajouté(s)", "sélectionné(s)", "Associé", "Précédent" / "Suivant"), because they were hardcoded instead of going through the translation keys already used by the rest of the tab. All twelve UI locales now see these strings translated.
  • FixedCategory product counts on the categories list and category detail page always used the plural noun form ("1 produits"). They now use the same plural rule as the rest of the catalogue, including Romanian and Polish's distinct "few" forms.
  • FixedThe Categories API reference documented the reorder field as `position`; the API has always accepted and returned `sortOrder`. Corrected in both the English and French articles.
  • FixedThe Categories and Products help articles said a product "can belong to multiple categories." A product has always belonged to exactly one category at a time — picking a new one moves it out of the one it was in. Corrected in both languages, including the description of what happens when you reassign a product from the category page.

v2.18.0

minor

September 21, 2026

  • FixedA store URL saved without a scheme no longer breaks every sync on the channel. The field accepts a bare host such as example.com, but each connector builds its request with new URL(), which rejects a value with no https:// prefix — one PrestaShop channel had failed 168 of 168 sync jobs on it. Store URLs are now normalised when a channel is created or updated, and existing channels holding a bare host were corrected.
  • FixedMercado Libre no longer throws while handling a token response that carries no user identifier. The re-authorisation path read that field outside its error handling, so the failure replaced the real cause with an unrelated one.
  • FixedShopify answers a request for a store that does not exist, or that has uninstalled the application, by naming the shop domain instead of reporting a bare HTTP 404 or 406.
  • ChangedOtto and Zalando no longer expose OAuth redirect routes. Both marketplaces authenticate with client credentials — the redirect endpoints returned a fabricated authorisation URL and their callbacks were unreachable. Connect both from the channel wizard with the client ID and secret issued by the marketplace.
  • FixedFrench copy in the channel integration code samples was missing its accents (“Recuperer les produits”, “Gerer les notifications de mise a jour produits”), as were two messages elsewhere in the interface.
  • ImprovedThe per-channel stock override label on a product’s Distribution tab, and the source and destination fields of a stock transfer, are translated in all twelve interface languages. They were shown in English regardless of the language selected.

v2.17.0

minor

September 20, 2026

  • AddedeBay: GET|POST /api/v1/webhooks/platform/ebay/account-deletion. This is the marketplace account-deletion endpoint eBay requires before a production keyset works, and it is per application rather than per channel. GET answers the challenge with sha256(challengeCode + verificationToken + endpoint); POST always answers 200 and logs the receipt with the eBay identifiers hashed.
  • AddedAmazon channels cover all seventeen marketplaces Kataio supports. Australia, Brazil, Canada, India, Japan, Mexico and Singapore had no Seller Central consent host, so connecting them answered HTTP 400; each now routes to its own host. The channel wizard asks for a store by name instead of a marketplace identifier — the stored credential is still marketplaceId.
  • FixedAmazon: the Appstore log-in handshake carries Amazon’s version parameter through to the confirm URL. Without it the workflow authorizes the published application rather than the draft one. The handshake also accepts the callback host Amazon documents (amazon.com), which it previously rejected.
  • FixedEtsy: the access token cache is keyed per shop. It was keyed on the application key, which is shared by every tenant now that Etsy uses the Kataio application — one shop’s token could be used for another tenant’s channel. Test connection is openapi-ping then getMe, so a failure names either the Kataio application key or the shop’s own grant.
  • FixedWalmart: a 4xx from the token endpoint marks the channel needs_reauth instead of failing every sync silently, and a wrong key pair is reported as “Invalid Client ID or Client Secret” (Walmart answers 400, not 401). Test connection uses GET /v3/orders?limit=1, which is healthy even for a seller with no listings.

v2.16.0

minor

September 20, 2026

  • ChangedProducts are pushed to Shopify with one GraphQL productSet mutation instead of the deprecated REST /products.json create and update. The product, its options, its variants and its media travel in a single call; Shopify user errors are reported verbatim on the channel product (syncStatus ERROR, syncError). The remote id stored on the channel product stays numeric, so pulls, webhooks and existing rows are unaffected.
  • ChangedShopify product limits follow the GraphQL model: up to 3 options and 2048 variants per product (REST capped variants at 100). Variant weights move to the inventory item measurement, and a product with no weight no longer pushes a 0.

v2.15.0

minor

September 20, 2026

  • ChangedShopify connects with app keys: POST /channels/{id}/connect exchanges the Client ID and secret for a 24-hour token (client-credentials grant) and answers { status: "connected", shopName } with the granted scopes — no browser redirect, no callback URL to register. Channels created before this release keep their stored token and still answer { status: "redirect" }; connect?mode=oauth forces the redirect on any channel, connect?mode=credentials converts one to the key exchange. credentials.connectionMode records which mode a channel uses.
  • ChangedMercado Libre and Allegro accept the Kataio app credentials from the environment when a channel carries none, and their single-use refresh tokens are persisted before use (authExpiresAt +180 d / +90 d) so a rotation can no longer lose a connection.
  • AddedAllegro sends the GPSR responsible producer: it is created once per channel from the tenant company profile and attached to every productSet call. An incomplete company profile fails the push with a message naming the missing field.
  • FixedShopify inbound webhooks are verified with the app client secret. The previous lookup read a credential key (apiSecretKey) that no connect path ever wrote, so signature verification could not succeed.

v2.14.0

minor

September 20, 2026

  • AddedPOST /channels/{id}/connect?returnTo&mode — one contract for all 16 platforms (channels:update, no body). OAuth platforms answer { status: "redirect", authorizationUrl }; key-based platforms are validated against the platform and answer { status: "connected", shopName? }. Errors are plain 4xx.
  • AddedPOST /channels/{id}/meta/token { accessToken, catalogId, appId?, appSecret? } — Meta System User token (catalog-only, no orders). The token is validated against Graph and stored encrypted; answers { success, shopName, message }. Facebook Login stays available through connect?mode=oauth.
  • AddedTikTok Shop bring-your-own-app connector: the merchant’s own Partner Center app (appKey, appSecret, serviceId, region) builds the service_id authorize link, the shop is discovered after consent, and each channel gets its own inbound webhook endpoint POST /webhooks/platform/{channelId}/tiktok-shop (Authorization = HMAC-SHA256(appKey + rawBody, appSecret) hex; deduplicated on tts_notification_id for 24 h).
  • AddedSDKs v1.1.0 — channels resource in all 7 languages: list, get, create, update, delete, testConnection, connect, setMetaSystemToken, sync (TypeScript client.channels.*; PHP/Java/C#/Go/Ruby as listChannels / connectChannel / syncChannel…, Python client.channels.test_connection / set_meta_system_token).
  • ChangedChannel credentials are encrypted at rest and redacted in API responses: GET /channels and GET /channels/{id} return secret fields as •••••••• and omit accessToken / refreshToken; PATCH /channels/{id} ignores the sentinel, so a read → edit → write round-trip never wipes a stored secret. POST /channels/test-connection accepts channelId to reuse stored secrets.
  • ChangedDaily authorization-expiry sweep: settings.oauthStatus becomes "expiring" 7 days before credentials.authExpiresAt (syncs keep running) and "needs_reauth" once the grant has lapsed (channel deactivated, settings.disconnectReason set). Call connect again to renew.
  • ChangedScheduled sync now covers every connector platform.
  • ChangedAdd-channel wizard rebuilt: 3 steps (platform → connection details → activation), near full-screen, fields driven by the shared connection descriptor, TikTok Shop region select, no success banner before the platform has actually answered.
  • FixedInbound platform webhook signatures were never verified because req.rawBody was empty; WooCommerce, Shopify, PrestaShop and eBay deliveries with a bad signature are now rejected.
  • FixedMeta Graph API pinned to v26.0 and every call carries appsecret_proof.

v2.13.0

minor

September 19, 2026

  • AddedMedia folders: PATCH /media/folders/{id} { name?, parentId? } renames or moves a folder (400 on a cycle); DELETE /media/folders/{id} unfiles its content and answers 409 while it still has subfolders. GET /media?filter[folderId]=none lists unfiled files; POST /media/upload accepts folderId. The media library gets a folder tree with drag-and-drop and bulk move.
  • AddedGET /media/{id}/usage lists the products using a file with their channels — shown as "Used by" in the media preview.
  • AddedFocal point: PATCH /media/{id} { focalX, focalY } (0–1, both or null) re-crops the thumbnail around that point. Medium/large variants, the original and its url are untouched, so nothing changes on the channels. Click the image in the media preview to set it.
  • FixedFiles uploaded through POST /media/upload now get thumbnail / medium / large variants like files attached through the product media endpoints.
  • AddedE-mail digest: PATCH /settings/me/digest { frequency: "DAILY" | "WEEKLY" | null } opts the signed-in user into a 07:30 summary (new orders, failed syncs, stock alerts, catalogue gaps — only the sections their role can read; no mail when nothing happened). GET /settings returns user.digestFrequency. Each mail carries a signed unsubscribe link. Settings → Notifications.

v2.12.0

minor

September 19, 2026

  • AddedPartial shipments. POST /orders/{id}/shipments { carrier?, trackingNumber?, trackingUrl?, shippedAt?, notes?, items: [{ orderItemId, quantity }] } creates a parcel (orders:update); GET /orders/{id}/shipments lists them; DELETE /orders/{id}/shipments/{shipmentId} removes one and recomputes the status. GET /orders/{id} returns shipments[] and shippedQuantity per line. Over-shipping a line answers 400; a cancelled, returned or delivered order answers 409.
  • AddedNew order status PARTIALLY_SHIPPED (between PROCESSING and SHIPPED) — returned by GET /orders, accepted by ?status= and by PATCH /orders/{id}/status. Integrations that switch on the status should treat it like PROCESSING. The last parcel moves the order to SHIPPED; PATCH … { status: "SHIPPED" } still works and creates one parcel for whatever is left.
  • AddedEdit order lines before fulfilment: PUT /orders/{id}/items { items: [{ id?, sku, quantity, unitPrice, name?, description?, metadata? }], shippingAmount?, discountAmount?, taxAmount? } (orders:update) replaces the lines in one call while the order is PENDING / PROCESSING with no parcel and no return (409 otherwise). Totals and tax are recomputed, stock reservations and the customer total spent follow the delta, the audit log gets items_edited and the new order.updated webhook fires. "Edit lines" on the order page.
  • AddedRelated products: POST/PATCH/DELETE /products/{id}/relations (types RELATED, UPSELL, CROSS_SELL, ACCESSORY; one link per target and type; directional). GET /products/{id} returns relationsFrom[]. WooCommerce pushes them as upsell_ids / cross_sell_ids for targets already listed on the store. "Related" tab on the product page.
  • AddedTemplates as products: POST /products/{id}/duplicate takes an optional body { sku?, nameI18n?, copyChannels?, copyMedia?, copyRelations?, asTemplate? } (409 when the SKU exists). A product with isTemplate = true is a starting point for new products and is never pushed to a channel; ?filter[isTemplate]=true lists them. "Save as template" on the product list and "Start from a template" on the new-product page.
  • AddedWebhook order.shipment_created — one per parcel, payload = the shipment with its lines plus orderId, externalId, externalReference, orderStatus. order.shipped still fires exactly once, when the order becomes SHIPPED; the shipped e-mail is sent per parcel.

v2.11.0

minor

September 19, 2026

  • FixedTwo-factor login. Enabling 2FA used to lock the account out of the web app: POST /auth/login answers { requiresTwoFactor: true, challengeToken } and the login page now shows the 6-digit code step and finishes with the new public POST /auth/2fa/login { challengeToken, code } (challenge valid 5 min, throttled). API clients that drive the login flow must handle requiresTwoFactor the same way. POST /auth/2fa/verify stays for the setup flow (JWT).
  • AddedPer-API-key rate limiting. Each key has rateLimitPerMinute (default 600, settable 10–10 000 on create/update) applied across all routes; 429 answers carry Retry-After and X-RateLimit-Limit/Remaining/Reset, and 429s show in GET /api-usage per key. JWT users keep 300/min; login/signup/forgot-password are throttled per account, not per IP.
  • AddedGDPR erasure: POST /customers/{id}/erase { confirm: "ERASE" } (orders:delete) anonymises the customer and every linked or e-mail-matched order (name, e-mail, addresses, notes → null; amounts, items and PSP ids kept), records a GdprOperation, and fires the new customer.erased webhook { id, erasedAt, orders } — no PII in the payload. 409 on a second call; PATCH on an erased customer answers 409.
  • AddedProduct change history on every write path (UI, SDK keys, import, channel pulls): GET /products/{id}/history returns one row per changed field with old/new value, actor or source (ui, api_key:<name>, import, sync:<channel>) and batchId; filter[field], action, userId, dateFrom/dateTo. History tab on the product page.
  • AddedTenant activity log: user/role/channel/settings/order-status/bulk/import/API-key/webhook changes are written to the audit log (no credentials, no customer PII) and browsable at /settings/activity; GET /settings/audit-log gains entityType, entityId, action, userId, from, to filters. Order detail shows its own history.
  • AddedBulk edit with preview and undo: POST /products/bulk/update { ids | filter, dryRun?, data } — status, category, price/costPrice operations (set, ±%, ±fixed, rounding .99/unit/cent), taxRate, customAttributes set/unset, channels add/remove; up to 2 000 products by filter. POST /products/bulk/undo/{batchId} reverts fields that still hold the bulk value.
  • AddedImport with column mapping and dry-run: POST /products/import/preview (multipart file + optional mapping + mode) returns headers, suggested mapping (fr/en synonyms), sample rows and a per-row create/update/error preview without writing; POST /products/import accepts mapping and mode = upsert | create_only | update_only. Re-imports update only the columns present — they no longer wipe attributes, dimensions or SEO fields, nor reset the completeness score.
  • AddedSaved views: GET/POST/PATCH/DELETE /saved-views — named filter presets per list (products, orders, customers, inventory), personal or shared with the team, with an optional default that applies on open.
  • AddedPer-channel completeness rules: GET/PATCH /channels/{id}/completeness { requiredFields[], blockPush } (fields or attr:<code>). The channel × product matrix reports readiness per cell (missing fields in the tooltip); a push blocks incomplete products with a sync error when blockPush is on, otherwise pushes them with a warning in the job log. GET /channels/{id}/sync/validate reflects the same rules.
  • AddedUser avatar: POST /auth/profile/avatar (multipart `file`, ≤ 2 MB image, stored as 256×256 JPEG) and DELETE /auth/profile/avatar; avatarUrl is returned by /auth/me, login and the users list.
  • ImprovedRoles: the system role matrix is applied on every API boot (administrator, product manager, logistician, viewer …) and both the sidebar and the API are gated by it; /profile is available to every role; GET /settings only needs a session.
  • ImprovedMobile-first dashboard: slim top bar, phone layouts for the dashboard, product editor, customers, orders, inventory, users, media, docs and integrations; every data table renders as a card list below 768 px; KPI grids are two columns on phones; the docs read correctly in dark mode.
  • ChangedDeploys apply pending Prisma migrations from the new image before the API container is swapped, so new code never meets an old schema.

v2.10.0

minor

September 18, 2026

  • AddedEnglish UI across the whole app, admin and legal pages; the API answers with the caller's language from Accept-Language (French remains the default). Error messages, e-mails and CSV headers follow it.
  • AddedStatistics module: GET /analytics/overview, /sales, /products, /customers, /inventory, /operations with period and multi-value filters (channel, status, warehouse, category), CSV export and an Excel management report; orders now carry shipped_at / delivered_at / cancelled_at so lead-time KPIs accrue from this release. Cancelled orders are excluded from revenue by default.
  • AddedOrder money breakdown: orders store taxAmount, taxRate, shippingAmount, discountAmount, pricesIncludeTax and subtotal; tenants set a default tax rate and whether prices include tax (Settings → Company). Invoices, packing slips and the order page use the order's own breakdown, then the tenant default — the hardcoded 20 % VAT is gone. Cdiscount and Meta order amounts are imported correctly (gross / decimals).
  • AddedChannel × product matrix: GET /channels/products/matrix — every product against every channel with active/synced state, bulk activate per column, and one-click access from the channel page.
  • AddedCompany profile (Settings → Company): legal name, address, SIRET, VAT number, contact — printed on invoices and packing slips.
  • AddedCustomer detail page (/customers/{id}) with orders, addresses, segments, "customer since" from the first order; customers list gains real server-side filters (segment, channel, spend, dates), bulk actions and CSV export.
  • AddedOrders: amount-range filter (totalAmountGte / totalAmountLte on list, summary and export), bulk status change, bulk delete, printable packing slips with line images, phone and ship-from warehouse; CSV export cells are protected against spreadsheet formula injection.
  • AddedInventory: server-side filters and summaries, warehouse detail page with movements, transfer dialog with per-warehouse stock and direction swap, low-stock alerts open a replenishing transfer; Adjust / Transfer bodies validated against the DTOs (previously 400 in production).
  • ImprovedRoles are tenant-scoped: custom roles, counts and edits no longer cross companies; system roles are read-only; role codes are unique per tenant.
  • ImprovedNotifications: the bell and the account panel show the user's real inbox (alerts, sync failures, catalogue quality) and clicking one opens what it is about; notification e-mails target roles by permission.
  • ImprovedChannels page gains a sortable list view next to the grid; sidebar highlights only the most specific entry.
  • FixedNon-French tenants: currency and locale were hardcoded in several places (catalogue, dashboard, exports) — they now follow the tenant's settings. The Media Library route was shadowed by the media file proxy in nginx.

v2.9.0

minor

August 3, 2026

  • AddedOnboarding v2: a 7-screen wizard (profile, catalogue, first products, channel, stock, team, done) wired to real endpoints; progress is stored server-side so it resumes on any device.
  • Added.xlsx product import next to CSV, with a raised upload size limit.
  • AddedCatalogue-quality scanner: a periodic job raises notifications for products missing images, descriptions or translations, honouring each tenant's notification preferences.
  • FixedSecurity: the platform_owner role can no longer be assigned through invitations or user updates; findings from an adversarial safety audit closed (input validation, error leakage).
  • FixedProduct media import used an unreachable presigned URL; deploy scripts no longer copy secrets to the server.

v2.8.0

minor

June 26, 2026

  • AddedDiscount codes module (Marketing): central CRUD for percentage / fixed / free-shipping codes with conditions, validity window, usage limits and per-channel sync to Shopify, WooCommerce and PrestaShop native coupons. New permission family discounts:*.
  • AddedMarketing home with KPIs and tools; products list gains a STOCK column with a per-variant / per-warehouse drawer; bulk delete for orders and customers.
  • ChangedDashboard rebuilt on a single GET /analytics/dashboard?period= call (server-side aggregates: revenue, orders, AOV, new customers, to-ship, out-of-stock, returns, catalogue health, per-channel breakdown).
  • FixedMedia uploads: storage moved to self-hosted MinIO (the previous object-storage key had no write rights) and the upload body limit raised to 50 MB. GET media URLs are stable and public.
  • FixedVariant stock saved from the main button; full stock delete; variant create and bulk import return clean 400s instead of 500s; DOM crashes caused by browser auto-translation are guarded.

v2.7.0

minor

June 13, 2026

  • FixedOrder line items now resolve by VARIANT SKU. POST /orders matches each line against variant SKUs first (then product SKUs), so productId, variantId and a display name are populated instead of coming back null. GET /products?search now also matches variant SKUs, returning the parent product.
  • AddedOrder line items accept and return per-line `description` (string) and `metadata` (object) — use them to carry made-to-measure / manufacturing detail per line. GET /orders/{id} also returns each line's resolved name, productName (i18n), variantSku and variantAxisValues (axis label → option label).
  • FixedOrder-level `notes` now round-trips: it was accepted on create but silently dropped — it is now stored and returned in the order response (and shown in the dashboard).
  • AddedDELETE /api/v1/orders/{id} — permanently delete a local Kataio order (line items + returns cascade). Local only; does not cancel the order on the source channel. Requires the write:orders scope.
  • AddedShipping tracking on orders. PATCH /orders/{id}/status accepts optional trackingCarrier, trackingNumber and trackingUrl (typically with status=SHIPPED). They are stored on the order, returned in the order response, and included in the order.shipped + order.status_changed webhook payloads.
  • AddedProducts gain `additionalDescriptions` — an array of { id, label, contentI18n } named description blocks (e.g. a technical description). Each block has a stable `id` so you can fetch a specific block by id. Accepted on PATCH /products/{id} and returned by GET /products/{id}.

v2.6.0

minor

June 1, 2026

  • AddedStock per variant — PATCH /inventory/{productId}/stock now accepts an optional `variantId` in the body to target a specific variant of a configurable product (the variant must belong to the URL :productId — otherwise 400). Omit `variantId` to keep the legacy product-level behaviour. StockMovement and StockLevel rows are written with the variantId so movement history and per-variant counts are tracked independently.
  • AddedNew shortcut endpoint: GET /inventory/variants/{variantId}/stock — returns the variant's identifier, axisValues, and per-warehouse physical/reserved/available counts in a single call without needing to know the parent product id.
  • AddedNew shortcut endpoint: PATCH /inventory/variants/{variantId}/stock — variant-scoped adjustment with the same body shape as PATCH /inventory/:productId/stock (minus `variantId`, which is taken from the URL).
  • ImprovedGET /inventory/{productId}/stock response now includes a `variants[]` array alongside the existing product-level `warehouses[]`. Each variant entry carries variantId, variantSku, axisValues, totalPhysical/Reserved/Available plus its own per-warehouse breakdown. Empty array on simple products. `warehouses[]` continues to expose only product-level rows (variantId=null) so existing integrations are unchanged.

v2.5.0

minor

June 1, 2026

  • FixedPOST /billing/checkout and POST /billing/portal now accept their documented bodies. The inline DTOs in BillingController carried only Swagger decorators, so the global `forbidNonWhitelisted` ValidationPipe was silently rejecting every field with "property planCode/successUrl/cancelUrl/returnUrl should not exist" (400). Added class-validator decorators (@IsString / @IsBoolean / @IsOptional). The upgrade and Stripe portal flows work end-to-end again.
  • FixedPOST /products/{id}/convert-to-configurable is now idempotent on retry. The cartesian variant-generation loop no longer 500s on `PrismaClientKnownRequestError: Unique constraint failed on (tenant_id, sku)` when a variant with the generated SKU already exists (re-conversion, or a manual variant left over from an earlier attempt). Pre-existing SKUs are skipped; sortOrder continues from the existing max+1.
  • FixedPOST /media/upload returns 400 "No file received" when the `file` multipart part is missing or sent under the wrong field name. Previously the service crashed reading `file.originalname` and returned 500.
  • ImprovedPOST /developer/api-keys and PATCH /developer/api-keys/{id} validate scopes against AVAILABLE_SCOPES via @IsIn. A typo like `write:product` (singular) now returns 400 with the full list of valid scopes; previously it was accepted and silently granted nothing. `write:attributes` is now also offered in the API-key creation UI.
  • ImprovedDELETE /attributes/{id} returns an actionable French message when blocked: «{name}» est utilisé comme axe de variante sur N produit(s). Retirez-le des axes de variation … (onglet Variantes → Gérer les axes) avant de pouvoir le supprimer. The 409 status and behavior are unchanged; only the message text moved from English to actionable French.
  • ImprovedObservability — every client-visible 4xx/5xx (except routine 401 refreshes) is captured to Sentry from the central web `api()` client with endpoint, method, status, response body and page URL tags. Backend captures partner-key 4xx (auth_type=api_key tag) in addition to the 5xx and 422 cases it already covered. Media upload + CSV import fetch paths are wired through the same capture.

v2.4.0

minor

May 14, 2026

  • AddedNew GET /developer/webhooks/events endpoint returns the full catalog of every emittable webhook event name (19 events). Use these strings verbatim when creating or updating webhook subscriptions instead of relying on the dashboard UI list.
  • AddedGranular order webhook events: order.shipped, order.delivered, order.cancelled, order.returned. Fired alongside the generic order.status_changed when the transition matches, so integrations can subscribe to a single firehose OR only specific lifecycle steps. Payload includes previousStatus + status + currency + totalAmount.
  • Fixedorder.status_changed is now actually emitted on every status transition. It was listed in the webhook catalog since v2.3 but never wired into the OrdersService — manual status changes via the dashboard and connector pulls now both trigger it. Payload includes previousStatus, status, customerId, customerEmail, currency, totalAmount, updatedAt.
  • AddedPOST + PATCH /customers/{id}/addresses now accept `postalCode` as an alias for `zip`. Either field satisfies validation; the service normalizes either input to `zip` for storage; address responses emit BOTH `zip` and `postalCode` keys with the same value. Resolves the divergence with the POST /orders shippingAddress shape and the camelCase convention used by Stripe/Shopify integrations.
  • AddedAddress DTO gains `company` (≤200 chars, address-level B2B name independent of Customer.company) and `type` (one of "shipping" | "billing" | "both"). Both optional — omit `type` for an address usable for either purpose.
  • ImprovedGET /customers/{id} now lazily derives the `addresses[]` array from the customer's unique order shippingAddresses when the explicit address book is empty. Deduplicated by line1|city|zip; each derived entry carries `_derived: true` so integrations can distinguish from explicit POSTed entries. Lets you use this endpoint as the canonical address book even before seeding it.
  • AddedGET /orders and GET /customers/{id}/orders accept `?status=PENDING,PROCESSING,SHIPPED` (multi-value, comma-separated) using Prisma `{ in: [...] }`. Takes precedence over the single-value `filter[status]` when both are sent.
  • AddedGET /orders accepts bracket-style date range aliases: `?createdAt[gte]=...&createdAt[lte]=...`. The flat `?createdAtGte=` / `?createdAtLte=` aliases still work for backwards compat.
  • AddedGET /orders accepts compact sort syntax `?sort=createdAt:desc`. The split `?sort=createdAt&order=desc` syntax still works.
  • AddedPUT /customers/{id} added as an explicit alias of PATCH /customers/{id}. Same handler, same partial-update DTO — for integrations that default to PUT and don't want to special-case Kataio.
  • AddedNew nullable `bundleSpecialPrice` column on products (migration 20260513_bundle_special_price). UpdateProductDto accepts `bundleSpecialPrice` (decimal, ≥0). PATCH a value to override the auto-summed component total on bundle pricing; PATCH null to clear and fall back to the auto-sum.

v2.3.0

minor

May 10, 2026

  • AddedPATCH /customers/{id} now accepts `siret` (14-digit FR business ID) and `vatIntracomNumber` (≤20 chars, EU intra-community VAT). Previously these B2B fields could only be set at create time — existing customers can now be enriched after the fact without going through the merge flow.
  • AddedNew address subresource on customers: POST /customers/{id}/addresses (add), PATCH /customers/{id}/addresses/{addressId} (update), DELETE /customers/{id}/addresses/{addressId} (remove). Multiple shipping/billing addresses per customer are now first-class — useful for B2B accounts with multiple delivery sites.
  • ChangedEmail validation on /customers and /orders now accepts addresses with UTF-8 local parts and the `+tag` convention (e.g. `marie+ikadran@example.com`, `m.dupont@société.fr`). Previously these were rejected as malformed.
  • AddedGET /orders accepts four new optional query filters: `customerId` (UUID — canonical link), `customerEmail` (string, case-insensitive), `createdAtGte` and `createdAtLte` (ISO 8601). Combine with the existing `status` / `channelId` / `search` filters. Mirrors the same shape used by /customers/{id}/orders.
  • AddedPOST /orders accepts `customerId` (UUID). When supplied, it takes precedence over `customerEmail` and links the new order to that canonical customer record directly — useful when you already have the customer id from a prior /customers create call.
  • AddedPOST /orders line items accept an optional `name` field — useful when the SKU resolves to a variant whose display name you want to override (e.g. a localized name from your storefront). Persisted in the new `order_items.name` column (migration 20260510_order_item_name) and round-tripped on every read.
  • AddedGET /customers/{id}/orders accepts: `page`, `limit`, `status` (PENDING|PROCESSING|SHIPPED|DELIVERED|CANCELLED|REFUNDED), `createdAtGte`, `createdAtLte` (ISO 8601). Returns the same paginated envelope as GET /orders.
  • AddedNew webhook events emitted: `customer.created` (fired on POST /customers and on auto-upsert from POST /orders) and `customer.updated` (fired on PATCH /customers/{id} and on address subresource mutations). Subscribe in the dashboard under Integrations → Webhooks. Payload shape: `{event, data: {customer}, timestamp}` — same envelope as the other 13 events.
  • ImprovedLinked products: any product type (not only BUNDLE) can now have linked items. The new `linkType` enum on bundle items distinguishes `REQUIRED` (auto-added on add-to-cart) from `OPTIONAL` (suggested upsell, default). Migration 20260510_bundle_link_type. Existing rows backfill as OPTIONAL.
  • FixedGET /products/{id} and PATCH /products/{id} no longer leak phantom custom attributes — keys that exist on the product's JSONB blob but no longer correspond to an active Attribute definition are stripped from the response and rejected on write. The frontend's TEXT-shim fallback is gone — attribute UIs render the real attribute type or skip the row.
  • FixedCSV template download (GET /products/template) now returns `Content-Type: text/csv; charset=utf-8` with a UTF-8 BOM prepended and two example rows. Excel and Numbers open it with correct accents on first try.
  • ImprovedSentry observability upgrade: per-request user + tenant tagging on every API span; worker error capture wired across all 8 BullMQ queues (sync, webhooks, billing, exports, imports, media, search-index, notifications); browser session replay (10% baseline, 100% on error); browser profiling at 10%; console capture on both client and server; trace propagation client ↔ API. Errors now surface with full who/what/where context within seconds.

v2.2.0

minor

April 28, 2026

  • AddedPOST /orders accepts four new optional fields: `externalReference` (your own ID — surfaced back as a field on GET /orders, useful for reconciling with your ERP), `initialStatus` (one of PENDING|PROCESSING|SHIPPED|DELIVERED|CANCELLED, default PENDING), `billingAddress` (full address object, falls back to shippingAddress when omitted), and `paymentMetadata` (free-form JSON: payment provider ID, transaction reference, gateway response).
  • AddedPOST /customers accepts `siret` (14-digit French business identifier) and `vatIntracomNumber` (EU VAT) for B2B integrations.
  • AddedIdempotency support on POST /orders and POST /customers: send `Idempotency-Key: <uuid>` and a duplicate request within 24h returns the original response instead of creating a second record. Stripe-style — safe to retry on network errors.
  • ChangedProduct publish status is now preserved on inbound sync from all 16 connectors (Shopify, WooCommerce, PrestaShop, Magento, Amazon, eBay, Etsy, Bol.com, TikTok Shop, Allegro, Mercado Libre, Otto, Walmart, Meta, Google Merchant, Onbuy). Previously every pulled product was forced to DRAFT regardless of source state. Existing draft products synced before this fix can be republished via POST /products/{id}/publish or bulk via the admin UI.
  • FixedWooCommerce connector: variation prices and stock are now pushed in a follow-up POST per variation after the parent product create (WC v3 silently ignores variation arrays sent in the parent body). Out-of-stock products correctly write `stock_status: "outofstock"` instead of leaving the field unset.
  • FixedWooCommerce inbound webhook signatures now verify against the raw request body, not the JSON-parsed body. The previous behaviour rejected legitimate webhooks whenever WC included whitespace or key ordering different from our parser.
  • ChangedOutbound webhook contract clarified: payloads include the header `X-Webhook-ID: <uuid>` which is the recommended dedup key (not any `id` field in the body). Body shape is `{event, data, timestamp}` (ISO 8601 timestamp). HMAC payload remains `${timestamp}.${rawBody}` with `X-Webhook-Signature: t=<unix>,v1=<hex>`. Delivery timeout is 10 seconds; failures retry with exponential backoff (5min, 30min, 2h, 6h, 24h) up to 5 attempts.
  • ChangedWebhook signing scheme: each endpoint has its own `whsec_` secret returned once at creation time. There is no shared platform-wide signing secret — every endpoint signs with its own key.
  • ImprovedDatabase indexes added on hot tables (orders.tenant_id+created_at, products.tenant_id+status, channel_products.channel_id+sync_status) — built CONCURRENTLY to avoid downtime. Catalog read latency on tenants with 100k+ products dropped 60-80%.

v2.1.0

minor

April 20, 2026

  • FixedPOST /products returned 500 on every API-key-authenticated call due to a FK violation when req.user.sub (the API key ID) was written to products.created_by. Now null-safe across products, inventory, sync jobs, media uploads, and product history services.
  • FixedPOST /inventory/transfer, POST /inventory/adjust, and bulk import hit the same FK on stock_movements.user_id — all four code paths now resolve the userId through users before writing.
  • AddedGET /products/{id}/variants/axes — list variation axes assigned to a configurable product with attribute details + options.
  • AddedPUT /products/{id}/variants/axes — replace the full set of axes. Body: `{axes:[{attributeId, sortOrder?}]}`. Validates CONFIGURABLE type + SELECT/MULTISELECT attributes + no duplicates. Atomic via transaction.
  • AddedPOST /products/{id}/variants/generate — auto-create variants as the cartesian product of every axis's option values. Body: `{skuPrefix?, skipExisting?}`. Returns `{generated, skipped, total, created[], skippedDetails[]}`. Capped at 500 variants per call; idempotent by default.
  • AddedNew API scopes: `read:attributes`, `write:attributes`, `read:webhooks`, `write:webhooks`, `read:users`, `read:analytics`.
  • AddedBearer token auth alias: `Authorization: Bearer pk_live_...` now works alongside `X-API-Key: pk_live_...` for clients that only support the OAuth-style header.
  • AddedAnalytics endpoints: GET /analytics/kpis, /analytics/revenue?from&to, /analytics/completeness (distribution + worst offenders).
  • AddedIncremental sync: GET /sdk/categories/changes?since=ISO, GET /sdk/inventory/changes?since=ISO (complementing existing /sdk/products/changes).
  • AddedRMA / Returns: POST /orders/{id}/returns (with optional restock) and GET /orders/{id}/returns.
  • AddedProduct publish shortcuts: POST /products/{id}/publish (sets PUBLISHED, enforces 75% completeness) and POST /products/{id}/unpublish.
  • AddedPOST /inventory/transfers — plural alias for POST /inventory/transfer.
  • ChangedWebhook create response now returns `signingSecret` (plaintext, once) instead of `secret` — aligns with the documented field name. List / detail responses continue to mask the secret.
  • ChangedWebhook event catalogue finalized to 15 events: product.created/updated/deleted, order.received, order.status_changed, stock.updated, stock.low, channel.sync_completed, channel.sync_failed, category.created/updated/deleted, customer.created/updated/merged. All are underscore-form (no dots in the action segment).
  • ChangedWebhook HMAC signature header now documented as `X-Webhook-Signature: t=<unix>,v1=<hex>` with signed payload `${timestamp}.${body}` (HMAC-SHA256).
  • FixedWebhook edit dialog in the admin UI opened empty when switching from create to edit mode — useEffect([open, editWebhook]) now re-syncs form state.
  • FixedPencil edit button wired into the Integrations > Webhooks row (was imported but unused).
  • AddedCustom Channels API: GET /developer/custom-channels (list), POST /developer/custom-channels (create channel + scoped API key in one call, rawKey returned once), DELETE /developer/custom-channels/{id} (revokes keys + removes channel). Gated by channels:read / channels:create / channels:delete scopes.
  • FixedBilling endpoints (/billing/invoices, /billing/status, /billing/entitlements, /billing/checkout, /billing/portal, /billing/change-plan, /billing/cancel, /billing/reactivate) returned 400 "No tenant associated with this user" under API-key auth because the tenant was being resolved via a User lookup keyed on req.user.sub (which is the apiKey.id for API-key principals). Now reads tenantId directly from the JWT payload for both JWT and API-key auth.

v2.0.0

major

April 4, 2026

  • AddedEtsy connector — OAuth 2.0 + PKCE, listings, receipts, taxonomy mapping
  • AddedWalmart connector — Feed-based sync, 15-min token refresh, inventory & pricing
  • AddedTikTok Shop connector — HMAC-SHA256 signing, products, orders, inventory
  • AddedMercado Libre connector — Multi-region (5 countries), OAuth, currency auto-detection
  • AddedBol.com connector — Offers API, EAN matching, NL+BE market
  • AddedAllegro connector — Product-offers model, PLN currency, Poland market
  • AddedZalando connector — zDirect Partner API, fashion-specific attributes
  • AddedOtto connector — 2-step product validation, Germany market
  • AddedMagento 2 connector — REST API v1, configurable products, integration token auth
  • AddedMeta/Facebook Shops connector — Batch API (5K items/call), Instagram Shopping
  • AddedGoogle Analytics (GA4) integration — automated dashboard with sessions, users, traffic sources
  • AddedGoogle Search Console integration — clicks, impressions, CTR, position tracking
  • AddedGoogle Ads integration — spend, conversions, ROAS, campaign performance
  • AddedApp Store page — connect Google services with single OAuth consent
  • AddedStripe billing — checkout sessions, customer portal, plan changes with proration
  • AddedInvoice history & PDF downloads via Stripe
  • AddedFeature gating by plan — locked overlays, usage limits, upgrade prompts
  • AddedTrial countdown banner with automatic upgrade prompts
  • AddedEntitlement API — GET /api/v1/billing/entitlements for plan feature checks
  • ImprovedPlatform count: 6 → 16 marketplace connectors (+ Website + Custom = 18 total)
  • ImprovedShared platform config — single source of truth for all 18 platform definitions
  • ImprovedDeveloper portal updated to v2.0 with new platform logos and highlights
  • ImprovedLanding page reworked — 8 feature cards, grouped platform logos, Google & pricing sections
  • ImprovedDocumentation expanded — 10 new connector setup guides (FR+EN bilingual)

v1.1.0

minor

March 31, 2026

  • AddedBulk Product Import endpoint — import up to 1,000 products with variants, images, stock, and categories in a single API call
  • AddedBulk Category Import endpoint — import entire category trees with automatic parent-child resolution
  • AddedBulk Inventory Update endpoint — update stock levels for up to 5,000 SKUs per request
  • AddedBulk Price Update endpoint — update prices for up to 5,000 SKUs per request
  • AddedIncremental Sync endpoint — fetch only products changed since a given timestamp
  • AddedSync Status endpoint — monitor active sync jobs and last completed sync per channel
  • AddedPush Inventory — sync stock levels from PIM to any connected channel
  • AddedPush Prices — sync pricing data from PIM to any connected channel
  • AddedPush Images — sync product images from PIM to any connected channel
  • AddedPush Categories — create/update categories on CMS platforms (Shopify, WooCommerce, PrestaShop)
  • Added5-step sync orchestration: categories → attributes → products → orders → carriers with real-time progress
  • AddedCancel/stop sync — gracefully cancel running sync jobs while preserving already-processed data
  • AddedTrigger source tracking — every sync records its source (manual, scheduled, webhook, API, system)
  • ImprovedStaged batch sync with real-time progress reporting for all channel sync operations

v1.0.0

major

March 2026

  • AddedInitial public API release
  • AddedREST endpoints for Products, Categories, Orders, Inventory, and Channels
  • AddedAPI Key authentication with scoped permissions (12 scopes)
  • AddedJWT Bearer token authentication with refresh token flow
  • AddedWebhook support with HMAC-SHA256 signature verification (15 event types)
  • AddedRate limiting per plan (Starter: 100/min, Pro: 500/min, Enterprise: 2000/min)
  • AddedPagination, filtering, and sorting on all list endpoints
  • AddedMulti-language support via Accept-Language header
  • AddedOpenAPI 3.0 specification with interactive Scalar documentation
  • Added6 marketplace connectors: Shopify, Amazon, WooCommerce, PrestaShop, eBay, Cdiscount
  • AddedBulk product import/export via CSV and JSON
  • AddedReal-time inventory tracking across multiple warehouses

Stay updated

Subscribe to API changelog notifications from your dashboard under Settings → Notifications.

Contact us for enterprise API updates