1. 概述与基础地址
开放 API 用于让您的自有系统(ERP、BI、财务中台、连锁总部系统等)以机器对机器(M2M)方式安全读取或写入咖莱翼各业务数据。所有接口均为 HTTPS,返回统一 JSON 信封。
生产基础地址(Base URL):
https://open.calay.com.cn/api/open/v1
open.calay.com.cn 即接入入口,所有开放接口均挂在 /api/open/v1 前缀下。测试/演示环境路径带 /v050 前缀(如 https://test.calay.com.cn/v050/api/open/v1),字段与行为完全一致。能力范围
利润中心(读/写) 人事中心(读) 报表中心(读) 渠道订单(读) 共享商业(读)
2. 对接准备与步骤
调用接口前请先完成以下步骤,与通用约定一起阅读。
由租户 owner/admin 在控制台「开放平台」创建密钥,勾选所需 scope(如 profit:read、report:read)。
创建后一次性获得 client_id(calay_ 前缀)与 client_secret(sk_ 前缀)。secret 仅展示一次,请妥善保存。
阅读下方「通用数据格式约定」「鉴权方式」「响应格式」,了解金额 fen / 比率 bp 规则。
用 client_id+secret 换取 JWT,或用 sk_ 直连,按接口目录调用业务接口。
3. 通用数据格式约定
为确保跨语言(Java / Python / Node / PHP 等)解析一致,平台对数据格式做如下统一约定:
3.1 编码与内容类型
- 请求与响应统一使用
UTF-8编码。 - 请求体固定为
application/json(除 GET 查询外),请勿使用表单提交。 - 时间字段:会计月用
yyyy-MM(如2026-09);订单支付时间等时间戳用yyyy-MM-dd HH:mm:ss,不带毫秒。
3.2 金额单位:一律以「分」整数返回 _fen
_fen 结尾,例如 revenue_fen、cost_fen、profit_fen、amount_fen、total_profit_fen。换算:
元 = fen / 100。例如 revenue_fen = 123456 表示 1234.56 元。写入时同样传「分」整数(如营收录入传
amount_fen: 123456)。请勿使用浮点直接运算或 toFixed 做金额换算,避免精度丢失(如 1.005 元会被误算为 1.00 元)。3.3 比率单位:一律以「基点 bp」整数返回
_bp 结尾,例如 gross_margin_bp、dividend_bp、payback_multiple_bp。换算:
百分比 = bp / 100。例如 gross_margin_bp = 3500 表示毛利率 35.00%;dividend_bp = 4000 表示分红比例 40.00%。payback_multiple_bp = 10000 表示回本倍数 1.00 倍。3.4 标识型字段(ID)与精度
id/store_id/org_id/tenant_id等标识字段为整型或字符串,请原样透传,不要在前端用浮点解析(如 JavaScript 的Number()对超长整型会丢失精度,建议以字符串处理或保持原类型)。- 枚举/状态字段(如
status、doc_type、channel)为小写字符串,取值见各接口说明。
3.5 分页约定(当前版本)
为降低接入复杂度,当前版本多数接口返回全量匹配数据;部分高频写后查询接口采用「最近 N 条」截断:
| 接口 | 返回策略 |
|---|---|
GET /channel/orders | 返回该租户最近 200 条渠道订单(可按 /:channel 过滤单一渠道),无游标分页 |
GET /sb/bills(不带 store_id/month) | 返回最近 100 条结算单 |
| 其余列表/详情接口 | 返回全部匹配行(数据量可控,一般无需分页) |
page/pageSize 游标分页,将以响应中新增 page/pageSize/total 字段为准,本文档同步更新。4. 鉴权方式
每个请求必须在 Authorization 头携带 Bearer 令牌。支持两种获取/携带方式:
方式 A:OAuth2 client_credentials(推荐服务端调用)
用 client_id + client_secret 调用令牌接口换取短期 JWT(默认 1 小时有效),再以 JWT 调业务。JWT 可服务端缓存复用,避免每次请求都换取。
# 1) 换取令牌
curl -X POST https://open.calay.com.cn/api/open/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "calay_a6856eec8010e5fa87d93084",
"client_secret": "sk_336a257b366abb9a8410c502099f1761fe38fb3d61d4e297"
}'
# 2) 用返回的 access_token 调业务
curl https://open.calay.com.cn/api/open/v1/profit/dashboard \
-H "Authorization: Bearer <access_token>"
方式 B:PAT 个人访问令牌(sk_ 直连)
直接用密钥本身(sk_ 前缀)作为 Bearer 令牌调用业务,无需先换 JWT,适合简单脚本与快速联调。
curl https://open.calay.com.cn/api/open/v1/profit/dashboard \
-H "Authorization: Bearer sk_336a257b366abb9a8410c502099f1761fe38fb3d61d4e297"
5. 请求头与公共参数
| 请求头 | 必填 | 说明 |
|---|---|---|
Authorization | 是 | Bearer <token>,token 为方式 A 换取的 access_token 或方式 B 的 sk_ 明文密钥 |
Content-Type | 是(POST) | 固定 application/json; charset=UTF-8 |
Accept | 否 | 建议 application/json |
公共路径/查询参数
:month/month:会计月份,格式YYYY-MM(如2026-09)。:store_id/store_id:门店 ID(整型,原样透传)。:org_id:组织编码,用于按组织定位门店利润。:months:趋势回溯月数(整型,上限 24)。
6. 响应格式(统一信封)
所有接口返回统一结构:
| 字段 | 类型 | 说明 |
|---|---|---|
code | int | 0 = 业务成功;1 = 业务失败 |
bizCode | string | 业务码,如 SUCCESS / APIKEY_INVALID / SCOPE_DENIED |
message | string | 人类可读描述 |
data | object/array | 业务数据载体(各接口字段见下文) |
tenant_id | string | 数据所属租户 |
// 成功
{
"code": 0,
"bizCode": "SUCCESS",
"message": "ok",
"data": { ... },
"tenant_id": "tenant_xxxx"
}
// 失败
{
"code": 1,
"bizCode": "APIKEY_INVALID",
"message": "密钥凭证错误或已吊销",
"data": null,
"tenant_id": null
}
code 配合判断:鉴权/授权类失败通常返回 401/403,参数错误返回 400;请同时检查 HTTP 状态码与 bizCode。7. 错误码(bizCode)
业务失败时在响应 bizCode 返回,HTTP 状态码见下表:
| bizCode | HTTP | 含义 | 处理建议 |
|---|---|---|---|
PARAM_INVALID | 400 | 必需参数缺失或格式错误 | 检查请求体字段是否完整、类型是否正确 |
UNSUPPORTED_GRANT | 400 | grant_type 非 client_credentials | 固定传 client_credentials |
AUTH_REQUIRED | 401 | 未携带 Authorization 头 | 补充 Authorization: Bearer <token> |
APIKEY_INVALID | 401 | client_id / client_secret 不匹配 | 核对密钥,或重新创建 |
APIKEY_REVOKED | 401 | 密钥已被吊销 | 在控制台重新生成密钥 |
APIKEY_EXPIRED | 401 | 密钥已过期 | 在控制台延长有效期或重新生成 |
SCOPE_DENIED | 403 | 当前密钥 scope 不包含该资源 | 在控制台为密钥追加对应 scope |
SB_PERMISSION_DENIED | 403 | 角色不满足接口所需权限(如写接口需 write) | 使用具备 write 角色的密钥,或检查 role |
NOT_FOUND | 404 | 资源不存在(如门店/月份无数据) | 确认参数 org_id/store_id/month 正确 |
STORE_NOT_FOUND | 404 | 门店不存在或无权限 | 确认 store_id 属于本租户 |
STORE_PROFIT_NOT_FOUND | 404 | 该门店该月无利润数据 | 先录入营收/成本再查询 |
CHANNEL_UNSUPPORTED | 400 | 渠道不支持(仅 meituan/eleme/jd/taobao) | 使用受支持渠道枚举 |
PAYLOAD_TOO_LARGE | 413 | 请求体超出最大允许大小 | 拆分批量请求 |
REQUEST_TIMEOUT | 408 | 请求体读取超时(慢速) | 提升网络或减小包体 |
8. 权限 Scope 矩阵
密钥创建时按以下 scope 子集授权,调用对应接口时由网关强制校验:
| Scope | 说明 | 覆盖接口 |
|---|---|---|
profit:read | 利润中心只读 | dashboard / stores / store profit / profit report / 趋势 |
profit:write | 利润中心写入 | 创建门店 / 营收录入 / 成本录入 |
hr:read | 人事中心只读 | hr dashboard / employees |
report:read | 报表中心只读 | 利润趋势 / 月度利润 / 人事薪资 / 账单发票 |
channel:read | 渠道订单只读 | channel orders |
sb:read | 共享商业只读 | sb agreements / bills / snapshots |
/keys 系列)不走 scope 体系,需控制台登录且具备 config 权限(owner/admin)。9.1 令牌换取
请求体参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
grant_type | string | 是 | 固定 client_credentials |
client_id | string | 是 | 控制台生成的客户端 ID(calay_ 前缀) |
client_secret | string | 是 | 密钥明文(sk_ 前缀) |
api_key | string | 否* | 也可用 sk_ 明文直接换令牌(*与 client_secret 二选一) |
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
access_token | string | JWT,用于后续业务调用的 Bearer 令牌 |
token_type | string | 固定 Bearer |
expires_in | int | 有效期(秒),默认 3600 |
scope | string | 该密钥被授予的 scope(逗号分隔字符串) |
{
"code": 0,
"bizCode": "SUCCESS",
"message": "ok",
"data": {
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "profit:read,report:read"
},
"tenant_id": "tenant_xxxx"
}
9.2 密钥管理(控制台内操作)
config 权限(owner/admin),不走开放 scope 体系。密钥明文仅创建时返回一次。| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 否 | 密钥备注名(≤60 字) |
access | string | 否 | read(默认,仅读)/ readwrite(读写数据,不含 config) |
scopes | array | 是 | scope 子集,如 ["profit:read","report:read"] |
响应 data:client_id、client_secret(仅创建时明文返回一次)、role、scopes、expires_in。
响应 data:items(数组,含 id/client_id/name/role/scopes/status/created_at/last_used_at/expires_at)、total。
响应 data:revoked: true。
9.3 利润中心(scope: profit:read / profit:write)
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
month | string | 统计会计月 YYYY-MM |
stores | array | 各门店汇总,元素见下 |
total_profit_fen | int | 全部门店净利润合计(分) |
total_gross_margin_bp | int | 整体毛利率(基点,/100=%) |
stores[] 元素:id(int)、name(string)、org_id(string|null)、revenue_fen(int)、cost_fen(int)、profit_fen(int)、gross_margin_bp(int)。
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
total | int | 门店总数 |
list | array | 门店数组 |
list[] 元素:id(int)、name(string)、org_id(string|null)、bound_months(int,已绑定利润的月数)。
路径参数
| 参数 | 说明 | 示例 |
|---|---|---|
org_id | 组织/门店编码 | org_1001 |
month | 会计月份 YYYY-MM | 2026-09 |
响应 data 字段(单对象)
| 字段 | 类型 | 说明 |
|---|---|---|
store_id | int | 门店 ID |
revenue_fen | int | 营收(分) |
cost_fen | int | 成本(分) |
profit_fen | int | 净利润(分) |
gross_margin_bp | int | 毛利率(基点) |
路径参数
| 参数 | 说明 | 示例 |
|---|---|---|
store_id | 门店 ID | 1 |
month | 会计月份 YYYY-MM | 2026-09 |
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
store | object | 门店主记录(含 id/name/org_id 等) |
profit | object | {revenue_fen,cost_fen,profit_fen,gross_margin_bp} |
revenue | array | 按渠道营收汇总,元素 {channel, s}(s 为分) |
costItems | array | 成本明细,元素 {cost_type, source, s}(s 为分) |
请求体参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 门店名称 |
org_id | string | 否 | 组织编码 |
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | int | 新建门店 ID |
name | string | 门店名称 |
org_id | string|null | 组织编码 |
请求体参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
store_id | int | 是 | 门店 ID |
month | string | 是 | 会计月份 YYYY-MM |
channel | string | 否 | 渠道标识,默认 offline |
amount_fen | int | 是 | 营收金额(分,整数) |
响应 data 字段(重算后门店利润)
| 字段 | 类型 | 说明 |
|---|---|---|
revenue_fen | int | 累计营收(分) |
cost_fen | int | 累计成本(分) |
profit_fen | int | 净利润(分) |
gross_margin_bp | int | 毛利率(基点) |
请求体参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
store_id | int | 是 | 门店 ID |
month | string | 是 | 会计月份 YYYY-MM |
cost_type | string | 是 | 成本类型(如 LABOR/RENT/MATERIAL 等) |
amount_fen | int | 是 | 成本金额(分,整数) |
source | string | 否 | 来源,默认 manual |
响应 data 同营收接口(重算后门店利润四字段)。
9.4 人事中心(scope: hr:read)
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
employee_count | int | 员工总数 |
monthly_payroll_fen | int | 当月薪资合计(分) |
响应 data(数组,无外层包装)
| 字段 | 类型 | 说明 |
|---|---|---|
id | int | 员工 ID |
org_id | string|null | 所属组织 |
name | string | 姓名 |
post_id | int|null | 岗位 ID |
status | string | 状态,如 active |
data 直接为数组(非 {list:[]} 包裹),请勿按对象解析。9.5 报表中心(scope: report:read)
| 参数 | 说明 | 示例 |
|---|---|---|
store_id | 门店 ID | 1 |
months | 回溯月数(≤24) | 12 |
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
store_id | int | 门店 ID |
months | int | 实际回溯月数 |
list | array | 按月升序序列,元素 {month, revenue_fen, cost_fen, profit_fen, gross_margin_bp} |
| 参数 | 说明 | 示例 |
|---|---|---|
months | 回溯月数(≤24) | 12 |
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
months | int | 实际回溯月数 |
list | array | 全部门店合并净利润趋势,元素 {month, total_profit_fen} |
| 参数 | 说明 | 示例 |
|---|---|---|
month | 会计月份 YYYY-MM | 2026-09 |
响应 data 字段(二维表)
| 字段 | 类型 | 说明 |
|---|---|---|
month | string | 会计月 |
header | array | 列标题:["门店ID","门店名称","营收(元)","成本(元)","净利润(元)","毛利率(%)"] |
rows | array | 数据行(数组的数组),末行为「合计」 |
| 参数 | 说明 | 示例 |
|---|---|---|
month | 会计月份 YYYY-MM | 2026-09 |
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
month | string | 会计月 |
header | array | ["员工姓名","岗位","月份","底薪(元)","奖金(元)","合计(元)"] |
rows | array | 数据行(数组的数组) |
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
header | array | ["发票ID","套餐","金额(元)","状态","到期","支付时间","创建时间"] |
rows | array | 数据行(数组的数组),金额已为「元」字符串 |
9.6 渠道订单(scope: channel:read)
渠道(外卖/第三方平台)订单列表。支持按渠道过滤:/api/open/v1/channel/orders/meituan。返回该租户最近 200 条(单一渠道同样上限 200)。
响应 data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
total | int | 返回条数(≤200) |
list | array | 订单数组 |
list[] 元素关键字段:id(int)、store_id(int)、channel(string,如 meituan/eleme/jd/taobao)、order_id_ext(string,平台订单号)、paid_at(string|null,支付时间)、amount_fen(int,用户实付,分)、platform_fee_fen(int,平台佣金,分)、item_json(string,原始订单 JSON,体积较大建议按需解析)、created_at(string)。
9.7 共享商业(scope: sb:read)
共享商业投资协议列表(data 直接为数组)。
响应 data[] 元素字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | int | 协议 ID |
store_id | int | 关联门店 ID |
investor_name | string | 投资人/协议名称 |
principal_fen | int | 本金(分) |
dividend_bp | int | 分红比例(基点) |
payback_multiple_bp | int | 回本倍数(基点,10000=1 倍) |
region_fen | int | 区域分润(分) |
status | string | enabled / disabled |
created_at | string | 创建时间 |
共享商业分红结算单列表。可按门店/月份过滤:/sb/bills/:store_id、/sb/bills/:store_id/:month;不带参数返回最近 100 条。
响应 data[] 元素字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | int | 结算单 ID |
store_id | int | 门店 ID |
month | string | 会计月 |
doc_type | string | NORMAL 正常单 / REVERSE 冲正单 |
version | int | 版本号(冲正后递增) |
net_profit_fen | int | 净利润池(分) |
dividends | string | 各投资人分红明细 JSON 字符串(需解析),元素含 agreement_id/name/amount_fen/region_fen/payback_remaining_fen |
status | string | 如 generated |
created_at | string | 创建时间 |
分红测算快照列表(data 直接为数组)。
响应 data[] 元素字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | int | 快照 ID |
name | string | 快照名称 |
created_at | string | 创建时间 |
10. 多语言对接示例
以下演示从「换取令牌 → 读取利润总览」的完整链路。示例中的 calay_... / sk_... 为占位,请替换为控制台实际密钥。
10.1 cURL
# 步骤 1:换取访问令牌
TOKEN=$(curl -s -X POST https://open.calay.com.cn/api/open/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type":"client_credentials","client_id":"calay_替换为你的ID","client_secret":"sk_替换为你的密钥"}' \
| python -c "import sys,json;print(json.load(sys.stdin)['data']['access_token'])")
# 步骤 2:读取利润总览
curl https://open.calay.com.cn/api/open/v1/profit/dashboard \
-H "Authorization: Bearer $TOKEN"
# 步骤 3(可选):用 PAT 直连,跳过换令牌
curl https://open.calay.com.cn/api/open/v1/profit/stores \
-H "Authorization: Bearer sk_替换为你的密钥"
10.2 Python(requests)
import requests, json
BASE = "https://open.calay.com.cn/api/open/v1"
CLIENT_ID = "calay_替换为你的ID"
CLIENT_SECRET = "sk_替换为你的密钥"
# 1) 换取令牌(建议缓存,到期前复用)
r = requests.post(f"{BASE}/oauth/token", json={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
})
token = r.json()["data"]["access_token"]
headers = {"Authorization": f"Bearer {token}"}
# 2) 读取利润总览
dash = requests.get(f"{BASE}/profit/dashboard", headers=headers).json()
# 金额单位为「分」,换算成元:
for s in dash["data"]["stores"]:
print(s["name"], "净利润(元)=", s["profit_fen"] / 100,
"毛利率(%)=", s["gross_margin_bp"] / 100)
10.3 Node.js(fetch,Node 18+)
const BASE = "https://open.calay.com.cn/api/open/v1";
const CLIENT_ID = "calay_替换为你的ID";
const CLIENT_SECRET = "sk_替换为你的密钥";
// 1) 换取令牌
const t = await fetch(`${BASE}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ grant_type: "client_credentials", client_id: CLIENT_ID, client_secret: CLIENT_SECRET }),
}).then(r => r.json());
const token = t.data.access_token;
// 2) 读取门店列表
const stores = await fetch(`${BASE}/profit/stores`, {
headers: { Authorization: `Bearer ${token}` },
}).then(r => r.json());
console.log(stores.data.list);
11. 限流与配额矩阵
为保障服务稳定,开放接口按下列规则限流(超出返回 HTTP 429,建议指数退避重试):
| 接口 / 类别 | 限流规则 | 维度 |
|---|---|---|
POST /oauth/token | 20 次 / 分钟 | 按客户端 IP |
| 写接口(profit:write 三类) | 600 次 / 分钟 | 按密钥 clientId |
| 读接口(其余全部) | 默认不限流(共享集群保护) | — |
| 请求体大小 | 单请求上限见 PAYLOAD_TOO_LARGE(413) | 按请求 |
- 令牌缓存:JWT 默认 1 小时有效,请在服务端缓存并重用,避免每次请求都换取(换取接口限流 20 次/分钟/IP)。
- 最小权限:为每个对接系统创建独立密钥,仅授予所需 scope;下线时及时吊销。
- 密钥保管:
client_secret等同密码,请勿写入前端代码或公开仓库;泄露立即吊销。 - 错误重试:遇
401/403先检查 scope 与令牌有效性;遇网络错误采用指数退避重试。 - 时间参数:所有月份参数统一
YYYY-MM格式。
12. 常见问题与红线
Q1:金额字段为什么是「分」而不是「元」?
为避免浮点精度问题(如 1.005 元被误算为 1.00 元),平台内部与开放接口统一以最小货币单位「分」整数传递。展示时除以 100 即可。仅「报表中心」的月报/薪资/发票三类接口为便于直接展示,已将金额格式化为「元」字符串(字段名不含 _fen)。
Q2:比率字段为什么是「基点 bp」?
同理,gross_margin_bp(毛利率)、dividend_bp(分红比例)等以「万分之一」整数传递,除以 100 即百分比。避免浮点比较误差。
Q3:支持回调 / Webhook 推送吗?
当前开放 API 为拉取(pull)模式,暂不支持事件回调推送。如需近实时数据,请按业务节奏轮询对应读接口。
Q4:为什么渠道订单没有分页参数?
当前版本渠道订单返回最近 200 条(按渠道过滤同理),未做游标分页。如需全量历史,请在控制台导出或联系技术支持。