API token lifecycle
Keep vda_ tokens server-side and treat the plaintext as a one-time credential. Follow this mint → store → rotate → revoke sequence to keep an integration live without leaving the API reference.
1. Mint. Use the account API-key endpoint with an authenticated session. Send Cookie: auth_token=<session> and Content-Type: application/json; the JSON body must include a name.
POST /api/account/api-keys
Cookie: auth_token=<session>
Content-Type: application/json
{"name":"production"}
2. Store. The 201 response’s key field contains the vda_ plaintext exactly once. Copy it immediately into server-side secret storage. Never put it in source control, browser code, request or console logs, or client-visible configuration. It cannot be reconstructed later: the service retains only the SHA-256 hash and token prefix. See the mint reference for the one-time response.
3. Rotate. For zero downtime, mint a second key, deploy the new secret, switch callers to Authorization: Bearer vda_<new-token>, verify traffic, then revoke the old key. For in-place rotation, use the POST /api/v1/compliance/api-keys/rotate endpoint with the current bearer header; its 201 response returns a one-time replacement key and invalidates the old token immediately.
POST /api/v1/compliance/api-keys/rotate Authorization: Bearer vda_<current-token>
4. Revoke. Get the numeric key ID from the account key list endpoint, then use the account revoke endpoint with DELETE /api/account/api-keys/:id and Cookie: auth_token=<session>. Revocation is immediate; subsequent bearer calls return 401 with invalid_key.
DELETE /api/account/api-keys/<id> Cookie: auth_token=<session>
Error handling
Branch on the HTTP status and stable error.code, not the human-readable message. The response body follows the examples below; messages may become more specific as validation and platform behavior evolve.
{
"error": {
"code": "invalid_key_format",
"message": "API key required. Use Authorization: Bearer vda_<token>"
}
}
Authorization: Bearer vda_<token> header, then follow the token lifecycle guidance. For invalid_key or key_expired, rotate or replace the revoked/expired token and see the detailed error-code contract.
{
"error": {
"code": "invalid_payload",
"message": "first_name is required",
"errors": [
"first_name is required",
"last_name is required",
"At least one location is required",
"purpose is required",
"Consent certification is required"
]
}
}
error.errors before retrying.
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded: 60 subject checks/hour. Check Retry-After header."
}
}
Retry-After response header, whose value is in seconds; it is not part of the JSON body. Follow the rate-limit guidance and sandbox guidance, then wait/back off instead of retrying immediately.
{
"error": {
"code": "internal_error",
"message": "Failed to start subject check"
}
}
Live endpoints
Every path below is registered by the current application. This page documents case decision history, analyst correction, re-review, and verdict-override request and response contracts; use the broader Compliance API reference for the other endpoints and full API guidance.
POST/GET /api/account/api-keys; revocation uses DELETE /api/account/api-keys/:id. In-place bearer rotation uses POST /api/v1/compliance/api-keys/rotate.
Subject check
POST/api/v1/compliance/subject-checksAuthorization: Bearer vda_<token>Content-Type: application/jsonauthenticateAccountApiKey.201 handoff with a running check ID.{
"error": {
"code": "invalid_payload",
"message": "first_name is required",
"errors": [
"first_name is required",
"last_name is required",
"At least one location is required",
"purpose is required",
"Consent certification is required"
]
}
}
{
"error": {
"code": "invalid_key_format",
"message": "API key required. Use Authorization: Bearer vda_<token>"
}
}
{
"error": {
"code": "internal_error",
"message": "Failed to start subject check"
}
}
vda_ bearer token, or retry and contact support if the server returns 500.
Sandbox subject check
POST/api/v1/compliance/subject-checks/sandboxAuthorization: Bearer vda_<token>Content-Type: application/jsonauthenticateAccountApiKey.clear_no_match verdict without a database write, quota decrement, or rate-limit decrement.{
"error": {
"code": "invalid_payload",
"message": "first_name is required",
"errors": [
"first_name is required",
"last_name is required",
"At least one location is required",
"purpose is required",
"Consent certification is required"
]
}
}
{
"error": {
"code": "invalid_key_format",
"message": "API key required. Use Authorization: Bearer vda_<token>"
}
}
{
"error": {
"code": "internal_error",
"message": "Failed to run sandbox subject check"
}
}
vda_ bearer token. Retry a 500 sandbox failure.
Override a subject-check verdict
POST/api/v1/compliance/subject-checks/:id/verdict-overrideAuthorization: Bearer vda_<token>Content-Type: application/jsonvda_ bearer token is scoped to its account. The account user associated with the token must currently have the analyst role.replacement_verdict: accepted, rejected, or escalated, and must differ from the current effective verdict. reason: a trimmed, nonempty string of at most 500 characters.id must be a positive safe integer identifying a completed subject check in the token's account.{
"replacement_verdict": "rejected",
"reason": "Analyst review confirmed a sanctions match."
}
{
"success": true,
"subject_check_id": 12345,
"verdict_override": {
"id": 905,
"person_check_id": 12345,
"previous_disposition": "accepted",
"corrected_disposition": "rejected",
"corrected_by": 42,
"reason": "Analyst review confirmed a sanctions match.",
"created_at": "2026-10-11T14:02:11.082Z"
},
"correction_history": [{ /* newest record first; includes the saved override */ }]
}
404 not_found. Missing or invalid credentials return 401; a user without current analyst access gets 403 analyst_required. 400 invalid_payload covers an invalid ID, missing or unsupported replacement_verdict, or a missing, blank, or over-500-character reason. 409 decision_not_ready means the check is incomplete. 409 verdict_not_overridable means the current effective verdict is unsupported or already equals the replacement; no correction-history record is appended. On success the correction record is returned as verdict_override alongside correction_history, and the API emits the signed subject_check.verdict_overridden event with the persisted reason in override_context.Correct a subject-check disposition
POST/api/v1/compliance/subject-checks/:id/disposition-correctionAuthorization: Bearer vda_<token>Content-Type: application/jsonvda_ bearer token is scoped to its account. The account user associated with the token must currently have the analyst role.disposition: accepted, rejected, or escalated. reason: a trimmed, nonempty string of at most 500 characters.id must be a positive safe integer identifying a completed subject check.{
"disposition": "rejected",
"reason": "Analyst review confirmed a sanctions match."
}
{
"success": true,
"subject_check_id": 12345,
"analyst_correction": {
"id": 987,
"person_check_id": 12345,
"previous_disposition": "accepted",
"corrected_disposition": "rejected",
"corrected_by": 42,
"actor_name": "Alex Analyst",
"actor_email": "alex@example.com",
"reason": "Analyst review confirmed a sanctions match.",
"created_at": "2026-09-29T12:00:00.000Z"
},
"correction_history": [
{
"id": 987,
"person_check_id": 12345,
"previous_disposition": "accepted",
"corrected_disposition": "rejected",
"corrected_by": 42,
"actor_name": "Alex Analyst",
"actor_email": "alex@example.com",
"reason": "Analyst review confirmed a sanctions match.",
"created_at": "2026-09-29T12:00:00.000Z"
}
]
}
{
"error": {
"code": "invalid_payload",
"message": "reason must be nonempty and at most 500 characters"
}
}
401 (invalid_key_format, invalid_key, or key_expired); a user without current analyst access gets 403 analyst_required. 400 invalid_payload covers a path ID that is not a positive safe integer, an unsupported disposition, or a missing, blank, or over-500-character reason. 404 not_found means the check is unknown or belongs to another tenant; those cases intentionally look the same. 409 decision_not_ready means the check is not completed.
Request an analyst re-review
POST/api/v1/compliance/subject-checks/:id/re-reviewAuthorization: Bearer vda_<token>Content-Type: application/jsonoutcome: accepted, rejected, or escalated. reason: a string trimmed by the API, nonempty after trimming, and at most 500 characters.id must be a positive safe integer identifying a completed subject check in the token's account.{
"outcome": "accepted",
"reason": "Updated review found no actionable match."
}
{
"success": true,
"subject_check_id": 12345,
"analyst_correction": {
"id": 988,
"person_check_id": 12345,
"previous_disposition": "rejected",
"corrected_disposition": "accepted",
"corrected_by": 42,
"actor_name": "Alex Analyst",
"actor_email": "alex@example.com",
"reason": "Updated review found no actionable match.",
"created_at": "2026-10-07T12:00:00.000Z"
},
"correction_history": [
{
"id": 988,
"person_check_id": 12345,
"previous_disposition": "rejected",
"corrected_disposition": "accepted",
"corrected_by": 42,
"actor_name": "Alex Analyst",
"actor_email": "alex@example.com",
"reason": "Updated review found no actionable match.",
"created_at": "2026-10-07T12:00:00.000Z"
}
]
}
404 not_found response as an unknown check. The correction_history array is ordered newest first, so analyst_correction matches its first entry. Missing or invalid credentials return 401 (invalid_key_format, invalid_key, or key_expired); a user without current analyst access gets 403 analyst_required. 400 invalid_payload covers an ID that is not a positive safe integer, a missing or unsupported outcome, or a missing, non-string, blank-after-trimming, or over-500-character reason. 409 decision_not_ready means the check is not completed.
{
"error": {
"code": "invalid_payload",
"message": "reason must be nonempty and at most 500 characters"
}
}
Case decision history
GET/api/v1/compliance/cases/:caseId/decision-historyAuthorization: Bearer vda_<token>caseId must be a UUID.automated_verdict and enhanced_review entries include id, type, timestamp, verdict, actor_id, actor_name, actor_email, and reason. re_review entries include id, type, timestamp, previous_disposition, resulting_disposition, the actor fields, reason, and is_override. Timestamps, actor fields, reasons, and previous dispositions may be null. Events are returned oldest first; fields specific to other event types are omitted.curl -i -X GET https://veridact.solutions/api/v1/compliance/cases/550e8400-e29b-41d4-a716-446655440000/decision-history \ -H "Authorization: Bearer vda_<token>"
{
"success": true,
"case_id": "550e8400-e29b-41d4-a716-446655440000",
"decision_history": [
{
"id": "automated:12345",
"type": "automated_verdict",
"timestamp": "2026-09-28T13:00:00.000Z",
"verdict": "concerning",
"actor_id": null,
"actor_name": null,
"actor_email": null,
"reason": null
},
{
"id": "enhanced_review:550e8400-e29b-41d4-a716-446655440000",
"type": "enhanced_review",
"timestamp": "2026-09-28T13:30:00.000Z",
"verdict": "escalate",
"actor_id": 44,
"actor_name": "Analyst Name",
"actor_email": "analyst@example.com",
"reason": "Additional review required"
},
{
"id": "correction:901",
"type": "re_review",
"timestamp": "2026-09-28T14:02:11.082Z",
"previous_disposition": "accepted",
"resulting_disposition": "rejected",
"actor_id": 44,
"actor_name": "Analyst Name",
"actor_email": "analyst@example.com",
"reason": "Disposition overridden",
"is_override": true
}
]
}
400 invalid_payload. A missing case, a case owned by another account, or a case whose associated person check belongs to another account returns the same 404 not_found response. Missing or invalid bearer credentials return 401.
Mint an account API key
POST/api/account/api-keysCookie: auth_token=<session>Content-Type: application/jsonauthenticateToken, scoped to the signed-in account.vda_ integration key and returns its plaintext exactly once.{
"success": false,
"message": "Authentication required"
}
{
"success": false,
"message": "name is required (1-100 characters)"
}
{
"success": true,
"id": 42,
"name": "production",
"token_prefix": "vda_a1b2",
"created_at": "2026-09-12T12:00:00.000Z",
"key": "vda_<token>"
}
{
"success": false,
"message": "Failed to mint API key"
}
List account API keys
GET/api/account/api-keysCookie: auth_token=<session>authenticateToken, scoped to the signed-in account.{
"success": true,
"keys": [
{
"id": 42,
"name": "production",
"token_prefix": "vda_a1b2",
"created_at": "2026-09-12T12:00:00.000Z",
"last_used_at": null,
"revoked_at": null
}
]
}
id from this response for revocation. token_prefix is prefix-only metadata; the plaintext key is not listed or recoverable.
Rotate an account API key in place
POST/api/v1/compliance/api-keys/rotateAuthorization: Bearer vda_<current-token>authenticateAccountApiKey.{
"success": true,
"id": 42,
"account_id": 7,
"token_prefix": "vda_c3d4",
"created_at": "2026-09-12T12:05:00.000Z",
"key": "vda_<replacement-token>"
}
401 with invalid_key after rotation.
Revoke an account API key
DELETE/api/account/api-keys/:idCookie: auth_token=<session>No body header required.
authenticateToken, scoped to the signed-in account.{
"success": false,
"message": "Authentication required"
}
{
"success": false,
"message": "Invalid key id"
}
{
"success": false,
"message": "API key not found"
}
{
"success": false,
"message": "Failed to revoke API key"
}
Webhook signing
POST setupPOST delivery/api/v1/compliance/webhooks setup<registered callback URL> deliveryAuthorization: Bearer vda_<token> Content-Type: application/jsonDelivery:
Content-Type: application/json X-Veridact-Signature: sha256=<hex>signing_secret.const crypto = require('crypto'); const signature = req.get('X-Veridact-Signature'); const expected = 'sha256=' + crypto .createHmac('sha256', signingSecret) .update(rawBody) .digest('hex'); const valid = signature === expected;
signing_secret returned by the setup call and preserve the raw request bytes before parsing the JSON body.