Collega i tuoi sistemi a Cashod.
L'API di Cashod permette ai tuoi sistemi di inviare ordini COD a Cashod, leggerne lo stato di consegna e tenere sincronizzati catalogo prodotti e giacenze. È un'API HTTP JSON — nessun SDK necessario.
https://api.cashod.ma/api/v1$ Una chiave, un negozio, solo gli scope che selezioni.
Crea una chiave in Cashod da Impostazioni → Sviluppatori. Il segreto viene mostrato una sola volta, alla creazione, e non è recuperabile in seguito — se lo perdi, revoca la chiave e creane un'altra.
Inviala come bearer token in ogni richiesta:
Authorization: Bearer csk_live_…Vede e modifica i dati di quel negozio e nient'altro, quindi non passi mai un id negozio — la chiave indica già a quale negozio ti riferisci. Un merchant con più negozi crea una chiave per negozio.
Ogni chiave ha anche degli scope, selezionati al momento della creazione. Uno scope di scrittura concede anche la lettura corrispondente, quindi una chiave che crea ordini può anche rileggerli. Dai a uno script di reportistica una chiave di sola lettura: non potrà creare né annullare nulla, anche se trapela.
orders:readLeggere gli ordini e il loro stato di consegnaorders:writeCreare ordini — concede anche orders:readproducts:readLeggere il catalogo prodottiproducts:writeCreare e aggiornare prodotti — concede anche products:readstock:writeImpostare le giacenze delle varianti
Il tuo primo ordine in tre passaggi.
- 01Crea una chiave
In Cashod apri Impostazioni → Sviluppatori, seleziona gli scope che ti servono e copia il segreto — viene mostrato una sola volta.
- 02Invia un ordine
Invialo in POST a
/v1/orderscon il tuoexternal_id, così una richiesta ripetuta non potrà mai diventare un secondo pacco. - 03Segui la consegna
Leggi
status,confirmation_statusetracking_numberda/v1/orders/{id}man mano che il corriere lo sposta.
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 } ]
}'Una superficie ridotta, da imparare in un pomeriggio.
Tutto si trova sotto https://api.cashod.ma/api/v1.
/v1/ordersCrea un ordine dal tuo checkout. Si può ripetere in sicurezza con external_id.
Scopeorders:write/v1/orders/{id}Leggi un ordine: stato di spedizione, stato di conferma e numero di tracking.
Scopeorders:read/v1/ordersElenca gli ordini dal più recente, filtra su created_after, pagina con un cursore.
Scopeorders:read/v1/productsCrea o aggiorna un prodotto tramite SKU, con varianti e immagini.
Scopeproducts:write/v1/products/{productId}/variants/{variantId}/stockImposta la giacenza di una variante a un livello assoluto.
Scopestock:write/v1/openapi.jsonIl documento OpenAPI leggibile dalle macchine da cui è generato il riferimento.
Costruiscilo con un assistente IA
Usi un assistente di programmazione IA? Copia questo prompt invece di tradurre la pagina a mano. Non contiene mai una chiave: il codice che produce legge la tua da una variabile d'ambiente CASHOD_API_KEY.
Incollalo in ChatGPT, Claude, Cursor o Lovable. Contiene le regole di questa pagina e un link al documento OpenAPI, così l'assistente scrive l'integrazione per il tuo 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.Quello che l'elenco dei campi non ti dice.
Creare un ordine
Identifica ogni articolo tramite il suo SKU — lo SKU della variante o del prodotto, in base a come vendi. Ometti unit_price e viene usato il prezzo di catalogo; invialo e prevale, che è ciò che vuoi quando la landing page aveva una promozione.
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 } ]
}'Idempotenza — invia external_id
Invia il tuo id dell'ordine come external_id e la chiamata diventa sicura da ripetere. Se lo stesso external_id arriva di nuovo, Cashod restituisce l'ordine già creato invece di crearne un secondo:
- 201 Created — ora esiste un nuovo ordine.
- 200 OK — questo
external_idaveva già un ordine; viene restituito invariato.
Conta più di quanto sembri. Una richiesta andata in timeout di solito è stata comunque elaborata; senza external_id, il tuo nuovo tentativo diventa un secondo ordine reale, affidato a un corriere reale, e il cliente riceve due pacchi.
Leggere lo stato di consegna
Un ordine contiene status (spedizione), confirmation_status (la chiamata di conferma) e tracking_number una volta spedito.
curl https://api.cashod.ma/api/v1/orders/ORDER_ID \
-H "Authorization: Bearer csk_live_…"Per seguire molti ordini, elencali con GET /v1/orders e filtra su created_after. Interroga al massimo una volta al minuto; cicli più stretti non servono, perché gli stati cambiano al ritmo del corriere.
Creare e aggiornare prodotti
Un solo endpoint basato sul tuo sku: uno SKU sconosciuto crea il prodotto, uno noto lo aggiorna. Così puoi inviare tutto il catalogo a intervalli regolari senza controllare cosa esiste già.
- 201 Created — questo SKU era nuovo.
- 200 OK — un prodotto con questo SKU esisteva ed è stato aggiornato.
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 }
]
}'Le varianti vengono abbinate per SKU, e quelle che non elenchi restano intatte — mai eliminate. Di solito una variante ha uno storico ordini alle spalle, quindi una sincronizzazione che per caso la omette non deve distruggerla. Elimina le varianti dalla dashboard di Cashod.
Le immagini funzionano al contrario: ometti il campo images e quelle esistenti restano; invia una lista e diventa il nuovo insieme; invia [] per rimuoverle tutte.
cost_price è obbligatorio. Cashod lo usa per calcolare COGS e profitto, e un prodotto creato senza riporta un margine del 100% su ogni ordine in cui compare. In aggiornamento viene ignorato non appena esistono scorte acquistate per il prodotto — cambiarlo riscriverebbe il costo delle scorte che hai già.
Impostare stock su una variante tramite questo endpoint richiede anche lo scope stock:write. Senza, la chiamata viene rifiutata invece di salvare in silenzio tutto tranne le giacenze — così una chiave di catalogo resta una chiave di catalogo.
Due conflitti vanno gestiti: un altro prodotto del negozio usa già quel nome (i nomi sono unici per negozio, indipendentemente dagli SKU), oppure uno dei tuoi SKU di variante appartiene a un altro prodotto. Entrambi tornano come 409 conflict indicando la collisione.
Mantenere le giacenze sincronizzate
Le giacenze si impostano a un livello assoluto, non con una variazione. Invia il livello che vuoi per la variante — così una chiamata ripetuta non può essere applicata due volte, cosa che un -1 farebbe in silenzio.
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 }'Paginazione
Gli endpoint di elenco sono paginati con cursore, dal più recente. Una risposta contiene data, has_more e next_cursor. Ripassa quel cursore come ?cursor= per la pagina successiva. La dimensione di pagina predefinita è 50, con un massimo di 100.
Cursori invece di numeri di pagina perché gli ordini arrivano di continuo: con gli offset, un nuovo ordine sposta di una posizione tutte le pagine successive, e un client che scorre lo storico salta alcune righe e ne rilegge altre senza accorgersene.
Errori
Ogni errore ha la stessa forma:
{
"error": {
"type": "invalid_request",
"message": "No product or variant with SKU \"GHOST-1\" in this store."
}
}Basati su type, mai sul testo di message — il testo può essere migliorato, il tipo non cambierà.
invalid_requestC'è qualcosa nel tuo payload. Il messaggio indica cosa.authentication_errorChiave mancante, sconosciuta o revocata.permission_errorAlla chiave manca lo scope, oppure l'abbonamento dell'account non è attivo.not_foundNessun ordine o prodotto simile nel negozio di questa chiave.rate_limit_exceededRallenta e riprova.server_errorUn problema nostro. Puoi riprovare in sicurezza, anche due volte se hai inviato un external_id.Limiti di frequenza
120 richieste al minuto per chiave. Il limite è per chiave e non per indirizzo IP, così un altro client sullo stesso hosting non può consumare la tua quota.
Versioni
La versione è nel percorso: /v1. All'interno di una versione possono essere aggiunti nuovi campi opzionali alle risposte, quindi analizzale in modo difensivo e ignora ciò che non riconosci. Tutto ciò che romperebbe un'integrazione esistente va invece in una nuova versione.
Ogni endpoint, ogni campo.
Generato dal documento OpenAPI del backend stesso, quindi cambia con il codice a ogni deploy e non può diventare obsoleto.