Skip to Content
API ReferenceVerify API

Verify API

The Verify API powers KYC verification. Organizations register, create verification sessions, and receive results through webhooks or polling. End users complete verification through a hosted flow or direct API calls.

Base URL: https://verify.solidus.network/v1


Authentication

Register

Create a new organization account.

POST /auth/register

Auth: None

// Request body { "email": "[email protected]", "password": "secureP@ssw0rd!", "name": "Acme Corp" }
// Response 201 { "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...", "user": { "id": "usr_8Kj2mNpQxR", "email": "[email protected]", "name": "Acme Corp", "organizationId": "org_3Vn9wLkDfT", "createdAt": "2026-05-07T10:30:00Z" } }

Login

Authenticate with email and password. If TOTP is enabled, a totpCode is required.

POST /auth/login

Auth: None

// Request body { "email": "[email protected]", "password": "secureP@ssw0rd!" }
// Response 200 — success { "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...", "user": { "id": "usr_8Kj2mNpQxR", "email": "[email protected]", "name": "Acme Corp", "organizationId": "org_3Vn9wLkDfT" } }
// Response 200 — TOTP required { "totpRequired": true }

When totpRequired is true, resend the login request with the totpCode field.

// Request body with TOTP { "email": "[email protected]", "password": "secureP@ssw0rd!", "totpCode": "482901" }

Get Current User

Retrieve the authenticated user and organization details.

GET /auth/me

Auth: JWT Bearer

curl https://verify.solidus.network/v1/auth/me \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIs..."
// Response 200 { "organization": { "id": "org_3Vn9wLkDfT", "name": "Acme Corp", "email": "[email protected]", "plan": "starter", "totpEnabled": false, "createdAt": "2026-05-07T10:30:00Z" } }

Setup TOTP

Generate a TOTP secret and QR code for two-factor authentication.

POST /auth/totp/setup

Auth: JWT Bearer

// Response 200 { "secret": "JBSWY3DPEHPK3PXP", "qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..." }

Enable TOTP

Confirm TOTP setup by providing the secret and a valid code from the authenticator app.

POST /auth/totp/enable

Auth: JWT Bearer

// Request body { "secret": "JBSWY3DPEHPK3PXP", "totpCode": "482901" }
// Response 200 { "message": "TOTP enabled successfully" }

Forgot Password

Send a password reset email.

POST /auth/forgot-password

Auth: None

// Request body { "email": "[email protected]" }
// Response 200 { "message": "If an account with that email exists, a reset link has been sent" }

Reset Password

Set a new password using the token from the reset email.

POST /auth/reset-password

Auth: None

// Request body { "token": "rst_7Hk3mRtQZvXp2...", "password": "newSecureP@ssw0rd!" }
// Response 200 { "message": "Password reset successfully" }

OAuth Login

Redirect the user to Google or GitHub for OAuth authentication. After authorization, the user is redirected back with a JWT.

GET /auth/google — Redirects to Google OAuth

GET /auth/github — Redirects to GitHub OAuth


Verification Sessions

Create a Session

Create a new verification session. The response includes a sessionUrl that you can redirect users to for the hosted verification flow.

POST /verifications

Auth: API Key (x-api-key)

// Request body { "level": 2, "sandbox": false, "redirectUrl": "https://acme.com/onboarding/complete", "subjectDid": "did:solidus:testnet:7Hk3mRtQZvXp2nBw9Lj4K", "metadata": { "userId": "usr_acme_12345", "plan": "premium" } }
FieldTypeRequiredDescription
level1 | 2 | 3YesVerification level. 1 = document only, 2 = document + liveness, 3 = document + liveness + address proof
sandboxbooleanNoIf true, runs in sandbox mode with no real verification
sandboxOutcomestringNoForce a sandbox result: "pass", "fail", "timeout", "document_rejected"
subjectDidstringNoAssociate the session with a specific DID
redirectUrlstringNoURL to redirect the user after completion
metadataobjectNoArbitrary key-value pairs stored with the session
// Response 201 { "session": { "id": "ver_9Xm4pLkWnR", "organizationId": "org_3Vn9wLkDfT", "status": "pending", "level": 2, "sandbox": false, "subjectDid": "did:solidus:testnet:7Hk3mRtQZvXp2nBw9Lj4K", "redirectUrl": "https://acme.com/onboarding/complete", "metadata": { "userId": "usr_acme_12345", "plan": "premium" }, "sessionToken": "sess_tok_7Hk3mRtQZv...", "createdAt": "2026-05-07T14:20:00Z", "expiresAt": "2026-05-07T15:20:00Z" }, "sessionUrl": "https://verify.solidus.network/v/s/sess_tok_7Hk3mRtQZv..." }

List Sessions

Retrieve all verification sessions for your organization.

GET /verifications

Auth: API Key (x-api-key)

Query ParamTypeDescription
statusstringFilter by status: pending, processing, completed, failed, expired
limitnumberResults per page (default: 20, max: 100)
offsetnumberOffset for pagination
curl "https://verify.solidus.network/v1/verifications?status=approved&limit=10" \ -H "x-api-key: sk_live_abc123def456..."
// Response 200 { "sessions": [ { "id": "ver_9Xm4pLkWnR", "status": "completed", "level": 2, "subjectDid": "did:solidus:testnet:7Hk3mRtQZvXp2nBw9Lj4K", "credentialId": "vc_3Kn8rTpXm2", "createdAt": "2026-05-07T14:20:00Z", "completedAt": "2026-05-07T14:25:30Z" } ], "total": 1 }

Get Session

Retrieve a single verification session by ID.

GET /verifications/:id

Auth: API Key (x-api-key)

curl https://verify.solidus.network/v1/verifications/ver_9Xm4pLkWnR \ -H "x-api-key: sk_live_abc123def456..."
// Response 200 { "session": { "id": "ver_9Xm4pLkWnR", "organizationId": "org_3Vn9wLkDfT", "status": "completed", "level": 2, "sandbox": false, "subjectDid": "did:solidus:testnet:7Hk3mRtQZvXp2nBw9Lj4K", "redirectUrl": "https://acme.com/onboarding/complete", "metadata": { "userId": "usr_acme_12345", "plan": "premium" }, "documents": [ { "type": "passport", "side": "front", "status": "verified", "extractedData": { "firstName": "John", "lastName": "Doe", "dateOfBirth": "1990-03-15", "documentNumber": "AB1234567", "expiryDate": "2030-06-20", "nationality": "US" } } ], "liveness": { "status": "passed", "score": 0.97, "completedAt": "2026-05-07T14:24:10Z" }, "credentialId": "vc_3Kn8rTpXm2", "createdAt": "2026-05-07T14:20:00Z", "completedAt": "2026-05-07T14:25:30Z" } }

Hosted Flow

These endpoints are used by the hosted verification UI. They authenticate with the session token embedded in the URL path rather than an API key.

Get Session by Token

Retrieve the session for the hosted flow.

GET /verifications/s/:sessionToken

Auth: Session token (in URL)

// Response 200 { "session": { "id": "ver_9Xm4pLkWnR", "status": "pending", "level": 2, "organizationName": "Acme Corp", "steps": ["document", "liveness"], "currentStep": "document", "expiresAt": "2026-05-07T15:20:00Z" } }

Upload Document (Hosted)

Upload an identity document for the current session.

POST /verifications/s/:sessionToken/documents

Auth: Session token (in URL)

Content-Type: multipart/form-data

FieldTypeRequiredDescription
fileFileYesThe document image (JPEG or PNG, max 10 MB)
sidestringYesfront or back
typestringYespassport, driving_license, national_id, or residence_permit
finalbooleanNoIf true, marks the document step as complete
curl -X POST "https://verify.solidus.network/v1/verifications/s/sess_tok_7Hk3mRtQZv.../documents" \ -F "file=@passport_front.jpg" \ -F "side=front" \ -F "type=passport" \ -F "final=true"
// Response 201 { "document": { "id": "doc_5Wn2kLpMxR", "type": "passport", "side": "front", "status": "uploaded", "createdAt": "2026-05-07T14:22:00Z" } }

Get Liveness Challenge (Hosted)

Request a liveness challenge for the session.

GET /verifications/s/:sessionToken/liveness-challenge

Auth: Session token (in URL)

// Response 200 { "challenge": { "id": "chl_8Rn3mKpWxT", "type": "head_turn", "instructions": "Please turn your head slowly to the left", "expiresAt": "2026-05-07T14:30:00Z" } }

Submit Liveness (Hosted)

Submit liveness frames for the challenge.

POST /verifications/s/:sessionToken/liveness

Auth: Session token (in URL)

Content-Type: multipart/form-data

FieldTypeRequiredDescription
framesFile[]YesVideo frames captured during the liveness challenge
// Response 200 { "liveness": { "status": "passed", "score": 0.97 } }

Document Upload (API Key)

Upload documents and complete liveness checks using your API key instead of the hosted flow.

Upload Document

POST /verifications/:id/documents

Auth: API Key (x-api-key)

Content-Type: multipart/form-data

FieldTypeRequiredDescription
fileFileYesThe document image (JPEG or PNG, max 10 MB)
sidestringYesfront or back
typestringYespassport, driving_license, national_id, or residence_permit
finalbooleanNoIf true, marks the document step as complete and triggers processing
curl -X POST "https://verify.solidus.network/v1/verifications/ver_9Xm4pLkWnR/documents" \ -H "x-api-key: sk_live_abc123def456..." \ -F "file=@passport_front.jpg" \ -F "side=front" \ -F "type=passport" \ -F "final=true"
// Response 201 { "document": { "id": "doc_5Wn2kLpMxR", "type": "passport", "side": "front", "status": "uploaded", "createdAt": "2026-05-07T14:22:00Z" } }

Get Liveness Challenge

GET /verifications/:id/liveness-challenge

Auth: API Key (x-api-key)

// Response 200 { "challenge": { "id": "chl_8Rn3mKpWxT", "type": "head_turn", "instructions": "Please turn your head slowly to the left", "expiresAt": "2026-05-07T14:30:00Z" } }

Submit Liveness

POST /verifications/:id/liveness

Auth: API Key (x-api-key)

Content-Type: multipart/form-data

FieldTypeRequiredDescription
framesFile[]YesVideo frames captured during the liveness challenge
// Response 200 { "liveness": { "status": "passed", "score": 0.97 } }

API Keys

Manage API keys for your organization. Each key is scoped to live or sandbox mode.

Create API Key

POST /api-keys

Auth: JWT Bearer

// Request body { "name": "Production Key", "mode": "live" }
// Response 201 { "key": "sk_live_abc123def456ghi789jkl012mno345pqr678", "id": "key_4Tn7mLkPxR" }

The full key is only returned once at creation. Store it securely.

List API Keys

GET /api-keys

Auth: JWT Bearer

// Response 200 { "keys": [ { "id": "key_4Tn7mLkPxR", "name": "Production Key", "mode": "live", "prefix": "sk_live_abc123...", "lastUsedAt": "2026-05-07T14:00:00Z", "createdAt": "2026-05-01T09:00:00Z" }, { "id": "key_6Wn9pMkRxT", "name": "Test Key", "mode": "sandbox", "prefix": "sk_sandbox_xyz789...", "lastUsedAt": null, "createdAt": "2026-05-03T11:00:00Z" } ] }

Delete API Key

DELETE /api-keys/:keyId

Auth: JWT Bearer

curl -X DELETE https://verify.solidus.network/v1/api-keys/key_4Tn7mLkPxR \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIs..."
// Response 200 { "message": "API key deleted" }

Webhooks

Receive real-time notifications when verification events occur. Webhook payloads are signed with HMAC-SHA256 for verification.

Create Webhook Endpoint

POST /webhooks

Auth: JWT Bearer

// Request body { "url": "https://acme.com/webhooks/solidus", "events": [ "verification.completed", "verification.failed", "verification.expired" ], "description": "Production webhook for verification results" }

Available events: verification.completed, verification.failed, verification.expired, credential.issued, credential.revoked. Default (if events is omitted): verification.completed, credential.issued.

// Response 201 { "endpoint": { "id": "wh_2Kn5mTpLxR", "url": "https://acme.com/webhooks/solidus", "events": [ "verification.completed", "verification.failed", "verification.expired" ], "description": "Production webhook for verification results", "secret": "whsec_7Hk3mRtQZvXp2nBw9Lj4K...", "enabled": true, "createdAt": "2026-05-07T10:00:00Z" } }

The secret is only returned once. Use it to verify webhook signatures.

List Webhook Endpoints

GET /webhooks

Auth: JWT Bearer

// Response 200 { "endpoints": [ { "id": "wh_2Kn5mTpLxR", "url": "https://acme.com/webhooks/solidus", "events": ["verification.completed", "verification.failed", "verification.expired"], "description": "Production webhook for verification results", "enabled": true, "createdAt": "2026-05-07T10:00:00Z" } ] }

Update Webhook Endpoint

PATCH /webhooks/:endpointId

Auth: JWT Bearer

// Request body { "url": "https://acme.com/webhooks/solidus-v2", "events": ["verification.completed", "verification.failed"], "enabled": true }

All fields are optional. Only provided fields are updated.

// Response 200 { "endpoint": { "id": "wh_2Kn5mTpLxR", "url": "https://acme.com/webhooks/solidus-v2", "events": ["verification.completed", "verification.failed"], "description": "Production webhook for verification results", "enabled": true, "createdAt": "2026-05-07T10:00:00Z" } }

Delete Webhook Endpoint

DELETE /webhooks/:endpointId

Auth: JWT Bearer

curl -X DELETE https://verify.solidus.network/v1/webhooks/wh_2Kn5mTpLxR \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIs..."
// Response 200 { "message": "Webhook endpoint deleted" }

List Webhook Deliveries

View delivery history for a specific endpoint.

GET /webhooks/:endpointId/deliveries

Auth: JWT Bearer

Query ParamTypeDescription
statusstringFilter by status: success, failed, pending
limitnumberResults per page (default: 20, max: 100)
offsetnumberOffset for pagination
// Response 200 { "deliveries": [ { "id": "dlv_9Xm4pLkWnR", "event": "verification.completed", "status": "success", "statusCode": 200, "payload": { "event": "verification.completed", "data": { "sessionId": "ver_9Xm4pLkWnR", "status": "completed", "credentialId": "vc_3Kn8rTpXm2" }, "timestamp": "2026-05-07T14:25:30Z" }, "attemptCount": 1, "deliveredAt": "2026-05-07T14:25:31Z" } ], "total": 1 }

Bridge Export

Verify issues credentials, and three routes copy the status of one of them to another chain through Solidus Bridge . The paths below sit under /v1/bridge. They are not part of the read-only Bridge API, which only reports what happened. Everything here runs on testnets.

An export needs proof that the caller holds the credential. Verify mints a challenge, Solidus Identity signs a presentation over it with the holder’s DID key, and Verify checks the presentation before it submits to Solidus as the issuer. A challenge works once, within five minutes. How to export a credential  walks through the holder’s side.

Create a Challenge

POST /bridge/challenge

Auth: none. Limited to 10 requests a minute per client.

// Request body {}
// Response 200 { "challenge": "<one-time challenge>" }

Export a Credential

POST /bridge/exports

Auth: none. The presentation is the proof.

// Request body { "presentation": "<presentation signed by Solidus Identity>", "challenge": "<from POST /bridge/challenge>", "credentialId": "urn:solidus:credential:EXAMPLE", "domain": 11155111, "holder": "0x0000000000000000000000001111111111111111111111111111111111111111", "validUntil": 0, "consentSignature": "0x...", "consentExpiry": 1900000000 }

Every field is required, and a field not listed here is rejected.

FieldMeaning
presentationThe proof from Solidus Identity, signed over the challenge.
challengeThe challenge Verify issued for this presentation.
credentialIdThe credential to export.
domainThe destination. On EVM it equals the chain ID. GET https://bridge.solidus.network/v1/routes lists them.
holder0x plus 64 hex characters: the holder’s EVM address padded with zeros, or a Solana public key.
validUntilWhen the destination stops honoring the export, in Unix seconds. 0 means none.
consentSignatureThe holder’s consent: EIP-712 typed data on EVM, an ed25519 signature on Solana. exportConsentTypedData and ed25519ConsentBytes in @solidus-network/bridge build what is signed.
consentExpiryWhen the consent stops being valid, in Unix seconds. It must be in the future.
// Response 202 { "txHash": "0x...", "exportId": "0x..." }

A 202 means Solidus accepted the transaction, not that the destination applied it. Follow the export with GET /v1/credentials/{exportId} on the Bridge API.

Take an Export Back

POST /bridge/unexports

Auth: none. The presentation is the proof.

// Request body { "presentation": "<presentation signed by Solidus Identity>", "challenge": "<from POST /bridge/challenge>", "credentialId": "urn:solidus:credential:EXAMPLE", "domain": 11155111, "holder": "0x0000000000000000000000001111111111111111111111111111111111111111" }

The fields mean what they mean for an export. An unexport needs no consent and no validUntil. It succeeds only for an active export; Solidus then queues an unbound status for the destination. The response is the same 202 with txHash and exportId.

Bridge Export Errors

Errors are application/problem+json with type, title, status, code and detail.

StatuscodeMeaning
400invalid_requestThe body is not valid. detail names the first field and the reason.
400unknown_domaindomain is not a registered, enabled destination.
400invalid_consentThe consent has expired, is malformed, does not verify for this holder, or is for a chain type that is not supported.
401invalid_presentationThe presentation did not verify against the challenge.
403not_credential_subjectThe presentation is not from the credential’s subject.
404bridge_disabledBridge export is switched off on this deployment.
409issuer_not_servedThis Verify instance did not issue the credential.
409already_exportedThe credential is already exported to this holder and domain.
409chain_refusedSolidus refused the transaction. detail carries the reason.
429More than 10 challenges a minute from one client.

A 500 means Solidus could not be reached; the request can be repeated.

Last updated on