API reference
Every endpoint, generated from the OpenAPI spec — so this page cannot drift from the contract your client is built against. Download the spec to generate a client or import it into Postman.
Base URLs
https://sandbox.identityinc.io- Sandbox — free test verifications (real Shufti test account), simulated chain. Keys are bi_test_… and must not be sent to production. Mint via POST /v1/billing/checkout on this host (see Quickstart).
https://api.identityinc.io- Production — the base URL for the API key issued at signup (bi_…)
All endpoints take Authorization: Bearer <key> unless marked Public. See Authentication.
Accounts
POST/v1/accounts/freeSponsor free provisional account
Request body
ownerPublicKeyrequired | string | |
activePublicKey | string | |
passkeyCredentialId | string | |
preferredName | string | Requested account name. Permanent once created. Conflicts with an existing name return 409 and hand the kycCreateToken back, so the caller can retry with a different name without repeating KYC. Pattern: ^[a-z1-5.]{1,12}$ |
deviceFingerprint | string | |
personDid | string | |
kycCreateToken | string | One-time token from GET /v1/kyc/status after approval. |
emailProofToken | string | From POST /v1/email/otp/confirm. Binds the verified address to the new account. Required when the organization sets requireEmailProofBeforeFreeCreate. |
phoneProofToken | string | From POST /v1/phone/otp/confirm. |
Responses
201 | Created |
409 | Requested name, email, or number already taken |
GET/v1/accounts/name-availableCheck whether an account name can be claimed
Advisory only. Nothing is reserved: the chain is the authority and /v1/accounts/free re-checks, so a name can still be lost between this call and create.
Parameters
namerequired | string · query |
Responses
200 | Availabilityname: string · available: boolean · reason: TAKEN | INVALID · message: string |
POST/v1/accounts/unlockRemove faucet cosigner after bind
Responses
200 | Unlocked |
GET/v1/accounts/{did}Account status, lifecycle, and active bindings
Parameters
didrequired | string · path | Account DID, URL-encoded (`did:antelope:<chain>:<name>`) |
Responses
200 | Account with non-revoked bindings |
404 | Account not found in this organization |
KYC
POST/v1/kyc/startStart KYC; returns verification URL / access token
Person-first by default: omit `accountDid` to create a Person before any chain account (required when the organization gates free-create on KYC). Identity checks are document + face via Shufti Pro — BoundIdentity does not enable Shufti phone OTP for KYC. On sandbox, complete `verificationUrl` with a Shufti test ID (free on the test account).
Request body
accountDid | string | Optional. Omit for person-first KYC before free-create. |
levelName | string |
Responses
200 | Token issued |
GET/v1/kyc/statusKYC status for a person
Parameters
personDidrequired | string · query |
Responses
200 | Status, level, provider, and country |
404 | Person not found in this organization |
KYB
POST/v1/kyb/startStart Know-Your-Business for a legal entity
Responses
200 | Organization pending |
Bind
POST/v1/bind/challengeIssue bind nonce for wallet signature
Responses
200 | Challenge |
POST/v1/bind/confirmConfirm bind with signature; issue AccountBinding VC
Responses
200 | Bound |
Compliance
POST/v1/aml/screen-applicantScreen applicant id hash (sanctions / denylist)
Responses
200 | Screen result |
POST/v1/risk/assessAnti-abuse risk score before free create / bind
Responses
200 | Assessment |
POST/v1/compliance/exportExport hash-chained audit log for a date range
Responses
200 | Export job |
GET/v1/compliance/exportsList recent compliance export jobs
Responses
200 | Jobs |
GET/v1/compliance/export/{jobId}Fetch completed export artifact
Parameters
jobIdrequired | string · path |
Responses
200 | Job + artifact JSON |
POST/v1/compliance/audit/verifyVerify audit hash-chain integrity
Responses
200 | Verification result |
Admin
POST/v1/admin/accounts/suspendSuspend an account (compliance action)
Responses
200 | Suspended |
GET/v1/admin/dashboardOrganization dashboard aggregates (lifecycle, AML, recent audit)
Responses
200 | Dashboard payload |
GET/v1/admin/accountsList chain accounts for the organization
Responses
200 | Account list |
GET/v1/admin/peopleList people / KYC subjects
Responses
200 | People list |
GET/v1/admin/organizationsList KYB businesses
Responses
200 | Businesses |
GET/v1/admin/auditHash-chained audit log entries
Responses
200 | Audit entries |
GET/v1/admin/membersOrganization console memberships (requires admin scope)
Responses
200 | Members |
POST/v1/admin/membersAdd a console member, or restore and re-role a revoked one
The membership roster decides who may sign in to the admin console and what they may do there. Roles, weakest first: `viewer`, `operator`, `compliance_officer`, `admin`, `owner`. Re-adding an existing address updates its role and reactivates it, so a returning colleague does not collide with their old row. While an organization has no members at all, the console accepts any address with the correct password and treats it as an `owner` — adding the first member closes that path.
Request body
emailrequired | string (email) | |
rolerequired | viewer | operator | compliance_officer | admin | owner |
Responses
201 | Member added or updated |
PATCH/v1/admin/members/{email}Change a member's role or reactivate them
Parameters
emailrequired | string (email) · path |
Request body
role | viewer | operator | compliance_officer | admin | owner | |
active | boolean |
Responses
200 | Updated |
404 | No such member in this organization |
DELETE/v1/admin/members/{email}Revoke a member's console access
Deactivates rather than deletes. The address remains the recorded actor on every audit entry the member produced, and removing the row that explains who they were would leave those entries unresolvable.
Parameters
emailrequired | string (email) · path |
Responses
200 | Revoked |
404 | No such member in this organization |
GET/v1/admin/amlPEP / sanctions alerts + AML audit events
Responses
200 | Screening feed |
GET/v1/admin/riskAnti-abuse posture (velocity, shared devices, risk events)
Responses
200 | Risk summary |
GET/v1/admin/tenantCurrent organization + policy (scoped by API key)
Responses
200 | Organization detail |
GET/v1/admin/api-keysList API keys (prefixes only)
Responses
200 | Keys |
POST/v1/admin/api-keysCreate or rotate API key (raw returned once)
Request body
namerequired | string | Max length 64. |
revokeKeyId | string (uuid) | Deactivate this key after minting the new one |
scopes | read | accounts_write | kyc_write | bind_write | unlock_write | admin | compliance_export | webhook_manage[] | Least-privilege scope set. **Omitting this grants every scope, including `admin`** — the historical behaviour of this endpoint. Pass a narrower set for a key that only needs part of the API, for example `["read"]` for a dashboard or `["accounts_write","kyc_write"]` for an onboarding service. |
Responses
201 | Created — the raw key is returned once and never againid: string (uuid) · prefix: string · apiKey: string · scopes: string[] · revokedKeyId: string · warning: string · scopeNotice: string |
DELETE/v1/admin/api-keys/{id}Revoke API key
Parameters
idrequired | string · path |
Responses
200 | Revoked |
POST/v1/admin/signing/enableEnable HMAC request signing (secret once)
Responses
200 | HMAC secret |
POST/v1/admin/signing/disableDisable HMAC request signing
Responses
200 | Disabled |
PUT/v1/admin/ip-allowlistReplace organization IP allowlist
Responses
200 | Allowlist |
POST/v1/admin/dpa/acceptAccept Data Processing Agreement (sets dpaAcceptedAt)
Responses
200 | Acceptance recorded |
GET/v1/admin/webhooksList outbound webhooks
Responses
200 | Webhooks |
POST/v1/admin/webhooksRegister outbound webhook (signing secret once)
Responses
201 | Registered |
DELETE/v1/admin/webhooks/{id}Deactivate outbound webhook
Parameters
idrequired | string · path |
Responses
200 | Deactivated |
GET/v1/admin/webhooks/deliveriesRecent outbound delivery attempts
Diagnostics for a receiver that is rejecting events: status, attempt count, and the last error per delivery. Scoped to your own webhooks.
Parameters
status | pending | failed | delivered | dead · query | |
limit | integer · query |
Responses
200 | Delivery attempts |
POST/v1/admin/webhooks/deliveries/{id}/replayResend a delivery that gave up
Re-queues a `failed` or `dead` delivery and resets its attempt counter, for use once the receiving system has been repaired. The signed body is stored with the delivery, so the replay carries the original payload and signature inputs rather than a regenerated approximation. Deliveries that already succeeded are refused — deduplicate on `X-BI-Delivery-Id` if you need to reprocess one.
Parameters
idrequired | string · path |
Responses
200 | Re-queued |
409 | Already delivered, or the payload is no longer available |
GET/v1/tenantCurrent organization posture (signing, allowlist, policy)
Responses
200 | Organization |
PUT/v1/admin/country-policySet the organization's jurisdiction policy
Enforced when binding a person to an account: a country on `blockedCountries`, or absent from a non-empty `allowedCountries`, is refused. The blocklist takes precedence. When either list is non-empty a subject whose country is unknown is also refused, so the control cannot be bypassed by an unresolvable jurisdiction. Empty lists mean no restriction. Omit a field to leave it unchanged.
Request body
allowedCountries | string[] | ISO-3166 alpha-2 codes |
blockedCountries | string[] |
Responses
200 | Policy appliedallowedCountries: string[] · blockedCountries: string[] |
400 | A code was not a valid alpha-2 |
AML
POST/v1/aml/screen-accountKYT-style screen of a chain account
Request body
chainAccountrequired | string |
Responses
200 | Score and labels |
POST/v1/aml/clearClear PEP / sanctions flags on a person
Compliance-officer action. Resets `pepFlag`, `sanctionsFlag`, and `riskScore`, and writes an `aml.cleared` audit entry with the reason. Requires `compliance_export`.
Request body
subjectDidrequired | string | |
reasonrequired | string | Max length 500. |
Responses
200 | Flags cleared |
404 | Person not found in this organization |
Billing
POST/v1/billing/chain/confirmPublicSettle an on-chain A payment for a Growth checkout
Verifies a transfer to the treasury carrying this checkout's memo, then provisions the organization. A transaction id can only ever pay for one checkout. Returns 425 while the transfer is still reversible — retry shortly rather than treating it as a rejection.
Request body
sessionIdrequired | string | |
transactionIdrequired | string | |
account | string | Optional payer; when set the transfer must originate from it |
Responses
201 | Organization set up (API key returned once) |
400 | Payment not verified |
409 | Already provisioned |
425 | Transfer seen but not yet irreversible — retry |
GET/v1/billing/checkout/{sessionId}PublicPoll checkout status
Parameters
sessionIdrequired | string · path |
Responses
200 | Status with a masked email; never includes the API key |
404 | Unknown session |
Phone
POST/v1/phone/otp/startSend an SMS one-time code
Rate limited per phone and per client IP. Requires `accounts_write`. On hosted sandbox and production the code is delivered by SMS only — it is never returned in the response body.
Request body
phonerequired | string | E.164, or a local Ghana number |
Responses
200 | Challenge issuedchallengeId: string (uuid) · expiresAt: string (date-time) · phoneMasked: string |
429 | Too many codes for this phone or IP |
POST/v1/phone/otp/confirmConfirm an SMS code, returning a short-lived phone proof
Capped at 5 attempts per challenge. The returned `phoneProofToken` is accepted by `POST /v1/accounts/free` for 15 minutes.
Request body
challengeIdrequired | string (uuid) | |
coderequired | string |
Responses
200 | Proof issuedphoneProofToken: string · expiresInSec: integer · personDid: string · softIdentity: boolean |
400 | Invalid or expired code |
429 | Attempt cap reached for this challenge |
Contact
Verified email and phone bound to an account. Contact channels are never identity evidence - KYC owns that.
POST/v1/email/otp/startEmail a verification code
Confirming an email never creates or promotes a Person. Email is a contact channel; KYC remains the only identity authority.
Request body
emailrequired | string (email) |
Responses
200 | Challenge issuedchallengeId: string (uuid) · expiresAt: string (date-time) · emailMasked: string · devCode: string |
409 | Address already bound to an account |
POST/v1/email/otp/confirmConfirm an emailed code; returns a short-lived emailProofToken
Request body
challengeIdrequired | string (uuid) | |
coderequired | string |
Responses
200 | VerifiedemailProofToken: string · expiresInSec: integer · emailMasked: string |
429 | Too many attempts on this challenge |
GET/v1/accounts/{did}/contactMasked view of the contacts bound to an account
Parameters
didrequired | string · path | Account DID, or the bare chain name. |
Responses
200 | Masked contacts; raw values are never returned |
POST/v1/accounts/contact/challengeIssue a nonce the account's owner key must sign to change a contact
Contact changes are authorized by the account key, not by the session — whoever can change the address controls where account notices go. The nonce is single-use and consumed by the matching start call.
Request body
accountDidrequired | string | |
channelrequired | email | phone |
Responses
200 | Challengenonce: string · payloadToSign: string · expiresAt: string (date-time) |
POST/v1/accounts/contact/email/startSend a code to a proposed new email
Nothing is unbound until the code is confirmed.
Request body
accountDidrequired | string | |
emailrequired | string (email) | |
noncerequired | string | |
signaturerequired | string |
Responses
200 | Challenge issued |
400 | INVALID_SIGNATURE - the signature does not verify against the account's active key. |
409 | Address already bound to another account |
POST/v1/accounts/contact/email/confirmConfirm the code and swap the bound email
Detach and attach run in one transaction, so the account is never left without a verified email. The previous address is notified.
Request body
challengeIdrequired | string (uuid) | |
coderequired | string |
Responses
200 | Changed |
POST/v1/accounts/contact/phone/startSend a code to a proposed new phone number
Request body
accountDidrequired | string | |
phonerequired | string | |
noncerequired | string | |
signaturerequired | string |
Responses
200 | Challenge issued |
POST/v1/accounts/contact/phone/confirmConfirm the code and swap the bound phone number
Request body
challengeIdrequired | string (uuid) | |
coderequired | string |
Responses
200 | Changed |
Resolution
GET/.well-known/did.jsonPublicIssuer DID document (W3C did:web)
Publishes the Ed25519 `assertionMethod` key used to sign issued credentials. A relying party needs only this document to verify a credential offline.
Responses
200 | DID document |
GET/people/{personId}/did.jsonPublicPerson DID document (W3C did:web)
Parameters
personIdrequired | string (uuid) · path |
Responses
200 | DID document exposing KYC status only |
404 | Unknown person |
GET/orgs/{orgId}/did.jsonPublicOrganization DID document (W3C did:web)
Parameters
orgIdrequired | string (uuid) · path |
Responses
200 | DID document exposing KYB status only |
404 | Unknown organization |
Ops
GET/v1/healthPublicLiveness
Responses
200 | OK |
GET/v1/whoamiPublicThe source IP this API attributes to you
Returns the address tenant IP allowlists are compared against. Call it from the server that will hold your API key before configuring an allowlist: NAT and egress gateways mean the address we see is often not the one you expect, and an allowlist that does not match fails closed — every request returns 403. Unauthenticated, and returns nothing beyond your own address.
Responses
200 | The caller's resolved source addressclientIp: string |
GET/v1/readyPublicReadiness
Responses
200 | Ready |
503 | Not ready |
GET/v1/metricsMetrics (Prometheus text exposition)
Requires `read` scope.
Responses
200 | text/plain exposition |
401 | Missing or invalid API key |
GET/v1/billing/plansPublicPublic Identity Inc. plan catalog
Responses
200 | Plans |
POST/v1/billing/ai/advisePublicAI checkout advisor (rate-limited)
Responses
200 | Recommendation |
429 | Rate limited |
POST/v1/billing/checkoutPublicBuilder signup, Growth Stripe Checkout, or Institutional lead
Responses
200 | Redirect or sales lead |
201 | Builder provisioned |
429 | Rate limited |
POST/v1/billing/stripe/webhookPublicStripe lifecycle + checkout.session.completed
Responses
200 | Received |
POST/v1/billing/checkout/completePublicComplete a pending Growth checkout (sandbox / test settlement)
Responses
200 | Provisioned or upgraded |
POST/v1/billing/checkout/revealPublicReveal API key once after Stripe Checkout (rate-limited)
Responses
200 | Key payload |
GET/v1/billing/usagePlan + usage meters for the authenticated organization
Responses
200 | Usage snapshot |
POST/v1/billing/upgradeBuilder → Growth Checkout (keeps organization + keys)
Responses
200 | Checkout redirect |
POST/v1/billing/portalStripe Customer Portal session
Responses
200 | Portal URL |