cubobase En castellano

cubobase API

The API for cubobase stores (Professional plan): read and change orders and products from another system, and get instant notifications (webhooks).

New AI agents: every store has its own MCP server at https://app.cubobase.com/mcp. The owner connects their Claude or ChatGPT by tapping «Authorize», no keys needed. How it works

Getting started

The store owner creates a key in their app: Menu → Connections → Another system → Create a key. Keys start with cb_ and are shown only once. Send it with every request:

curl https://app.cubobase.com/api/v1/pedidos?estado=nuevo \
  -H "Authorization: Bearer cb_YOUR_KEY"

Orders (pedidos)

MethodPathWhat it does
GET/api/v1/pedidosList. Filters: estado (status; one or more, comma-separated), pagado (paid: si/no), desde (created since), modificado_desde (modified since, oldest first, for syncing), telefono (phone), q (search, like the app's search box: every word in name, email, address or note, ignoring accents and case; a number also finds that order number or a phone containing it), pagina (page), por_pagina (up to 200). Returns {"pedidos": […], "pagina": 1, "hay_mas": false}.
GET/api/v1/pedidos/{numero}One order, by its number (the one owner and buyer see).
POST/api/v1/pedidosCreates an order. If paid by link (Mercado Pago or Stripe), it comes in link_de_pago.
PATCH/api/v1/pedidos/{numero}Changes the status, whether it's paid, the tracking code or your own data, and corrects the customer, delivery or items.
POST/api/v1/pedidos/{numero}/devolverRefunds the buyer, fully or partially, through Mercado Pago or Stripe.

An order has: numero, estado, pagado/pagado_el, creado, actualizado, origen (tienda, encargado, api), cliente {nombre, telefono, email}, entrega {tipo: retiro (pickup) / envio (delivery) / digital, envio, direccion, seguimiento {agencia, codigo}, en_agencia (waiting at a carrier's branch: {donde, cuando} or null), dac (DAC address in parts, Uruguay: {entrega, departamento, ciudad, barrio, calle, agencia, guia} or null)}, medio_pago, link_de_pago, items [{producto_id, variante_id, codigo (SKU), nombre, variante, precio, cantidad}], subtotal, descuento, envio (shipping cost), total, devuelto (refunded through Mercado Pago from the app), cobrado (what they paid by Mercado Pago or Stripe through the store, i.e. what can be refunded here, or null), a_devolver (what's left over because the total went down after payment: cobrado − devuelto − total, or 0; always up to date, also after a correction in the app; refund it with …/devolver. A cancelled order doesn't count everything paid, since chargebacks and refunds made in Mercado Pago don't go through cubobase: to cancel and refund everything, use "devolver_plata": true), moneda, nota, motivo (cancel/return reason), junto_con (the order it ships with, or null), avisos (the buyer emails already sent, with when: {"recibido": "…", "listo": "…"}; "sin_aviso" means marked as done without sending, after avisar: false; sending one updates actualizado), reclamo (a Mercado Pago dispute: {"tipo": "reclamo"} means on hold, don't ship yet; {"tipo": "contracargo"} means chargeback, the order is now devuelto; null if none), url (the buyer's order page) and datos (your own data).

Statuses: nuevo (new) → preparando (preparing) → listo (ready) → en_camino (shipped) → entregado (delivered); and two final ones: cancelado (with motivo) and devuelto (returned, only after delivered). «Paid» is separate. Moving to entregado marks it paid.

POST /api/v1/pedidos
{"cliente": "Lucy M.", "telefono": "+1 305 555 0101", "email": "lucy@example.com",
 "items": [{"codigo": "DRESS-01", "valores": ["M"], "cantidad": 1}, {"codigo": "CUSTOM", "precio": 800}],
 "entrega": {"tipo": "envio", "envio_id": 3, "direccion": "…"},
 "medio_pago": "stripe", "datos": {"my_id": "A-5521"}, "avisar": true}

PATCH /api/v1/pedidos/128
{"estado": "en_camino", "seguimiento": {"agencia": "UPS", "codigo": "1Z999…"}}

PATCH /api/v1/pedidos/128
{"en_agencia": {"donde": "SALTO", "cuando": "2026-10-27T17:12:51"}}

PATCH /api/v1/pedidos/129
{"estado": "cancelado", "motivo": "Sin stock"}

Waiting at a branch: if your system makes the shipping label, send en_agencia when the carrier leaves the parcel waiting at one of its branches (donde: the branch, up to 80; cuando: optional, without a time zone it's the store's time). The buyer gets the «Your order arrived at the branch, you can pick it up» email, with the branch, the seguimiento carrier and the tracking code. Only for a shipped order that is en_camino (it can go in the same change that sets en_camino; otherwise 409). It's recorded once: sending it again doesn't repeat the email or change the branch. With labels made by cubobase (DAC connected), this happens by itself.

Prices, stock, discounts and shipping are calculated by the store. Each line takes producto_id or codigo, plus variante_id or valores for products with sizes/colors. avisar: false sends no emails, now or later: in a change, that step's buyer email is recorded as "sin_aviso" and never goes out with a later change. Own price: a line with precio costs exactly that, with no discounts (also for «ask for price» products); an order totalling 0 is marked paid, with no payment link. DAC shipping (Uruguay): instead of direccion, "dac": {"departamento", "ciudad", "barrio", "calle"} for home delivery or {"departamento", "agencia"} for pickup at a DAC office, with DAC codes or names; 422 direccion_invalida if it is not in DAC's lists. Same box as another order: "junto_con": 127 (the order that already paid shipping) or "sin_envio": true: shipping isn't charged again, like «Sumarle cosas» in the app (shown in junto_con). Shipping paid on delivery: "envio_al_recibir": true, DAC shipping only: shipping isn't in the total, entrega.paga_el_que_recibe is true and the label is «paid by the recipient»; if shipping was already free, it stays free. Your own data: every order and product has a free datos JSON object; what you send is merged, a key set to null is removed (up to 16,000 characters).

PATCH /api/v1/pedidos/131
{"cliente": "Martin P.", "nota": "Ring the bell",
 "entrega": {"tipo": "envio", "envio_id": 3, "direccion": "…"},
 "items": [{"codigo": "JEAN-40", "cantidad": 2}, {"codigo": "CUSTOM", "precio": 800}]}

POST /api/v1/pedidos/128/devolver
{"monto": 500, "motivo": "Arrived scratched", "clave": "ref-128-1"}

Correcting an order works like «Corregir» in the app; what you don't send stays as it was: cliente, telefono, email, nota (up to 400), entrega (as when creating; direccion up to 200), para_el, para_hora, items. items is the full list of what the order must carry: existing lines change quantity (and price if the line has precio), missing ones are removed, new ones added; stock moves by itself and shipping and total are recalculated. Who pays for shipping, as when creating (next to entrega or inside it): "envio_al_recibir": true (DAC only) leaves shipping out of the total with entrega.paga_el_que_recibe true; "sin_envio": true sets shipping to 0 (the label goes on the store's account); false in either charges it in the total at the store's price. If you send neither, it stays as it was: an order paid on delivery or with free shipping stays that way even if the branch, address or shipping option changes; one that paid shipping in the total is recalculated. So a paid order can move from pickup to delivery, or from a DAC branch to home delivery, without the total going up. Not with retiro, and not both at once (400). If it was paid by bank transfer or cash and the total goes up, it becomes unpaid and the response has falta_cobrar; if paid by Mercado Pago or Stripe and the total goes down, it has a_devolver (also when shipping they already paid is no longer charged). A Mercado Pago or Stripe payment can't go up: 422, and if it's the shipping, the message says so. Cancelled/returned orders can't be corrected (409); shipped or delivered ones, only customer data. Refunds work like «Devolver la plata» in the app (Mercado Pago or Stripe only); without monto, everything left is refunded; the status doesn't change. Send a clave (your idempotency key, up to 60 characters): retrying with the same one never refunds twice (200, "nueva": false; a new refund is 201, "nueva": true). 422 no_se_puede_devolver with the reason if it can't be done. "devolver_plata": true together with estado cancelado/devuelto refunds the rest first.

Products (productos)

GET/api/v1/productosList (hidden ones too). Filters: q (searches name, SKU and short text, accent-insensitive), codigo, id_antes, visible, pagina, por_pagina.
GET/api/v1/productos/{id}One product.
POST/api/v1/productosCreate (nombre and precio required).
PATCH/api/v1/productos/{id}Change whatever you send.
DELETE/api/v1/productos/{id}Remove it from the store.

Fields: nombre (name), codigo (SKU), precio, precio_antes (compare-at price), detalle (short text), descripcion, stock (number, or null if not tracked), visible, categorias (names; the first is the main one), fotos_url (all photos in order, the first is the main one; we download them and they replace the old ones; if one fails, nothing changes and a new product isn't created. A photo on your disk instead of the web goes inside the request: "data:image/jpeg;base64,…" instead of the URL, JPG, PNG or WebP up to 15 MB. The name of a photo the store already has, like "12-a8f3c9e1b2c4d6f8.jpg", also works: a copy is saved), foto_url (only the main photo), id_antes (the product id in your previous store, used by the Meta catalog and pixel so ads keep recognizing it), opciones ([{"nombre": "Size", "valores": ["S","M"]}]), variantes (stock, price or SKU per combination, by id or valores), fotos_valor (which color, or other option value, each photo shows: [{"foto": 1, "valor": "Navy"}], with the photo number, 1 = the main one, or its URL; it replaces the previous ones, [] clears them; GET returns it with each photo's url) and datos.

Text limits, the same as in the app: nombre 120 characters, detalle 300 (one line), descripcion 3,000 (line breaks kept), codigo 60, each category 60. If one doesn't fit you get 400 dato_invalido naming it and its length, and nothing changes: text is never saved cut short.

Store, shipping and categories

GET /api/v1/tienda (name, country, currency, open or not, pickup, payment methods, shipping options), GET /api/v1/envios, GET /api/v1/categorias.

Webhooks

The owner sets your https:// address in Connections → Another system → Instant notifications. Events: pedido.nuevo (new order), pedido.pagado (paid), pedido.estado (status changed; includes antes), pedido.envio (tracking code set), pedido.corregido (customer, delivery or items corrected), pedido.plata_devuelta (refund made), pedido.aviso (an email or push went out to the buyer; includes avisos, which ones), producto.cambiado (price, stock, visibility, name, SKU changed or deleted), prueba (test).

POST https://your-system.com/webhooks
X-Cubobase-Evento: pedido.pagado
X-Cubobase-Id: 8123
X-Cubobase-Firma: sha256=…

{"id": 8123, "evento": "pedido.pagado", "creado": "…", "tienda": "malva", "pedido": {…}}

Reply 2xx within 10 seconds; otherwise we retry for 24 hours. It may arrive twice: use id. The signature is HMAC-SHA256 of the raw body with the secret shown in Connections:

esperada = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
hmac.compare_digest(esperada, request.headers["X-Cubobase-Firma"])

If your system reads often

A system with several parts polling all the time quickly goes over 120 requests per minute. What works (learned by the first system connected, which used to ask its old store for ~1,000 orders per minute at peaks) is your own copy: fill it once with GET /api/v1/pedidos (200 at a time), keep it up to date with webhooks (each one carries the whole order as it is now), and every few minutes ask GET /api/v1/pedidos?modificado_desde=… in case one got lost. The order with the newest actualizado always wins; a repeated webhook (same id) is stored once. Read everything from the copy (search too); write through the API. On 429, wait a few seconds and retry.

Errors

Always JSON: {"error": "code", "mensaje": "message in Spanish"}. 400 json_invalido/dato_invalido · 401 sin_llave/llave_invalida · 403 sin_profesional/tienda_inactiva · 404 no_existe · 409 no_se_puede (status doesn't allow it) · 422 pedido_invalido/producto_no_existe/variante_no_existe/foto_invalida/direccion_invalida/no_se_puede_devolver · 429 demasiados.

AI agents (MCP) New

Besides the API, stores have an MCP server (Model Context Protocol, the standard AI agents use to connect to other systems). The owner connects their Claude, ChatGPT or any compatible agent, and the agent runs the store with the same rules as the app. It's for every store, Base included.

https://app.cubobase.com/mcp

To try it from the terminal with Claude Code (then /mcp opens «Authorize»):

claude mcp add --transport http cubobase https://app.cubobase.com/mcp

New things get added without breaking what works: existing fields never change name or meaning. Questions? contacto@cubobase.com.