跳转至文档正文
商观开发者文档
SHOP DATA API v2.0

让经营数据,
有序连接。

从独立采集到完整看板。仅导入每日数据,周、月及 MTD 自动计算,
将每一项经营指标准确送达。

传输协议HTTP / JSON
数据粒度日 · 周 · 月
访问方式内部使用 · 无需认证

先确认数据口径成交金额为正向,退款金额为逆向(正数),GMV = 成交金额 − 退款金额。每项指标独立更新,省略字段会保留原值。

基本约定

  • 服务地址示例:http://localhost:3000。部署后替换为实际内部地址。
  • 内部使用:所有接口无需登录、Cookie、Authorization 或 API Key。
  • JSON 写入请求必须设置 Content-Type: application/json。
  • 经营日期按北京时间(Asia/Shanghai);日期格式 YYYY-MM-DD。
  • collectedAt 为工具实际取到指标值的时间,必须包含时区,如 2026-10-19T09:30:00+08:00。
  • 金额均为人民币元,不是分。推荐十进制字符串以保留精度;不能带千分位、货币符号或 %。
  • 单次 1–500 条记录、请求体不超过 1 MiB;任何校验或写入失败,整批回滚。
  • 页面 / 为看板;/admin 为无需验证的数据管理后台。
  • 在线文档:API 接入指南;Markdown 原文:GET /api/v1/docs?format=markdown;机器可读规范:GET /api/v1/openapi。

1. 店铺列表与维护

GET/api/v1/shops

响应 200:

JSON
{"shops":[{"account":"rakuten-main","id":"256730481","name":"日系运动店"}]}

PUT/api/v1/shops

创建或按店铺ID更新账号、店铺名称。店铺ID是唯一键,与账号无关;同一账号可以对应多家店铺,店铺名称也不要求唯一。店铺ID一旦关联数据,不应更改。

JSON
{"account":"rakuten-main","id":"256730481","name":"日系运动店"}

响应 200:{"shop":{"account":"rakuten-main","id":"256730481","name":"日系运动店"}}。

三个字段均必填。account 为账号,1–200 字符;id 为店铺ID,1–100 位字母、数字、下划线、点或连字符;name 为店铺名称,1–200 字符。首尾空白不接受,账号和 ID 均传字符串以保留前导零。账号不是登录凭据,接口不接收密码。

迁移前已有店铺的账号可能返回 null,后台显示 —;可编辑或导入补齐。迁移保留原店铺ID和经营数据,不会生成虚构账号。

DELETE/api/v1/shops?id=256730481

删除此店铺和它的所有经营数据。不存在返回 404 NOT_FOUND。已收录的采集请求历史保留,不会因重试旧请求恢复已删除数据。

批量删除使用 DELETE /api/v1/shops,设置 Content-Type: application/json,请求体为 {"ids":["shop-a","shop-b"]}。每次 1–500 项,重复 ID 自动去重,整批校验并在同一事务中删除;响应包含 deletedShops 实际删除店铺数。已不存在的店铺可安全重试。

POST/api/v1/shops/import

批量新增或按店铺ID更新账号、店铺名称。后台「店铺管理 → Excel 导入店铺」也使用此接口。

JSON
{
  "shops": [
    {"account": "rakuten-main", "id": "001234", "name": "运动旗舰店"},
    {"account": "rakuten-main", "id": "shop-002", "name": "生活优选店"}
  ]
}
字段必填说明
shops是1–1000 个店铺对象;同一批次不能重复 ID
shops[].account是账号,1–200 字符;多个店铺可使用相同账号
shops[].id是字符串,规则同单店维护;文本 ID 保留前导零
shops[].name是1–200 字符,已有 ID 更新账号与名称

响应 200:

JSON
{"total":2,"inserted":2,"updated":0}

整批在事务内写入;任一行不合法返回 422,不会部分导入。请求体最多 1 MiB。相同 ID 不会重复创建,重复提交会按当前库中已有 ID 统计为更新。

Excel 表头匹配

后台支持 .xlsx 文件(最多 5 MiB、每批 1000 家店铺),可下载空白模板、选择工作表并预览。自动扫描前 30 行寻找表头,列顺序不限,忽略空格与大小写;多余列不导入。

目标字段可识别表头
账号账号、帐号、店铺账号、店铺帐号、account、shopAccount、storeAccount
店铺 ID店铺 ID、店铺编号、店铺编码、店铺号、shopId、storeId、id
店铺名称店铺名称、店铺名、店名、shopName、storeName、name

模板列顺序为 账号、店铺id、店铺名称,三列表头均需匹配。前导零或较长的账号、ID 必须在 Excel 中设为文本,避免数字精度损失。重复 ID(即使账号不同)、缺失字段或非法值会标出行号,修正全部错误后才能确认导入。旧版 .xls 请先另存为 .xlsx。

2. 分项/批量录入指标

POST/api/v1/ingestion

JSON
{
  "requestId":"455ff1f4-8250-4c71-9599-833a09a0c7b1",
  "source":"golden-eye-crawler",
  "records":[
    {
      "shopId":"256730481",
      "shopAccount":"rakuten-main",
      "shopName":"日系运动店",
      "period":{"grain":"day","startDate":"2026-10-18","endDate":"2026-10-18"},
      "collectedAt":"2026-10-19T09:30:00+08:00",
      "metrics":{"gmv":"12345.67"}
    }
  ]
}
字段必填说明
requestId是UUID;每个新批次生成一次。网络重试必须使用相同 ID 和原请求体
source是1–100 字符的来源名,例如 golden-eye-crawler
records是1–500 条;允许混合店铺、不同日期及指标;只接收日数据
shopId是稳定店铺 ID,格式同店铺维护接口
shopAccount新店铺需要店铺账号,1–200 字符;已有店铺可省略,省略时保留原账号
shopName新店铺需要已有店铺可省略;提供时更新店铺名称;新店铺自动创建
period是周期对象,见下表
collectedAt是含时区的 ISO 8601 时间,最多毫秒;不得超出服务端当前时间 5 分钟
metrics是至少一个支持的指标;省略表示不更新,不是清空

未定义的字段会返回 422,避免拼写错误被静默忽略。

只导入日数据

写入及删除仅支持 grain: "day",startDate 与 endDate 必须相同,年份支持 2000–2099。数据日期必须早于 collectedAt 对应的北京时间日期,不能录入当天或未来数据。

原先导入 week / month / mtd 的请求现在返回 422。采集器只需导入每日五项指标,周、月、MTD 均自动计算,无需重复上传。 修改或删除某天后,相关计算结果及环比立即随下一次查询重算,不保存独立的周/月/MTD 观测值,不读取旧快照。

周、月及 MTD 的计算口径

报表计算范围环比参照
day当天上一自然日
week周一至周日上一周
month自然月第一天至最后一天上一自然月
mtd月初至指定日期,本月默认到北京时间昨天上月相同日期范围,超出上月天数时取上月月末

所有计算排除北京时间当天及未来数据,未结束的周/月只使用截至昨天的已有日记录。缺少日期不补零,金额和访客累加已有值;没有任何输入时返回空值。MTD 无上月对应数据时不显示环比,不回退到整月比较。环比分母为零时返回空值。

  • 正向 = 累计成交金额,逆向 = 累计退款金额,GMV = 正向 − 逆向。
  • 每日成交客户数 = 当日成交金额 / 当日客单价。
  • 每日商品访客数 = 当日成交客户数 / 当日商品成交转化率。
  • 周/月/MTD 客单价 = 累计成交金额 / 累计成交客户数。
  • 周/月/MTD 商品成交转化率 = 累计成交客户数 / 累计商品访客数。
  • 店铺访客数直接逐日累加,表示累计人次,不代表跨日去重人数。

推算人数保留小数,不平均每日比率,不用店铺访客数替代商品访客数,不用父单量替代成交客户数。任一天的必要输入不足或分母为零时,相应累计比率为空;成交金额为零时客户数计为零。只有一个有效日输入时保留该日来源客单价和商品转化率。

销售构成按店铺展示所选周及此前 4 周,末尾追加 MTD;报告周期仅选择周。历史月份 MTD 统计月初至所选周结束日。本月按昨天计算,表格和 CSV 不显示 MTD 截至日期。计算值附带已录入天数,CSV 同步携带说明。

指标名称与口径

metrics 字段对应抓取值范围与精度
gmv成交金额,正向、未扣退款(沿用原字段名)非负数,最多 2 位小数
refundAmount退款金额非负数,最多 2 位小数;无退款传 0
aov客单价非负数,最多 6 位小数,保留抓取原值
visitors店铺访客数非负整数,当天去重访客数
productConversionRate商品成交转化率0–1 小数,最多 8 位小数;2.5% 传 0.025
parentOrders父单量(可选扩展)非负整数;保留原始父单量,不参与客户数推算,未抓取可不传

数值最多 15 位整数。null、空字符串、NaN、负退款、带 % 的字符串均不接受。零是有效数据,与缺失不同。

看板正向 = 采集 gmv,逆向 = 采集 refundAmount(正数),GMV = 正向 − 逆向。 退款可以超过成交金额,此时看板 GMV 为负。成交或退款任一缺失,GMV 显示 —;无退款必须明确传 0。

金额字段口径已更新:写入接口及 /api/v1/metrics、/api/v1/records 中的 metrics.gmv 是原始成交金额;/api/dashboard 的 metrics[].gmv、daily[].gmv 是扣退款后的计算结果。现有采集观测值按成交金额解释,不改写原始值或请求历史。若采集器此前提交的是已扣退款的净额,需按新口径重新采集,并用新的 requestId、真实的新 collectedAt 更新。

原始日客单价和商品成交转化率保留来源值;跨日、跨店合计采用上面的推算公式,不直接平均。推算客户数不会写入父单量。

五个任务分别抓取的流程

  1. 成交金额任务提交 { "gmv": "12345.67" }。
  2. 退款任务提交 { "refundAmount": "345.67" }。
  3. 客单价任务提交 { "aov": "246.9134" }。
  4. 访客任务提交 { "visitors": 5000 }。
  5. 转化率任务提交 { "productConversionRate": "0.025" }。

每个任务使用相同的 shopId + grain=day + startDate=endDate,各自生成 requestId 和实际 collectedAt。可以只交一项,也可以一次交齐五项。最终组合为一条完整店铺周期记录,各任务不会清空其他任务的指标。

同一请求允许一个店铺周期出现多次,但同一个指标不能重复;重复指标返回 422。首次创建同一店铺时,批次里需提供 shopAccount 和 shopName,可以位于该店铺的不同记录;同一请求内同店铺的账号和名称必须各自一致。已有店铺只传 shopId 和指标即可,省略账号、名称会保留现值。

成功响应

JSON
{
  "requestId":"455ff1f4-8250-4c71-9599-833a09a0c7b1",
  "records":1,
  "metrics":1,
  "inserted":1,
  "updated":0,
  "stale":0,
  "unchanged":0,
  "replayed":false
}

统计单位:records 是请求记录数;其余 inserted / updated / stale / unchanged 是指标数量,它们之和等于 metrics。

  • 同 requestId、相同规范化内容重试:返回首次结果,replayed=true,不重复入库。
  • 同 requestId、不同内容:409 IDEMPOTENCY_CONFLICT,不写入。
  • 更新采用每项指标自身的 collectedAt,不是请求到达顺序。
  • 新采集时间更新旧值;较旧时间只计入 stale,不覆盖当前值。
  • 相同时间、相同值计入 unchanged;相同时间、不同值返回 409 TIMESTAMP_CONFLICT,整批回滚。
  • source 只标识来源,不作为隔离键;多个工具写同一店铺周期指标时,以较新 collectedAt 为准。
  • 后台编辑与抓取工具使用同一条更新链路。删除数据不会删除请求历史;若需要重新录入已删除值,使用新的 requestId。

3. 查询某店铺某周期的录入完整度

GET/api/v1/metrics?shopId=256730481&grain=day&startDate=2026-10-18&endDate=2026-10-18

JSON
{
  "shopId":"256730481",
  "period":{"id":"2026-10-18:2026-10-18:day","grain":"day","startDate":"2026-10-18","endDate":"2026-10-18"},
  "metrics":{
    "gmv":{"value":"12345.67000000","collectedAt":"2026-10-19T01:30:00.000Z","source":"golden-eye-crawler","requestId":"455ff1f4-8250-4c71-9599-833a09a0c7b1"}
  },
  "complete":false,
  "missingMetrics":["refundAmount","aov","visitors","productConversionRate"]
}

读取 value 返回十进制字符串,避免浮点精度丢失;返回时间为 UTC。没有记录时返回 200、空 metrics 和全部五项 missingMetrics。parentOrders 不影响 complete。

查询计算后的周、月及 MTD

同一 GET /api/v1/metrics 支持 grain=week|month|mtd,只读计算结果,不创建记录。例如:

GET /api/v1/metrics?shopId=256730481&grain=mtd&startDate=2026-10-01&endDate=2026-10-19

响应中的 calculated: true 表示日数据计算结果;每项 metrics 仍返回 value 字符串,source 为 daily-calculation,没有虚构的 requestId 或采集时间。metrics.gmv 为累计成交金额,顶层 netGmv 为扣退款后的 GMV。growth 包含 GMV、访客、客单价、商品转化率环比,无比较数据或分母为零时为 null。

coverage 包含 recordedDays(已录入天数)、expectedDays(统计范围应有天数,最多到昨天)、completeDays(五项指标齐全的天数)、metricDays(各指标有值天数)。complete 仅在所有应有日期的五项输入齐全且结果可计算时为 true。没有日数据时 metrics 为空、complete 为 false。

日查询返回 calculated: false,保留实际采集时间和来源。写入、分页记录和删除接口只操作 day;不能编辑或删除计算出的周/月/MTD,应修改对应日数据。

4. 分页查询与删除指标记录

GET/api/v1/records?page=1&pageSize=20&shopId=256730481&grain=day

page、pageSize 默认 1、20。page 最大 100000,pageSize 最大 500,界面支持每页 20 / 50 / 100 / 200 / 500 条。shopId、grain 可省略;grain 仅允许 day。默认按开始日期倒序、店铺 ID、粒度排序。可传 sortBy(shopId、shopName、grain、startDate、endDate、gmv、refundAmount、aov、visitors、productConversionRate、missingMetrics)及 sortOrder(asc / desc)。服务端对完整筛选结果排序后分页,数值按数字排序,缺失值始终置底;不支持的字段或方向返回 422。

可选 startDate、endDate(有效的 YYYY-MM-DD,2000–2099)筛选完整落在范围内的记录:记录开始日期不早于 startDate,结束日期不晚于 endDate。起止日期可单独传入,同时传入时开始不能晚于结束。

JSON
{"total":1,"page":1,"pageSize":20,"records":[{"shopId":"256730481","shopName":"日系运动店","periodId":"2026-10-18:2026-10-18:day","grain":"day","startDate":"2026-10-18","endDate":"2026-10-18","metrics":{"gmv":"12345.67000000"},"collectedAt":"2026-10-19T01:30:00.000Z","missingMetrics":["refundAmount","aov","visitors","productConversionRate"]}]}

DELETE/api/v1/records?shopId=256730481&grain=day&startDate=2026-10-18&endDate=2026-10-18

删除这一个店铺周期的全部采集指标。响应 {"deletedMetrics":5}。不存在时返回 {"deletedMetrics":0}。请求历史保留。

批量删除使用 DELETE /api/v1/records,设置 Content-Type: application/json:

JSON
{"records":[{"shopId":"256730481","grain":"day","startDate":"2026-10-18","endDate":"2026-10-18"}]}

每次 1–500 条完整记录标识,重复标识自动去重。整批通过校验后在同一事务中删除,失败全部回滚;响应 deletedMetrics 为实际删除指标数量。后台的全选仅选当前页,切换页码或筛选条件会清空选择。

5. 看板查询

GET /api/dashboard 返回前端经营看板数据。录入成功后刷新看板即可读取;不会缓存旧值,也不会回退到本地示例数据。

未录入的指标显示 —;筛选合计累加全部筛选结果中已录入的值,不受分页影响。全部未录入时显示 —,已录入的零值计为零。合计环比按本期与参照期的已录入 GMV 合计计算,不累加或平均各店环比。日、周、月报告周期由已有日数据自动生成,经营数据默认优先月维度。

前台支持具体起止日期。占比自动计算为当前店铺 GMV / 同期全部店铺已录入 GMV 合计,未录入店铺不参与分母,店铺筛选、搜索和分页不改变分母。所有日期均保留完整指标列;月报与按月查看对比上一自然月,自定义范围对比紧邻的等长上一期。参照期数据不因日期筛选而丢失,并在经营表和导出中保留。

所有周期和自定义范围均按已有日记录计算,UV 为累计人次,客单价和转化率按推算人数计算。分母为零或缺失时不显示误导性的占比或环比。

6. 错误与重试

错误响应统一为:

JSON
{"error":{"code":"VALIDATION_ERROR","message":"提交数据不符合接口约定。","details":[{"path":"records[0].metrics.productConversionRate","message":"比例范围 0–1;2.5% 应传 0.025"}]}}
HTTPcode处理方式
400INVALID_JSON修正 JSON
404NOT_FOUND删除店铺时检查 ID
409IDEMPOTENCY_CONFLICT新内容生成新的 requestId
409TIMESTAMP_CONFLICT核对来源值,以实际新的采集时间提交修正
413PAYLOAD_TOO_LARGE拆分到 1 MiB 以内,每个新批次使用新 requestId
415UNSUPPORTED_MEDIA_TYPE设置 application/json
422VALIDATION_ERROR / UNKNOWN_SHOP修正字段、日期、数值;新店铺提供 shopAccount 和 shopName
503DATABASE_UNAVAILABLE使用原 requestId 和原请求体退避重试

遇到网络超时也使用原 requestId 和原请求体重试,避免服务端已提交但客户端没收到响应造成重复。重试时不要改变 collectedAt。业务校验错误不应自动无限重试。

7. Python 工具调用

仓库提供 scripts/submit-metrics.py(只依赖 Python 标准库)。将请求写到 batch.json:

SHELL
python scripts/submit-metrics.py --base-url http://localhost:3000 --file batch.json

脚本原样发送文件,不补造 ID 和时间;网络错误或 503 最多重试 3 次,始终保留相同请求体。可直接把 HTTP 调用部分集成到抓取程序。

PYTHON
import json, uuid
from datetime import datetime, timezone, timedelta
from urllib.request import Request, urlopen

# 在抓取成功后生成一次并持久化;失败重试复用此 payload。
payload = {
    "requestId": str(uuid.uuid4()),
    "source": "my-crawler",
    "records": [{
        "shopId": "256730481",
        "shopAccount": "rakuten-main",
        "shopName": "日系运动店",
        "period": {"grain": "day", "startDate": "2026-10-18", "endDate": "2026-10-18"},
        "collectedAt": datetime.now(timezone(timedelta(hours=8))).isoformat(timespec="milliseconds"),
        "metrics": {"gmv": "12345.67", "refundAmount": "345.67", "aov": "246.9134", "visitors": 5000, "productConversionRate": "0.025"}
    }]
}
request = Request("http://localhost:3000/api/v1/ingestion",
                  data=json.dumps(payload).encode("utf-8"),
                  headers={"Content-Type": "application/json"}, method="POST")
with urlopen(request, timeout=30) as response:
    print(json.load(response))

8. 数据库结构

全部表位于独立 schema shop_dashboard。

表主键/粒度用途
shopsid(唯一主键)account 账号、id 店铺ID、name 店铺名称;账号与名称不唯一
collection_periodsid;唯一 grain + start_date + end_date仅 day 日周期,数据库约束起止日期必须相同
metric_observationsshop_id + period_id + metric每项指标的当前值、实际采集时间、来源及请求 ID
ingestion_requestsrequest_id幂等去重、原始规范化请求、写入结果和接收时间;保留历史
snapshotsid原报表快照结构,当前无预置数据
periodsid原报表周期,含 MTD/YTD
period_metricssnapshot_id + shop_id + period_id原周期指标、广告与动销 SPU 扩展字段
daily_metricssnapshot_id + shop_id + date原日指标结构
listing_snapshotssnapshot_id + shop_id + period_id京麦/火种上架数量扩展

新抓取工具只需调用采集接口,由接口写入前四张表;无需创建整份快照,也不需直接操作 PostgreSQL。旧快照相关表保留结构但不再参与看板读取。广告、动销/上架 SPU 暂不在这版抓取接口范围内,未录入时展示 —。

数据关系

来源表关系指标表关联字段
shops一家店铺 → 多项周期指标metric_observations.shop_id
collection_periods一个日期 → 多家店铺指标metric_observations.period_id
ingestion_requests一次写入请求 → 多项最新指标metric_observations.request_id

每项指标以 shop_id + period_id + metric 唯一定位,按自身 collectedAt 更新。请求历史单独保存,便于重试去重与追溯。

每日销售按已录入日记录汇总 GMV 和 UV,空数据店铺不使其他店铺的数据变空;合计统计当前范围已录入的日数据,不要求整月录满。每行环比仍读取上一自然日,合计环比对比参照范围内的日数据。UV 合计未跨店、跨日去重。转化率和客单价不直接平均,缺少计算依据时可切换“按店铺明细”查看各店原始值。销售构成、每日销售及后台表格均支持表头排序,先排序后分页,导出沿用前台排序。