v0.1.0-beta

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.

https://api.13-61-145-21.sslip.io/api/v1
Note
The Veil API uses a job queue for all on-chain operations. Most write endpoints return a 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

GET/healthHealth check

Returns service status. Use this to confirm the backend is reachable.

jsonResponse
{ "success": true, "service": "veil-backend", "version": "v1" }
GET/contractContract address

Returns the deployed Midnight contract address.

jsonResponse
{
  "success": true,
  "contractAddress": "7c7d7b78ebcf6a67862fde64d5717f08109cadf0666b90b3b8179aef15ec1b9e"
}

DID Resolution

GET/dids/resolveResolve a Veil DID to a DID Document

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

ParameterRequiredDescription
didYesThe Veil DID to resolve, e.g. did:veil:0x1a2b3c…

Response (200 OK)

json
{
  "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

StatusCause
400Missing or malformed did query parameter
404DID not found — user has not minted an identity pass yet
409DID found but not yet registered on Midnight (mint in progress)

Score Entries

POST/score-entriesRegister a user and prepare their identity pass

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

json
{
  "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)

json
{
  "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

POST/ckb/veil-identity/mint-intentCreate an identity pass mint intent

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

json
{
  "veilIdHash": "0x...",
  "userCkbAddress": "ckb1qyq..."
}

Response (200 OK)

json
{
  "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": [{ "...": "..." }]
  }
}
POST/ckb/veil-identity/recordRecord a completed identity pass mint and register the DID

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

json
{
  "veilIdHash": "0x...",
  "userPk": "0x...",              // optional — used to register DID on Midnight
  "userCkbAddress": "ckb1qyq...",
  "sporeId": "0x...",
  "txHash": "0x..."
}

Response (201 Created)

json
{
  "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)
}
GET/ckb/veil-identity/:sporeIdFetch identity pass content and validity

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)

json
{
  "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.

Issuer authorization required
Your issuerPk must be registered on-chain. Unregistered issuers are rejected by the ZK circuit — the job will fail with a circuit validation error.
POST/scoring-events/repaymentsSubmit a loan repayment event
json
{
  "userPk": "0xabc123...",
  "issuerPk": "0xdef456...",
  "paidOnTimeFlag": 1,
  "amountWeight": 1000,
  "eventEpoch": 5,
  "eventId": "0x..."          // optional — auto-generated if omitted
}
jsonResponse (202)
{
  "success": true,
  "job": {
    "id": "job_9a2b3c4d",
    "status": "pending",
    "createdAt": "2025-06-07T12:00:00.000Z"
  }
}
POST/scoring-events/liquidationsSubmit a liquidation event
json
{
  "userPk": "0xabc123...",
  "issuerPk": "0xdef456...",
  "severity": 2,              // 1 = minor, 2 = moderate, 3 = severe
  "eventEpoch": 5,
  "eventId": "0x..."
}
jsonResponse (202)
{
  "success": true,
  "job": {
    "id": "job_9a2b3c4d",
    "status": "pending",
    "createdAt": "2025-06-07T12:00:00.000Z"
  }
}
POST/scoring-events/protocol-usageSubmit a protocol usage event
json
{
  "userPk": "0xabc123...",
  "issuerPk": "0xdef456...",
  "protocolId": "0x...",      // stable 32-byte protocol identifier
  "eventEpoch": 5
}
jsonResponse (202)
{
  "success": true,
  "job": {
    "id": "job_9a2b3c4d",
    "status": "pending",
    "createdAt": "2025-06-07T12:00:00.000Z"
  }
}
POST/scoring-events/debt-statesSubmit a debt state snapshot
json
{
  "userPk": "0xabc123...",
  "issuerPk": "0xdef456...",
  "activeDebtFlag": 1,        // 1 = has active debt, 0 = none
  "riskBand": 1,              // 0 = healthy → 3 = high-risk
  "eventEpoch": 5,
  "eventId": "0x..."
}
jsonResponse (202)
{
  "success": true,
  "job": {
    "id": "job_9a2b3c4d",
    "status": "pending",
    "createdAt": "2025-06-07T12:00:00.000Z"
  }
}

Jobs

GET/jobs/:idPoll job status

Returns the current status of any asynchronous job. Poll every 3–5 seconds until the status is succeeded or failed.

jsonpending
{
  "success": true,
  "job": {
    "id": "job_9a2b3c4d",
    "status": "pending",
    "createdAt": "2025-06-07T12:00:00.000Z"
  }
}
jsonsucceeded
{
  "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"
  }
}
jsonfailed
{
  "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

POST/challengesObtain a one-time challenge

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.

jsonResponse (201)
{
  "challenge": "0x1a2b3c4d...",   // 32-byte hex — include in /credit-decisions
  "challengeExpiresAt": 1749294060000  // unix ms — expires in 60 seconds
}

Credit Decisions

POST/credit-decisionsRequest a ZK-verified credit decision

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.

DID authorization message
The user's CKB wallet signs the following newline-delimited message:Veil credit decision authorization did:{did:veil:0x...} challenge:{challengeHex} verificationMethod:{did:veil:0x...#ckb-owner-1} registryVersion:1 purpose:credit-decision
jsonDID request body
{
  "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.

jsonLegacy request body
// 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)

json
{
  "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

StatusCause
400Missing required fields or malformed DID
401Challenge expired or already used
401Identity pass veilIdHash does not match request
401CKB address does not match identity pass owner lock hash
401Invalid or unrecognized Veil Identity Pass
401Authorization signature does not verify
404DID not found
409DID not yet registered on Midnight
500Backend or Midnight node error — retry with backoff

Type Reference

typescripttypes.ts
// 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;
}