Developer guide

U.S. CSL API in Go: screen trading partners

Build a Go worker for U.S. CSL API checks on trading partners. Link results to local records and keep the originating list visible during review.

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

Check shipment parties from Go

A name is not a reliable key for merging Consolidated Screening List candidates. Use the documented record and source references when connecting evidence, and preserve the component list. Two entries can concern one real-world entity while still representing different authority records.

If the same company appears in two CSL components, show both source references within the party’s review. Keep the component-list evidence attached to each candidate so the analyst can assess the relevant measures without treating duplicate appearances as independent identity confirmation.

Prepare the synthetic exercise

Use Example Harbor Trading as an invented shipment party and create a local fixture with a second similarly named organization at another address. Keep each party’s operation reference. This checks whether your Go service reconciles evidence by identity references rather than by a convenient display name.

Go setup and request files
RequirementValue or action
RuntimeUse Go 1.22 or later. The example uses only the standard library and can run as a small command-line program or be adapted into a backend service.
Client fileSave the client as screen.go
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"
}

Implement the Go request boundary

The Go client sends one organization request and validates the result boundary. A surrounding portfolio worker must retain the party reference when results arrive out of order. Concurrency should not allow a later response to replace another party’s evidence.

Go: authenticated screening client
// Go 1.22+. Standard library only. Backend or trusted CLI.
package main

import (
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

func run() error {
    key, operation := os.Getenv("SANCTIONSKIT_API_KEY"), os.Getenv("SCREENING_OPERATION_ID")
    if key == "" || operation == "" { return errors.New("set the API key and a persistent operation ID") }
    path := "request.json"
    if len(os.Args) > 1 { path = os.Args[1] }
    raw, err := os.ReadFile(path)
    if err != nil { return errors.New("cannot read request file") }
    if len(raw) > 64*1024 { return errors.New("request file exceeds 64 KiB") }
    var body map[string]json.RawMessage
    if json.Unmarshal(raw, &body) != nil { return errors.New("request is not a JSON object") }
    _, sources := body["sources"]; _, pack := body["package"]
    if len(body["subject"]) == 0 || sources == pack { return errors.New("supply a subject and exactly one of sources or package") }
    request, err := http.NewRequest(http.MethodPost, "https://www.sanctionskit.com/api/v1/screenings", bytes.NewReader(raw))
    if err != nil { return err }
    request.Header.Set("Authorization", "Bearer "+key)
    request.Header.Set("Content-Type", "application/json")
    request.Header.Set("Idempotency-Key", operation)
    client := &http.Client{
        Timeout: 25 * time.Second,
        CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
    }
    response, err := client.Do(request)
    if err != nil { return errors.New("transport incomplete; check the operation before retrying") }
    defer response.Body.Close()
    const limit = 8 * 1024 * 1024
    payload, err := io.ReadAll(io.LimitReader(response.Body, limit+1))
    if err != nil || len(payload) > limit { return errors.New("incomplete or oversized response") }
    if response.StatusCode < 200 || response.StatusCode >= 300 { return fmt.Errorf("screening incomplete: HTTP %d", response.StatusCode) }
    var envelope struct {
        Data *struct {
            ID string `json:"id"`
            Status string `json:"status"`
            Matches []json.RawMessage `json:"matches"`
        } `json:"data"`
    }
    if json.Unmarshal(payload, &envelope) != nil || envelope.Data == nil { return errors.New("invalid response envelope") }
    data := envelope.Data
    if data.ID == "" || (data.Status != "potential_match" && data.Status != "no_match") || data.Matches == nil {
        return errors.New("incomplete response: unexpected screening shape")
    }
    count := len(data.Matches)
    if (data.Status == "no_match" && count != 0) || (data.Status == "potential_match" && count == 0) {
        return errors.New("incomplete response: inconsistent screening shape")
    }
    return json.NewEncoder(os.Stdout).Encode(map[string]any{"id":data.ID,"status":data.Status,"candidateCount":count})
}
func main() {
    if err := run(); err != nil { fmt.Fprintln(os.Stderr, err.Error()); os.Exit(1) }
}

Run the client

Run the sandbox request
go run screen.go request.json

Confirm U.S. CSL scope before production

us-csl is the API source, while the returned component list and authority explain the underlying restriction. Do not label every CSL candidate as an OFAC asset freeze. The party’s proposed role and the applicable authority remain part of the trade review.

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

Store data.id and data.coverage with the shipment-party reference, then preserve addresses and component-list details for comparison. Missing address information is unknown evidence, not proof that two organizations differ. Similar names must not collapse separate candidate records.

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
  • Deliver two synthetic party responses out of order and keep their result links correct.
  • Retain two component-list records for one similar display name.
  • Show an absent address as unknown instead of recording an identity conflict.

Sources and references

Frequently asked questions

How should Go reconcile out-of-order CSL responses?

Use the persisted operation and shipment-party reference associated with each request. Do not join results to the latest party name in memory; equal or changed names can attach evidence to the wrong subject.

Does a missing CSL address prove a different company in Go?

No. Missing information cannot establish a conflict. Keep the available names, identifiers, addresses and authority references together so the reviewer can assess both supporting evidence and remaining uncertainty.

Why should a Go shipment service keep address evidence for CSL matches?

Addresses can help a reviewer compare an organization with a candidate. Keep the returned names, addresses, stable record reference, and component list together. Missing address detail is unknown information, not proof that two organizations are different.

Why retain both the CSL source and its component list?

The source identifies the consolidated transport, while the component list identifies the underlying authority context. A trade review needs both to interpret the candidate and locate the relevant original restriction.