cashod
开发者 · API v1

把您自己的系统接入 Cashod。

通过 Cashod API,您的系统可以把 COD 订单推送到 Cashod、读取配送状态,并保持商品目录和库存同步。这是一个 JSON HTTP API——无需 SDK。

Base URLhttps://api.cashod.ma/api/v1
创建订单
$ 
响应
身份验证

一个密钥,一个店铺,只开放您勾选的权限范围。

在 Cashod 的 设置 → 开发者 中创建密钥。密钥只在创建时显示一次,之后无法找回——如果丢失,请吊销该密钥并重新创建。

每次请求都以 Bearer 令牌的形式发送:

Authorization
Authorization: Bearer csk_live_…
一个密钥只属于一个店铺。

它只能查看和修改该店铺的数据,因此您无需传入店铺 ID——密钥本身已经指明了店铺。运营多个店铺的商家需为每个店铺分别创建密钥。

权限范围

每个密钥还带有创建时勾选的 权限范围(scopes)。写权限同时包含对应的读权限,因此能创建订单的密钥也能读回订单。给报表脚本一个只读密钥,即使泄露,也无法创建或取消任何内容。

  • orders:read读取订单及其配送状态
  • orders:write创建订单——同时授予 orders:read
  • products:read读取商品目录
  • products:write创建和更新商品——同时授予 products:read
  • stock:write设置变体库存数量
快速开始

三步创建您的第一笔订单。

  1. 01
    创建密钥

    在 Cashod 中打开 设置 → 开发者,勾选所需的权限范围并复制密钥——它只会显示一次。

  2. 02
    发送订单

    将订单 POST 到 /v1/orders,并带上您自己的 external_id,这样重试的请求永远不会变成第二个包裹。

  3. 03
    跟踪配送

    随着物流推进,从 /v1/orders/{id} 读取 status、confirmation_status 和 tracking_number。

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 } ]
  }'
接口

接口不多,一个下午就能上手。

所有接口都位于 https://api.cashod.ma/api/v1 之下。

POST/v1/orders

从您的结账流程创建订单。带上 external_id 即可安全重试。

权限范围orders:write
GET/v1/orders/{id}

读取单笔订单:配送状态、确认状态和运单号。

权限范围orders:read
GET/v1/orders

按时间倒序列出订单,可按 created_after 筛选,使用游标分页。

权限范围orders:read
POST/v1/products

按 SKU 创建或更新商品,包括变体和图片。

权限范围products:write
PATCH/v1/products/{productId}/variants/{variantId}/stock

将变体库存设置为一个绝对数量。

权限范围stock:write
GET/v1/openapi.json

机器可读的 OpenAPI 文档,API 参考即据此生成。

AI 助手

用 AI 助手来开发

在用 AI 编程助手?直接复制这段提示词,不必手动转述本页内容。提示词中不含任何密钥:它生成的代码会从 CASHOD_API_KEY 环境变量读取您的密钥。

粘贴到 ChatGPT、Claude、Cursor 或 Lovable 中。它包含本页的规则和 OpenAPI 文档链接,助手会针对您的技术栈编写集成代码。

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.
指南

字段列表不会告诉您的事。

创建订单

用 SKU 标识每件商品——变体 SKU 或商品 SKU 均可,取决于您按哪个销售。不传 unit_price 就使用目录价格;传了则以它为准,落地页做促销时正需要这样。

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 } ]
  }'

幂等性——请发送 external_id

把您自己的订单 ID 作为 external_id 发送,调用就可以安全重试。如果相同的 external_id 再次到达,Cashod 会返回已创建的订单,而不会再创建一笔:

  • 201 Created——已创建一笔新订单。
  • 200 OK——该 external_id 已有订单,原样返回。

这比看上去更重要。超时的请求通常其实已经被处理了;如果没有 external_id,您的重试就会变成第二笔真实订单,发给真实的物流公司,客户会收到两个包裹。

读取配送状态

订单包含 status(配送)、confirmation_status(确认电话),发货后还有 tracking_number。

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

如需跟踪大量订单,请用 GET /v1/orders 列出并按 created_after 筛选。轮询频率不要超过每分钟一次;状态按物流的节奏变化,更频繁的轮询没有任何好处。

创建和更新商品

一个以 sku 为键的接口:未知的 SKU 会创建商品,已知的则更新。因此您可以定时推送整个商品目录,无需先检查哪些已存在。

  • 201 Created——这是一个新 SKU。
  • 200 OK——已存在该 SKU 的商品,并已更新。
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 }
    ]
  }'

变体按 SKU 匹配,未列出的变体保持不变——绝不会被删除。变体背后通常有订单记录,所以一次恰好漏掉某个变体的同步不能把它毁掉。如需删除变体,请在 Cashod 后台操作。

图片 的规则正好相反:不传 images 字段,现有图片保持不变;传入列表则以该列表为准;传 [] 则清空。

cost_price 为必填项。Cashod 据此计算 COGS 和利润,没有成本价的商品在它出现的每笔订单中都会显示 100% 利润率。更新时,一旦该商品已有采购入库的库存,此字段将被忽略——否则会改写您已持有库存的成本。

通过此接口设置变体的 stock 还需要 stock:write 权限。缺少该权限时调用会被拒绝,而不是悄悄保存除库存以外的所有内容——这样目录密钥就始终只是目录密钥。

有两种冲突值得处理:店铺中已有其他商品使用了该 名称(名称在店铺内唯一,与 SKU 分开校验),或者您的某个变体 SKU 属于另一个商品。两者都会返回 409 conflict 并指明冲突项。

保持库存同步

库存设置的是 绝对数量,而不是增减量。发送您希望该变体拥有的数量——这样重试的调用不会重复生效,而 -1 却会悄无声息地重复扣减。

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 }'

分页

列表接口使用游标分页,按时间倒序。响应包含 data、has_more 和 next_cursor。将该游标作为 ?cursor= 传回即可获取下一页。每页默认 50 条,最多 100 条。

之所以用游标而不是页码,是因为订单源源不断地进来:使用偏移量时,每来一笔新订单,后面所有页都会顺延一位,客户端遍历历史时就会悄悄漏掉一些行、又重复读取另一些。

错误

所有错误的结构都相同:

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

请根据 type 判断,而不是 message 的措辞——措辞可能会改进,type 不会变。

400invalid_request请求内容有误,message 会指明具体字段。
401authentication_error密钥缺失、未知或已吊销。
403permission_error密钥缺少所需权限,或账户订阅未激活。
404not_found该密钥所属店铺中 不存在此订单或商品。
429rate_limit_exceeded稍等片刻后重试。
500server_error我们这边的问题。可以安全重试;如果您发送了 external_id,重试两次也安全。

速率限制

0每个密钥每分钟请求数

每个密钥每分钟 120 次请求。限制按密钥而不是按 IP 地址计算,因此同一托管环境下的其他客户端不会占用您的额度。

版本管理

版本号在路径中:/v1。同一版本内,响应可能新增可选字段,因此请做好容错解析,忽略无法识别的字段。任何会破坏现有集成的改动都会放到新版本中。

API 参考

每个接口,每个字段。

直接由后端自身的 OpenAPI 文档生成,每次部署都随代码同步更新,永不过时。

openapi.json
Cashod 开发者 API:订单、库存与 Webhook