What changed, call by call, between api.kentro.live/v1/store/* and the Store API.
The legacy Store API (https://api.kentro.live/v1/store/*) keeps answering for the storefronts that use it, but it is deprecated: it receives no new fields or endpoints, its reference is no longer published, and it is retired once every storefront has moved. New storefronts use the Store API at https://apis.kentro.live/store/v1/*; existing ones migrate with this page, whose call-by-call table is the record of what each legacy call did.
What changed
Legacy
Store API
Host api.kentro.live, REST paths with GET and POST (GET /v1/store/order/list, POST /v1/store/cart/add)
Host apis.kentro.live, every call POST /store/v1/<family>.<operation> with a JSON body (conventions).
The store was resolved from the request's Origin or Host header, or X-Host-Override on the sandbox; browsers could call it directly
The store is X-Storefront-Host; the caller is the storefront server with an API key (X-API-Key) that must hold storefront:serve. Browsers never call the API.
A Feathers session: a cookie set by POST /v1/store/login, or its access token in Authorization
A buyer access token from POST https://apis.kentro.live/auth/login/customer, sent as X-Storefront-Buyer, refreshed with POST /auth/refresh; guests get a token from session.guest and send X-Storefront-Guest.
No concurrency control on the cart
revision and ownershipVersion on every cart answer; expectedRevision on every mutation; stale is 409 CONFLICT.
Checkout in one call (cart/checkout) with the payment intent created separately
A quote (cart.get { quote: true } with quoteFingerprint), then checkout.start with an idempotency key, then checkout.status and checkout.confirm around the provider.
Query parameter ?idempotency=
idempotencyKey (or requestId) in the body where an operation takes one.
Amounts as integers in minor units, plus totals-beautify for formatted strings
Integers in minor units everywhere; no formatted strings. Format on your side with currency.
One envelope, { code, message, fieldErrors }, with a stable code list and buyer-ready messages.
Addresses and payment profiles as free-form records
Books with version; writes send expectedVersion; the answer is the whole book.
Signup, password reset and login on the store paths
Login, refresh, logout and password reset on apis.kentro.live/auth; sign-up, guest tokens, account confirmation and password change as Store API operations.
What stays the same
The data. Both APIs read and write the same storefront, buyers, carts, orders and invoices. A buyer created through the legacy API signs in through POST /auth/login/customer with the same credentials; orders placed through either API appear in orders.list.
The storefront host. The host the legacy API resolved the store from is the X-Storefront-Host the new one takes.
Buyer approval. Stores that required approval still do; an unapproved buyer answers 403 APPROVAL_REQUIRED instead of an empty catalog.
Call by call
Legacy paths are relative to https://api.kentro.live/v1/store; replacements are Store API operations relative to https://apis.kentro.live/store/v1, or authentication service routes relative to https://apis.kentro.live/auth.
Session
Legacy call
Replacement
POST signup
POST /session.signup, then sign in; POST /account.confirm for the confirmation code
GET login (is the session authenticated)
POST /session.get
POST login
POST /auth/login/customer { email, password, host }; the answer's accessToken is the buyer token
GET logout
POST /auth/logout { refreshToken }
POST password/reset
POST /auth/password-reset/customer/request { host, email }; by text message POST /auth/password-reset/customer/phone/request { host, phone }
POST password/update
POST /auth/password-reset/customer/confirm { host, token, password }; by text message .../phone/confirm { host, phone, code, password }
Catalog
Legacy call
Replacement
GET product/list
POST /catalog.list
GET product/details/{productId}
POST /catalog.get { id, selector: "id" } (or by handle with selector: "handle")
GET product/categories
POST /catalog.categories
GET product/category-details/{categoryId}
No replacement. catalog.categories carries each category's id, name, handle, parentId, image, description and productCount; catalog.list { category, categoryNamespace } lists its products.
GET product/tags
No replacement. Tags the store shows come on each product as tags.
GET product/brands
No replacement. The brand comes on each product as brand.
Cart
Legacy call
Replacement
POST cart/details
POST /cart.get {}
POST cart/items
POST /cart.get {}: lines
POST cart/items-count
POST /cart.get {}: itemCount
POST cart/items-count-unique
POST /cart.get {}: lines.length
POST cart/add
POST /cart.add; several lines at once POST /cart.addMany
POST cart/update
POST /cart.set { lineId, quantity }
POST cart/remove
POST /cart.remove { lineId }
POST cart/coupon-code-apply
POST /cart.update { coupon }
POST cart/coupon-code-remove
POST /cart.update { coupon: "" }
POST cart/referral-code-apply
POST /cart.update { referralCode }
POST cart/referral-code-remove
POST /cart.update { referralCode: "" }
POST cart/totals
POST /cart.get { quote: true }
POST cart/totals-beautify
No replacement. Format the integer amounts with the cart's currency on your side.
POST cart/get-shipping-rates
POST /cart.get { quote: true }: shippingRates
POST cart/set-shipping-rate
POST /cart.update { shippingRateId }
POST cart/get-fulfillment-zones
POST /fulfillment.locations { postalCode, country } or { searchLocation }
POST cart/set-fulfillment-zones
POST /cart.update { fulfillment, locationId, lineLocations }
POST cart/update-shopping-geo-location
POST /cart.update { searchLocation: { lat, lng } }
POST cart/update-shipping
Buyers: POST /addresses.save then POST /cart.update { shipToId }; guests: POST /cart.contact
POST cart/update-billing
Buyers: POST /addresses.save then POST /cart.update { billToId }; guests: POST /cart.contact
POST cart/can-checkout
POST /cart.get { quote: true }: checkoutAllowed and issues
POST cart/create-payment-intent
Folded into POST /checkout.start: its answer carries clientSecret or redirectUrl; Authorize.net card entry is configured by POST /payments.cardConfig
POST cart/checkout
POST /cart.get { quote: true }, then POST /checkout.start, then POST /checkout.status and POST /checkout.confirm
Orders
Legacy call
Replacement
GET order/list
POST /orders.list { page, status, search, from, to }
GET order/details/{orderId}
POST /orders.get { id }; POST /orders.reorder { id } to buy it again
Carriers
Legacy call
Replacement
GET carrier/list-all
POST /carriers.book {}: options
GET carrier/list-personal
POST /carriers.list {} (or carriers.book: entries)
POST carrier/add
POST /carriers.save without id
POST carrier/update
POST /carriers.save { id, expectedVersion, ... }; POST /carriers.default { id, expectedVersion } to make it the default
POST carrier/remove
POST /carriers.remove { id, expectedVersion }
Account
Legacy call
Replacement
GET account/details
POST /account.get {}
POST account/update
POST /account.update { expectedVersion, ... }; the password through POST /account.changePassword
GET account/credits
POST /account.get {}: creditBalance, referralCredit, availableCredit, creditLimit, balanceDue
GET account/address/list
POST /addresses.list {}
POST account/address/add
POST /addresses.save without id (createId for an idempotent retry)
POST account/address/update
POST /addresses.save { id, expectedVersion, ... }
POST account/address/remove
POST /addresses.remove { id, expectedVersion }
GET account/payment/list-profiles
POST /payments.list {}
POST account/payment/add-profile
POST /payments.save (Authorize.net: after POST /payments.cardConfig; Stripe: follow redirectUrl, then POST /payments.confirm); POST /payments.status to poll
POST account/payment/update-profile
POST /payments.save { id, expectedVersion, ... }; POST /payments.default { id, expectedVersion }
POST account/payment/remove-profile
POST /payments.remove { id, expectedVersion }
GET account/price-list
No replacement. Every catalog and cart answer is already priced at the buyer's price list; account.get names it (priceList.name).
Subscriptions
Legacy call
Replacement
GET subscription/list-plans
POST /subscription.list-plans {}
GET subscription/details/{planId}
POST /subscription.plan-details { id }
GET subscription/active
POST /subscription.active {}
POST subscription/update
POST /subscription.update
POST subscription/update-items
POST /subscription.update-items { action, ... }
POST subscription/activate
POST /subscription.activate
POST subscription/cancel
POST /subscription.cancel
POST subscription/subscribe
POST /subscription.subscribe { planId, selectedFrequency }
POST subscription/skip
POST /subscription.skip
POST subscription/pause
POST /subscription.pause
POST subscription/resume
POST /subscription.resume
GET subscription/history
POST /subscription.history {}
Every subscription change takes expectedRevision and requestId (flows).
Extras and applications
Legacy call
Replacement
GET extra/countries
POST /addresses.options {}: countries
GET extra/provinces/{alpha2}
POST /addresses.options { country }: regions
POST extra/address-validation
No replacement. Validate with your own geocoder and send the result as geoLocation on addresses.save; delivery eligibility is answered by fulfillment.locations and the cart's deliveryEligible.
POST signup-application
POST /application.start, application.upload.permit, application.upload.put, application.upload.confirm, POST /application.submit
Behavior the new API refuses on purpose
A cart mutation with a stale expectedRevision: 409 CONFLICT. The legacy API applied the last write.
A checkout without a current quote, or with a quoteFingerprint that no longer matches: 409 CONFLICT. The legacy API re-priced silently at checkout.
A second checkout.start while one is in flight: 409 CHECKOUT_PENDING. Poll checkout.status with the first key.
A write to an address, payment method, carrier account or account profile without its current expectedVersion: 409 CONFLICT.
A product group named instead of its variant: 409 PRODUCT_VARIANT_REQUIRED.
A guest reading account, address, payment, order history or invoice operations: 401 UNAUTHORIZED.
A migration plan that has worked
In Kentro OS create an API key for the storefront server with a permission group holding storefront:serve. Keep the legacy storefront running.
Move the reads first: store.get, catalog.*, session.get, orders.list, orders.get, account.get, addresses.list, payments.list. Reads have no side effects and show the new shapes, prices in minor units included.
Move sign-in to POST /auth/login/customer and keep the two tokens server-side against your session cookie; add the refresh on 401.
Move the cart, sending expectedRevision from the last answer on every mutation and rendering the cart the API answers.
Move checkout: quote, checkout.start with an idempotency key, checkout.status and checkout.confirm. Test a decline and a lost response on the sandbox.
Move account writes, carriers, subscriptions and applications; retire the legacy calls; tell [email protected] the storefront has moved so its legacy traffic can be confirmed at zero.
Questions during a migration go to [email protected] with the message and the request's X-Request-Id response header, when present, of any refusal.