Developer guide

U.S. CSL API in Rust: review component list evidence

Use the U.S. CSL API in a Rust trade-screening service. Preserve the component list behind each candidate so reviewers can check the actual restriction.

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

Review CSL candidates in a Rust service

A typed application model should preserve the screening ID and the source context needed to retrieve and interpret candidate evidence. Avoid a single sanctioned boolean that treats all Consolidated Screening List entries as equivalent. Component lists can raise different questions for a trade reviewer.

For synthetic fixtures, give two similarly named organizations different authority references. Verify that serialization and any local deduplication preserve both candidate records. Identity review can connect related evidence without discarding where each record originated.

Prepare the synthetic exercise

Make two local candidate fixtures with similar organization names but different component-list authorities. The blocking Rust client should preserve both after deserialization. A deduplication function keyed by normalized name would discard the very context a trade reviewer needs.

Rust setup and request files
RequirementValue or action
RuntimeUse a maintained Rust toolchain with Cargo. Save the client as src/main.rs and use the Cargo.toml shown below. This is a blocking CLI or worker example, not a function to run directly inside an async executor task.
Client fileSave the client as src/main.rs
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.jsonExample Harbor Trading: invented subject; sandbox@1: synthetic records
request.json: synthetic sandbox request
{
  "subject": {
    "name": "Example Harbor Trading",
    "entityType": "organization"
  },
  "package": "sandbox@1",
  "retention": "standard"
}

Add the project configuration

Cargo.toml
[package]
name = "sanctions-screen"
version = "0.1.0"
edition = "2021"

[dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
serde_json = "1"

Implement the Rust request boundary

Use reqwest’s blocking client in the trusted worker shown here and keep your typed review model separate from transport. Represent the source record, list and authority explicitly. A generic Vec<String> of names is too narrow for the evidence this workflow must preserve.

Rust: authenticated screening client
// Trusted CLI/backend worker. Do not run blocking I/O inside an async executor task.
use reqwest::{blocking::Client, redirect::Policy};
use serde_json::{json, Value};
use std::{env, error::Error, fs, io::Read, time::Duration};

fn run() -> Result<(), Box<dyn Error>> {
    let key = env::var("SANCTIONSKIT_API_KEY")?;
    let operation = env::var("SCREENING_OPERATION_ID")?;
    if operation.is_empty() { return Err("missing operation ID".into()); }
    let path = env::args().nth(1).unwrap_or_else(|| "request.json".to_owned());
    let raw = fs::read(path)?;
    if raw.len() > 65_536 { return Err("request file exceeds 64 KiB".into()); }
    let body: Value = serde_json::from_slice(&raw)?;
    if !body.is_object() || body.get("subject").is_none() || body.get("sources").is_some() == body.get("package").is_some() {
        return Err("invalid request object".into());
    }
    let client = Client::builder().connect_timeout(Duration::from_secs(10))
        .timeout(Duration::from_secs(25)).redirect(Policy::none()).build()?;
    let response = client.post("https://www.sanctionskit.com/api/v1/screenings")
        .bearer_auth(&key).header("Idempotency-Key", operation).json(&body).send()?;
    let status_code = response.status();
    let limit = 8 * 1024 * 1024;
    let mut payload = Vec::new();
    response.take((limit + 1) as u64).read_to_end(&mut payload)?;
    if payload.len() > limit { return Err("oversized response".into()); }
    if !status_code.is_success() { return Err("screening HTTP request incomplete".into()); }
    let envelope: Value = serde_json::from_slice(&payload)?;
    let data = envelope.get("data").ok_or("missing response data")?;
    let id = data.get("id").and_then(Value::as_str).filter(|s| !s.is_empty()).ok_or("missing screening ID")?;
    let status = data.get("status").and_then(Value::as_str).ok_or("missing screening status")?;
    let matches = data.get("matches").and_then(Value::as_array).ok_or("missing matches array")?;
    if !["potential_match", "no_match"].contains(&status) ||
        (status == "no_match" && !matches.is_empty()) || (status == "potential_match" && matches.is_empty()) {
        return Err("inconsistent screening response".into());
    }
    println!("{}", json!({"id":id,"status":status,"candidateCount":matches.len()}));
    Ok(())
}
fn main() {
    if run().is_err() {
        // Do not print parser errors, submitted identity, or the raw response.
        eprintln!("Screening incomplete. Check the operation before retrying.");
        std::process::exit(1);
    }
}

Run the client

Run the sandbox request
cargo run -- request.json

Confirm U.S. CSL scope before production

The us-csl source combines lists whose restrictions differ. Rust’s type system can help keep that distinction visible, but it cannot turn the transport source into a legal conclusion. Retain the component list and inspect its authority before interpreting a potential match.

Production source-discovery contract
CheckRequired evidence
GET /api/v1/sourcesProduction key with sources:read; response data array
idus-csl
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 clientAdd these discovery checks before its production call
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 us-csl in the production request

Explicit change from the sandbox exercise
SettingProduction request
KeySeparate production credential with screenings:write
Coveragesources: ["us-csl"]; 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 us-csl selection
{
  "subject": {
    "name": "Example Harbor Trading",
    "entityType": "organization"
  },
  "sources": [
    "us-csl"
  ],
  "retention": "standard"
}

Interpret U.S. CSL evidence in this workflow

Carry data.id and data.coverage with each candidate’s source context. If related authority records concern the same real-world organization, preserve both references rather than deleting one as a duplicate name. Identity comparison and restriction interpretation are separate review steps.

U.S. CSL candidate fields to retain
Fields in data.matches[]Review use
record.sourceId, record.list and record.authorityDistinguish the CSL transport source from the component list and issuing authority.
record.designations[].legalReferences and wordingPresent the actual restriction context instead of a universal sanctioned flag.
record.id, names and addressesKeep each party and candidate separately traceable, including address evidence relevant to an organization.
  • 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
  • Deserialize two same-name candidate records without merging their authorities.
  • Round-trip the review model and retain both original references.
  • Keep a response-contract error distinct from an empty candidate vector.

Sources and references

Frequently asked questions

Why is a vector of names insufficient for the Rust CSL integration?

It drops the component-list and authority context behind each candidate. Keep the record reference and restriction evidence in the model so the reviewer can understand why the party appears in the consolidated source.

Can Rust deduplicate CSL records by normalized company name?

A name alone is not enough. Different authority records may concern related or distinct entities and carry different measures. Preserve their source references until reliable evidence supports an explicit relationship.

Does one Rust CSL request screen every party in a shipment?

No. A request checks the subject you supply. Screen the buyer, consignee, intermediaries, and other required parties separately under your policy. Keep each party reference with its own result so a completed buyer check cannot stand in for the rest.

What should the Rust CSL serialization test retain?

Use invented candidates with equal display names but different lists and authorities. Assert that both identities and their references survive serialization, rather than accepting a compact output that erases one record.