Developer guide
U.S. CSL API in Java: screen shipment parties
Screen shipment parties through the U.S. CSL API in Java. Keep each candidate’s originating list and restriction with the review evidence.

On this page
Screen shipment parties from Java
The Consolidated Screening List concerns multiple source lists, so begin with the buyer, consignee, end user and other parties selected by the trade-compliance process. Create a separate subject and operation for each relevant legal identity and link them to the internal shipment reference.
In an invented export, the buyer and end user are different organizations. The Java service should retain both screening references rather than attach the buyer’s result to the whole shipment. Goods classification, destination and licensing assessments continue in their own controls.
Prepare the synthetic exercise
Create an invented shipment with a buyer and a different end user. Run the organization sandbox request once for each saved party operation. The Java exercise should preserve two screening references; a completed buyer check must not become a completed shipment-wide review.
| Requirement | Value or action |
|---|---|
| Runtime | Use Java 21 and Maven. Save the client as src/main/java/Screen.java and the pom.xml shown below at the project root. The example uses the JDK HTTP client and Jackson 2.x for JSON tree validation. |
| Client file | Save the client as src/main/java/Screen.java |
| 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 | Example Harbor Trading: invented subject; sandbox@1: synthetic records |
{
"subject": {
"name": "Example Harbor Trading",
"entityType": "organization"
},
"package": "sandbox@1",
"retention": "standard"
}Add the project configuration
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion><groupId>example</groupId><artifactId>sanctions-screen</artifactId><version>1.0.0</version>
<properties><maven.compiler.release>21</maven.compiler.release><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties>
<dependencies><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.18.10</version></dependency></dependencies>
<build><plugins>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.13.0</version></plugin>
<plugin><groupId>org.codehaus.mojo</groupId><artifactId>exec-maven-plugin</artifactId><version>3.5.0</version><configuration><mainClass>Screen</mainClass></configuration></plugin>
</plugins></build>
</project>Implement the Java request boundary
Keep the Java transport adapter independent of the shipment coordinator. The coordinator supplies one legal subject at a time and associates the result with a party role. Jackson parsing should preserve source-list fields instead of flattening every candidate into one boolean.
// Java 21+, Jackson 2.x. Backend or trusted CLI. See the adjacent pom.xml.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public final class Screen {
public static void main(String[] args) {
try { run(args); }
catch (Exception error) {
// Parser exceptions may contain excerpts, so do not print their messages.
System.err.println("Screening incomplete. Check the operation before retrying.");
System.exit(1);
}
}
static void run(String[] args) throws Exception {
String key = System.getenv("SANCTIONSKIT_API_KEY");
String operation = System.getenv("SCREENING_OPERATION_ID");
if (key == null || operation == null || operation.isBlank()) throw new IllegalArgumentException("Missing configuration");
byte[] raw = Files.readAllBytes(Path.of(args.length > 0 ? args[0] : "request.json"));
if (raw.length > 65536) throw new IllegalArgumentException("Oversized request");
ObjectMapper mapper = new ObjectMapper();
JsonNode body = mapper.readTree(raw);
if (body == null || !body.isObject() || !body.has("subject") || body.has("sources") == body.has("package")) {
throw new IllegalArgumentException("Invalid request object");
}
HttpRequest request = HttpRequest.newBuilder(URI.create("https://www.sanctionskit.com/api/v1/screenings"))
.timeout(Duration.ofSeconds(25))
.header("Authorization", "Bearer " + key).header("Content-Type", "application/json")
.header("Idempotency-Key", operation).POST(HttpRequest.BodyPublishers.ofByteArray(raw)).build();
try (HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build()) {
// A file subscriber avoids buffering the complete body in memory.
Path temp = Files.createTempFile("sanctions-response-", ".json");
try {
var future = client.sendAsync(request, HttpResponse.BodyHandlers.ofFile(temp));
HttpResponse<Path> response;
try { response = future.get(30, TimeUnit.SECONDS); }
catch (Exception error) { future.cancel(true); throw error; }
if (response.statusCode() < 200 || response.statusCode() >= 300 || Files.size(temp) > 8 * 1024 * 1024) {
throw new IllegalStateException("Incomplete HTTP response");
}
JsonNode envelope = mapper.readTree(temp.toFile());
JsonNode data = envelope == null ? null : envelope.get("data");
if (data == null || !data.isObject() || !data.path("id").isTextual() || data.path("id").asText().isEmpty()
|| !data.path("status").isTextual() || !data.path("matches").isArray()) {
throw new IllegalStateException("Unexpected screening response");
}
String status = data.get("status").asText(); int count = data.get("matches").size();
if (!(status.equals("potential_match") || status.equals("no_match"))
|| (status.equals("no_match") && count != 0) || (status.equals("potential_match") && count == 0)) {
throw new IllegalStateException("Inconsistent screening response");
}
System.out.println(mapper.writeValueAsString(Map.of("id", data.get("id").asText(), "status", status, "candidateCount", count)));
} finally { Files.deleteIfExists(temp); }
}
}
}Run the client
mvn compile exec:java -Dexec.args="request.json"Confirm U.S. CSL scope before production
us-csl aggregates component lists from different authorities. A candidate may raise an export-control, procurement or sanctions question, depending on its originating list. This API request screens the supplied party; it does not classify goods, determine the destination rules or grant an export license.
| Check | Required evidence |
|---|---|
| GET /api/v1/sources | Production key with sources:read; response data array |
| id | us-csl |
| 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 | Add these discovery checks before its production call |
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
| Setting | Production request |
|---|---|
| Key | Separate production credential with screenings:write |
| Coverage | sources: ["us-csl"]; 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": "Example Harbor Trading",
"entityType": "organization"
},
"sources": [
"us-csl"
],
"retention": "standard"
}Interpret U.S. CSL evidence in this workflow
Keep record.list and record.authority beside data.id and data.coverage. The shipment reviewer needs the originating restriction, not just the fact that a company appeared in the CSL. A buyer and consignee can have separate evidence and separate unresolved decisions.
| Fields in data.matches[] | Review use |
|---|---|
| record.sourceId, record.list and record.authority | Distinguish the CSL transport source from the component list and issuing authority. |
| record.designations[].legalReferences and wording | Present the actual restriction context instead of a universal sanctioned flag. |
| record.id, names and addresses | Keep 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
| 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 |
- Keep separate buyer and end-user results under one synthetic shipment reference.
- Preserve different component-list authorities during JSON mapping.
- Prevent a completed buyer check from satisfying an unchecked consignee step.
Sources and references
Authority and technical sources
SanctionsKit documentation
Frequently asked questions
What should Java preserve when mapping a CSL candidate?
Keep record.list and record.authority with the record identity and restriction evidence. A DTO that retains only a name and sanctioned flag loses the context needed to interpret different component lists.
Does one Java CSL request check an entire shipment?
No. It checks the subject in that request. Your trade workflow chooses the buyer, consignee, end user and other relevant parties, and keeps a separate operation for each legal identity.
How should Java display candidates from different CSL component lists?
Show the component list and issuing authority beside the candidate, together with available restriction wording and legal references. Do not collapse every record into one sanctioned flag. A reviewer needs the underlying list to understand the result.
What remains outside the Java CSL party-screening example?
Goods classification, destination restrictions, licensing and other transaction controls remain separate tasks. A completed party comparison does not decide whether the planned export is permitted or replace the responsible review process.