Developer guide

EU sanctions API in Node.js: customer screening

Use the EU sanctions API from Node.js to check financial-sanctions records. Preserve source IDs, partial dates and regulation references for review.

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

Screen EU customers from a Node.js backend

Call the client from the server route that receives an onboarding request. Store the local operation ID before submitting the subject, then save the returned screening ID against that customer. Return a pending-review state when a candidate needs investigation.

Keep the API key on the server. Use the retained result to display EU record references and evidence to authorized reviewers; the browser only needs your application’s screening summary.

Prepare the synthetic exercise

Run the invented customer through sandbox before attaching the Node.js handler to onboarding. Your local fixture should include a potential match and an incomplete response, with different application states. Neither should advance the customer merely because fetch returned a JSON object.

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

This Node.js integration calls SanctionsKit rather than downloading EU source files. Native fetch performs discovery and screening, while the backend controls authentication, operation persistence and which evidence an authorized reviewer may see.

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 EU sanctions scope before production

eu-consolidated selects the EU consolidated financial sanctions list. It is a designated-party check, not a complete test of sectoral, goods or trade restrictions. Confirm the source’s current availability before the production request instead of assuming a tutorial establishes eligibility.

Production source-discovery contract
CheckRequired evidence
GET /api/v1/sourcesProduction key with sources:read; response data array
ideu-consolidated
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 eu-consolidated in the production request

Explicit change from the sandbox exercise
SettingProduction request
KeySeparate production credential with screenings:write
Coveragesources: ["eu-consolidated"]; 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 eu-consolidated selection
{
  "subject": {
    "name": "Mira Calder",
    "entityType": "person",
    "birthDate": "1984"
  },
  "sources": [
    "eu-consolidated"
  ],
  "retention": "standard"
}

Interpret EU sanctions evidence in this workflow

Keep the EU candidate’s regulation reference beside data.id and data.coverage. Route unresolved identities into the review workflow. Your application can show a concise onboarding state while retaining the fuller source evidence for authorized investigation.

EU sanctions candidate fields to retain
Fields in data.matches[]Review use
record.id and record.sourceIdSave the EU record identity with the screening result instead of indexing the returned names yourself.
record.names, identifiers and birthDatesShow primary names, aliases and available identifiers; keep a year or range as published.
record.designations[].legalReferences and programsRead the applicable regulation and regime context beside the candidate.
  • 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
  • Route a mocked EU candidate to pending review instead of approval.
  • Fail an incomplete response without rendering an empty match list.
  • Verify that the browser receives only the authorized application summary.

Sources and references

Frequently asked questions

Is an EU XML parser required for this Node.js guide?

No. The guide uses the SanctionsKit API for selected-source screening. Your Node.js backend submits the known subject, validates the response and connects retained evidence to review; it does not operate an EU import pipeline.

Does the EU Node.js result cover sectoral and goods restrictions?

The selected source is the consolidated financial sanctions list. Other restrictions relevant to a transaction need their own assessment. Do not describe a completed party-list comparison as a complete EU sanctions decision.

Must my Node.js application download and parse EU source files?

No. This integration submits a subject to SanctionsKit and reads the returned candidates and source versions. Your application handles the customer workflow and review. Use source discovery to confirm coverage instead of building a separate list parser for this example.

How should an EU onboarding route distinguish a candidate from failure?

A validated potential_match is completed screening with evidence to review. An HTTP, coverage or response-contract failure is incomplete work. Give these separate application states rather than returning one generic empty result.