Veridact Compliance API

Token-authenticated subject screening for mid-market integrations

Run KYC, sanctions, PEP, and adverse-media checks against 20+ global data sources from your own product. Mints in one call, rotates without downtime, and writes every action to your audit trail — built for FinCEN 2026 and EU 6AMLD.

Get a token →
Bearer-token auth
60 checks / hour
Granular audit trail

Authentication model

Every API call is authenticated with a bearer token named vda_… — sent in the standard Authorization: Bearer vda_… header. Tokens are 168-bit random secrets scoped to a single Veridact account; they are not shared across teams, not embedded in client-side code, and never logged after issuance.

The plaintext secret is shown to you exactly once, at mint time. After that, only its SHA-256 hash is stored — if you lose the plaintext, you mint a new key. This design mirrors how modern signing keys (HMAC, OAuth client secrets) are handled: nothing on our side can reconstruct what you hold.

1. RequestAuthenticated account calls the mint endpoint inside /portal.
2. MintServer generates a 168-bit vda_… secret and hashes it.
3. StorePlaintext is returned once. Only the SHA-256 hash + prefix are persisted.
4. UseCaller sends Authorization: Bearer vda_… on every request.
5. RevokeCaller deletes the key in /portal; hash is flagged as revoked.

Rotation works without downtime: mint a new key, swap the bearer header in your client, then revoke the old one once your traffic rolls over. Revocation is immediate — a deleted key returns 401 invalid_key on the very next request. Read the security posture page for the broader conversation about API credential handling.

Endpoints exposed

The Compliance API exposes v1 endpoints for kicking off single or batch subject-check pipeline runs and for submitting analyst corrections, re-reviews, or explicit verdict overrides to completed decisions. All endpoints live under /api/v1/compliance/* and require bearer-token authentication. Registered callbacks receive the same signed lifecycle for every check created by either submission path; a batch also receives one signed batch_check.completed summary after every input reaches a terminal state. Each saved correction or re-review emits one event: subject_check.verdict_overridden when the disposition changes, or subject_check.re_reviewed when it stays the same. A successful verdict override always emits subject_check.verdict_overridden. Subscribe to both event types to receive every analyst decision.

Method Path Auth Description
POST /api/v1/compliance/subject-checks Bearer vda_… Start a Person Check pipeline run for the bearer token's account. Returns 201 with a subject_check_id to poll.
POST /api/v1/compliance/subject-checks/batch Bearer vda_… Start up to 50 checks in one request. Each accepted subject receives its own lifecycle callbacks, followed by one tenant-scoped batch_check.completed summary containing ordered verdicts and failures.
POST /api/v1/compliance/subject-checks/:id/disposition-correction Bearer vda_… Submit an analyst correction to a completed, tenant-owned decision. Requires the account user to have the analyst role.
POST /api/v1/compliance/subject-checks/:id/verdict-override Bearer vda_… Override a supported verdict on a completed, tenant-owned subject check. Requires the account user to have the analyst role and a reason.
POST /api/v1/compliance/subject-checks/:id/re-review Bearer vda_… Submit an analyst re-review outcome for a completed, tenant-owned decision. Requires the account user to have the analyst role and emits one signed lifecycle event.
GET /api/v1/compliance/cases/:caseId/decision-history Bearer vda_… Return the chronological decision history for a UUID case owned by the token's account. Unknown and other-tenant cases both return 404.
POST /api/v1/compliance/subject-checks/sandbox Bearer vda_… Synchronous mock verdict for client-flow testing. Same response shape as subject-checks with a deterministic clear_no_match; no DB writes, no quota increment, no rate-limit decrement.

POST /api/v1/compliance/subject-checks

Starts a Person Check screening run for one subject. The endpoint validates the payload, inserts a person_checks row, increments the account quota, and asynchronously kicks off the matching/scoring pipeline. The HTTP response returns immediately with a run id; polling for the verdict happens against your own account data.

Request body
// POST /api/v1/compliance/subject-checks
{
  "first_name": "Jane",
  "middle_name": "Marie",           // optional
  "last_name": "Reyes",
  "dob": "1985-04-12",                   // optional, ISO 8601 date
  "aliases": ["J. Reyes"],            // optional array of strings
  "locations": [{                          // optional array
    "country": "US",
    "state": "NY",
    "city": "New York"
  }],
  "additional_identifiers": {                // optional free-form object
    "nationality": "US",
    "passport_country": "US"
  },
  "purpose": "customer onboarding",     // free text, recorded in audit
  "consent_given": true                  // required boolean; consent-obtained attestation
}
Successful response — 201
{
  "success": true,
  "subject_check_id": "ck_a7b3...",
  "status": "running",
  "subject_name": "Jane Marie Reyes",
  "verdict": null,
  "created_at": "2026-07-30T14:02:11.082Z"
}
Validation error — 400
{
  "error": {
    "code": "invalid_payload",
    "message": "First name is required",
    "errors": ["First name is required", "Last name is required"]
  }
}

Request & response examples

All requests go to https://veridact.solutions/api/v1/compliance authenticated with Authorization: Bearer vda_your_token_here. The three flavors below — curl, Python (requests), and JavaScript (fetch) — are functionally equivalent and call the same endpoints with the same payload.

POST /api/v1/compliance/subject-checks

Kick off a Person Check pipeline run. Returns HTTP 201 with a subject_check_id you poll against.

curl
curl -i -X POST https://veridact.solutions/api/v1/compliance/subject-checks \
  -H "Authorization: Bearer vda_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Jane",
    "middle_name": "Marie",
    "last_name": "Reyes",
    "dob": "1985-04-12",
    "aliases": ["J. Reyes"],
    "purpose": "customer onboarding",
    "consent_given": true
  }'
Python (requests)
import requests

resp = requests.post(
  "https://veridact.solutions/api/v1/compliance/subject-checks",
  headers={"Authorization": f"Bearer vda_your_token_here"},
  json={
    "first_name": "Jane",
    "middle_name": "Marie",
    "last_name": "Reyes",
    "dob": "1985-04-12",
    "aliases": ["J. Reyes"],
    "purpose": "customer onboarding",
    "consent_given": True,
  },
  timeout=30,
)

print(resp.status_code, resp.json())
JavaScript (fetch)
const resp = await fetch(
  "https://veridact.solutions/api/v1/compliance/subject-checks",
  {
    method: "POST",
    headers: {
      "Authorization": "Bearer vda_your_token_here",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      first_name: "Jane",
      middle_name: "Marie",
      last_name: "Reyes",
      dob: "1985-04-12",
      aliases: ["J. Reyes"],
      purpose: "customer onboarding",
      consent_given: true,
    }),
  }
);

const data = await resp.json();
console.log(resp.status, data);
Sample response — 201
{
  "success": true,
  "subject_check_id": "ck_a7b3f2",
  "status": "running",
  "subject_name": "Jane Marie Reyes",
  "verdict": null,
  "created_at": "2026-07-30T14:02:11.082Z"
}

GET /api/v1/compliance/subject-checks/:id

Poll the stored verdict for a previously-started run. verdict stays null while the pipeline is running and only resolves once status === "completed".

curl
curl -i -X GET https://veridact.solutions/api/v1/compliance/subject-checks/12345 \
  -H "Authorization: Bearer vda_your_token_here"
Python (requests)
import requests

subject_check_id = 12345

resp = requests.get(
  f"https://veridact.solutions/api/v1/compliance/subject-checks/{subject_check_id}",
  headers={"Authorization": f"Bearer vda_your_token_here"},
  timeout=30,
)

print(resp.status_code, resp.json())
JavaScript (fetch)
const subjectCheckId = 12345;

const resp = await fetch(
  `https://veridact.solutions/api/v1/compliance/subject-checks/${subjectCheckId}`,
  {
    method: "GET",
    headers: {
      "Authorization": "Bearer vda_your_token_here",
    },
  }
);

const data = await resp.json();
console.log(resp.status, data);
Sample response — 200
{
  "success": true,
  "subject_check_id": 12345,
  "status": "completed",
  "subject_name": "Jane Marie Reyes",
  "verdict": "clear",
  "created_at": "2026-07-30T14:02:11.082Z"
}

While the pipeline is still running you get the same shape but with "status": "running" and "verdict": null — poll until status is "completed" before reading verdict.

GET /api/v1/compliance/cases/:caseId/decision-history

Returns the same chronological decision records shown on the analyst case detail page. Send a UUID case id and authenticate with Authorization: Bearer vda_…. The case and its associated person check must both belong to the bearer key's account. A malformed UUID returns 400 invalid_payload; a missing or other-tenant case returns the same 404 not_found response.

curl
curl -i -X GET https://veridact.solutions/api/v1/compliance/cases/550e8400-e29b-41d4-a716-446655440000/decision-history \
  -H "Authorization: Bearer vda_your_token_here"
Sample response — 200
{
  "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
    },
    {
      "id": "correction:902",
      "type": "re_review",
      "timestamp": "2026-09-28T15:02:11.082Z",
      "previous_disposition": "rejected",
      "resulting_disposition": "rejected",
      "actor_id": null,
      "actor_name": null,
      "actor_email": null,
      "reason": "Re-review confirmed",
      "is_override": false
    }
  ]
}

POST /api/v1/compliance/subject-checks/:id/disposition-correction

Authenticate with Authorization: Bearer vda_… to record an analyst correction for a completed subject check owned by the token's account. The account user must currently have the database-backed analyst role. The correction and audit-history entry are saved atomically. Each committed correction emits one signed event: subject_check.verdict_overridden when the effective disposition changes, or subject_check.re_reviewed when it stays the same. Subscribe to both event types to receive every correction.

Request body
// POST /api/v1/compliance/subject-checks/12345/disposition-correction
{
  "disposition": "rejected", // accepted, rejected, or escalated
  "reason": "Analyst review recorded" // trimmed, 1–500 characters
}
Successful response — 200
{
  "success": true,
  "subject_check_id": 12345,
  "analyst_correction": {
    "id": 901,
    "previous_disposition": "accepted",
    "corrected_disposition": "rejected",
    "corrected_by": 77,
    "reason": "Analyst review recorded",
    "created_at": "2026-09-28T14:02:11.082Z"
  },
  "correction_history": [{
    "id": 901,
    "person_check_id": 12345,
    "previous_disposition": "accepted",
    "corrected_disposition": "rejected",
    "corrected_by": 77,
    "reason": "Analyst review recorded",
    "created_at": "2026-09-28T14:02:11.082Z"
  }]
}
Correction errors

400 invalid_payload means the path ID, disposition, or reason failed validation. 403 analyst_required means the account user lacks analyst privilege. 404 not_found covers both an unknown check and a check owned by another tenant. 409 decision_not_ready means the check has not completed.

POST /api/v1/compliance/subject-checks/:id/verdict-override

Authenticate with Authorization: Bearer vda_… to override the verdict on a completed subject check owned by the token's account. The account user must currently have the database-backed analyst role. The override and correction-history entry are saved atomically. The target must have a supported effective verdict (accepted, rejected, or escalated), and replacement_verdict must differ from it. Every successful override emits the signed subject_check.verdict_overridden lifecycle event with the recorded reason in override_context.

Request body
// POST /api/v1/compliance/subject-checks/12345/verdict-override
{
  "replacement_verdict": "rejected", // accepted, rejected, or escalated; must differ from current verdict
  "reason": "Analyst review confirmed a sanctions match." // trimmed, 1–500 characters
}
Successful response — 200
{
  "success": true,
  "subject_check_id": 12345,
  "verdict_override": {
    "id": 905,
    "person_check_id": 12345,
    "previous_disposition": "accepted",
    "corrected_disposition": "rejected",
    "corrected_by": 77,
    "reason": "Analyst review confirmed a sanctions match.",
    "created_at": "2026-10-11T14:02:11.082Z"
  },
  "correction_history": [{ /* newest record first; includes the saved override */ }]
}
Override event
{
  "event": "subject_check.verdict_overridden",
  "previous_verdict": "accepted",
  "replacement_verdict": "rejected",
  "override_context": {
    "correction_id": 905,
    "analyst_id": 77,
    "reason": "Analyst review confirmed a sanctions match."
  }
}
Override errors

400 invalid_payload means the ID is not a positive safe integer, replacement_verdict is missing or unsupported, or reason is missing, blank after trimming, or longer than 500 characters. 401 means bearer credentials are missing or invalid. 403 analyst_required means the account user lacks analyst privilege. 404 not_found covers both an unknown check and a check owned by another tenant. 409 decision_not_ready means the check is incomplete; 409 verdict_not_overridable means its current effective verdict is unsupported or the replacement matches it. 500 internal_error means an unexpected failure while saving the override.

POST /api/v1/compliance/subject-checks/:id/re-review

Authenticate with Authorization: Bearer vda_… to record an analyst re-review for a completed subject check owned by the token's account. The account user must currently have the database-backed analyst role. The outcome and correction-history entry are saved atomically through the same path as disposition-correction; each saved re-review emits exactly one signed event: subject_check.re_reviewed when the effective disposition is unchanged, or subject_check.verdict_overridden when it changes.

Request body
// POST /api/v1/compliance/subject-checks/12345/re-review
{
  "outcome": "accepted", // accepted, rejected, or escalated
  "reason": "Analyst re-review recorded" // required, trimmed, 1–500 characters
}
Successful response — 200
{
  "success": true,
  "subject_check_id": 12345,
  "analyst_correction": {
    "id": 902,
    "previous_disposition": "accepted",
    "corrected_disposition": "accepted",
    "corrected_by": 77,
    "reason": "Analyst re-review recorded",
    "created_at": "2026-09-28T14:02:11.082Z"
  },
  "correction_history": [{ /* newest record first; same fields as analyst_correction */ }]
}
Re-review errors

400 invalid_payload means the path ID, outcome, or reason failed validation. 403 analyst_required means the bearer account user lacks analyst privilege. 404 not_found covers both an unknown check and a check owned by another tenant. 409 decision_not_ready means the check has not completed. Unexpected failures return 500 internal_error.

POST /api/v1/compliance/webhooks

Registers (or updates) an https:// callback URL for the calling token's account. The optional event_types array selects which supported Compliance API events the endpoint receives. New subscriptions default to all supported events; clients that omit this field when updating an existing subscription keep its current selection. The saved selection is returned in this response. webhook.test is an operator probe and is not selectable; it remains available from the account dashboard's test button. Veridact POSTs selected subject-check lifecycle, batch, monitoring, and manual retry events. Deliveries use a per-subscription HMAC secret returned in this response; event order is not guaranteed.

Request body
// POST /api/v1/compliance/webhooks
{
  "url": "https://hooks.example.com/veridact/results",
  "event_types": ["subject_check.completed", "batch_check.completed"]
}
Successful response — 200
{
  "success": true,
  "subscription_id": "wh_sub_a1f3...",
  "url": "https://hooks.example.com/veridact/results",
  "signing_secret": "whsec_3x9p...",
  "event_types": ["subject_check.completed", "batch_check.completed"],
  "created_at": "2026-07-30T14:02:11.082Z"
}

Supported event names: subject_check.processing, subject_check.review_required, subject_check.escalated, subject_check.completed, subject_check.failed, subject_check.re_reviewed, subject_check.verdict_overridden, subject_check.retry, batch_check.completed, monitoring.alert, monitoring.verdict_changed, and monitoring.risk_changed. The saved array may be empty. webhook.test is never part of this selectable list.

Lifecycle payload summary
EventStatusEvent timestampDecision context
subject_check.processingrunningprocessing_atCheck and owning account_id.
subject_check.review_requiredneeds_reviewreview_required_atInconclusive result, confidence, finding counts, source coverage, and review reason.
subject_check.escalatedescalatedescalated_atInvestigation, triggering tiers and scores, requested scope, and priority.
subject_check.completedcompletedcompleted_atExisting overall_result, confidence_level, and findings_count.
subject_check.failedfailedfailed_atExisting error_message.
batch_check.completedcompleted, failed, or partialcompleted_atStable batch_id, counts, ordered subjects, failures, and partial-completion details.

Every subject lifecycle payload includes subject_check_id, the tenant account_id, and its stage status. Batch summaries use batch_id as their stable receiver idempotency key and are emitted only after every input is terminal. See the full webhook reference and canonical JSON examples for field-level schemas, raw-body verification, idempotency, and the unchanged completed/failed compatibility contract.

Every delivery's body is HMAC-SHA256–signed with the per-subscription signing_secret returned above, and arrives on the X-Veridact-Signature header as sha256=<hex>. Verify the signature against the raw request bytes — before any JSON parsing — using the snippets below, then respond 200 on success or 400 on mismatch so the delivery is not retried.

Verifying the signature — Node.js (built-in crypto + http only)
// node-webhook-receiver.js — drop-in verifier.
// Buffers the raw body byte-for-byte so the HMAC matches Verdicts exactly.
const http = require('http');
const crypto = require('crypto');

const PORT = process.env.PORT || 8787;
const SIGNING_SECRET = process.env.VERIDACT_WEBHOOK_SECRET;

const server = http.createServer((req, res) => {
  const chunks = [];
  req.on('data', (c) => chunks.push(c));
  req.on('end', () => {
    const rawBody = Buffer.concat(chunks);

    const signature = (req.headers['x-veridact-signature'] || '').toString();
    const expected = 'sha256=' + crypto
      .createHmac('sha256', SIGNING_SECRET)
      .update(rawBody)
      .digest('hex');

    const sigBuf = Buffer.from(signature, 'utf8');
    const expBuf = Buffer.from(expected, 'utf8');
    const verified = sigBuf.length === expBuf.length
      && crypto.timingSafeEqual(sigBuf, expBuf);

    console.log(`signature_verified=${verified} signature=${signature} expected=${expected}`);

    if (verified) {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end('{"ok":true}');
    } else {
      res.writeHead(400, { 'Content-Type': 'application/json' });
      res.end('{"ok":false,"reason":"signature_mismatch"}');
    }
  });
});

server.listen(PORT, () => console.log(`listening on :${PORT}`));

# Example run — precomputed digest for the body below.
# export VERIDACT_WEBHOOK_SECRET='whsec_3x9pK7qZvY8R2mN4cQwL5jX6bDfHaEgT'
# node node-webhook-receiver.js &
# curl -sS -X POST http://localhost:8787/veridact \
#   -H 'Content-Type: application/json' \
#   -H 'X-Veridact-Signature: sha256=9519693faae7420007458e2b9213460caf5bbe1d7b6c9d3b8e6d31f77ffbc192' \
#   --data-binary '{"event":"subject_check.completed","subject_check_id":"ck_a7b3f2","status":"completed","verdict":"clear","created_at":"2026-07-30T14:02:11.082Z"}'
# expected: signature_verified=true signature=sha256=... expected=sha256=...  response {"ok":true}
Verifying the signature — Python (stdlib only)
# webhook_receiver.py — stdlib-only verifier.
# Reads raw body bytes off self.rfile so the HMAC matches Verdicts byte-for-byte.
import hashlib
import hmac
import os
from http.server import BaseHTTPRequestHandler, HTTPServer

SIGNING_SECRET = os.environ['VERIDACT_WEBHOOK_SECRET'].encode('utf-8')


class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers.get('Content-Length', '0'))
        raw_body = self.rfile.read(length)

        signature = self.headers.get('X-Veridact-Signature', '')
        expected = 'sha256=' + hmac.new(SIGNING_SECRET, raw_body, hashlib.sha256).hexdigest()

        verified = hmac.compare_digest(signature, expected)
        print(f'signature_verified={verified} signature={signature} expected={expected}')

        if verified:
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"ok":true}')
        else:
            self.send_response(400)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"ok":false,"reason":"signature_mismatch"}')

    def log_message(self, fmt, *args):
        pass  # quieter — we already print signature_verified=...


if __name__ == '__main__':
    HTTPServer(('127.0.0.1', 8787), Handler).serve_forever()

# Example run — precomputed digest for the body below.
# export VERIDACT_WEBHOOK_SECRET='whsec_3x9pK7qZvY8R2mN4cQwL5jX6bDfHaEgT'
# python3 webhook_receiver.py &
# curl -sS -X POST http://localhost:8787/veridact \
#   -H 'Content-Type: application/json' \
#   -H 'X-Veridact-Signature: sha256=9519693faae7420007458e2b9213460caf5bbe1d7b6c9d3b8e6d31f77ffbc192' \
#   --data-binary '{"event":"subject_check.completed","subject_check_id":"ck_a7b3f2","status":"completed","verdict":"clear","created_at":"2026-07-30T14:02:11.082Z"}'
# expected: signature_verified=True signature=sha256=... expected=sha256=...  response {"ok":true}

For end-to-end client-flow testing without consuming quota or producing real subjects — see POST /api/v1/compliance/subject-checks/sandbox, which returns the same response shape with a deterministic verdict and writes no rows.

POST /api/v1/compliance/subject-checks/sandbox

Synchronous mock verdict for client-flow testing. Identical request shape to the real subject-checks endpoint, but performs no person_checks insert, no quota increment, no async pipeline kickoff, and no rate-limit decrement — the per-token 60/hr bucket is untouched. Useful for exercising the full request/response flow (including the invalid_payload 400 path) without committing a real subject.

Request body

Same payload as POST /api/v1/compliance/subject-checks: first_name, last_name, locations[], purpose, consent_given are required; middle_name, dob, aliases[], and additional_identifiers are optional.

Successful response — 201
{
  "success": true,
  "subject_check_id": "sandbox",
  "status": "completed",
  "subject_name": "Jane Marie Reyes",
  "verdict": "clear_no_match",
  "confidence_score": 1.0,
  "created_at": "2026-07-30T14:02:11.082Z"
}

The subject_check_id is the literal string "sandbox" and verdict is fixed to "clear_no_match" on every call — use this to assert on the shape of your client wrapper without coupling to real matching results. No person_checks row, no quota increment, no rate-limit decrement.

Replace vda_your_token_here with the secret minted in /portal. POST returns 201 with a subject_check_id to poll the result against.

Rate limits & error codes

60 subject checks per hour per token, returning HTTP 429 with a standard Retry-After header when exceeded. The window slides; quotas do not reset on a fixed clock. Enterprise tiers can request a higher ceiling via the contact-sales path — the subjectChecksLimiter ceiling is keyed to the calling token, so each token in your account draws from its own bucket.

Errors are returned as a single error object with a stable code and a human-readable message. The codes below are the full set you should expect to handle:

Code HTTP status Returned by What it means
invalid_key_format 401 authenticateAccountApiKey Header missing, not Bearer-prefixed, or the token does not start with vda_.
invalid_key 401 authenticateAccountApiKey Hash not found in the live token table — typically a revoked or mistyped key.
key_expired 401 authenticateAccountApiKey Token revocation was paired with an explicit expiry timestamp; mint a new key.
invalid_payload 400 POST /subject-checks; POST /subject-checks/:id/disposition-correction; POST /subject-checks/:id/re-review; POST /subject-checks/:id/verdict-override Required submission fields or correction input failed validation; correction requests require a positive safe integer ID, an allowed disposition, and a nonempty reason of at most 500 characters.
analyst_required 403 POST /subject-checks/:id/disposition-correction; POST /subject-checks/:id/re-review; POST /subject-checks/:id/verdict-override The authenticated account user does not currently have the analyst role.
not_found 404 GET /subject-checks/:id; POST /subject-checks/:id/disposition-correction; POST /subject-checks/:id/re-review; POST /subject-checks/:id/verdict-override The subject check does not exist or belongs to another account; cross-tenant checks are hidden behind the same response.
decision_not_ready 409 POST /subject-checks/:id/disposition-correction; POST /subject-checks/:id/re-review; POST /subject-checks/:id/verdict-override The target subject check is not completed, so it has no final decision to correct.
verdict_not_overridable 409 POST /subject-checks/:id/verdict-override The current effective verdict is unsupported or the requested replacement matches it; no correction record is appended.
rate_limited 429 subjectChecksLimiter Hourly ceiling reached; back off using the Retry-After header.
internal_error 500 POST /subject-checks; POST /subject-checks/:id/disposition-correction; POST /subject-checks/:id/re-review; POST /subject-checks/:id/verdict-override Unexpected server fault while starting a subject check or saving a correction.

The error.code field is the contract — machine-readable and stable across API versions. Don't pattern-match on error.message.

Compliance-tier SLA

Support windows and audit retention by tier. Common ceiling is the one your token ships with — Enterprise is contact-sales.

Tier Rate ceiling Support response Audit log retention
Essentials 60 checks / hour Email, best-effort Not applicable
Professional 60 checks / hour Priority support (24h response) Aligned with FinCEN 5-year retention
Enterprise Custom ceiling (contact sales) Custom SLA agreement Aligned with FinCEN 5-year retention

The 5-year retention default reflects FinCEN 31 CFR §1020.320 record-keeping requirements for covered financial institutions — the same window your existing screening data already meets per the security page. Custom retention windows and dedicated integration engineers are negotiated with the Enterprise agreement.

Shareable verdicts

Once a subject check completes inside your account, you can mint a read-only share URL via the /share/:token endpoint exposed in the account UI. The URL embeds a signed, expiring token — it never exposes your bearer credential, never exposes the raw subject_check_id, and can be revoked independently of the underlying screening record. Use it to send verdicts to compliance reviewers, auditors, or counter-parties who need to see results without holding their own Veridact key.

The share button lives next to each completed run in /account/checks; the API returns the same shape your UI calls internally.

Successful response — 200
{
  "subject_check_id": "ck_a7b3f2",
  "share_url": "https://veridact.solutions/share/s_a91c...",
  "expires_at": "2026-09-30T14:02:11.082Z"
}

Requesting a token

Compliance API tokens are available to accounts on the Professional and Enterprise tiers — gating is enforced at the API-key mint endpoint, and lower-tier accounts receive a clear 403. To get a token, you can either upgrade your account or sign in and mint one directly.

Existing customers: open /portal → Settings → API Keys, name the integration (e.g. prod-customer-onboarding-prod-1), and the plaintext vda_… secret will be shown exactly once. Copy it into your secret manager immediately — we cannot recover it later.

Request a token →