取得 Base URL、Partner ID、Secret 及产品授权范围。Secret 只通过双方约定的安全渠道交付。
资产方研发从这里开始
联调前由安小信提供环境域名、Partner ID、Partner Secret 和已授权产品类型。资产方生成批次报文、计算摘要和签名,提交后按逐行结果完成对账。
先固定 JSON 原始请求体,再计算 rowsChecksum、bodyHash 和 HMAC 签名,发送时不得重新序列化。
保存 packageRef;读取 rowResults。只重推失败业务数据,并为新请求生成新的 nonce 和 Idempotency-Key。
本 API 同步完成客户归户、资产、账单、逾期状态和案件头建档,不创建外呼任务,也不会拨打电话。建档后的策略、合规审核和作业由安小信内部流程决定。
一笔资产从哪里进、从哪里出
批次先通过请求级门槛,再按行独立处理。最终从 HTTP 响应和查询接口输出批次结果、案件编号与失败原因。
- 01身份验签确认 Partner、时间戳、nonce 和报文签名
- 02批次校验核对 JSON、数量、rows 摘要及 5000 条上限
- 03客户归户优先用 borrowerRef 关联同一客户的多笔借据
- 04逐行建档每行一个事务,写客户、资产、账单、逾期和案件
- 05结果回执返回 packageRef、caseRef、assetRef 和逐行错误
接口清单
下表路径相对于正式接入时分配的 Base URL。签名必须使用网关转发后应用实际收到的路径,联调时以安小信提供的环境参数为准。
| 方法 | 路径 | 鉴权 | 幂等键 | 用途 |
|---|---|---|---|---|
| GET | /partner/v1/health | 无需 | 无需 | 检查接入服务是否可用,不代表某个 Partner 的授权状态。 |
| POST | /partner/v1/documents/upload-credentials | HMAC | 无需 | 按批次申请合同上传临时凭证;需接入环境启用 OSS。 |
| POST | /partner/v1/asset-packages | HMAC | 必填 | 提交 1~5000 条资产,同步返回批次及逐行建档结果。 |
| GET | /partner/v1/asset-packages/{packageRef} | HMAC | 无需 | 查询批次汇总;异常明细位于 failedRows。 |
| GET | /partner/v1/asset-packages/{packageRef}/rows/{rowRef} | HMAC | 无需 | 查询一条资产的建档状态、caseRef、assetRef 或错误原因。 |
请求和响应编码均为 UTF-8,Content-Type 为 application/json。POST 最大行数为 5000。
原协议还定义了 POST /partner/v1/documents/upload-credentials(申请合同上传凭证)。接口须由接入环境启用并绑定 OSS;未启用时返回 503。字段及上传流程见“合同上传”章节。
请求头与 HMAC 签名
除健康检查外,所有请求均须验签。时间戳允许与服务端相差 300 秒;nonce 只能成功使用一次,用于防止重放。
| 请求头 | 是否必填 | 格式 | 说明 |
|---|---|---|---|
Content-Type | 必填 | application/json | POST 请求使用。 |
X-Partner-Id | 必填 | 字符串 | 安小信分配的资产方身份。 |
X-Timestamp | 必填 | Unix 秒 | 例如 1789383600,与服务端时间差不得超过 300 秒。 |
X-Nonce | 必填 | 唯一随机字符串 | 每次 HTTP 请求重新生成;即使重试也不得复用。 |
X-Signature | 必填 | Base64 | HMAC-SHA256 的 Base64 结果。 |
Idempotency-Key | 资产包 POST 必填 | 业务唯一字符串 | 提交资产包时用于安全重试同一原始请求体;GET 和申请上传凭证不需要。 |
签名原文
5 行内容按顺序用换行符 \n 拼接,末尾不增加换行。
HTTP_METHOD
REQUEST_PATH
X_TIMESTAMP
X_NONCE
SHA256_RAW_BODY_HEX计算规则
- POST bodyHash对实际发送的 UTF-8 原始 JSON 字节计算 SHA-256 小写 hex。
- GET bodyHash对空字节串计算 SHA-256,小写 hex 为
e3b0c442...b855。 - signature
Base64(HMAC-SHA256(partnerSecret, signBase))。 - 路径只使用路径部分,不含域名与 query string;具体值以接入环境说明为准。
import { createHash, createHmac, randomUUID } from "node:crypto";
const method = "POST";
const path = "/partner/v1/asset-packages";
const baseUrl = process.env.ANSON_PARTNER_BASE_URL;
const partnerId = process.env.ANSON_PARTNER_ID;
const partnerSecret = process.env.ANSON_PARTNER_SECRET;
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = randomUUID();
const idempotencyKey = randomUUID();
const rawBody = JSON.stringify(payload); // 发送时必须原样使用 rawBody
const bodyHash = createHash("sha256").update(rawBody, "utf8").digest("hex");
const signBase = [method, path, timestamp, nonce, bodyHash].join("\n");
const signature = createHmac("sha256", partnerSecret)
.update(signBase, "utf8")
.digest("base64");
const response = await fetch(`${baseUrl}${path}`, {
method,
headers: {
"Content-Type": "application/json",
"X-Partner-Id": partnerId,
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Signature": signature,
"Idempotency-Key": idempotencyKey
},
body: rawBody
});
console.log(response.status, await response.json());计算签名后又格式化 JSON、签名路径与服务端实际路径不同、时间戳使用毫秒、重试时复用 nonce,都会返回 401。
POST 请求字段字典
金额统一按“元”传递,推荐使用字符串并保留最多两位小数;日期使用 YYYY-MM-DD,时间使用 ISO 8601。
枚举字段只能填写列出的英文取值,括号内为中文含义,不作为接口传参。还款方式为字符串字段,下方列出建议取值。
批次报文
request body| 字段 | 必填 | 类型 | 约束与说明 |
|---|---|---|---|
schemaVersion | 必填 | string | 固定为 partner-asset-package.v1。 |
batchRef | 必填 | string | 资产方侧批次号;同一 Partner 下应稳定唯一。 |
submittedAt | 必填 | datetime | 资产方生成批次的 ISO 8601 时间。 |
rowCount | 必填 | integer | 1~5000,必须等于 rows.length。 |
rowsChecksum | 必填 | string | 对 JSON.stringify(rows) 的 UTF-8 字节计算 SHA-256 小写 hex。 |
rows | 必填 | array | 资产行数组;每一行独立事务建档。 |
资产行与借据信息
rows[] / loanInfo| 字段 | 必填 | 类型 | 约束与说明 |
|---|---|---|---|
rows[].rowRef | 必填 | string | 批次内唯一的单行编号,用于逐行查询和对账。 |
loanInfo.contractRef | 必填 | string | 资产方借据或合同编号;同一 Partner 和工作空间内不可重复建档。 |
loanInfo.borrowerRef | 选填 | string | 资产方稳定客户号,强烈建议提供。同一客户多笔借据使用同一个值。 |
loanInfo.borrowerName | 必填 | string | 借款人姓名,通过 TLS 传输,进入安小信边界后加密保存。 |
loanInfo.borrowerPhone | 必填 | string | 中国大陆 11 位手机号或 E.164 格式。 |
loanInfo.productCategory | 选填 | enum(枚举) | consumer_credit(消费信贷)、business_loan(经营性贷款)、credit_card_installment(信用卡分期)、other(其他);还须在 Partner 授权范围内。 |
loanInfo.productName | 选填 | string | 资产方产品名称。 |
loanInfo.originatorMerchantId | 选填 | string | 原始放款机构或商户标识。 |
loanInfo.principalAmount | 必填 | decimal string | 原始本金,单位元,非负且最多两位小数。 |
loanInfo.interestRate | 选填 | decimal string | 年化利率百分数,如 18.00 表示 18%。 |
loanInfo.repaymentMethod | 选填 | string | 建议使用 equal_installment(等额本息)、interest_first(先息后本)、lump_sum(到期一次性还本付息) 或 other(其他)。 |
loanInfo.loanTerm | 选填 | integer | 借款期数,至少为 1。 |
loanInfo.disbursementDate | 选填 | date | 放款日期,YYYY-MM-DD。 |
loanInfo.finalDueDate | 选填 | date | 最终到期日,YYYY-MM-DD。 |
逾期信息
overdueInfo| 字段 | 必填 | 类型 | 约束与说明 |
|---|---|---|---|
overdueDays | 必填 | integer | 当前逾期天数 DPD,非负整数。 |
firstOverdueInstallmentNo | 选填 | integer | 最早逾期期次,至少为 1。 |
remainingPrincipal | 必填 | decimal string | 剩余未还本金,单位元。 |
overdueAmount | 必填 | decimal string | 当前逾期总金额,单位元。 |
overduePrincipal | 选填 | decimal string | 逾期本金,单位元。 |
overdueService | 选填 | decimal string | 逾期服务费,单位元。 |
penaltyAmount | 选填 | decimal string | 罚息或违约费用,单位元。 |
联系人
contactInfo[] · optional| 字段 | 必填 | 类型 | 约束与说明 |
|---|---|---|---|
role | 数组项必填 | enum(枚举) | emergency_contact(紧急联系人) 或 guarantor(担保人)。借款人本人联系方式直接取自 loanInfo。 |
relationship | 选填 | string | 与借款人的关系。 |
name | 选填 | string | 联系人姓名。 |
phone | 数组项必填 | string | 中国大陆 11 位手机号或 E.164 格式。 |
还款计划
repaymentPlan[] · optional| 字段 | 必填 | 类型 | 约束与说明 |
|---|---|---|---|
installmentNo | 数组项必填 | integer | 期次编号,至少为 1。 |
dueDate | 选填 | date | 应还日期,YYYY-MM-DD。 |
dueAmount | 选填 | decimal string | 本期应还金额,单位元。 |
paidAmount | 选填 | decimal string | 本期已还金额,单位元。 |
overduePrincipal | 选填 | decimal string | 本期逾期本金。 |
overdueService | 选填 | decimal string | 本期逾期服务费。 |
penaltyAmount | 选填 | decimal string | 本期罚息或违约费用。 |
status | 选填 | enum(枚举) | pending(待还款)、overdue(已逾期) 或 settled(已结清)。 |
合同文档元数据
contractDocument · optional| 字段 | 必填 | 类型 | 约束与说明 |
|---|---|---|---|
fileName | 对象内必填 | string | 必须等于本行 contractRef 加小写扩展名,支持 pdf、ofd、png、jpg、jpeg、webp。 |
docHash | 对象内必填 | string | 文件 SHA-256,小写 64 位 hex。 |
objectRef | 选填 | string | 可省略;如提供,必须等于平台分配的 uploadPrefix 加本行 fileName,不接受外部地址或其他目录。 |
先申请上传凭证并将合同写入指定 OSS 目录,再提交文件名和 SHA-256。平台读取实际文件、验证绑定和哈希,归档成功后才记录为已验证。文件上限 20 MiB,不支持空文件。
partnerId、tenantKey、workspaceId、ownerRef 等归属字段禁止出现在行数据中。系统只根据验签成功的 Partner 注册关系确定租户和工作空间。
申请合同上传凭证与文件校验
原协议将合同文件上传与资产报文提交分开:按批次领取临时凭证,将合同直接上传至平台 OSS,再提交文件引用。
接口已实现按批次签发凭证、文件名绑定、文件读取和 SHA-256 校验。校验通过后,将文件归档到资产方无写权限的目录,再将文档标记为已验证。未配置存储时返回服务不可用,不会跳过校验。
POST /partner/v1/documents/upload-credentials
使用 Partner 的 HMAC 签名请求头。每批申请一次凭证,同一批次内所有合同使用该凭证上传。请求仅接受 batchRef 和 docFormat。partnerId、batchRef、contractRef 必须以字母或数字开头,后续只允许字母、数字及 . _ : -,最长 191 字符;完整文件路径最长 500 字符。
| 请求字段 | 必填 | 说明 |
|---|---|---|
batchRef | 是 | 资产方批次号,与后续资产包中的批次号相同。 |
docFormat | 是 | 本批合同统一格式:pdf(PDF 文件)、image(图片)、ofd(OFD 电子文档)。 |
{"batchRef":"batch-20260914-001","docFormat":"pdf"}响应字段(HTTP 200)
| 字段 | 含义 |
|---|---|
uploadPrefix | 允许写入的目录:partner-docs/{partnerId}/{batchRef}/。 |
namingRule | 文件名规则:{contractRef}.{ext},合同号取自对应资产的 loanInfo.contractRef。 |
credentials.accessKeyId | 临时访问密钥 ID。 |
credentials.accessKeySecret | 临时访问密钥 Secret。 |
credentials.securityToken | 临时安全令牌。 |
credentials.expiration | 凭证到期时间。原协议建议 45 分钟内完成上传,实际使用以返回的到期时间为准。 |
bucket / endpoint / region | OSS 存储桶、服务地址与地域。 |
合同与资产绑定流程
- 以
batchRef和docFormat申请本批上传凭证,写权限仅覆盖返回的目录。 - 通过 OSS SDK 将文件上传到
{uploadPrefix}{contractRef}.{ext}。 - 提交资产包时填写
contractDocument.fileName和文件内容的 SHA-256 摘要docHash。 - 平台根据验签身份、批次号和合同号确定文件路径,验证文件名、对象存在性及内容哈希。
合同校验错误
资产包仍按行处理:正常受理的批次返回 HTTP 201,合同错误在对应 rowResults 中返回,其他行继续建档。下表 422 为行校验错误分类。存储不可用返回行错误 document_storage_unavailable(可重试);空文件和超限分别为 contract_document_empty、contract_document_too_large。
| 校验状态 | 错误码 | 中文含义 |
|---|---|---|
| 422 | contract_document_filename_mismatch | 文件名与本行合同号及扩展名不匹配。 |
| 422 | contract_document_not_found | 对应目录下找不到合同文件。 |
| 422 | contract_document_hash_mismatch | 文件内容的哈希与报文声明不一致。 |
合同引用为可选组;选择提供合同后,应按上述流程绑定文件。查看原协议。
完整请求报文
示例同时展示客户、借据、逾期、联系人、还款计划和合同元数据。示例 rowsChecksum 与下方 rows 的紧凑 JSON 序列化结果对应;修改任一字段后必须重新计算。
{
"schemaVersion": "partner-asset-package.v1",
"batchRef": "batch-20260914-001",
"submittedAt": "2026-09-14T19:00:00+08:00",
"rowCount": 1,
"rowsChecksum": "f9e8c366edb64d8a33fa42050f502ee0e0c0939cf8f0d3689c2e78a9752b3014",
"rows": [
{
"rowRef": "row-001",
"loanInfo": {
"contractRef": "loan-001",
"borrowerRef": "customer-001",
"borrowerName": "张三",
"borrowerPhone": "",
"productCategory": "consumer_credit",
"productName": "消费分期",
"principalAmount": "12000.00",
"interestRate": "18.00",
"repaymentMethod": "equal_installment",
"loanTerm": 12,
"disbursementDate": "2026-06-01",
"finalDueDate": "2027-05-01"
},
"overdueInfo": {
"overdueDays": 45,
"firstOverdueInstallmentNo": 3,
"remainingPrincipal": "9360.00",
"overdueAmount": "1260.50",
"overduePrincipal": "1100.00",
"overdueService": "120.50",
"penaltyAmount": "40.00"
},
"contactInfo": [
{
"role": "emergency_contact",
"relationship": "配偶",
"name": "李四",
"phone": ""
}
],
"repaymentPlan": [
{
"installmentNo": 3,
"dueDate": "2026-07-31",
"dueAmount": "1260.50",
"paidAmount": "0.00",
"overduePrincipal": "1100.00",
"overdueService": "120.50",
"penaltyAmount": "40.00",
"status": "overdue"
}
],
"contractDocument": {
"fileName": "loan-001.pdf",
"docHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"objectRef": "partner-docs/partner-demo-001/batch-20260914-001/loan-001.pdf"
}
}
]
} rowsChecksum 计算
const rowsJson = JSON.stringify(payload.rows);
payload.rowsChecksum = createHash("sha256")
.update(rowsJson, "utf8")
.digest("hex");
// 最后再固定整个请求体;签名和 HTTP 请求必须使用同一个 rawBody
const rawBody = JSON.stringify(payload);响应、状态和对账方式
POST 正常受理返回 HTTP 201。即使其中一行数据错误,只要批次门槛通过,仍返回 201,并在 rowResults 标明每行结果。
| 层级 | 状态 | 含义 |
|---|---|---|
| 批次 | admitted(全部成功) | 全部资产行建档成功。 |
| 批次 | admitted_with_restrictions(部分成功) | 部分成功、部分失败;成功行不回滚。 |
| 批次 | rejected(已拒绝) | 批次通过请求级门槛,但没有任何一行建档成功。 |
| 行 | build_success(建档成功) | 该行已完成客户、资产、账单、逾期状态和案件头建档。 |
| 行 | rejected(已拒绝) | 确定性业务或数据错误;修正该行后再作为新请求提交。 |
| 行 | build_failed(建档失败) | 建档阶段异常,可在确认原因后重试。 |
原协议采用自动校验后人工准入流程,admitted 表示已准入,admitted_with_restrictions 表示有条件准入。当前实现采用同步逐行建档,上表两值分别表示全部建档成功和部分成功,不能据此认定已完成人工准入或合同校验。
部分成功响应示例
{
"packageRef": "partner-package:example",
"batchRef": "batch-20260914-001",
"status": "admitted_with_restrictions",
"qualityGrade": "needs_review",
"validatedRowCount": 1,
"admittedRowCount": 1,
"rejectedRowCount": 1,
"idempotentReplay": false,
"rowResults": [
{
"rowPosition": 1,
"rowRef": "row-001",
"status": "build_success",
"errorCode": null,
"errorField": null,
"errorMessage": null,
"caseRef": "case:partner:example",
"assetRef": "asset:partner:example"
},
{
"rowPosition": 2,
"rowRef": "row-002",
"status": "rejected",
"errorCode": "invalid_request",
"errorField": "rows[].loanInfo.borrowerPhone",
"errorMessage": "手机号格式不正确",
"caseRef": null,
"assetRef": null
}
]
}GET 批次接口返回数量汇总,并只把异常行放在 failedRows 中,避免 5000 条成功明细拖大响应。需要核对某一成功行时,使用逐行查询接口。
单条原子、幂等与重复数据
先判断“整批能否读取和信任”,再判断“每一行能否建档”。因此 5000 条里一条手机号错误,只失败这一条,其余 4999 条照常建档。
整批拒绝的情况
- 签名、时间戳或 nonce 校验失败
- 请求体不是合法 JSON
- schemaVersion 不支持
- rowCount 与 rows.length 不一致
- rowsChecksum 不一致
- rows 为空或超过 5000 条
只失败单行的情况
- 必填字段缺失或格式错误
- 产品类型未获授权
- 批次内 rowRef 重复
- 同一 Partner 下借据已存在
- 报文试图指定租户归属字段
- 该行数据库事务执行失败
重试和重复语义
| 场景 | 结果 | 资产方处理建议 |
|---|---|---|
| 同一 Idempotency-Key + 完全相同原始请求体 | 返回第一次结果,idempotentReplay=true | 网络超时后可安全重试,不会重复建档。 |
| 同一 Idempotency-Key + 不同请求体 | HTTP 409 idempotency_conflict | 修正调用逻辑,使用新的幂等键。 |
| 同一 batchRef + 相同业务内容 | 返回原批次结果 | 按原 packageRef 对账。 |
| 同一 batchRef + 内容变化 | HTTP 409 batch_content_conflict | 生成新的 batchRef 后提交。 |
| 同一 contractRef 再次建档 | 该行 asset_already_exists | 不要重推成功借据;只提交已修正的失败借据。 |
| 同一 borrowerRef 的另一笔 contractRef | 复用客户档案,创建新资产和案件 | 保证 borrowerRef 长期稳定。 |
为了兼容旧调用可以不传 borrowerRef,系统会退化为按 contractRef 建立客户,无法把同一人的多笔借据归到同一客户。
HTTP 与业务错误码
请求级错误通过非 2xx HTTP 状态返回;行级错误出现在 201 响应的 rowResults 或查询结果中。
请求级错误
{
"code": "rows_checksum_mismatch",
"message": "rowsChecksum 校验失败"
}| HTTP | errorCode | 触发条件 | 建议 |
|---|---|---|---|
| 400 | invalid_json | 请求体不是合法 JSON。 | 检查编码和 JSON 语法。 |
| 400 | invalid_request | 批次结构错误、缺字段、为空或超过 5000 条。 | 按 errorField 修正请求。 |
| 400 | unsupported_schema_version | schemaVersion 不受支持。 | 使用 partner-asset-package.v1。 |
| 422 | row_count_mismatch | rowCount 与实际 rows 数量不一致。 | 重新计算数量。 |
| 422 | rows_checksum_mismatch | rowsChecksum 与实际 rows 摘要不一致。 | 对最终 rows 紧凑序列化后重算。 |
| 401 | unauthenticated | 签名错误、时间戳超窗或 nonce 已使用。 | 重新生成 timestamp、nonce 和签名。 |
| 403 | partner_not_registered | Partner 不存在、已停用或未绑定环境。 | 联系安小信核对接入注册。 |
| 409 | idempotency_conflict | 幂等键相同但请求体不同。 | 使用新幂等键。 |
| 409 | batch_content_conflict | batchRef 相同但批次内容不同。 | 使用新 batchRef。 |
| 404 | package_not_found / row_not_found | 在当前 Partner 范围内找不到批次或行。 | 核对 packageRef、rowRef 和环境。 |
| 500 | internal_error | 服务端未预期异常。 | 保留请求标识并联系支持。 |
| 503 | status=unavailable | 健康检查发现服务不可用。 | 稍后重试。 |
行级错误
| errorCode | 含义 | 是否建议原样重试 |
|---|---|---|
invalid_request | 该行字段缺失、类型或格式不正确。 | 否,先修正字段。 |
ownership_field_forbidden | 该行携带了由服务端管理的租户归属字段。 | 否,删除字段。 |
product_not_allowed | 产品类型不在 Partner 授权范围。 | 否,先核对授权。 |
duplicate_row_ref | 同一批次内 rowRef 重复。 | 否,修正 rowRef。 |
asset_already_exists | 借据已经成功建档。 | 否,查询原资产。 |
row_build_failed | 单行建档事务异常。 | 确认服务状态后可重试。 |
敏感数据与租户隔离
生产环境通过 HTTPS 传输。资产方身份完成验签后,服务端根据注册关系确定工作空间,报文不能覆盖租户归属。
姓名、手机号等业务原文只放在 HTTPS 请求体中,Secret 不放 URL 和请求体。
姓名和完整号码进入安小信边界后使用 AES-256-GCM 密文保存。
Partner 注册关系绑定租户和工作空间,查询同样受 Partner 身份范围限制。
每个资产方使用独立 Secret。请存放在密钥管理服务或受控环境变量中,不写入代码、日志、截图或工单;泄露后应立即申请轮换。
先用一小批脱敏样本完成联调
邮件说明机构、资产类型、预计批量和回执需求,我们会提供接入参数与联调支持。