Developer guide
UN sanctions API in Python: customer screening workers
Use the UN sanctions API in a Python worker for people and entities. Save screening IDs and preserve weak-alias labels for review.
On this page
Screen UN-listed identities from Python
Store one screening operation per customer in your Python worker queue. Submit the customer’s known name, entity type and identifying details; save the returned screening ID with the local customer reference.
Retrieve the retained result to construct the review record. Keep UN references and alias quality attached to each candidate instead of merging candidate identities or flattening them to names.
Prepare the synthetic exercise
Prepare an invented Python queue item and a local UN-style candidate fixture with a permanent reference and alternative birth dates. Test that the retained review record keeps those distinctions. A flattened name-and-date pair would lose useful information before a person ever reviews it.
| Requirement | Value or action |
|---|---|
| Runtime | Use Python 3.11 or later. The HTTP client uses urllib, json, and the standard library, so the screening example needs no pip package or provider SDK. |
| Client file | Save the client as screen.py. |
| Screening endpoint | POST /api/v1/screenings |
| SANCTIONSKIT_API_KEY | Sandbox key; screenings:write and results:read scopes |
| SCREENING_OPERATION_ID | Persist one operation ID; reuse only with the identical request body |
| request.json | Mira Calder: invented subject; sandbox@1: synthetic records |
{
"subject": {
"name": "Mira Calder",
"entityType": "person",
"birthDate": "1984"
},
"package": "sandbox@1",
"retention": "standard"
}Implement the Python request boundary
The urllib worker performs source discovery before the production call and validates the selected coverage afterward. Save the screening reference before acknowledging the queue item. A worker restart should recover the same operation, not rebuild evidence from the newest public list.
"""Python 3.11+. Run on your trusted backend; standard library only."""
import json, os, sys
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl): return None
class ApiFailure(Exception): pass
def main():
key, operation = os.environ['SANCTIONSKIT_API_KEY'], os.environ['SCREENING_OPERATION_ID']
request = json.loads(Path(sys.argv[1] if len(sys.argv) > 1 else 'request.json').read_text(encoding='utf-8'))
sandbox = request.get('package') == 'sandbox@1'
source_ids = ['sandbox-synthetic'] if sandbox else request.get('sources')
if not isinstance(source_ids, list) or not source_ids: raise ValueError('Select your sources.')
opener = build_opener(NoRedirect())
def api(path, body=None):
headers = {'Authorization': 'Bearer ' + key}
if body is not None:
headers.update({'Content-Type': 'application/json', 'Idempotency-Key': operation})
call = Request('https://www.sanctionskit.com/api/v1' + path, headers=headers,
data=None if body is None else json.dumps(body).encode(),
method='GET' if body is None else 'POST')
try:
response = opener.open(call, timeout=25)
except HTTPError as error:
response = error
with response:
envelope = json.load(response)
if not 200 <= response.status < 300:
code = envelope.get('error', {}).get('code', 'unknown')
if not isinstance(code, str) or not 1 <= len(code) <= 80 or any(c not in 'abcdefghijklmnopqrstuvwxyz0123456789_' for c in code): code = 'unknown'
raise ApiFailure('API HTTP ' + str(response.status) + ': ' + code)
return envelope.get('data')
if not sandbox:
sources = api('/sources')
if not isinstance(sources, list): raise ValueError('Invalid discovery response.')
for source_id in source_ids:
found = [source for source in sources if isinstance(source, dict) and source.get('id') == source_id]
if len(found) != 1: raise ValueError('Required source missing or ambiguous.')
source = found[0]
if (source.get('availability') != 'available' or source.get('fresh') is not True or
not isinstance(source.get('capabilities'), list) or
request['subject']['entityType'] not in source['capabilities']):
raise ValueError('Required source unavailable or incompatible.')
result = api('/screenings', request)
matches, coverage = result.get('matches'), result.get('coverage')
if (not isinstance(result.get('id'), str) or not result['id'] or
result.get('environment') != ('sandbox' if sandbox else 'production') or
result.get('status') not in ['potential_match', 'no_match'] or
not isinstance(matches, list) or (result['status'] == 'no_match') != (len(matches) == 0) or
not isinstance(coverage, list) or len(coverage) != len(source_ids) or
any(len([item for item in coverage if item.get('sourceId') == source_id and item.get('fresh') is True]) != 1 for source_id in source_ids)):
raise ValueError('Incomplete screening result.')
# Store result['id'] with your customer reference; retrieve evidence with results:read.
print(json.dumps({'id': result['id'], 'status': result['status'], 'candidateCount': len(matches),
'sources': [item['sourceId'] for item in coverage]}))
try:
main()
except ApiFailure as error:
print(str(error), file=sys.stderr)
sys.exit(1)
except Exception as error:
print(str(error) if str(error) == 'Required source unavailable or incompatible.' else
'Screening incomplete. Check credentials, request and source coverage.', file=sys.stderr)
sys.exit(1)Run the client
python screen.py request.jsonConfirm UN sanctions scope before production
Use un-consolidated for the Security Council’s consolidated individuals and entities source. Keep committee or regime context with the candidate and assess any separate national implementation needed by your process. This request does not select the separate designated-vessels source.
| Check | Required evidence |
|---|---|
| GET /api/v1/sources | Production key with sources:read; response data array |
| id | un-consolidated |
| availability / fresh | available / true |
| capabilities | Contains the requested subject.entityType |
| currentVersion | Active version; retrieved_at and last_checked_at are not publication dates |
| lastImport | May describe an import that is not the active version |
| Missing or ineligible source | Inspect disabledReason; required coverage stays incomplete |
| Displayed client | Performs production discovery before screening |
curl --fail-with-body --max-time 25 --header "Authorization: Bearer $SANCTIONSKIT_API_KEY" "https://www.sanctionskit.com/api/v1/sources"Select un-consolidated in the production request
| Setting | Production request |
|---|---|
| Key | Separate production credential with screenings:write |
| Coverage | sources: ["un-consolidated"]; omit package |
| Operation ID | New ID for the changed body and environment |
| Usage | Production access and screening allowance apply |
| Subject | Only lawfully held, known identifying facts; no invented date precision |
{
"subject": {
"name": "Mira Calder",
"entityType": "person",
"birthDate": "1984"
},
"sources": [
"un-consolidated"
],
"retention": "standard"
}Interpret UN sanctions evidence in this workflow
Store data.id and data.coverage with the permanent UN record reference. Retain alias quality and alternative or partial dates when constructing the review packet. When an analyst reopens the case, retrieve the saved result instead of replacing it with current source data.
| Fields in data.matches[] | Review use |
|---|---|
| record.id and record.designations[].legalReferences | Keep the permanent UN reference with the candidate and use the published references to review the listing. |
| record.list and record.designations[].programs | Show the committee or regime context; entries are not all subject to identical measures. |
| record.names[].quality and record.birthDates | Retain weak versus strong aliases and alternative or partial dates. A weak alias by itself does not establish identity. |
- 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
| Condition | Next action |
|---|---|
| 401 / 403 | Correct credentials or scopes before retrying |
| coverage_unavailable / stale_coverage | Keep required coverage incomplete; no silent source substitution |
| Timeout after submission | Recover with the original Idempotency-Key and identical body |
| 409 | Investigate an operation-key conflict |
| 429 / 503 | Follow the specific error and Retry-After; an allowance cap may require a plan or usage change |
Contract and safe diagnostic references
| Reference | Use |
|---|---|
| error.code / error.requestId | Safe recovery diagnostics; never log the submitted subject, key or full candidate response |
| potential_match / no_match | Completed comparison outcomes; neither is an automatic business approval |
- Round-trip alternative birth-date values without choosing an invented single date.
- Retain the permanent UN reference alongside the worker operation.
- Reopen a retained result after a simulated source update without rewriting its evidence.
Sources and references
Authority and technical sources
SanctionsKit documentation
Frequently asked questions
Should a Python UN worker pick one birth date from several alternatives?
Preserve the available alternatives and their precision for review. Selecting one merely to simplify a local model can hide uncertainty or make the customer comparison appear more conclusive than the source supports.
Why keep the UN reference when the Python job already has a screening ID?
They identify different things. The screening ID identifies your completed comparison; the permanent UN reference identifies the source candidate. Retaining both lets reviewers trace the operation and the listing evidence.
What UN reference should my Python worker keep for later review?
Keep the permanent record reference and available committee or regime context with the screening ID. Also preserve the coverage and versions from that completed check. Current source data may change and cannot replace the history of the original result.
Can a Python UN review packet substitute today’s list for the saved result?
No. The packet should describe the evidence used by the completed operation. Current source data can support a separate new comparison, but it must not silently rewrite the earlier screening history.
