发起一次 L1 实名(我方托管屏,返回一条链接)
x-on-behalf-of
需 x-idempotency-key
代会员调用,必带 x-on-behalf-of。
这是 kyc_required 之后的那一步:建会员 → 下单拿 kyc_required →
GET /v1/kyc/requirements 得知要 level 1 → 在这里换一条链接,
把它转给终端用户去填。
⚠ 证件影像与档案原件一个字节都不经过你的服务器 —— 表单是我方
托管的。你拿到的只有一个 URL 和到期时间,这既是合规要求,
也是为什么开放 API 上没有「提交实名资料」那种入参。
只发 hosted_url,不单独下发会话 id
hosted_url 最后那一段就是凭据本身:那条 URL 上没有任何附加
鉴权,谁拿到它,谁就能在 24 小时内以那个会员的名义提交一整份 L1
档案并上传证件影像。
所以我方刻意不把它做成一个「看起来不敏感的会话标识」单独出参 ——
那样你最自然的做法是把它写进自己的业务表和请求日志,而那份日志的
每一个读者都因此拿到了一次冒名实名的能力。
要做关联请用你自己的 external_member_id。
同理:别把这条链接记进日志、别放进 URL 参数转发给第三方。
有效期 24 小时,过期就再要一条
这一张要经你转交给终端用户、他再去翻证件、拍照 —— 所以不是几分钟。
过期没有宽限,重新调一次这个端点即可(x-idempotency-key 记得换)。
⚠ 幂等窗口与票据有效期都是 24 小时,而它们从同一刻起算。
窗口内同键重发拿回的是首次那份响应原文(带
X-Idempotent-Replay: true 响应头)—— 也就是一条剩余寿命更短的
链接,第 23 小时重发拿到的那条 1 小时后就废了。
要一条新链接就换一把新键,别靠重发同一把键去「续期」。
⚠ 幂等键一把只能给一个会员用
这个端点的路径与请求体双双恒定(body 恒为空对象),
所以「这次是给哪个会员办」只在 x-on-behalf-of 头里。
我方把这个头并进了幂等指纹:**同一把键换一个会员 → `409
idempotency_key_reused`**,而不是把上一个会员的链接回放给你。
照批次给一整批会员发链接时,请逐会员一把新 UUID。
已有档案的会员不发票
这个会员若已经有一份未被驳回的实名档案(含正在审核中的),
这里当场回 400 state_invalid,不会给你一条点进去注定失败的
链接 —— 否则你会把它转给终端用户,而他填完十五个框才被拒。
进度看 GET /v1/kyc(pending = 已提交待审)与
GET /v1/kyc/supplements(要补件时那里有链接)。
被驳回过的会员可以再发一次。
前置条件
- 该会员名下没有未被驳回的 L1 档案(有则
state_invalid)
请求头
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x-on-behalf-of |
string | 必填 | 代哪个会员发起。external_member_id 或 mem_<uuid> 都认。
缺了回 member_context_required。
⚠ 它参与幂等指纹,见上。 |
x-idempotency-key |
string | 必填 | UUID v4。逐会员一把 —— 一批会员共用一把会撞
409 idempotency_key_reused。 |
请求体
这个 operation 还没有在 spec 里声明请求体字段。
响应
hosted_url 转给终端用户即可。
响应里没有会话 id,这是刻意的(见上)。{
"hosted_url": "https://api.zise.com/hosted/kyc/kyc_8f2a1c9e5b7d403a91e6c4d2b7f05a13",
"expires_at": "2026-08-14T05:00:00.000Z"
}member_context_required 没带 x-on-behalf-of ·
state_invalid 这个会员已经有一份未被驳回的实名档案
(不下发内部原因 —— 进度请查 GET /v1/kyc)insufficient_scope 缺 kyc:writemember_not_found(含「属于别的商户」与「已停用」)idempotency_key_reused 同一把键换了会员或换了请求体 ·
idempotency_in_progress 首次请求还在处理中curl -X POST 'https://api.zise.com/v1/kyc/sessions' \
-H 'x-auth-token: Bearer $TOKEN' \
-H 'x-on-behalf-of: $MEMBER_ID' \
-H 'x-idempotency-key: $IDEMPOTENCY_KEY' \
-H 'content-type: application/json' \
-d '{}'const res = await fetch("https://api.zise.com/v1/kyc/sessions", {
method: "POST",
headers: {
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
"x-idempotency-key": "$IDEMPOTENCY_KEY",
"content-type": "application/json",
},
body: JSON.stringify({}),
});
// 金额按字符串读,别让它变成 number
const data = await res.json();import requests
res = requests.post(
"https://api.zise.com/v1/kyc/sessions",
headers={
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
"x-idempotency-key": "$IDEMPOTENCY_KEY",
"content-type": "application/json",
},
json={},
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()req, _ := http.NewRequest("POST", "https://api.zise.com/v1/kyc/sessions",
strings.NewReader(`{}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-on-behalf-of", "$MEMBER_ID")
req.Header.Set("x-idempotency-key", "$IDEMPOTENCY_KEY")
req.Header.Set("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
// 金额字段用 string 接,不要 float64HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.zise.com/v1/kyc/sessions"))
.header("x-auth-token", "Bearer $TOKEN")
.header("x-on-behalf-of", "$MEMBER_ID")
.header("x-idempotency-key", "$IDEMPOTENCY_KEY")
.header("content-type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{}
"""))
.build();
// 金额字段用 String / BigDecimal,不要 double$ch = curl_init('https://api.zise.com/v1/kyc/sessions');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-auth-token: Bearer $TOKEN',
'x-on-behalf-of: $MEMBER_ID',
'x-idempotency-key: $IDEMPOTENCY_KEY',
'content-type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{}
JSON,
]);
$res = curl_exec($ch);
// 金额用 bcmath / 字符串,不要 floatval
{
"hosted_url": "https://api.zise.com/hosted/kyc/kyc_8f2a1c9e5b7d403a91e6c4d2b7f05a13",
"expires_at": "2026-08-14T05:00:00.000Z"
}