API Reference
Complete REST API reference for the Veil Protocol backend service. All endpoints accept and return JSON over HTTPS and use standard HTTP status codes.
Base URL
Set VEIL_API_URL to the Veil backend endpoint provided during issuer onboarding. The current preview API base is https://api.13-61-145-21.sslip.io/api/v1. All paths below are relative to this base URL.
job object immediately — use GET /jobs/:id to poll for completion before taking action on the result. Credit decisions (POST /credit-decisions) are synchronous and return the result directly.System
Returns service status. Use this to confirm the backend is reachable.
{ "success": true, "service": "veil-backend", "version": "v1" }Returns the deployed Midnight contract address.
{
"success": true,
"contractAddress": "7c7d7b78ebcf6a67862fde64d5717f08109cadf0666b90b3b8179aef15ec1b9e"
}DID Resolution
Resolves a did:veil:…identifier to a W3C-compliant DID Document. The document contains the user's verification method (CKB public key hash), authentication references, and service endpoints for credit decisions and identity pass lookup. Use this endpoint to verify a user's identity or to discover the credit decision service endpoint from their DID alone.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
did | Yes | The Veil DID to resolve, e.g. did:veil:0x1a2b3c… |
Response (200 OK)
{
"success": true,
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://veil.id/contexts/veil-did/v1"
],
"id": "did:veil:0x...",
"controller": "did:veil:0x...",
"verificationMethod": [{
"id": "did:veil:0x...#ckb-owner-1",
"type": "CkbSecp256k1VerificationKey2026",
"controller": "did:veil:0x...",
"blockchainAccountId": "ckb:ckb1qyq...",
"publicKeyHash": "0x..."
}],
"authentication": ["did:veil:0x...#ckb-owner-1"],
"assertionMethod": ["did:veil:0x...#ckb-owner-1"],
"service": [
{
"id": "did:veil:0x...#credit-decision",
"type": "VeilCreditDecisionService",
"serviceEndpoint": "https://api.13-61-145-21.sslip.io/api/v1/credit-decisions"
},
{
"id": "did:veil:0x...#identity-dob",
"type": "CkbSporeDobService",
"serviceEndpoint": "ckb:spore:0x..."
}
],
"veil": {
"veilIdHash": "0x...",
"status": "active",
"version": 1,
"sporeId": "0x...",
"sporeIdHash": "0x...",
"ckbOwnerLockHash": "0x...",
"midnightRegistry": "7c7d7b78...",
"createdAt": "2025-06-07T12:00:00.000Z"
}
}
}Error responses
| Status | Cause |
|---|---|
400 | Missing or malformed did query parameter |
404 | DID not found — user has not minted an identity pass yet |
409 | DID found but not yet registered on Midnight (mint in progress) |
Score Entries
Creates an empty credit score entry on Midnight for the user and returns a CKB identity pass mint intent plus the user's Veil DID. Call this when a user first joins the Veil protocol. If the score entry already exists, the endpoint returns the existing state without creating a new job.
Request Body
{
"userPk": "0xabc123...", // user's Veil public key (32 bytes hex)
"veilIdHash": "0x...", // optional — auto-derived from userPk if omitted
"userCkbAddress": "ckb1qyq..." // user's CKB wallet address for DOB minting
}Response (202 Accepted — new entry / 200 OK — already exists)
{
"success": true,
"created": true,
"did": "did:veil:0x...", // the user's Veil DID (W3C DID format)
"scoreEntry": { "exists": true, "hasAccumulator": false, "hasCreditScore": false },
"ckbMintIntent": { ... }, // mint intent for the CKB identity pass
"job": {
"id": "job_7f3a91b2",
"status": "pending",
"createdAt": "2025-06-07T12:00:00.000Z"
}
}CKB Identity
Returns a Spore DOB mint intent for the user's Veil Identity Pass. The frontend passes this intent to the user's CKB wallet to submit the mint transaction on the CKB network. If the identity pass is already minted, returnsalreadyMinted: true with the existing DOB record.
Request Body
{
"veilIdHash": "0x...",
"userCkbAddress": "ckb1qyq..."
}Response (200 OK)
{
"success": true,
"intent": {
"contentType": "application/json",
"content": {
"protocol": "Veil",
"objectType": "VeilIdentity",
"veilIdHash": "0x...",
"ownerCkbLockHash": "0x...",
"midnightNetwork": "preprod",
"midnightContract": "7c7d7b78...",
"version": "1"
},
"lockScript": {
"codeHash": "0x...",
"hashType": "data2",
"args": "0x..."
},
"cellDeps": [{ "...": "..." }]
}
}After the user submits the mint transaction from their CKB wallet, call this endpoint to record the Spore ID and transaction hash. When userPk is provided, the backend also registers the user's DID on Midnight via the DID Registry contract. The response includes the user's did:veil:… identifier.
Request Body
{
"veilIdHash": "0x...",
"userPk": "0x...", // optional — used to register DID on Midnight
"userCkbAddress": "ckb1qyq...",
"sporeId": "0x...",
"txHash": "0x..."
}Response (201 Created)
{
"success": true,
"did": "did:veil:0x...", // the user's Veil DID — store this for credit checks
"sporeId": "0x...",
"sporeIdHash": "0x...",
"txHash": "0x...",
"veilIdHash": "0x...",
"ckbOwnerLockHash": "0x...",
"didRegistryTxHash": "0x..." // Midnight DID registration tx hash (when userPk provided)
}Returns the content stored inside the user's Veil Identity Pass (Spore DOB) and verifies that the DOB content and lock script match the expected Veil identity anchor format.
Response (200 OK)
{
"sporeId": "0x...",
"content": {
"protocol": "Veil",
"objectType": "VeilIdentity",
"veilIdHash": "0x...",
"ownerCkbLockHash": "0x...",
"midnightNetwork": "preprod",
"midnightContract": "7c7d7b78...",
"version": "1"
},
"validVeilIdentity": true
}Scoring Events
All four event endpoints accept userPk, issuerPk, and aneventEpoch. Each returns a job — poll GET /jobs/:id for completion.
issuerPk must be registered on-chain. Unregistered issuers are rejected by the ZK circuit — the job will fail with a circuit validation error.{
"userPk": "0xabc123...",
"issuerPk": "0xdef456...",
"paidOnTimeFlag": 1,
"amountWeight": 1000,
"eventEpoch": 5,
"eventId": "0x..." // optional — auto-generated if omitted
}{
"success": true,
"job": {
"id": "job_9a2b3c4d",
"status": "pending",
"createdAt": "2025-06-07T12:00:00.000Z"
}
}{
"userPk": "0xabc123...",
"issuerPk": "0xdef456...",
"severity": 2, // 1 = minor, 2 = moderate, 3 = severe
"eventEpoch": 5,
"eventId": "0x..."
}{
"success": true,
"job": {
"id": "job_9a2b3c4d",
"status": "pending",
"createdAt": "2025-06-07T12:00:00.000Z"
}
}{
"userPk": "0xabc123...",
"issuerPk": "0xdef456...",
"protocolId": "0x...", // stable 32-byte protocol identifier
"eventEpoch": 5
}{
"success": true,
"job": {
"id": "job_9a2b3c4d",
"status": "pending",
"createdAt": "2025-06-07T12:00:00.000Z"
}
}{
"userPk": "0xabc123...",
"issuerPk": "0xdef456...",
"activeDebtFlag": 1, // 1 = has active debt, 0 = none
"riskBand": 1, // 0 = healthy → 3 = high-risk
"eventEpoch": 5,
"eventId": "0x..."
}{
"success": true,
"job": {
"id": "job_9a2b3c4d",
"status": "pending",
"createdAt": "2025-06-07T12:00:00.000Z"
}
}Jobs
Returns the current status of any asynchronous job. Poll every 3–5 seconds until the status is succeeded or failed.
{
"success": true,
"job": {
"id": "job_9a2b3c4d",
"status": "pending",
"createdAt": "2025-06-07T12:00:00.000Z"
}
}{
"success": true,
"job": {
"id": "job_9a2b3c4d",
"status": "succeeded",
"result": { "veilIdHash": "0x..." },
"createdAt": "2025-06-07T12:00:00.000Z",
"completedAt": "2025-06-07T12:00:08.000Z"
}
}{
"success": true,
"job": {
"id": "job_9a2b3c4d",
"status": "failed",
"error": "Midnight node timeout — retry with backoff",
"createdAt": "2025-06-07T12:00:00.000Z",
"completedAt": "2025-06-07T12:00:30.000Z"
}
}Challenges
Issues a cryptographically random, single-use challenge for the credit decision authorization flow. Challenges expire after 60 seconds and cannot be reused — request a fresh one for each credit decision.
{
"challenge": "0x1a2b3c4d...", // 32-byte hex — include in /credit-decisions
"challengeExpiresAt": 1749294060000 // unix ms — expires in 60 seconds
}Credit Decisions
Verifies the user's Veil identity, validates their authorization signature, and returns a credit decision based on their private Midnight score. This is a synchronous endpoint — the decision is returned directly in the response. Two request formats are supported.
DID Flow (Recommended)
Pass only the user's did:veil:… identifier. The backend resolves the DID internally to find the identity pass, Midnight score, and CKB wallet.
Veil credit decision authorization
did:{did:veil:0x...}
challenge:{challengeHex}
verificationMethod:{did:veil:0x...#ckb-owner-1}
registryVersion:1
purpose:credit-decision{
"did": "did:veil:0x...", // the user's Veil DID (recommended)
"challenge": "0x1a2b3c4d...", // from POST /challenges
"authorization": {
"signature": "0x...", // CKB wallet signature over DID message
"identity": "0x...", // signing public key (hex)
"signType": "ckbSecp256k1", // CKB signing scheme
"verificationMethod": "did:veil:0x...#ckb-owner-1"
}
}Legacy Flow
Used when the user's DID has not yet been registered on Midnight (older accounts). Pass userPk, veilIdHash, sporeId, and userCkbAddress explicitly.
// Legacy flow — use only when the user's DID is not yet registered on Midnight
{
"userPk": "0xabc123...",
"veilIdHash": "0x...",
"sporeId": "0x...",
"userCkbAddress": "ckb1qyq...",
"challenge": "0x1a2b3c4d...",
"authorization": {
"signature": "0x...",
"identity": "0x...",
"signType": "ckbSecp256k1"
}
}Response (200 OK)
{
"success": true,
"approved": true,
"scoreBand": "gold",
"maxLtvBps": 6500,
"riskPremiumBps": 150,
"hasCreditScore": true,
"reason": "Approved for gold tier",
"veilIdHash": "0x...",
"validAt": "2025-06-07T12:00:10.000Z"
}Error responses
| Status | Cause |
|---|---|
400 | Missing required fields or malformed DID |
401 | Challenge expired or already used |
401 | Identity pass veilIdHash does not match request |
401 | CKB address does not match identity pass owner lock hash |
401 | Invalid or unrecognized Veil Identity Pass |
401 | Authorization signature does not verify |
404 | DID not found |
409 | DID not yet registered on Midnight |
500 | Backend or Midnight node error — retry with backoff |
Type Reference
// Scoring event response (all four event endpoints)
interface EventResponse {
success: boolean;
job: Job;
}
// Job states
type JobStatus = 'pending' | 'succeeded' | 'failed';
interface Job {
id: string;
status: JobStatus;
result?: Record<string, unknown>;
error?: string;
createdAt: string; // ISO 8601
completedAt?: string; // ISO 8601, present when terminal
}
// Credit tiers
type ScoreBand = 'unranked' | 'bronze' | 'silver' | 'gold' | 'platinum';
// Credit decision response
interface CreditDecision {
success: boolean;
approved: boolean;
scoreBand: ScoreBand;
maxLtvBps: number; // max LTV in basis points, e.g. 6500 = 65%
riskPremiumBps: number; // risk premium in basis points, e.g. 150 = 1.5%
hasCreditScore: boolean;
reason: string;
veilIdHash: string;
validAt: string; // ISO 8601
}
// CKB signing schemes supported by authorization
type SignType =
| 'ckbSecp256k1'
| 'btcEcdsa'
| 'nostrEvent';
// Authorization object (included in credit decision requests)
interface Authorization {
signature: string; // hex
identity: string; // public key hex
signType: SignType;
verificationMethod?: string; // e.g. "did:veil:0x...#ckb-owner-1"
}
// DID credit decision request body
interface DidCreditDecisionRequest {
did: string; // e.g. "did:veil:0x..."
challenge: string; // from POST /challenges
authorization: Authorization;
}