{"openapi":"3.0.0","paths":{"/api/v1/orders":{"post":{"description":"Send `external_id` — your own id for the order — and this call becomes idempotent: retrying it returns the order already created (200) instead of creating a second one (201).","operationId":"PublicOrdersController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePublicOrderDto"}}}},"responses":{"200":{"description":"This external_id already had an order; it is returned unchanged."},"201":{"description":"The order was created."}},"security":[{"apiKey":[]}],"summary":"Create an order","tags":["Orders"]},"get":{"description":"Newest first, cursor-paginated. Pass the `next_cursor` from a response as `cursor` to fetch the following page.","operationId":"PublicOrdersController_list","parameters":[{"name":"status","required":false,"in":"query","description":"Shipping status, e.g. NOT_SHIPPED, SHIPPED, DELIVERED, RETURNED.","schema":{"type":"string"}},{"name":"confirmation_status","required":false,"in":"query","description":"Confirmation status, e.g. PENDING, CONFIRMED, CANCELLED.","schema":{"type":"string"}},{"name":"created_after","required":false,"in":"query","description":"ISO 8601 timestamp; orders created at or after it.","schema":{"type":"string"}},{"name":"created_before","required":false,"in":"query","description":"ISO 8601 timestamp; orders created at or before it.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"maximum":100,"default":50,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"The next_cursor from the previous page. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List orders","tags":["Orders"]}},"/api/v1/orders/{id}":{"get":{"description":"Includes the current delivery status and tracking number.","operationId":"PublicOrdersController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"Retrieve an order","tags":["Orders"]}},"/api/v1/products":{"post":{"description":"Keyed on your SKU: an unknown SKU creates the product (201), a known one updates it (200). Safe to call repeatedly with your whole catalogue.\n\nVariants are matched by SKU — one you do not list is left untouched, never deleted. Images: omit the field to leave them alone, send a list to replace them, send [] to clear them.\n\nSetting `stock` on a variant here also needs the `stock:write` scope; without it the request is refused rather than silently ignored.","operationId":"PublicProductsController_upsert","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertProductDto"}}}},"responses":{"200":{"description":"A product with this SKU existed and was updated."},"201":{"description":"The product was created."},"403":{"description":"The key lacks products:write, or the payload sets variant stock and the key lacks stock:write."},"409":{"description":"Another product in this store already uses that name, or one of the variant SKUs belongs to a different product."}},"security":[{"apiKey":[]}],"summary":"Create or update a product","tags":["Products"]},"get":{"description":"Cursor-paginated, newest first. Each product carries its variants and stock.","operationId":"PublicProductsController_list","parameters":[{"name":"sku","required":false,"in":"query","description":"Exact SKU match.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Case-insensitive substring of the product name.","schema":{"type":"string"}},{"name":"is_active","required":false,"in":"query","description":"Restrict to active or inactive products.","schema":{"type":"boolean"}},{"name":"limit","required":false,"in":"query","schema":{"maximum":100,"default":50,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"The next_cursor from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List products","tags":["Products"]}},"/api/v1/products/{id}":{"get":{"operationId":"PublicProductsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"Retrieve a product","tags":["Products"]}},"/api/v1/products/{id}/variants/{variantId}/stock":{"patch":{"description":"Sets the stock to an absolute level. Send the level you want the variant to have, not the change — that way a retried call cannot double-apply.","operationId":"PublicProductsController_setStock","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"variantId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStockDto"}}}},"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"Set a variant's stock","tags":["Products"]}}},"info":{"title":"Cashod API","description":"Push orders into Cashod, read their delivery status back, and keep your product catalogue and stock in sync.\n\n**Authentication** — every request carries an API key as a bearer token:\n\n```\nAuthorization: Bearer csk_live_…\n```\n\nCreate a key in Cashod under Settings → Developers. A key belongs to one store and carries the scopes you tick when creating it; the secret is shown once.\n\n**Idempotency** — send your own `external_id` when creating an order. A retry then returns the order that already exists (200) instead of creating a second one (201).\n\n**Rate limit** — 120 requests per minute per key. Over it, requests answer 429.\n\n**Errors** — every failure has the same shape: `{ \"error\": { \"type\": \"…\", \"message\": \"…\" } }`. Branch on `type`, never on the wording of `message`.","version":"1.0","contact":{}},"tags":[],"servers":[{"url":"https://api.cashod.ma","description":"Production"}],"components":{"securitySchemes":{"apiKey":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"Your API key, e.g. csk_live_…"}},"schemas":{"PublicCustomerDto":{"type":"object","properties":{"name":{"type":"string","example":"Amine Berrada"},"phone":{"type":"string","example":"+212600000000","description":"Any format; normalised on arrival."}},"required":["name","phone"]},"PublicShippingDto":{"type":"object","properties":{"address":{"type":"string","example":"12 Rue Tarik Ibn Ziad, Apt 4"},"city":{"type":"string","example":"Casablanca"},"phone":{"type":"string","description":"Delivery phone, when it differs from the customer's. Defaults to it."}},"required":["address","city"]},"PublicOrderItemDto":{"type":"object","properties":{"sku":{"type":"string","example":"MON-001-M-NOIR","description":"Product or variant SKU. The usual way to identify an item — a variant SKU wins over a product SKU."},"product_id":{"type":"string","description":"Cashod product id, as an alternative to sku."},"name":{"type":"string","description":"Free-text name, for something not in the catalogue. The product is created on first use, and unit_price is then required."},"quantity":{"type":"number","example":2,"minimum":1},"unit_price":{"type":"number","example":149,"description":"What the customer was actually charged per unit. Defaults to the catalogue price when the item resolves by SKU."}},"required":["quantity"]},"CreatePublicOrderDto":{"type":"object","properties":{"external_id":{"type":"string","example":"order-77","description":"Your own id for this order. Sending it makes the call idempotent: a retry returns the order already created rather than creating a second one. Strongly recommended."},"customer":{"$ref":"#/components/schemas/PublicCustomerDto"},"shipping":{"$ref":"#/components/schemas/PublicShippingDto"},"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicOrderItemDto"}},"total":{"type":"number","description":"Final agreed total, when it differs from the sum of the lines. The difference is recorded as a discount."},"discount":{"type":"number","description":"Discount amount, if you prefer to state it directly."},"notes":{"type":"string","description":"Note for the confirmation agent."},"scheduled_for":{"type":"string","description":"When the order is due, ISO 8601 — e.g. \"2026-09-21T10:00:00+01:00\". Shown on the Planning calendar."},"source_name":{"type":"string","description":"Where the order came from, for the merchant's own reporting — e.g. \"Landing page Ramadan\"."},"confirmed":{"type":"boolean","default":false,"description":"Marks the order confirmed on arrival, skipping the confirmation call. Only set this if YOU confirmed it with the customer."}},"required":["customer","shipping","items"]},"UpsertVariantDto":{"type":"object","properties":{"sku":{"type":"string","example":"MON-001-M-NOIR","description":"Unique within the store."},"size":{"type":"string","example":"M"},"color":{"type":"string","example":"Noir"},"price":{"type":"number","description":"This variant's price, when it differs from the product's."},"sale_price":{"type":"number"},"cost_price":{"type":"number","description":"Ignored on update once the variant has stock bought against it — see the note on the product-level cost_price."},"stock":{"type":"number","description":"Stock level. Absolute, not a delta."}},"required":["sku"]},"UpsertProductDto":{"type":"object","properties":{"sku":{"type":"string","example":"MON-001","description":"Your SKU, and the identity this endpoint works on: an unknown one creates a product (201), a known one updates it (200)."},"name":{"type":"string","example":"Montre Or"},"category":{"type":"string","example":"Accessoires"},"price":{"type":"number","example":199,"description":"Retail price."},"cost_price":{"type":"number","example":60,"description":"What the product costs you. Required: Cashod computes COGS and profit from it, and a product created without one reports a 100% margin on every order. Ignored on update once there is stock bought against it, since changing it then would rewrite the cost of stock you already hold."},"description":{"type":"string"},"sale_price":{"type":"number","description":"Discounted price, when the product is on offer."},"is_active":{"type":"boolean","default":true},"track_stock":{"type":"boolean","default":true,"description":"Whether Cashod decrements stock as orders ship."},"min_stock":{"type":"number","description":"Level at which the product counts as low stock."},"images":{"description":"Image URLs. Omit the field and existing images are left alone; send a list and it becomes the set; send [] to remove them all.","type":"array","items":{"type":"string"}},"variants":{"description":"Matched by SKU. A variant you do not list is left untouched, not deleted — delete one in the dashboard.","type":"array","items":{"$ref":"#/components/schemas/UpsertVariantDto"}}},"required":["sku","name","category","price","cost_price"]},"UpdateStockDto":{"type":"object","properties":{"stock":{"type":"number","example":12,"minimum":0,"description":"The stock level after this call — an absolute count, not a delta. Stating the level makes a retry harmless; a delta applied twice is wrong twice."}},"required":["stock"]}}}}