cashod
Desarrolladores · API v1

Conecta tus propios sistemas a Cashod.

La API de Cashod permite que tus sistemas envíen pedidos COD a Cashod, lean su estado de entrega y mantengan sincronizados tu catálogo de productos y tu stock. Es una API HTTP JSON: no necesitas SDK.

URL basehttps://api.cashod.ma/api/v1
Crear un pedido
$ 
Respuesta
Autenticación

Una clave, una tienda, solo los permisos que marques.

Crea una clave en Cashod en Configuración → Desarrolladores. El secreto se muestra una sola vez, al crearla, y no se puede recuperar después: si lo pierdes, revoca la clave y crea otra.

Envíala como bearer token en cada solicitud:

Authorization
Authorization: Bearer csk_live_…
Una clave pertenece a una tienda.

Ve y modifica los datos de esa tienda y nada más, así que nunca envías un id de tienda: la clave ya indica a qué tienda te refieres. Un comerciante con varias tiendas crea una clave por tienda.

Permisos (scopes)

Cada clave también lleva scopes, que marcas al crearla. Un scope de escritura otorga también la lectura correspondiente, así que una clave que crea pedidos puede leerlos. Dale a un script de reportes una clave de solo lectura y no podrá crear ni cancelar nada, aunque se filtre.

  • orders:readLeer pedidos y su estado de entrega
  • orders:writeCrear pedidos; también otorga orders:read
  • products:readLeer el catálogo de productos
  • products:writeCrear y actualizar productos; también otorga products:read
  • stock:writeDefinir el stock de las variantes
Inicio rápido

Tu primer pedido en tres pasos.

  1. 01
    Crea una clave

    En Cashod, abre Configuración → Desarrolladores, marca los scopes que necesitas y copia el secreto: solo se muestra una vez.

  2. 02
    Envía un pedido

    Haz POST a /v1/orders con tu propio external_id, para que una solicitud reintentada nunca se convierta en un segundo paquete.

  3. 03
    Sigue su entrega

    Lee status, confirmation_status y tracking_number desde /v1/orders/{id} a medida que el transportista lo mueve.

POST /v1/orders
curl -X POST https://api.cashod.ma/api/v1/orders \
  -H "Authorization: Bearer csk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "order-77",
    "customer": { "name": "Amine Berrada", "phone": "+212600000000" },
    "shipping": { "address": "12 Rue Tarik Ibn Ziad", "city": "Casablanca" },
    "items": [ { "sku": "MON-001-M-NOIR", "quantity": 2 } ]
  }'
Endpoints

Una API pequeña que aprendes en una tarde.

Todo vive bajo https://api.cashod.ma/api/v1.

POST/v1/orders

Crea un pedido desde tu checkout. Se puede reintentar sin riesgo con external_id.

Scopeorders:write
GET/v1/orders/{id}

Lee un pedido: estado de envío, estado de confirmación y número de rastreo.

Scopeorders:read
GET/v1/orders

Lista pedidos del más reciente al más antiguo, filtra por created_after y pagina con un cursor.

Scopeorders:read
POST/v1/products

Crea o actualiza un producto por SKU, con sus variantes e imágenes.

Scopeproducts:write
PATCH/v1/products/{productId}/variants/{variantId}/stock

Define el stock de una variante a un nivel absoluto.

Scopestock:write
GET/v1/openapi.json

El documento OpenAPI legible por máquina del que se genera la referencia.

Asistente de IA

Constrúyelo con un asistente de IA

¿Usas un asistente de programación con IA? Copia este prompt en lugar de traducir la página a mano. Nunca contiene una clave: el código que genera lee la tuya desde una variable de entorno CASHOD_API_KEY.

Pégalo en ChatGPT, Claude, Cursor o Lovable. Incluye las reglas de esta página y un enlace al documento OpenAPI, para que el asistente escriba la integración para tu stack.

cashod-api-prompt.md
You are helping me integrate my system (online store, landing page or ERP) with the Cashod API, a cash-on-delivery order platform. Write the integration code following the specification below exactly. Ask me which language/framework I use if you cannot tell from context, and which of the tasks below I need.

The full machine-readable specification is at https://api.cashod.ma/api/v1/openapi.json. Fetch it if you can, and trust it over this summary for field details.

## Rules
- Call the API from my SERVER only, never from browser JavaScript: the key must not reach visitors.
- Read the key from an environment variable named CASHOD_API_KEY (it looks like csk_live_…). Never hard-code it. I create it in Cashod under Settings → Developers; one key belongs to one store.
- Every request: header "Authorization: Bearer <CASHOD_API_KEY>", JSON bodies with "Content-Type: application/json".
- Send only the fields listed here or in the OpenAPI document: unknown fields are rejected with 400 invalid_request.
- Rate limit: 120 requests per minute per key.
- Never let a Cashod failure break my customer's checkout: log it and retry in the background.

## Task 1: send an order
POST https://api.cashod.ma/api/v1/orders  (scope orders:write)
{
  "external_id": "order-77",
  "customer": { "name": "Amine Berrada", "phone": "+212600000000" },
  "shipping": { "address": "12 Rue Tarik Ibn Ziad", "city": "Casablanca" },
  "items": [ { "sku": "MON-001-M-NOIR", "quantity": 2, "unit_price": 149 } ]
}
- Always send external_id, set to my own order id. It makes the call idempotent: a retry returns the existing order (200) instead of creating a second one (201). Without it a retried timeout becomes a second real parcel.
- customer.name, customer.phone, shipping.address, shipping.city are required. shipping.phone is optional (defaults to the customer's).
- items: at least one. Each needs quantity (integer ≥ 1) and one of: sku (variant or product SKU, the usual way), product_id (Cashod product id), or name (free text for an item not in the catalogue; unit_price is then required).
- unit_price: what the customer actually paid per unit. Omit it to use the catalogue price.
- Optional: total (final agreed total; the difference is recorded as a discount), discount, notes, scheduled_for (ISO 8601), source_name (e.g. "Landing page Ramadan"), confirmed (true ONLY if I already confirmed the order with the customer; it skips the confirmation call).

## Task 2: read delivery status
GET https://api.cashod.ma/api/v1/orders/{id}  or  GET https://api.cashod.ma/api/v1/orders?created_after=<ISO 8601>  (scope orders:read)
- An order has status (shipping, e.g. NOT_SHIPPED, SHIPPED, DELIVERED, RETURNED), confirmation_status (e.g. PENDING, CONFIRMED, CANCELLED) and tracking_number once shipped.
- Lists are cursor-paginated, newest first: read data, has_more and next_cursor, and pass ?cursor=<next_cursor> for the next page. limit defaults to 50, max 100.
- Poll at most once a minute.

## Task 3: sync products
POST https://api.cashod.ma/api/v1/products  (scope products:write)
- Keyed on sku: an unknown sku creates the product (201), a known one updates it (200). Safe to send the whole catalogue on a schedule.
- Required: sku, name, category, price, cost_price. Optional: description, sale_price, is_active, track_stock, min_stock, images (URLs), variants.
- variants: each needs sku; optional size, color, price, sale_price, cost_price, stock. Variants are matched by sku; one I do not list is left untouched, never deleted.
- images: omit to keep the existing ones, send a list to replace them, send [] to clear them.
- Setting stock here also needs the stock:write scope.
- 409 conflict: another product already uses this name, or a variant sku belongs to another product. Report it to me; do not retry.

## Task 4: keep stock in sync
PATCH https://api.cashod.ma/api/v1/products/{productId}/variants/{variantId}/stock  (scope stock:write)
{ "stock": 12 }
- Stock is set to an absolute level, never a delta, so a retried call cannot double-apply.

## Errors
Every failure looks like {"error": {"type": "...", "message": "..."}}. Branch on error.type, never on the message text.
- invalid_request (400): fix the payload, do not retry.
- authentication_error (401): missing, unknown or revoked key. Do not retry.
- permission_error (403): the key lacks the scope, or the subscription is not active. Do not retry.
- not_found (404): no such order or product in this key's store.
- rate_limit_exceeded (429) and server_error (500), plus timeouts and network errors: retry with backoff (for example 1 min, 5 min, 30 min). Orders are safe to retry because of external_id.

New optional fields may appear in responses: ignore what you do not recognise.

## When you are done
Show me where the calls happen in my code, the environment variable to set, and a curl command to send one test order.
Guías

Lo que la lista de campos no te dice.

Crear un pedido

Identifica cada artículo por su SKU: un SKU de variante o de producto, según cómo vendas. Si omites unit_price se usa el precio del catálogo; si lo envías, prevalece, que es lo que quieres cuando la landing tuvo una promoción.

POST /v1/orders
curl -X POST https://api.cashod.ma/api/v1/orders \
  -H "Authorization: Bearer csk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "order-77",
    "customer": { "name": "Amine Berrada", "phone": "+212600000000" },
    "shipping": { "address": "12 Rue Tarik Ibn Ziad", "city": "Casablanca" },
    "items": [ { "sku": "MON-001-M-NOIR", "quantity": 2 } ]
  }'

Idempotencia: envía external_id

Envía tu propio id del pedido como external_id y la llamada se puede reintentar sin riesgo. Si el mismo external_id vuelve a llegar, Cashod devuelve el pedido que ya creó en lugar de crear otro:

  • 201 Created: ahora existe un pedido nuevo.
  • 200 OK: este external_id ya tenía un pedido; se devuelve sin cambios.

Esto importa más de lo que parece. Una solicitud que expira casi siempre ya se procesó; sin un external_id, tu reintento se convierte en un segundo pedido real, despachado a un transportista real, y el cliente recibe dos paquetes.

Leer el estado de entrega

Un pedido lleva status (envío), confirmation_status (la llamada de confirmación) y tracking_number una vez que se envía.

GET /v1/orders/{id}
curl https://api.cashod.ma/api/v1/orders/ORDER_ID \
  -H "Authorization: Bearer csk_live_…"

Para seguir muchos pedidos, lístalos con GET /v1/orders y filtra por created_after. Consulta como máximo una vez por minuto; no ganas nada con ciclos más cortos, porque los estados cambian al ritmo del transportista.

Crear y actualizar productos

Un solo endpoint identificado por tu sku: un SKU desconocido crea el producto, uno conocido lo actualiza. Así puedes enviar todo tu catálogo de forma programada sin revisar qué existe ya.

  • 201 Created: este SKU era nuevo.
  • 200 OK: ya existía un producto con este SKU y se actualizó.
POST /v1/products
curl -X POST https://api.cashod.ma/api/v1/products \
  -H "Authorization: Bearer csk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "MON-001",
    "name": "Montre Or",
    "category": "Accessoires",
    "price": 199,
    "cost_price": 60,
    "images": [ "https://cdn.example/montre.jpg" ],
    "variants": [
      { "sku": "MON-001-M-NOIR", "size": "M", "color": "Noir", "stock": 7 }
    ]
  }'

Las variantes se emparejan por SKU, y la que no incluyas queda intacta: nunca se elimina. Una variante suele tener historial de pedidos detrás, así que una sincronización que la omita por accidente no debe destruirla. Elimina variantes desde el panel de Cashod.

Las imágenes funcionan al revés: si omites el campo images, las existentes se conservan; si envías una lista, esa pasa a ser el conjunto; envía [] para borrarlas.

cost_price es obligatorio. Cashod calcula a partir de él el COGS y la ganancia, y un producto creado sin él reporta un margen del 100% en cada pedido donde aparece. En una actualización se ignora una vez que hay stock comprado para el producto: cambiarlo entonces reescribiría el costo del stock que ya tienes.

Definir stock en una variante mediante este endpoint también requiere el scope stock:write. Sin él, la llamada se rechaza en lugar de guardar todo en silencio excepto el stock; así una clave de catálogo sigue siendo solo de catálogo.

Vale la pena manejar dos conflictos: otro producto de la tienda ya usa ese nombre (los nombres son únicos por tienda, aparte de los SKU), o uno de tus SKU de variante pertenece a otro producto. Ambos vuelven como 409 conflict indicando la colisión.

Mantén el stock sincronizado

El stock se define a un nivel absoluto, no se ajusta con una diferencia. Envía el nivel que quieres que tenga la variante: así una llamada reintentada no se aplica dos veces, cosa que un -1 haría sin avisar.

PATCH /v1/products/{productId}/variants/{variantId}/stock
curl -X PATCH https://api.cashod.ma/api/v1/products/PRODUCT_ID/variants/VARIANT_ID/stock \
  -H "Authorization: Bearer csk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "stock": 12 }'

Paginación

Los endpoints de listado se paginan con cursor, del más reciente al más antiguo. Una respuesta incluye data, has_more y next_cursor. Devuelve ese cursor como ?cursor= para obtener la siguiente página. El tamaño de página es 50 por defecto y 100 como máximo.

Cursores en lugar de números de página porque los pedidos llegan todo el tiempo: con offsets, un pedido nuevo desplaza una posición cada página posterior, y un cliente que recorre su historial se salta filas sin darse cuenta y lee otras dos veces.

Errores

Todos los errores tienen la misma forma:

application/json
{
  "error": {
    "type": "invalid_request",
    "message": "No product or variant with SKU \"GHOST-1\" in this store."
  }
}

Decide según type, nunca según el texto de message: el texto puede mejorar, el tipo no cambiará.

400invalid_requestAlgo en tu payload. El mensaje lo indica.
401authentication_errorClave ausente, desconocida o revocada.
403permission_errorA la clave le falta el scope, o la suscripción de la cuenta no está activa.
404not_foundNo existe ese pedido o producto en la tienda de esta clave.
429rate_limit_exceededEspera y vuelve a intentar.
500server_errorEs de nuestro lado. Puedes reintentar, e incluso dos veces si enviaste un external_id.

Límites de uso

0solicitudes por minuto, por clave

120 solicitudes por minuto por clave. El límite es por clave y no por dirección IP, así otro cliente en el mismo hosting no puede consumir tu cupo.

Versionado

La versión va en la ruta: /v1. Dentro de una versión pueden agregarse campos opcionales nuevos a las respuestas, así que parsea de forma defensiva e ignora lo que no reconozcas. Todo lo que rompería una integración existente va a una versión nueva.

Referencia de la API

Cada endpoint, cada campo.

Generada a partir del propio documento OpenAPI del backend, así que cambia con el código en cada despliegue y nunca queda desactualizada.

openapi.json
API de Cashod para desarrolladores: pedidos, stock, webhooks