Binden Sie Ihre eigenen Systeme an Cashod an.
Über die Cashod-API übertragen Ihre eigenen Systeme COD-Bestellungen an Cashod, lesen deren Lieferstatus zurück und halten Produktkatalog und Bestand synchron. Es ist eine JSON-HTTP-API — kein SDK nötig.
https://api.cashod.ma/api/v1$ Ein Schlüssel, ein Shop, nur die Scopes, die Sie wählen.
Erstellen Sie einen Schlüssel in Cashod unter Einstellungen → Entwickler. Das Secret wird nur einmal bei der Erstellung angezeigt und lässt sich danach nicht wiederherstellen — geht es verloren, widerrufen Sie den Schlüssel und erstellen einen neuen.
Senden Sie ihn bei jeder Anfrage als Bearer-Token:
Authorization: Bearer csk_live_…Er sieht und ändert die Daten dieses Shops und sonst nichts. Sie übergeben daher nie eine Shop-ID — der Schlüssel sagt bereits, welchen Shop Sie meinen. Wer mehrere Shops betreibt, erstellt einen Schlüssel pro Shop.
Jeder Schlüssel hat außerdem Scopes, die Sie bei der Erstellung auswählen. Ein Schreib-Scope gewährt auch den passenden Lese-Scope, sodass ein Schlüssel, der Bestellungen erstellt, sie auch zurücklesen kann. Geben Sie einem Reporting-Skript einen reinen Leseschlüssel — dann kann es nichts erstellen oder stornieren, selbst wenn er geleakt wird.
orders:readBestellungen und ihren Lieferstatus lesenorders:writeBestellungen erstellen — gewährt auch orders:readproducts:readDen Produktkatalog lesenproducts:writeProdukte erstellen und aktualisieren — gewährt auch products:readstock:writeBestand von Varianten setzen
Ihre erste Bestellung in drei Schritten.
- 01Schlüssel erstellen
Öffnen Sie in Cashod Einstellungen → Entwickler, wählen Sie die benötigten Scopes und kopieren Sie das Secret — es wird nur einmal angezeigt.
- 02Bestellung senden
Senden Sie sie per POST an
/v1/ordersmit Ihrer eigenenexternal_id, damit eine wiederholte Anfrage nie zu einem zweiten Paket wird. - 03Zustellung verfolgen
Lesen Sie
status,confirmation_statusundtracking_numberüber/v1/orders/{id}zurück, während der Versanddienstleister das Paket bewegt.
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 } ]
}'Eine kleine API, die Sie an einem Nachmittag lernen.
Alles liegt unter https://api.cashod.ma/api/v1.
/v1/ordersBestellung aus Ihrem Checkout erstellen. Mit external_id sicher wiederholbar.
Scopeorders:write/v1/orders/{id}Eine Bestellung lesen: Versandstatus, Bestätigungsstatus und Sendungsnummer.
Scopeorders:read/v1/ordersBestellungen auflisten, neueste zuerst, nach created_after filtern, per Cursor paginieren.
Scopeorders:read/v1/productsProdukt per SKU erstellen oder aktualisieren, mit Varianten und Bildern.
Scopeproducts:write/v1/products/{productId}/variants/{variantId}/stockBestand einer Variante auf einen absoluten Wert setzen.
Scopestock:write/v1/openapi.jsonDas maschinenlesbare OpenAPI-Dokument, aus dem die Referenz erzeugt wird.
Mit einem KI-Assistenten umsetzen
Sie nutzen einen KI-Coding-Assistenten? Kopieren Sie diesen Prompt, statt die Seite von Hand zu übertragen. Er enthält nie einen Schlüssel: Der erzeugte Code liest Ihren aus der Umgebungsvariable CASHOD_API_KEY.
Fügen Sie ihn in ChatGPT, Claude, Cursor oder Lovable ein. Er enthält die Regeln dieser Seite und einen Link zum OpenAPI-Dokument, sodass der Assistent die Integration für Ihren Stack schreibt.
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.Was die Feldliste Ihnen nicht verrät.
Bestellung erstellen
Identifizieren Sie jeden Artikel über seine SKU — Varianten- oder Produkt-SKU, je nachdem, wonach Sie verkaufen. Lassen Sie unit_price weg, gilt der Katalogpreis; senden Sie ihn mit, hat er Vorrang — genau richtig, wenn auf der Landingpage eine Aktion lief.
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 } ]
}'Idempotenz — external_id senden
Senden Sie Ihre eigene ID der Bestellung als external_id, und der Aufruf lässt sich gefahrlos wiederholen. Kommt dieselbe external_id erneut an, gibt Cashod die bereits erstellte Bestellung zurück, statt eine zweite anzulegen:
- 201 Created — eine neue Bestellung existiert jetzt.
- 200 OK — zu dieser
external_idgab es bereits eine Bestellung; sie wird unverändert zurückgegeben.
Das ist wichtiger, als es aussieht. Eine Anfrage mit Timeout wurde meist trotzdem verarbeitet; ohne external_id wird Ihr erneuter Versuch zu einer zweiten echten Bestellung, die an einen echten Versanddienstleister geht — und der Kunde erhält zwei Pakete.
Lieferstatus lesen
Eine Bestellung enthält status (Versand), confirmation_status (der Bestätigungsanruf) und nach dem Versand tracking_number.
curl https://api.cashod.ma/api/v1/orders/ORDER_ID \
-H "Authorization: Bearer csk_live_…"Um viele Bestellungen zu verfolgen, listen Sie sie mit GET /v1/orders auf und filtern nach created_after. Fragen Sie höchstens einmal pro Minute ab; engere Schleifen bringen nichts, denn Status ändern sich im Tempo der Versanddienstleister.
Produkte erstellen und aktualisieren
Ein Endpunkt, der auf Ihre sku schlüsselt: Eine unbekannte SKU erstellt das Produkt, eine bekannte aktualisiert es. So können Sie Ihren ganzen Katalog regelmäßig senden, ohne zu prüfen, was schon existiert.
- 201 Created — diese SKU war neu.
- 200 OK — ein Produkt mit dieser SKU existierte und wurde aktualisiert.
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 }
]
}'Varianten werden per SKU zugeordnet, und eine nicht aufgeführte Variante bleibt unberührt — sie wird nie gelöscht. Hinter einer Variante steht meist ein Bestellverlauf; ein Sync, der sie zufällig auslässt, darf sie nicht zerstören. Löschen Sie Varianten im Cashod-Dashboard.
Bilder funktionieren umgekehrt: Lassen Sie das Feld images weg, bleiben die vorhandenen erhalten; senden Sie eine Liste, wird sie zum neuen Bestand; senden Sie [], um alle zu entfernen.
cost_price ist Pflicht. Cashod berechnet daraus Wareneinsatz und Gewinn, und ein Produkt ohne Einkaufspreis weist bei jeder Bestellung 100 % Marge aus. Bei einer Aktualisierung wird das Feld ignoriert, sobald Bestand zu diesem Produkt eingekauft wurde — eine Änderung würde sonst die Kosten Ihres vorhandenen Bestands umschreiben.
Um stock einer Variante über diesen Endpunkt zu setzen, ist zusätzlich der Scope stock:write nötig. Ohne ihn wird der Aufruf abgelehnt, statt stillschweigend alles außer dem Bestand zu speichern — so bleibt ein Katalogschlüssel ein Katalogschlüssel.
Zwei Konflikte sollten Sie behandeln: Ein anderes Produkt im Shop nutzt bereits diesen Namen (Namen sind pro Shop eindeutig, unabhängig von SKUs), oder eine Ihrer Varianten-SKUs gehört zu einem anderen Produkt. Beides kommt als 409 conflict zurück und benennt die Kollision.
Bestand synchron halten
Der Bestand wird auf einen absoluten Wert gesetzt, nicht um ein Delta angepasst. Senden Sie den Wert, den die Variante haben soll — so kann ein wiederholter Aufruf nicht doppelt wirken, was bei einem -1 stillschweigend passieren würde.
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 }'Paginierung
Listen-Endpunkte sind per Cursor paginiert, neueste zuerst. Eine Antwort enthält data, has_more und next_cursor. Übergeben Sie diesen Cursor als ?cursor= für die nächste Seite. Die Seitengröße ist standardmäßig 50, maximal 100.
Cursor statt Seitenzahlen, weil ständig Bestellungen eingehen: Mit Offsets verschiebt jede neue Bestellung alle folgenden Seiten um eins, sodass ein Client beim Durchlaufen seines Verlaufs stillschweigend Zeilen verpasst und andere doppelt liest.
Fehler
Jeder Fehler hat dieselbe Struktur:
{
"error": {
"type": "invalid_request",
"message": "No product or variant with SKU \"GHOST-1\" in this store."
}
}Verzweigen Sie nach type, nie nach dem Wortlaut von message — der Wortlaut kann sich verbessern, der Typ ändert sich nicht.
invalid_requestEtwas in Ihrem Payload. Die Meldung nennt es.authentication_errorSchlüssel fehlt, ist unbekannt oder widerrufen.permission_errorDem Schlüssel fehlt der Scope, oder das Abonnement des Kontos ist nicht aktiv.not_foundKeine solche Bestellung und kein solches Produkt im Shop dieses Schlüssels.rate_limit_exceededKurz warten und erneut versuchen.server_errorUnser Fehler. Sicher wiederholbar — auch zweimal, wenn Sie eine external_id gesendet haben.Rate Limits
120 Anfragen pro Minute und Schlüssel. Das Limit gilt pro Schlüssel statt pro IP-Adresse, damit ein anderer Client auf demselben Hosting Ihr Kontingent nicht aufbrauchen kann.
Versionierung
Die Version steht im Pfad: /v1. Innerhalb einer Version können neue optionale Felder in Antworten hinzukommen — parsen Sie also defensiv und ignorieren Sie, was Sie nicht kennen. Alles, was eine bestehende Integration brechen würde, kommt stattdessen in eine neue Version.
Jeder Endpunkt, jedes Feld.
Erzeugt aus dem OpenAPI-Dokument des Backends selbst — sie ändert sich also bei jedem Deployment mit dem Code und kann nicht veralten.