Skip to main content
The ecommerce surface is read-only. It exposes what Keebai knows about your catalog, the carts your customers built (including the ones they abandoned), and the sales that closed — everything under the /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.
A cart is not the draft of a sale. They are separate records with no foreign key between them: a cart is what a customer assembled on a storefront, a sale is what your team or the agent closed. Correlate on the customer’s phone or email, or on 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.
Reads are company-wide. Like CRM, these endpoints return records from every project in your company, not just the project your token belongs to.

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 with abandoned_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.
recovery_status is not advanced by this API. It is set by whoever does the following up — the portal, the agent, or an integration writing through the platform. Reading pending does not mean nobody has acted; it means nothing has recorded that they did.If your own outreach chases abandoned carts, you are responsible for keeping your side’s state, because you cannot write this field back.
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 the customer_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. Sending company 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 take limit 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 at limit=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.