# GroceryAI API changelog · GroceryAI

> Every change to the public contract of the GroceryAI API (/v1) and of its events, day by day since 7 September 2026: compatible, breaking or documentation only, with its source.

- HTML: https://groceryai.ai/developers/changelog · Back to the API reference: https://groceryai.ai/developers

## GroceryAI API · changelog

Every change to the public contract of the API (`/v1`) and of the events it sends, day by day since 7 September 2026.
The rules on what is compatible and on deprecation are in the guide, section 10 («Versions and deprecation»).

- **Compatible**: an integration written against the previous contract keeps working without changes (a new operation,
  a new optional field, a new response field, a new error code, a new event type). Ignore what you do not know.
- **Breaking**: an integration written correctly against the previous contract may stop working, or behave
  differently.
- **Documentation**: the document describes better what the API already did; no behaviour changed.

**Before 1 November 2026 there was no versioning policy**, and some breaking changes were made inside `/v1` while the
API had no integrator in production: they are listed below as **Breaking**, so that nothing is hidden. In the platform's
own register (`core.api_audit`, read on 1 November 2026) the only `live` calls ever made are 17 calls of 7 September 2026,
from three keys of a test account revoked the same day; no `live` key is active and no webhook endpoint is registered.
From 1 November 2026 a breaking change no longer happens inside `/v1`: it comes with a new path version or through a
deprecation with at least 12 months of notice (`Deprecation` and `Sunset` headers on every response).

The date is the day the change was written into the published specification (`docs/openapi.json`) or, for a change of
behaviour, into the code; the release that answers you is in the `x-core-version` header, and «live from» names the
first release that carried it where the platform's records say so. Each entry cites its source in the history of the
platform (commit). This file is checked against the history of the specification by
`tests/api/registro-contro-storia.mjs`: every operation and event type that ever appeared in the specification has its
entry on the day it appeared.

### 2026-11-01

- **Compatible** · new test route `GET /v1/_deprecation_probe` (`gai_test_…` keys only; `404 ROTTA_SCONOSCIUTA` with a
  live key): it carries the `Deprecation` (RFC 9745), `Sunset` (RFC 8594) and `Link: rel="deprecation"` headers of a
  deprecated operation, so you can test the code that reads them. It is deprecated by construction and is never retired.
  From today a deprecated operation carries `deprecated: true` and `x-deprecation` in the specification and the three
  headers on every response once the route is known (not on a `401`). No real operation is deprecated. Source: corsia
  VER-1, `e7238d40`.
- **Compatible** · `GET /v1/products`: `next_cursor` is present only when another page really exists (on a last page
  that happens to be full it is now `null`; before, it was set on every full page and the next call came back empty).
  Following `next_cursor` until `null` returns the same products. Live from `core-api` v23. Source: `96118f03`,
  `c4fcfa0d` (SANDBOX-2).

### 2026-10-31

- **Breaking** · webhooks follow **Standard Webhooks 1.0.0**: the headers `webhook-id` (the event id, the same on every
  retry), `webhook-timestamp` (Unix seconds of the attempt) and `webhook-signature: v1,<base64>` over
  `<id>.<timestamp>.<body>` (two signatures during a secret rotation) replace the five `x-gai-*` headers; the event type
  and the attempt stay in the body (`type`, `attempt`). Verify with the official `standardwebhooks` library. No endpoint
  was registered when the format changed. Live from `core-events` v20. Source: `525691396`, `7cc675668`, merged in
  `4b0a71225` (HOOK-STD-1).
- **Breaking** · the signing secret is `whsec_<base64 of 32 bytes>` and the HMAC key is the decoded bytes (before:
  `whsec_` and 64 hexadecimal characters, used whole). Source: `525691396` (HOOK-STD-1).
- **Breaking** · how your receiver's answer is read: `410 Gone` **disables the endpoint** (no more deliveries until it is
  created again); redirects are not followed; `Retry-After` lengthens the wait (at most 24 hours); every other 4xx/5xx is
  retried. Source: `525691396` (HOOK-STD-1).
- **Compatible** · longer retries and more time to answer: ten attempts over about three days (immediately, then 1 min,
  5 min, 30 min, 2 h, 5 h, 10 h, 14 h, 20 h, 24 h, with ±10 % jitter) instead of six in about 8.5 hours; 15 seconds to
  answer instead of 10. Source: `525691396` (HOOK-STD-1).
- **Compatible** · every event body carries the new field `timestamp` (equal to `occurred_at`). Source: `7cc675668`
  (HOOK-STD-1).
- **Compatible** · secret rotation from the portal, with two valid signatures for an overlap of up to 72 hours (a portal
  route, not `/v1`). Source: `b40a428eb` (HOOK-STD-1).
- **Compatible** · new operations `GET /v1/errors` and `GET /v1/errors/{code}`: the catalog of error codes, readable
  without a key; every error response carries the new field `error.doc_url`, the page of its code. Live from `core-api`
  v21. Source: `f85bb86f`, merged in `2cfb89f3` (DOCS-AGENTI-1).
- **Compatible** · `GET /v1/openapi.json` is readable **without a key**, with `ETag`, `Cache-Control: public,
  max-age=300`, `304` on `If-None-Match` and `Access-Control-Allow-Origin: *`. Live from `core-api` v21. Source:
  `f85bb86f` (DOCS-AGENTI-1).
- **Documentation** · the specification is published in English, with a tested request and response example for every
  operation and event; the `limit` bounds (1 to 200) already enforced by the code are written in the schemas;
  `AGENTS.md`, the integration prompt and the quickstart. Source: `f85bb86f` (DOCS-AGENTI-1).
- **Compatible** · `PUT /v1/customers` also accepts the field names of the customer model (`customer_ref` as an
  alternative to `external_ref`, `full_name`, `street`, `house_number`, `city`, `postal_code`, `floor`, `intercom`,
  `delivery_notes`, `language`, `exemption`); a field of neither list is not written and the response says so in
  `warnings` (`CAMPO_SCONOSCIUTO`). Live from `core-api` v19. Source: `81f58e6d`, merged in `db852976` (VALID-1).
- **Breaking** · `PUT /v1/customers`: the phone must be international E.164 (`TELEFONO_NON_E164`; before, any text was
  accepted) and is stored as `+` and digits; new rejections `CAMPO_IN_DUE_POSTI`, `VALORE_NON_AMMESSO`,
  `TESTO_TROPPO_LUNGO`. Live from `core-api` v19. Source: `81f58e6d` (VALID-1).

### 2026-10-30

- **Breaking** · `PUT /v1/products` became a **merge**: on a product that already exists only the fields the item names
  are updated, `attributes` are merged and a key set to `null` is removed (before, an item with only the price cleared
  the other fields). Live from `core-api` v16. Source: `c3be5b5e2`, `5f57be07` (DATA-C2).
- **Breaking** · the catalog contract fields (`min_order_qty`, `min_order_unit`, `sold_by_weight`,
  `avg_piece_weight_kg`, `items_per_pack`, `pack_only`, `stock_managed`, `unit_price`, `gross_weight_kg`, `tare_kg`,
  `list_price`, `subcategory`, `vat_rate`, `diet_tags`, the `offer_*` fields) are typed fields of the product and are
  validated **also when they arrive inside `attributes`**, where before they entered unchecked; new item codes
  `NUMERO_NON_VALIDO`, `UNITA_NON_AMMESSA`, `DATA_NON_VALIDA`, `BOOLEANO_NON_VALIDO`, `VALORE_NON_AMMESSO`,
  `DATE_INVERTITE`, `CAMPO_IN_DUE_POSTI`. Source: `c3be5b5e2` (DATA-C2).
- **Compatible** · the batch responses of products, inventory and customers carry the new field `warnings` (the item is
  written, but something in it will not be used, for example `OFFERTA_NON_SCONTATA`). Source: `5f57be07` (DATA-C2).
- **Breaking** · `PUT /v1/inventory`: an item with `store_code` is rejected (`PUNTO_VENDITA_SCONOSCIUTO`) unless the
  shop has that point of sale; before, `store_code` was ignored and the price applied to the whole shop. Source:
  `c3be5b5e2` (DATA-C2).
- **Compatible** · `PUT /v1/inventory` also accepts the strike-through `list_price`, the offer fields (`offer_price`,
  `offer_start`, `offer_end`, `offer_min_qty`, `offer_label`; `null` removes) and `is_online`. Source: `5f57be07`
  (DATA-C2).
- **Compatible** · new operations for the catalog lifecycle: `DELETE /v1/products/{sku}` (withdraw a product: it is
  switched off, not deleted; sending it again puts it back on sale), `POST /v1/products/snapshots` and
  `POST /v1/products/snapshots/{snapshot_id}/commit` (a full snapshot that switches off what it does not contain, with a
  90 % gate; optional `snapshot_id` in `PUT /v1/products`), `GET /v1/catalog/freshness`, `GET /v1/catalog/feed`,
  `PUT /v1/catalog/feed` and `POST /v1/catalog/feed/run` (a scheduled catalog feed). Live from `core-api` v16; the
  freshness read answered `500` on v16 and works from v17. Source: `5f57be07`, `359226607` (DATA-C2, API-TX1).
- **Compatible** · new operation `PUT /v1/order_history` with the new scope `history:write` (shops that live on
  GroceryAI only). Source: `201de93f`, merged in `50b4b304` (DATA-C3).
- **Compatible** · new operation `POST /v1/chat_identity_tokens` (scope `chat:write`, secret key): opens the site chat
  to a customer signed in on your site. Source: `a1d2e892`, merged in `e36ff354` (WEB-ID-1).
- **Compatible** · new operations `GET /v1/rules`, `PUT /v1/rules`, `POST /v1/rules/preview`, `GET /v1/store_texts` and
  `PUT /v1/store_texts` with the new scopes `rules:read`, `rules:write`, `texts:read`, `texts:write`; their error
  envelope may carry a list `errors` next to `error`. Live from `core-api` v17. Source: `cf3546b0`, merged in `853b5293`
  (DATA-C4).
- **Compatible** · new operations `GET /v1/stores` and `PUT /v1/stores` (points of sale; the `PUT` is the whole list);
  `PUT /v1/inventory` accepts a declared `store_code`; optional `store_code` parameter in `GET /v1/products`. Live from
  `core-api` v18. Source: `8db0588e`, merged in `3eaac8ed` (DATA-C6).

### 2026-10-27

- **Compatible** · new field `order.currency` (ISO 4217) in the orders of shops that live on GroceryAI. Live from
  `core-chat` v66 and `core-events` v16. Source: `b28ae23a` (COMM-4B).

### 2026-10-26

- **Compatible** · in the order event, `order.customer.phone` and `email` are filled when the customer gave them in the
  chat (fields that already existed and could be `null`). Source: `e1c7a56d7` (ORD-6).

### 2026-10-23

- **Breaking** · `Idempotency-Key` is scoped to the shop **and the environment** (before, the same key used first with
  a `test` key and then with a `live` one replayed the test response in live without writing) and **expires after 24
  hours** (before, never). Live from `core-api` v14. Source: `8327216b` (SEC-2).
- **Compatible** · new scope `chat:write` (opening chat sessions from your server with the secret key). Source:
  `8327216b` (SEC-2).

### 2026-10-22

- **Breaking** · the meaning of `delivered_amount` in `POST /v1/orders/{order_id}/status` changed: from «the value of
  the goods, without delivery or extras» to «the total the customer paid for what was delivered, delivery and extras
  included». The route accepts the same body; what the shop must send changed. Source: `05f4c844` (BILL-2).

### 2026-10-21

- **Compatible** · new operation `POST /v1/orders/{order_id}/status`: the shop reports what happened to an order
  (delivered, substituted, not delivered, returned). Live from `core-api` v13. Source: `aaf3773d`, merged in
  `098d60ef` (BILL-1).

### 2026-10-13

- **Documentation** · the specification describes the events sent to your endpoint, `order.confirmed`,
  `support.requested` and `webhook.ping` (section `webhooks`), the shapes of every response, the error codes per status
  and the headers; `info.version` stays `1.0.0`. The guides for integrators are published. Source: `6d4643a5`,
  `0e3b08c69` (PORTAL-D10).
- **Compatible** · a failure between the reservation of an `Idempotency-Key` and the handler no longer leaves the key
  stuck (before: `500`, then `409 RICHIESTA_IN_CORSO` forever; now the retry replays the `500`). Live from `core-api`
  v8. Source: `d6c9e326b` (PORTAL-D10).

### 2026-10-12

- **Compatible** · new operations `GET /v1/support_requests` and `POST /v1/support_requests/{support_id}/ack`: customer
  support requests in pull, with acknowledgement; new event type `support.requested`. Live from `core-api` v7. Source:
  `6d34ddcf` (P232).

### 2026-10-11

- **Compatible** · the order event `order.confirmed` reaches version 2 (`schema_version: 2`): `order.customer` carries
  `channel`, `fullName`, `address`, `city`, `zip`, `phone`, `email`, `notes`, `missing_fields`; no field removed. Source:
  `cafb061b0` (ORD-1), `51547f1a9` (ORD-3).

### 2026-10-07

- **Compatible** · a second kind of key, the publishable `gai_pk_<environment>_…`, without scopes: on `/v1` it opens
  nothing (`403 AMBITO_MANCANTE`); a key whose name and record disagree on its kind gets `401 CHIAVE_INCOERENTE`.
  Source: `c0cb23b9a` (W1).

### 2026-09-14

- **Breaking** · `PUT /v1/products`: a price in a currency other than the shop's is a rejected item (before, any ISO
  4217 code was accepted). Source: `32173ff30` (D4).
- **Compatible** · new header `x-core-version` on every response (the release that answered). Source: `9d1d53bff`
  (P29).

### 2026-09-07

- **Compatible** · first published contract: `GET /v1/openapi.json`, `PUT /v1/products`, `GET /v1/products`,
  `PUT /v1/inventory`, `PUT /v1/customers`, `GET /v1/customers/{external_ref}`, `GET /v1/orders` and
  `POST /v1/orders/{order_id}/ack`; API keys per account with environment (`test`, `live`) and scopes, rate limit with
  `Retry-After`, batches of up to 500 items validated one by one, `Idempotency-Key` on writes, `request_id` on every
  response, one error format. Source: `18453327` (D1).
- **Compatible** · signed webhooks (`x-gai-signature: sha256=<hex>`, HMAC-SHA256 of timestamp and body) with retries.
  Source: `78ce7e5fd` (D2).
- **Breaking, reverted the same day** · `GET /v1/orders` and `POST /v1/orders/{order_id}/ack` answered `409` to `test`
  keys; the same day the `409` became a filter: a `test` key sees the test orders, a `live` key the real ones, and every
  event carries `environment`. Source: `c78c8e11`, `def35b43` (D1, D3).
