Conecte seus próprios sistemas ao Cashod.
A API do Cashod permite que seus sistemas enviem pedidos COD ao Cashod, consultem o status de entrega e mantenham seu catálogo de produtos e estoque sincronizados. É uma API HTTP em JSON — sem necessidade de SDK.
https://api.cashod.ma/api/v1$ Uma chave, uma loja, só os escopos que você marcar.
Crie uma chave no Cashod em Configurações → Desenvolvedores. O segredo é exibido uma única vez, na criação, e não pode ser recuperado depois — se perdê-lo, revogue a chave e crie outra.
Envie-a como bearer token em todas as requisições:
Authorization: Bearer csk_live_…Ela vê e altera os dados dessa loja e de nenhuma outra, então você nunca passa um id de loja — a chave já diz de qual loja se trata. Quem tem várias lojas cria uma chave por loja.
Cada chave também tem escopos, marcados na criação. Um escopo de escrita também concede a leitura correspondente, então uma chave que cria pedidos pode lê-los de volta. Dê a um script de relatórios uma chave só de leitura e ele não conseguirá criar nem cancelar nada, mesmo que vaze.
orders:readLer pedidos e o status de entregaorders:writeCriar pedidos — também concede orders:readproducts:readLer o catálogo de produtosproducts:writeCriar e atualizar produtos — também concede products:readstock:writeDefinir o estoque das variações
Seu primeiro pedido em três passos.
- 01Crie uma chave
No Cashod, abra Configurações → Desenvolvedores, marque os escopos de que precisa e copie o segredo — ele aparece uma única vez.
- 02Envie um pedido
Faça um POST para
/v1/orderscom seu próprioexternal_id, assim uma requisição repetida nunca vira uma segunda encomenda. - 03Acompanhe a entrega
Leia
status,confirmation_statusetracking_numberem/v1/orders/{id}conforme a transportadora avança.
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 } ]
}'Uma API enxuta, que você aprende em uma tarde.
Tudo fica em https://api.cashod.ma/api/v1.
/v1/ordersCrie um pedido a partir do seu checkout. Pode repetir com segurança usando external_id.
Escopoorders:write/v1/orders/{id}Leia um pedido: status de envio, status de confirmação e código de rastreio.
Escopoorders:read/v1/ordersListe pedidos do mais novo ao mais antigo, filtre por created_after e pagine com um cursor.
Escopoorders:read/v1/productsCrie ou atualize um produto pelo SKU, com variações e imagens.
Escopoproducts:write/v1/products/{productId}/variants/{variantId}/stockDefina o estoque de uma variação em um valor absoluto.
Escopostock:write/v1/openapi.jsonO documento OpenAPI legível por máquina que gera a referência.
Construa com um assistente de IA
Usa um assistente de código com IA? Copie este prompt em vez de traduzir a página à mão. Ele nunca contém uma chave: o código gerado lê a sua da variável de ambiente CASHOD_API_KEY.
Cole no ChatGPT, Claude, Cursor ou Lovable. Ele traz as regras desta página e um link para o documento OpenAPI, para que o assistente escreva a integração na sua stack.
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.O que a lista de campos não conta.
Criar um pedido
Identifique cada item pelo SKU — o SKU da variação ou do produto, conforme você vende. Omita unit_price e o preço do catálogo é usado; envie-o e ele prevalece, que é o que você quer quando a landing page teve uma promoção.
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 } ]
}'Idempotência — envie external_id
Envie seu próprio id do pedido como external_id e a chamada passa a ser segura para repetir. Se o mesmo external_id chegar de novo, o Cashod devolve o pedido que já criou em vez de criar um segundo:
- 201 Created — um novo pedido foi criado.
- 200 OK — este
external_idjá tinha um pedido; ele é devolvido sem alterações.
Isso importa mais do que parece. Uma requisição que dá timeout geralmente já foi processada; sem um external_id, sua nova tentativa vira um segundo pedido real, despachado para uma transportadora real, e o cliente recebe duas encomendas.
Ler o status de entrega
Um pedido traz status (envio), confirmation_status (a ligação de confirmação) e tracking_number depois de enviado.
curl https://api.cashod.ma/api/v1/orders/ORDER_ID \
-H "Authorization: Bearer csk_live_…"Para acompanhar muitos pedidos, liste-os com GET /v1/orders e filtre por created_after. Consulte no máximo uma vez por minuto; não há ganho em loops mais curtos, já que os status mudam no ritmo da transportadora.
Criar e atualizar produtos
Um único endpoint indexado pelo seu sku: um SKU desconhecido cria o produto, um conhecido o atualiza. Assim você pode enviar o catálogo inteiro periodicamente sem verificar o que já existe.
- 201 Created — este SKU era novo.
- 200 OK — já existia um produto com este SKU e ele foi atualizado.
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 }
]
}'As variações são identificadas pelo SKU, e as que você não listar ficam intactas — nunca são excluídas. Uma variação costuma ter histórico de pedidos, então uma sincronização que a omita por acaso não pode apagá-la. Exclua variações no painel do Cashod.
As imagens funcionam ao contrário: omita o campo images e as existentes ficam como estão; envie uma lista e ela passa a ser o conjunto; envie [] para removê-las.
cost_price é obrigatório. O Cashod calcula o CMV e o lucro a partir dele, e um produto criado sem ele mostra margem de 100% em todos os pedidos em que aparece. Em uma atualização, ele é ignorado quando já existe estoque comprado para o produto — alterá-lo reescreveria o custo do estoque que você já tem.
Definir stock em uma variação por este endpoint também exige o escopo stock:write. Sem ele, a chamada é recusada em vez de salvar tudo em silêncio, menos o estoque — assim uma chave de catálogo continua sendo só de catálogo.
Vale tratar dois conflitos: outro produto da loja já usa aquele nome (os nomes são únicos por loja, independentemente dos SKUs), ou um dos SKUs de variação pertence a outro produto. Os dois voltam como 409 conflict, indicando a colisão.
Manter o estoque sincronizado
O estoque é definido em um valor absoluto, não ajustado por diferença. Envie o valor que a variação deve ter — assim uma chamada repetida não é aplicada duas vezes, o que um -1 faria sem avisar.
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 }'Paginação
Os endpoints de listagem são paginados por cursor, do mais novo ao mais antigo. A resposta traz data, has_more e next_cursor. Passe esse cursor de volta como ?cursor= para a próxima página. O tamanho padrão da página é 50, com máximo de 100.
Cursores em vez de números de página porque os pedidos chegam o tempo todo: com offsets, um novo pedido empurra cada página seguinte uma posição para baixo, e um cliente que percorre o histórico perde linhas e relê outras sem perceber.
Erros
Toda falha tem o mesmo formato:
{
"error": {
"type": "invalid_request",
"message": "No product or variant with SKU \"GHOST-1\" in this store."
}
}Decida pelo type, nunca pelo texto de message — o texto pode ser melhorado, o type não muda.
invalid_requestAlgo no seu payload. A mensagem diz o quê.authentication_errorChave ausente, desconhecida ou revogada.permission_errorA chave não tem o escopo, ou a assinatura da conta não está ativa.not_foundNenhum pedido ou produto assim na loja desta chave.rate_limit_exceededAguarde e tente de novo.server_errorProblema do nosso lado. Pode repetir com segurança, e duas vezes se você enviou um external_id.Limites de requisição
120 requisições por minuto por chave. O limite é por chave, não por endereço IP, então outro cliente na mesma hospedagem não consome sua cota.
Versionamento
A versão fica no caminho: /v1. Novos campos opcionais podem ser adicionados às respostas dentro de uma versão, então faça o parsing de forma tolerante e ignore o que não reconhecer. Qualquer mudança que quebraria uma integração existente vai para uma nova versão.
Cada endpoint, cada campo.
Gerada a partir do próprio documento OpenAPI do backend, então acompanha o código a cada deploy e nunca fica desatualizada.