Zoi ID で開発する
1 つのホスト上の 2 つの API、6 言語の型付きクライアント、ホスト型フロー、サンドボックス。ガイドはすべて公開しており、サンドボックスのキーは早期アクセスでお渡しします。
ベース URLhttps://kyc.zoi.network
クイックスタート
- 1
セッションを作る
バックエンドから、パートナーキーを使って
POST /v1/partner/sessionsを呼び出します。 - 2
フローを実行する
クライアントトークンを SDK に渡すか、ブラウザで
verificationUrlを開きます。 - 3
結果を受け取る
Webhook の署名を検証してから、本人情報の ID とステータスを保存します。
curl https://kyc.zoi.network/v1/partner/sessions \
-H "Authorization: Bearer $ZOI_ID_PARTNER_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: signup-user_42" \
-d '{ "externalUserId": "user_42", "level": "standard", "appId": "com.example.app" }'2 つの API
パートナー API
サーバー間通信で、/v1/partner の下にあります。パートナー鍵を Bearer トークンとして送ります。アプリには絶対に入れないでください。
クライアント API
SDK が /v1/client の下で使います。1 つのセッションに 60 分有効なクライアントトークンを送ります。
決まりごと
- 時刻はエポックからのミリ秒で、名前が
Atで終わるフィールドに入ります。 - 日付は
YYYY-MM-DDです。不明な部分はXXになります。例:1990-XX-XX。 - 何かを作成または判定する POST リクエストは
Idempotency-Keyヘッダーを受け付けます。キーは 24 時間保持します。 - セッションを変更するときは、想定しているリビジョンを送ります。変わっていれば
revision_conflictが返ります。
セッションの流れ
バックエンドがセッションを作り、SDK がステップを進めます。サーバーは順番を確認し、早すぎるステップには step_not_allowed を返します。
1. バックエンドでセッションを作る
同じユーザーで再度呼ぶと、開いているセッションを再開し、新しいクライアントトークンを返します。
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: 6f1c2a9e-signup-42
{
"externalUserId": "user_42",
"level": "standard",
"locale": "en",
"appId": "com.example.app"
}
200 OK
{
"sessionId": "ses_...",
"clientToken": "...",
"clientTokenExpiresAt": 1767225600000,
"status": "created",
"apiBaseUrl": "https://kyc.zoi.network",
"resumed": false
}2. SDK が進めるステップ
- 01
セッションと、残っているステップを読み込みます。
GET /v1/client/session
- 02
同意を記録します。顔の比較については、別の任意の選択も記録します。
POST /v1/client/consent
- 03
アプリと端末を確認します。その結果も判定の一部になります。
POST /v1/client/attestation/challenge, POST /v1/client/attestation
- 04
書類を送ります。MRZ を読み取ったあとチップを読むか、写真を送ります。
POST /v1/client/document/mrz-scan, POST /v1/client/document/chip/challenge, POST /v1/client/document/chip or POST /v1/client/document/photos
- 05
アプリが頭の動きを表示している間に、インカメラで短い動画を 1 本撮ります。
POST /v1/client/liveness/challenge, POST /v1/client/liveness
- 06
居住国を送ります。拡張レベルでは住所も送ります。
POST /v1/client/profile
- 07
住所の要件がある場合のみ:書類をアップロードし、住所を確認します。本人確認書類で住所がわかっている場合はスキップされます。
POST /v1/client/address/document, POST /v1/client/address/confirm
- 08
資金の要件がある場合のみ:回答を保存し、ポリシーで求められれば書類を追加して、ステップを完了します。
POST /v1/client/funds/answers, POST /v1/client/funds/document, POST /v1/client/funds/remove, POST /v1/client/funds/complete
- 09
2 つの質問に答えます。重要な公的地位にある人かどうか、本人のために行動しているかどうか。
POST /v1/client/declarations
- 10
現在のリビジョンで送信します。
POST /v1/client/submit
ユーザーは自動判定の結果を人に見直してもらうこと(POST /review-request)や、同意の取り消し(POST /withdraw)もできます。取り消すとセッションは中止され、ファイルはすぐに削除されます。
住所確認と資金源の確認
requirements を POST /v1/partner/sessions に加えると、本人確認以外も確認できます。address は住所の証明を求めます。source_of_funds は資金の出所を尋ね、source_of_wealth はさらに総資産とその形成の経緯も尋ねます。2 つの資金の値は合わせて 1 つのステップになり、どちらも address と組み合わせられます。
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
{
"externalUserId": "user_42",
"level": "standard",
"appId": "com.example.app",
"requirements": ["address", "source_of_funds"],
// Optional. Without them your partner default applies, else ours.
"addressPolicy": { "maxAgeDays": 90, "signedOnly": false },
"fundsPolicy": { "evidence": "optional", "currency": "EUR" }
}任意のポリシー
ポリシーを指定しない場合はパートナーの初期設定が、それもなければ当社の初期設定が適用されます。ポリシーはセッションの作成時に、そのセッションへコピーされます。
addressPolicy:maxAgeDays(初期値 90、1 から 366)、acceptedTypes(初期値:公共料金、通信、銀行の書類、税関係の書類、公的機関からの通知、居住証明書)、signedOnly(初期値 false)。fundsPolicy:questions、evidence(none、optional、requiredのいずれかで、初期値はnone)、acceptedTypes、maxAgeDays(初期値 180)、currency(ISO 4217、初期値 USD)、範囲を決めるbrackets。- 別の要件を持つ未送信のセッションが開いている場合は、レベルを変更したときと同じように、そのセッションは中止され、新しいセッションが始まります。
address_signed_pdf、address_document_review、funds_signed_documents、funds_reviewなどのサンドボックスシナリオで、結果ごとにテストできます。
フォローアップセッション
すでに本人情報が有効なユーザーについて、要件だけを確認します。reuseIdentity: true を requirements と一緒に送ってください。
- ユーザーは同意して、新しいステップだけを完了します。書類、セルフィー、申告を再び求めることはありません。
- 承認されると、本人情報は ID、確認日、有効期間をそのまま保ち、新しい結果が加わります。
session.approvedとidentity.updatedが届きます。 - 有効な本人情報がない場合、呼び出しは 409
step_not_allowedを返します。 - 高額の入金、PEP フラグ、リスクの高い国など、お客様自身のリスクルールに基づいて開始します。ルールはお客様のバックエンドの 1 か所にまとめておけます。
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: edd-user_42-2026-11
{
"externalUserId": "user_42",
"appId": "com.example.app",
"requirements": ["source_of_funds", "source_of_wealth"],
"reuseIdentity": true,
"fundsPolicy": { "evidence": "required" }
}
// No active identity for user_42:
409 { "error": { "code": "step_not_allowed", ... } }結果とスコープ
承認されたセッションには address と funds が含まれ、本人情報はそれぞれの最新のものを保持します。サマリーの項目はどのスコープでも受け取れます。詳細にはスコープが必要です。
- スコープ
address:住所。スコープがなくてもverified、method、evidenceLevel、documentType、documentDate、verifiedAt、countryは受け取れます。 - スコープ
financial_data:answers、documents、findings。スコープがなくてもkinds、evidenceLevel、reviewed、documentCount、verifiedAtは受け取れます。eligibilityに含まれることはありません。 evidenceLevelはhigh、medium、low、noneのいずれかです。住所の確認方法は、強い順にwallet、id_document、signed_pdf、official_code、document_review、declaredです。- 書類は API では送られません。環境に
financial_dataスコープがあれば、コンプライアンスロールを持つメンバーがダッシュボードで資金源の書類ページを閲覧できます。閲覧はすべて記録されます。
"session": {
"id": "ses_...",
"status": "approved",
"requirements": ["address", "source_of_funds"],
"reusesIdentity": false,
"address": {
"verified": true,
"method": "signed_pdf",
"evidenceLevel": "high",
"documentType": "bank_statement",
"documentDate": "2026-10-14",
"verifiedAt": 1767225600000,
"country": "DE",
// scope "address"
"address": { "line1": "Musterstrasse 12", "city": "Berlin", "postalCode": "10115", "country": "DE" }
},
"funds": {
"kinds": ["source_of_funds"],
"evidenceLevel": "medium",
"reviewed": true,
"documentCount": 2,
"verifiedAt": 1767225600000,
// scope "financial_data"
"answers": {
"employmentStatus": "employed",
"income": { "min": 50000, "max": 100000, "currency": "EUR" },
"sources": ["salary", "savings"],
...
},
"documents": [
{ "documentType": "payslip", "documentDate": "2026-10-31", "amount": 5200, "currency": "EUR",
"signature": "valid", "signedByKnownIssuer": true, "nameMatch": "match", "findings": [], ... },
...
],
"findings": []
}
}Web フロー
SDK を使わない場合は、ユーザーを当社がホストするページに送ってください。同じ API、同じチェック、同じ審査キューを使います。
1. リンクを取得する
POST /v1/partner/sessions は verificationUrl を返します。終了後にユーザーを戻すには redirectUrl を渡します。登録済みのリダイレクト用オリジンのいずれかで始まる必要があります。
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
{
"externalUserId": "user_42",
"level": "standard",
"locale": "en",
"appId": "com.example.app",
"redirectUrl": "https://example.com/kyc/done"
}
200 OK
{
"sessionId": "ses_...",
"clientToken": "...",
"verificationUrl": "https://kyc.zoi.network/verify/#token=...&lang=en",
...
}- クライアントトークンは URL のフラグメント(
#の後)に入ります。ブラウザはフラグメントをサーバーに送らないため、トークンはログやRefererヘッダーに残りません。ページはトークンを読み取り、アドレスバーから消して、メモリ内だけに保持します。 - 19 言語のいずれかを選ぶには、フラグメントに
langを追加します。ない場合はブラウザの言語を使います。 - 再読み込みするとトークンは失われます。バックエンドから同じセッションの新しいリンクを作成してください。
- ページは同意と一緒に
channel: "web"を送ります。ブラウザは本人確認書類の IC チップを読めないため、Web セッションでは書類の写真またはデジタル ID ウォレットを使います。端末チェックは行わず、Web からの書類写真は必ず訓練を受けた担当者が判定します。 - パソコンでは、ユーザーがスマートフォンで続けられるよう QR コードを表示します。
2. ユーザーが終えたとき
フローが終わると(送信、キャンセル、または最終ステータス)、ページは次のうち存在するものすべてにこのメッセージを送ります: window.ReactNativeWebView、window.webkit.messageHandlers.zoiId、window.ZoiIdAndroid、window.opener。その後、zoiIdSession と zoiIdStatus を付けて redirectUrl を開きます。
メッセージやリダイレクトのパラメーターは決して信頼しないでください。信頼してよいのは Webhook または GET /v1/partner/sessions/:id だけです。
// Sent to each bridge that exists:
// window.ReactNativeWebView.postMessage(JSON string)
// window.webkit.messageHandlers.zoiId.postMessage(object)
// window.ZoiIdAndroid.postMessage(JSON string)
// window.opener.postMessage(object, "<origin of your redirectUrl>")
// (popups only, and only when the session has a redirectUrl)
{
"source": "zoi-id",
"type": "finished", // or "cancelled"
"sessionId": "ses_...",
"status": "processing" // the session status at that moment
}
// Then, if redirectUrl was set (the page uses the one stored with the
// session, never one from the address bar):
https://example.com/kyc/done?zoiIdSession=ses_...&zoiIdStatus=processing3. Webview の設定
Android WebView
javaScriptEnabled と domStorageEnabled を有効にし、mediaPlaybackRequiresUserGesture を false にします。アプリが CAMERA 権限を得たあと、WebChromeClient の onPermissionRequest で RESOURCE_VIDEO_CAPTURE を許可します。写真による代替手段のためにファイル選択も用意してください。
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.mediaPlaybackRequiresUserGesture = false
webView.webChromeClient = object : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest) {
// Only after your app holds Manifest.permission.CAMERA.
if (request.resources.contains(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
request.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE))
} else {
request.deny()
}
}
override fun onShowFileChooser(
view: WebView, callback: ValueCallback<Array<Uri>>, params: FileChooserParams,
): Boolean {
// Open a picker and pass the result to callback (photo fallback).
return true
}
}iOS WKWebView
iOS 14.3 以降。allowsInlineMediaPlayback を true にし、アプリに NSCameraUsageDescription を追加します。iOS 15 以降は UI デリゲートでカメラ権限を判断できます。
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: .zero, configuration: config)
webView.uiDelegate = self // iOS 15+: decide camera access below
func webView(_ webView: WKWebView,
requestMediaCapturePermissionFor origin: WKSecurityOrigin,
initiatedByFrame frame: WKFrameInfo,
type: WKMediaCaptureType,
decisionHandler: @escaping (WKPermissionDecision) -> Void) {
decisionHandler(origin.host == "kyc.zoi.network" ? .grant : .deny)
}
// Info.plist: NSCameraUsageDescriptionReact Native WebView
mediaCapturePermissionGrantType="grant"、allowsInlineMediaPlayback、javaScriptEnabled を設定します。アプリにはカメラ権限が必要です。
<WebView
source={{ uri: verificationUrl }}
javaScriptEnabled
allowsInlineMediaPlayback
mediaCapturePermissionGrantType="grant"
onMessage={(event) => {
const message = JSON.parse(event.nativeEvent.data);
if (message.source === "zoi-id") {
// Close the webview, then ask your backend for the real status.
}
}}
/>カメラ権限とクリックジャッキングの理由から、iframe には対応していません。ブラウザのタブ、ポップアップ、または Webview を使ってください。
ステータス
セッション
created- セッションはあるが、まだ何もアップロードされていません。
in_progress- 同意済みで、ステップをアップロード中です。
processing- 送信済みで、確認を実行中です。
review- 訓練を受けた担当者の確認待ちです。
approved- 確定。本人確認が完了しています。
rejected- 確定。理由コードを確認してください。
resubmit- ユーザーは requiredSteps のステップをやり直す必要があります。
expired- 期限内に最終判定が出ませんでした。
cancelled- パートナー、ユーザー、または削除によって中止されました。
本人情報
active- 利用できます。
suspended- 保留中です。たとえば制裁リストとの新しい一致候補が出たあとなど。
expired- 書類または確認の有効期間が終わりました。新しいセッションで再確認します。
revoked- 確定。
SDK
React Native (Expo) 向けの SDK は、型付きの API クライアント、NFC での IC チップ読み取り、端末チェックを提供します。ヘッドレスなので、画面とカメラは完全にアプリ側で制御できます。サーバー側では、TypeScript、Python、Go、PHP、Java、Ruby 向けの型付きクライアントを使えます。
import { ZoiIdClient, nextStep } from "@zoi/id-sdk";
// clientToken and apiBaseUrl come from your backend (POST /v1/partner/sessions).
const client = new ZoiIdClient({ baseUrl: apiBaseUrl, clientToken });
const session = await client.getSession();
switch (nextStep(session)) {
case "consent": /* show the notice and the biometric choice */ break;
case "document": /* chip read or photos */ break;
case "liveness": /* record the video */ break;
case "address": /* upload a document, then confirm the address */ break;
case "funds": /* answers, then documents when the policy asks */ break;
case "submit": await client.submit(session.revision); break;
case "done": /* wait for your backend to get the webhook */ break;
}Webhook
セッションや本人情報が変わると Webhook を送ります。中身を信用する前に、必ず署名を確認してください。
- すべての Webhook には
Zoi-Id-Signatureヘッダーが付きます:t=unix seconds,v1=hex。 v1は、文字列t.raw bodyを Webhook シークレットで計算した HMAC-SHA256 です。JSON を解析する前の生のボディを使ってください。- シークレットの切り替え中は、有効なシークレットごとに
v1が 1 つずつ付きます。どれか 1 つが一致すれば受け付けてください。 tが現在時刻から 300 秒より離れていれば拒否してください。- 失敗した配信は 72 時間再送します。再送のたびに新しい
tで署名し直します。 - 本人情報には
revisionがあります。保存しているものより新しいリビジョンのイベントだけを反映してください。
POST <your webhook URL>
Zoi-Id-Signature: t=1767225600,v1=5f2b...e9
{
"v": 1,
"eventId": "evt_...",
"type": "identity.suspended",
"createdAt": 1767225600000,
"object": "identity",
"externalUserId": "user_42",
"identity": {
"identityId": "idn_...",
"revision": 7,
"status": "suspended",
"level": "standard",
"verifiedAt": 1764547200000,
"validUntil": 1859155200000,
"ageOver18": true,
"captureMethod": "nfc"
}
}イベントの種類
session.processingsession.reviewsession.approvedsession.rejectedsession.resubmitsession.expiredsession.cancelledidentity.updatedidentity.suspendedidentity.reinstatedidentity.expiredidentity.revoked
TypeScript で署名を確認する
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 300;
export function verifyZoiIdSignature(
rawBody: string,
header: string | null,
secret: string,
nowSeconds = Math.floor(Date.now() / 1000),
): boolean {
if (!header) return false;
let t: string | undefined;
const signatures: Buffer[] = [];
for (const part of header.split(",")) {
const [key, value] = part.trim().split("=");
if (!value) continue;
if (key === "t") t = value;
if (key === "v1" && /^[0-9a-f]+$/i.test(value)) signatures.push(Buffer.from(value, "hex"));
}
if (!t || !/^\d+$/.test(t) || signatures.length === 0) return false;
if (Math.abs(nowSeconds - Number(t)) > TOLERANCE_SECONDS) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
return signatures.some((sig) => sig.length === expected.length && timingSafeEqual(sig, expected));
}
// Example handler: read the raw body first, parse JSON only after the check.
export async function POST(request: Request) {
const rawBody = await request.text();
const ok = verifyZoiIdSignature(rawBody, request.headers.get("Zoi-Id-Signature"), process.env.ZOI_ID_WEBHOOK_SECRET!);
if (!ok) return new Response("bad signature", { status: 400 });
const event = JSON.parse(rawBody);
// Store event.eventId to skip duplicates, then apply it if the revision is newer.
return new Response(null, { status: 204 });
}Go で署名を確認する
package zoiid
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"time"
)
const toleranceSeconds = 300
// VerifySignature checks the Zoi-Id-Signature header against the raw body.
func VerifySignature(rawBody []byte, header, secret string, now time.Time) bool {
var t string
var sigs [][]byte
for _, part := range strings.Split(header, ",") {
key, value, ok := strings.Cut(strings.TrimSpace(part), "=")
if !ok || value == "" {
continue
}
switch key {
case "t":
t = value
case "v1":
if b, err := hex.DecodeString(value); err == nil {
sigs = append(sigs, b)
}
}
}
ts, err := strconv.ParseInt(t, 10, 64)
if err != nil || len(sigs) == 0 {
return false
}
if d := now.Unix() - ts; d > toleranceSeconds || d < -toleranceSeconds {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(t + "."))
mac.Write(rawBody)
expected := mac.Sum(nil)
for _, sig := range sigs {
if hmac.Equal(sig, expected) {
return true
}
}
return false
}イベントフィード
GET /v1/partner/events は直近 30 日のすべてのイベントを古い順に返します。次のページは、最後の nextCursor を after に渡して読みます。長い障害のあとや、漏れがないか確かめるときに使います。
GET /v1/partner/events?after=<nextCursor>&limit=100
Authorization: Bearer <partner key>
200 OK
{
"items": [ /* webhook payloads, oldest first */ ],
"nextCursor": "..." // null when there is nothing more
}不正報告
判定済みのセッションが不正だとわかったら(たとえばチャージバックのあと)、お知らせください。報告は誤った承認の測定に使われます。セッション、本人情報、ユーザーの結果は変わりません。
- スコープは
sessions:write。判定済み(承認または却下)のセッションだけが対象です。判定前に呼び出すと 409 が返ります。 - ボディが空の場合は、カテゴリ
otherのconfirmed_fraudとして報告されます。statusにはsuspected_fraudも指定でき、clearedを指定すると自分の報告を取り下げられます。 - カテゴリ:
forged_document、impostor、deepfake_or_replay、synthetic_identity、account_takeover、other。clearedにはカテゴリを付けません。 noteは当社のアナリスト向けの自由記述で、最大 1000 文字です。氏名、書類番号などの個人データは絶対に入れないでください。- 新しい報告には 201 が返ります。同じ報告をもう一度送ると、保存済みの報告が 200 で返ります。安全に再試行するには
Idempotency-Keyを送ってください。1 セッションあたりの報告は最大 20 件です。 - 同じ報告は、ダッシュボードのセッションページからワンクリックで行えます。
POST /v1/partner/sessions/ses_.../fraud-reports
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: chargeback-8812
{
"status": "confirmed_fraud",
"category": "impostor",
"note": "Chargeback on the first deposit"
}
201 Created
{
"id": "flb_...",
"sessionId": "ses_...",
"status": "confirmed_fraud",
"category": "impostor",
"noteKept": true,
"createdAt": 1767225600000
}削除
DELETE /v1/partner/users/:externalUserId は、ユーザーがアカウントを閉じたときや削除を求めたときに、そのデータを削除します。レスポンスには、今削除したものと、保持するもの、その理由と期限が入っています。
DELETE /v1/partner/users/user_42
Authorization: Bearer <partner key>
200 OK
{
"deleted": ["images", "chip_files", "video", "personal_data", "address"],
"retained": [
{ "category": "dedup_keys", "reason": "fraud_prevention", "until": 1924992000000 },
{ "category": "sanctions_screening_records", "reason": "legal_obligation", "until": 2082758400000 }
],
"backupsPurgedWithinDays": 30
}エラー
すべてのエラーは同じ形で、コードごとに決まった HTTP ステータスがあります。
{
"error": {
"code": "step_not_allowed",
"message": "This step is not allowed now.",
"requestId": "req_..."
}
}| コード | HTTP | 意味 |
|---|---|---|
bad_request | 400 | リクエストが仕様に合っていません。 |
unauthorized | 401 | 鍵またはトークンがない、または間違っています。 |
token_expired | 401 | クライアントトークンの期限が切れました。バックエンドで新しく作ってください。 |
forbidden | 403 | この鍵ではこの操作はできません。 |
not_found | 404 | そのセッションまたはユーザーはありません。 |
session_closed | 409 | セッションは確定しています。これ以上アップロードできません。 |
step_not_allowed | 409 | 順番が違うか、今はそのステップが不要です。 |
revision_conflict | 409 | セッションのリビジョンが変わりました。読み込み直してください。 |
idempotency_conflict | 409 | 同じ Idempotency-Key が別のリクエストで使われました。 |
challenge_expired | 410 | チャレンジの期限が切れたか、すでに使われています。新しいものを取得してください。 |
payload_too_large | 413 | アップロードが大きすぎます。 |
unsupported_media_type | 415 | このファイル形式は受け付けていません。 |
rate_limited | 429 | リクエストが多すぎます。しばらくしてからやり直してください。 |
internal | 500 | こちら側で問題が起きました。 |
dependency_unavailable | 503 | 必要なサービスが止まっています。しばらくしてからやり直してください。 |
理由コード
再提出が必要なときや却下されたとき、セッションには reasonCodes が入ります。コードごとに自分で翻訳したメッセージを表示してください。コードをそのままユーザーに見せないでください。
consent_missingconsent_withdrawnattestation_faileddocument_expireddocument_unreadabledocument_not_supporteddocument_suspected_tamperingdocument_number_visiblechip_verification_failedchip_proof_missingchip_data_mismatchmrz_invalidphoto_qualityface_not_foundface_mismatchliveness_failedunderagelocation_not_supportedresidence_not_supportedsanctions_matchpep_reviewduplicate_identitypossible_duplicatedeclaration_requires_reviewuser_requested_reviewreviewer_otheraddress_unreadableaddress_document_too_oldaddress_document_not_acceptedaddress_name_mismatchaddress_signature_requiredfunds_document_unreadablefunds_document_too_oldfunds_document_not_acceptedfunds_more_evidencefunds_evidence_rejected