/v1/ecommerce prefix, authenticated with a PAT and gated by scope.
Writes stay in the portal and in the integrations. If you need to create a sale or push a product from your own system, that goes through the platform integration, not this API.
Data model
Products
The catalog: price, stock, variants, images. Includes both products created in Keebai and those synced from an integration.
Carts
What a customer put together, whether or not they finished. Abandoned carts carry a recovery status and a link back to checkout.
Sales
Closed transactions with their line items, amount breakdown, and payment state.
Sale events
Three webhooks fire as a sale is created, paid, and cancelled.
customer_id, which both carry when Keebai could resolve the contact.
Scopes
The three are separate because they leak different things. Carts and sales carry customer contact details and money; the catalog carries neither. A pricing widget on your website needs
ecommerce:products:read and nothing else.
Products come from two places
stock is only meaningful when stock_managed is true. On integration products, stock is whatever the last sync brought over — not a live read of the platform.
Abandoned carts
A cart withabandoned_at set and completed_at absent is the recoverable case. recovery_status tracks what has been done about it:
recovery_url is the link that puts the customer back in checkout with the cart intact. Sending it over WhatsApp with a cta_url message is the intended pairing.
A cart becomes abandoned on a timer, not on an explicit action, so abandoned_at trails the customer’s last activity. Do not treat a cart as fresh just because abandoned_at is recent.
Identifiers
All ids are MongoDB ObjectIds — 24 lowercase hex characters. That covers products, carts, sales, and thecustomer_id that carts and sales carry when Keebai could resolve the buyer to a contact.
external_id is different: it is the id the record has in the source platform (Shopify, WooCommerce, Justo), in that platform’s own format. Use it to join back to your storefront; never pass it where Keebai expects an ObjectId.
Tenancy is implicit
Company comes from your token and is never sent. Sendingcompany returns 400.
As the warning above notes, reads span the whole company rather than one project — a token minted for project A lists products, carts, and sales created under project B.
Correlating with the rest of the platform
Ecommerce records are deliberately loosely coupled, which means joining them is your job:
If you need a hard link between a sale and a CRM ticket, emit a CRM event carrying your order id in a ticket-scoped custom field. That writes the id onto the ticket, which is the closest thing to a foreign key the platform offers.
Money is stored in minor units
Every amount —price, unit_price, subtotal, total, and the whole amounts block on a sale — is an integer in the currency’s smallest unit. Read currency alongside it, and never assume two decimals: JPY and CLP have none.
Pagination
List endpoints takelimit and offset and return the same envelope as the rest of the API:
limit defaults to 50 and caps at 200.
The catalog paginates by page internally, so an
offset that is not a multiple of limit is rounded down to the page containing it. Keep offset a multiple of limit — the standard offset += limit loop is always correct.Errors
Branch on
error.code, not on the message text.
Rate limits
These endpoints draw from the shared per-project quota: 1,000 requests a day across the whole public API. That is the binding constraint for anyone mirroring a catalog. A 4,000-product catalog atlimit=200 is 20 requests — fine. The same catalog re-synced every 15 minutes is 1,920 a day — not fine.
Sync on a webhook, not a timer
ecommerce.sale.created / .paid / .cancelled tell you when something changed. Polling the sales list to discover it costs a request every time nothing happened.Page at the ceiling
limit=200 is four times cheaper than the default 50 for the same data.Next steps
Browse the catalog
Start with products — the simplest of the three.
React to sales
Subscribe to
ecommerce.sale.paid and push it into your accounting system.Chase abandoned carts
Filter for
abandoned_at and send recovery_url over WhatsApp.Record it on the CRM
Emit a CRM event so the order shows up on the contact’s timeline and the agent knows about it.