Nordlet

← 文档 / 指南

API 规范

请求结构、权限范围、列表查询、金额、错误处理、幂等性与频率限制。

请求结构

每个操作均采用 RPC 风格的 POST 请求:

POST /v1/{module}/{resource}/{action}

模块包括: reference、partners、catalog、sales、purchases、ledger、bank、declarations、files、webhooks、audit。操作动词包括: create、get、update、delete、list,以及领域特定的操作,例如 issue、register、match、apply-advance、generate。

认证与权限范围 (Scopes)

Authorization: Bearer <api key>。API 密钥带有在每个操作中进行检查的权限范围 (Scopes):

  • {module}:read - 获取或列出操作
  • {module}:write - 变更操作
  • {module}:* 或 * - 通配符

缺少权限范围将返回 403 forbidden。密钥缺失或无效将返回 401 unauthorized。

列表查询

所有 list 操作均遵循统一的契约:

{
  "page": 1,
  "pageSize": 50,
  "sort": [{ "field": "createdAt", "dir": "desc" }],
  "filter": [{ "field": "paymentStatus", "op": "eq", "value": "unpaid" }]
}

过滤运算符: eq (等于)、ne (不等于)、contains (不区分大小写的文本匹配,仅限文本字段)、gte (大于或等于)、lte (小于或等于)、in (最多 500 个值的列表中的任意一个)。可排序和可过滤的字段在每个端点中均有白名单限制 (参见 OpenAPI 规范);未知字段将返回 400 错误。在请求中添加 totals: ["field", …] 可对所有匹配行的数值字段求和;当记录带有币种时,totalsByCurrency 会给出按币种代码拆分的相同总和。响应结构为: { rows, total, page, pageSize, totals?, totalsByCurrency? }.

金额与日期

货币金额为最多保留 4 位小数的十进制字符串 ("121.0000"),绝不使用浮点数。增值税 (VAT) 按明细行计算,采用四舍五入 (half-up) 方式。日期格式为 YYYY-MM-DD;时间戳格式为 ISO 8601 UTC。

错误处理

全局采用统一的错误信封结构:

{
  "error": {
    "code": "validation",
    "message": "Request validation failed",
    "requestId": "k1x…",
    "key": "request_invalid",
    "fieldErrors": { "lines.0.unitPriceExclVat": ["Invalid value"] }
  }
}

错误代码包括: validation (400/422,当上传的文件或请求正文过大时为 413)、unauthorized (401)、payment_required (402,试用期或预付额度已到期;错误信息会说明如何继续)、forbidden (403)、not_found (404)、conflict (409)、idempotency_key_reuse (422)、idempotency_in_progress (409)、rate_limited (429)、internal (5xx)。当错误信息属于固定提示信息之一时,key 字段会指明其键名,以便客户端显示其自身的翻译,而 params 字段则包含填充到其中的值 (例如,对于锁定的会计期间,其值为 { "period": "2026-09" })。在报告问题时,请务必提供 requestId。

幂等性

在任何变更性调用中添加 Idempotency-Key: <any string ≤255 chars> 即可实现幂等性:

  • 使用相同的密钥和有效负载重试 -> 将按字节原样重播存储的响应,并带有 x-idempotent-replay: true 标头。
  • 密钥相同但有效负载不同 -> 返回 422 idempotency_key_reuse。
  • 在首次请求仍在处理时发生并发重复请求 -> 返回 409 idempotency_in_progress。
  • 密钥归属于发送它们的 API 密钥或用户,并且在调用所针对的公司范围内有效,因此同一公司的两个集成系统可以使用相同的密钥。在公司成立之前进行的调用以及 account/* 调用,其密钥是按用户隔离的,因此重复调用 account/companies/create 只会创建一个公司。
  • 只有在调用方再次通过相同的权限检查 (权限范围、角色、公司和账单状态) 后,才会返回存储的响应;否则,调用将获取当前的错误信息。
  • 密钥在 24 小时后过期。错误响应 (4xx) 也会被重播;5xx 响应不会被存储,因此重试会重新执行。

频率限制

每个 API 密钥每分钟限制 300 个请求 (基于所提供的凭据;未认证的请求按 IP 地址限制)。收到 429 响应时,请遵循 retry-after 标头。x-ratelimit-* 标头报告当前的额度预算。

Webhooks

事件将被写入事务性发件箱 (transactional outbox),并与变更处于同一数据库事务中。这意味着回滚的文档绝不会发出事件,也不会丢失任何事件。投递机制:

  • 向您的 URL 发送包含事件 JSON 的 POST 请求
  • x-nordlet-timestamp: <unix seconds>,即投递签名的时间
  • x-nordlet-signature-v2: t=<timestamp>,v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>">,使用您的订阅密钥进行签名
  • x-nordlet-signature: sha256=<hex HMAC-SHA256 of the raw body>,为兼容现有接收方而保持不变
  • x-nordlet-delivery 包含投递 ID,同一投递的所有重试均保持该 ID 不变
  • 如果返回非 2xx 响应或重定向,系统将分别在 30 秒、2 分钟、10 分钟、30 分钟、1 小时、3 小时、6 小时和 12 小时后进行重试 (总共约 24 小时),之后该投递将被标记为失败
  • 每个投递由单个服务器一次发送一个,因此运行多个 API 实例不会导致重复发送

在信任有效负载之前,请使用恒定时间比较 (constant-time comparison) 验证 x-nordlet-signature-v2,并拒绝时间戳与您的系统时钟相差超过 5 分钟的投递。由于时间戳是签名文本的一部分,因此捕获的投递无法在稍后使用新的时间戳进行重播。请存储您已处理的 x-nordlet-delivery ID,以便在该时间窗口内忽略重复投递。失败的投递可以通过 webhooks/deliveries/redeliver 重新发送。

审计

每一项变更都会被记录 (actor、action、entity、diff),并且可以通过 audit/list 进行查询。

会计保证

  • 日记账分录保持借贷平衡,这由提交时的延迟数据库触发器 (deferred database trigger) 强制执行,而不仅依靠应用程序代码。
  • 按系列或年度划分的凭证编号连续无间隙,编号分配具备并发安全性。
  • 锁定的会计期间会拒绝任何记账日期落入其中的凭证 (返回 409)。
  • 软删除的文档会保留其审计线索;已发布或已登记的文档无法删除。