Identity (KYC / KYB)
A verification session verifies a person (KYC — a tenant member) or your business (KYB — the tenant itself). The human completes the vendor workflow on their phone: the API mints a one-time SafeBank handoff URL, your terminal or app renders it as a QR code, and the phone lands in the provider's hosted flow. SafeBank normalizes every provider status — no vendor names or vendor statuses ever appear in the API.
A successful session issues a durable credential
(PERSON_IDENTITY / BUSINESS_IDENTITY). Payment eligibility evaluates
current credential state at decision time — credentials expire, can be
revoked, and are superseded by re-verification.
Session lifecycle (normalized statuses):
CREATED → ACTION_REQUIRED → IN_PROGRESS → PROCESSING ─┬─▶ VERIFIED
▲ │ ├─▶ REJECTED
└── ADDITIONAL_INFORMATION_REQUIRED ◀┤
└─▶ PENDING_REVIEW ─▶ VERIFIED / REJECTED
CANCELED / EXPIRED / ERROR are terminal from any live state.
PENDING_REVIEW means a human reviewer has the case — it is not a
rejection, and the API never claims VERIFIED before the provider decision
lands.
Key endpoints
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /v1/verification-sessions | identity:write | Start a KYC (memberId required) or KYB session |
| GET | /v1/verification-sessions | identity:read | List sessions (filter by subject) |
| GET | /v1/verification-sessions/{id} | identity:read | Session status + requirements |
| POST | /v1/verification-sessions/{id}/handoff | identity:write | Mint the one-time QR handoff URL (~10 min TTL, single use) |
| POST | /v1/verification-sessions/{id}/cancel | identity:write | Cancel a live session |
| POST | /v1/verification-sessions/{id}/simulate | identity:write | Sandbox only (TEST key + mock provider): advance one step |
| GET | /v1/credentials | identity:read | List issued credentials |
| GET | /v1/credentials/{id} | identity:read | Get a credential |
The handoff QR encodes only a short-lived SafeBank URL — no personal data, no
provider secrets. It is single-use: the first scan consumes it, and a replayed
link is rejected with 410.
CLI
# Verify a person (cross-device: scan the QR with their phone)
sb kyc start --member mem_123 --qr --wait
# Verify your business
sb kyb start --qr --wait
# Check back later — Ctrl-C never cancels the session
sb kyc status vfy_abc --wait
sb kyc list
sb kyc credentials
# Sandbox: walk the mock provider one step per call
sb kyc simulate vfy_abc
--wait polls the SafeBank API (never the vendor) and exits after a terminal
status; --json emits a single parseable document with the handoff URL in the
payload and no QR bytes.
SDK
const session = await sb.verifications.create({ kind: 'KYC', memberId })
const handoff = await sb.verifications.createHandoff(session.id)
// render handoff.url as a QR code…
const final = await sb.waitForVerification(session.id, { timeoutMs: 15 * 60_000 })
if (final.status === 'VERIFIED') {
const credential = await sb.credentials.get(final.credentialId!)
}
Sandbox scenarios
The sandbox provider is deterministic: the subject's legal name steers the
scenario, and sessions only move when you call simulate.
| Legal name contains | Path |
|---|---|
| (anything else) | happy path → VERIFIED |
review | parks at PENDING_REVIEW, then VERIFIED |
docs | detours through ADDITIONAL_INFORMATION_REQUIRED |
reject | ends REJECTED |
error | ends ERROR |
Payout destinations
Once KYB-verified, a business can register where it wants to be paid —
e.g. USDT on TRON. Addresses are stored verbatim (base58check networks are
case-sensitive); ownership starts DECLARED, and only a VERIFIED
destination can become the default payout preference. Verifying issues a
DESTINATION_OWNERSHIP credential. (Ownership proof is sandbox-only until the
production proof method ships — the API refuses to pretend otherwise.)
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /v1/payout-destinations | identity:write | Save a destination (requires active KYB) |
| GET | /v1/payout-destinations | identity:read | List destinations |
| POST | /v1/payout-destinations/{id}/verify | identity:write | Verify ownership (sandbox) |
| POST | /v1/payout-destinations/{id}/set-default | identity:write | Default payout preference |
sb destinations add --network tron --asset USDT TR7NHqje…
sb destinations verify dst_abc
sb destinations set-default dst_abc
See also
- Authentication —
identity:*scopes; agent keys can never holdidentity:write - Payments — credential-gated payment eligibility (rolling out)