Developer guide

OFAC API in Node.js: backend customer screening

Build a Node.js OFAC API check with native fetch. Keep keys on the server, retry one saved operation, and send SDN matches to review.

250 free live screenings. One lifetime allowance. No credit card required.
On this page

Add OFAC screening to a Node.js backend

Place the provider request in a server-only module and let your application authenticate the user before accepting a screening operation. Validate the subject and permitted coverage on the backend. A browser should call your application boundary, where the SanctionsKit credential and source policy remain under your control.

Persist the customer reference, exact request and operation key before awaiting fetch. If the browser disconnects, the backend still needs to reconcile the operation. Return an application status that distinguishes a completed comparison from review work and an incomplete call; do not expose raw candidate evidence through a generic error response.

Prepare the synthetic exercise

Use the invented Mira Calder request to exercise an onboarding route before connecting customer records. Create a sandbox key in API keys, then save the operation before the route awaits the provider. Closing the browser must not create an untraceable screening.

Node.js setup and request files
RequirementValue or action
RuntimeUse Node.js 22 or later on a trusted server or CLI. This implementation uses the built-in fetch client and Node standard-library modules, so no SDK or npm dependency is required.
Client fileSave the client as screen.mjs.
Screening endpointPOST /api/v1/screenings
SANCTIONSKIT_API_KEYSandbox key; screenings:write and results:read scopes
SCREENING_OPERATION_IDPersist one operation ID; reuse only with the identical request body
request.jsonMira Calder: invented subject; sandbox@1: synthetic records
request.json: synthetic sandbox request
{
  "subject": {
    "name": "Mira Calder",
    "entityType": "person",
    "birthDate": "1984"
  },
  "package": "sandbox@1",
  "retention": "standard"
}

Implement the Node.js request boundary

The Node.js client uses native fetch and checks production source discovery before it posts a subject. Its abort signal bounds local waiting; it is not evidence that remote work was cancelled. Keep the operation outside the request handler so a returning user can reopen its saved state.

Node.js: authenticated screening client
// Node.js 22+. Run on your trusted backend; no npm dependencies.
import { readFileSync } from 'node:fs';
const base = 'https://www.sanctionskit.com/api/v1';
async function main() {
  const key = process.env.SANCTIONSKIT_API_KEY;
  const operation = process.env.SCREENING_OPERATION_ID;
  if (!key || !operation) throw new Error('Set your key and operation ID.');
  const request = JSON.parse(readFileSync(process.argv[2] ?? 'request.json', 'utf8'));
  const sandbox = request.package === 'sandbox@1';
  const sourceIds = sandbox ? ['sandbox-synthetic'] : request.sources;
  if (!Array.isArray(sourceIds) || !sourceIds.length) throw new Error('Select your sources.');
  async function api(path, body) {
    const response = await fetch(base + path, {
      method: body ? 'POST' : 'GET', redirect: 'error', signal: AbortSignal.timeout(25000),
      headers: { Authorization: 'Bearer ' + key,
        ...(body ? { 'Content-Type': 'application/json', 'Idempotency-Key': operation } : {}) },
      ...(body ? { body: JSON.stringify(body) } : {}),
    });
    const envelope = await response.json();
    if (!response.ok) {
      const code = /^[a-z0-9_]{1,80}$/.test(envelope?.error?.code ?? '') ? envelope.error.code : 'unknown';
      throw new Error('API HTTP ' + response.status + ': ' + code);
    }
    return envelope?.data;
  }
  if (!sandbox) {
    const sources = await api('/sources');
    if (!Array.isArray(sources)) throw new Error('Invalid discovery response.');
    for (const id of sourceIds) {
      const found = sources.filter(source => source?.id === id);
      const source = found[0];
      if (found.length !== 1 || source.availability !== 'available' || source.fresh !== true ||
          !Array.isArray(source.capabilities) || !source.capabilities.includes(request.subject.entityType)) throw new Error('Required source unavailable or incompatible.');
    }
  }
  const result = await api('/screenings', request);
  if (typeof result?.id !== 'string' || !result.id || result.environment !== (sandbox ? 'sandbox' : 'production') ||
      !['potential_match', 'no_match'].includes(result.status) ||
      !Array.isArray(result.matches) || (result.status === 'no_match') !== (result.matches.length === 0) ||
      !Array.isArray(result.coverage) || result.coverage.length !== sourceIds.length ||
      sourceIds.some(id => result.coverage.filter(item => item.sourceId === id && item.fresh === true).length !== 1)) {
    throw new Error('Incomplete screening result.');
  }
  // Store result.id with your customer reference; use results:read to retrieve the evidence.
  console.log(JSON.stringify({ id: result.id, status: result.status,
    candidateCount: result.matches.length, sources: result.coverage.map(item => item.sourceId) }));
}
await main().catch(error => {
  console.error((/^API HTTP [0-9]{3}: [a-z0-9_]{1,80}$/.test(error.message) ||
    error.message === 'Required source unavailable or incompatible.')
    ? error.message : 'Screening incomplete. Check credentials, request and source coverage.');
  process.exitCode = 1;
});

Run the client

Run the sandbox request
node screen.mjs request.json

Confirm OFAC scope before production

For an SDN onboarding step, select ofac-sdn explicitly. A policy that requires OFAC non-SDN lists needs a separate ofac-non-sdn selection and review of its different measures. The server, rather than a browser-supplied display label, should own that coverage choice.

Production source-discovery contract
CheckRequired evidence
GET /api/v1/sourcesProduction key with sources:read; response data array
idofac-sdn
availability / freshavailable / true
capabilitiesContains the requested subject.entityType
currentVersionActive version; retrieved_at and last_checked_at are not publication dates
lastImportMay describe an import that is not the active version
Missing or ineligible sourceInspect disabledReason; required coverage stays incomplete
Displayed clientPerforms production discovery before screening
Read source discovery with your production key
curl --fail-with-body --max-time 25 --header "Authorization: Bearer $SANCTIONSKIT_API_KEY" "https://www.sanctionskit.com/api/v1/sources"

Select ofac-sdn in the production request

Explicit change from the sandbox exercise
SettingProduction request
KeySeparate production credential with screenings:write
Coveragesources: ["ofac-sdn"]; omit package
Operation IDNew ID for the changed body and environment
UsageProduction access and screening allowance apply
SubjectOnly lawfully held, known identifying facts; no invented date precision
request.json: explicit ofac-sdn selection
{
  "subject": {
    "name": "Mira Calder",
    "entityType": "person",
    "birthDate": "1984"
  },
  "sources": [
    "ofac-sdn"
  ],
  "retention": "standard"
}

Interpret OFAC evidence in this workflow

Attach data.id to the onboarding record and use data.coverage to show what the completed operation checked. Route data.matches into the authorized evidence view. A no_match outcome should finish this comparison step without quietly marking ownership review or customer approval complete.

OFAC candidate fields to retain
Fields in data.matches[]Review use
record.sourceId and record.idKeep the SDN source and stable record reference attached to the candidate.
record.designations[].programs and legalReferencesShow the listed program and legal references; do not turn every non-SDN restriction into an asset freeze.
record.names, identifiers and birthDatesCompare the supplied identity with aliases and available identifiers, preserving partial-date precision.
  • data.id; data.coverage; data.versions; supplied disclaimer
  • data.matches[]: record, assessment, evidence, conflicts
  • GET /api/v1/results/{id}: results:read scope
  • Retained evidence and case review

Verify the application behavior

Recovery rules for the same saved operation
ConditionNext action
401 / 403Correct credentials or scopes before retrying
coverage_unavailable / stale_coverageKeep required coverage incomplete; no silent source substitution
Timeout after submissionRecover with the original Idempotency-Key and identical body
409Investigate an operation-key conflict
429 / 503Follow the specific error and Retry-After; an allowance cap may require a plan or usage change
Contract and safe diagnostic references
Operational reference
ReferenceUse
error.code / error.requestIdSafe recovery diagnostics; never log the submitted subject, key or full candidate response
potential_match / no_matchCompleted comparison outcomes; neither is an automatic business approval
  • Disconnect the browser after submission and reopen the same saved operation.
  • Mock stale_coverage and verify the onboarding state remains incomplete.
  • Return a potential_match and verify the browser receives a review state rather than an automatic approval.

Sources and references

Frequently asked questions

Can an OFAC Node.js route use fetch without installing an SDK?

Yes. This client targets Node.js 22 or later and uses native fetch. Keep the call behind your authenticated server route; the browser does not need the service credential or the full candidate response.

Where should a Node.js onboarding service choose SDN versus non-SDN coverage?

Put the approved source IDs in server-side workflow configuration. The displayed production request selects SDN only. A customer request must not remove required non-SDN coverage simply to avoid a source-availability error.

What if the browser closes while my Node.js OFAC request is running?

The backend still needs to recover the operation. Save its request, customer reference, and key before awaiting fetch. When the user returns, show the saved operation state instead of sending another check with a new key.

What does closing the browser prove about an OFAC request?

It proves only that the user stopped waiting. The provider may have accepted the screening. Recover the persisted operation using its original request and key before deciding whether a new comparison is needed.