Developer guide

OFAC API in Go: screening services

Build a Go service for OFAC API checks using the standard HTTP client. Select SDN coverage, handle failed requests, and save results for review.

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

Screen OFAC subjects in a Go service

Use an application-owned HTTP client and a defined timeout budget for provider calls. When adapting the example to a request handler, use context cancellation deliberately and close response bodies. Keep the operation persisted independently of the caller’s connection so a cancelled local request can still be reconciled.

If screening a portfolio, bound concurrency according to the API allowance and your service capacity. Spawning a goroutine for every customer is not a substitute for a controlled queue. Large workloads can use the documented batch workflow with per-row completion and review.

Prepare the synthetic exercise

Run the Go CLI against the invented sandbox person before using it in a portfolio service. Save one operation per subject and deliberately cancel one local request. Your test should leave an uncertain operation that can be recovered, not a false no-match result.

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.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 Go request boundary

The standard net/http client needs a timeout budget and closed response bodies. Reuse an application-owned client when adapting the CLI to a service. Context cancellation controls the local call; it does not undo a screening the server has already accepted.

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 OFAC scope before production

Validate the ofac-sdn selection before creating the Go request. Non-SDN coverage is a separate requirement with different list measures. Bound portfolio concurrency and retain each subject’s source selection so one successful request cannot stand in for the remaining customers.

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

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

Interpret OFAC evidence in this workflow

Map data.id back to the subject’s internal reference before releasing a worker slot. Record data.coverage with the outcome, and send potential candidates to review independently of the transport success. A partially failed portfolio needs per-subject recovery, not one successful batch-level flag.

OFAC candidate fields to retain
Fields in data.matches[]Review use
record.sourceId and record.idKeep the SDN source and stable record reference attached to the candidate.
record.designations[].programs and legalReferencesShow the listed program and legal references; do not turn every non-SDN restriction into an asset freeze.
record.names, identifiers and birthDatesCompare the supplied identity with aliases and available identifiers, preserving partial-date precision.
  • 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
  • Cancel a request after sending its body and recover the original operation.
  • Run several synthetic subjects with a fixed worker limit and distinct keys.
  • Verify that one source error leaves only its own subject incomplete.

Sources and references

Frequently asked questions

Is the Go OFAC example the official Go SDK?

No. This Go 1.22 example uses the standard HTTP library and is separate from the official Go SDK. Choose one integration path and preserve its timeout, response-validation and operation-recovery behavior.

Can Go goroutines replace an OFAC portfolio queue?

Goroutines provide concurrency, not workload policy. Limit the number of outstanding screens and retain per-subject operations. Use the batch workflow when the business task needs persistent portfolio progress and row-level recovery.

Does cancelling a Go request cancel the accepted OFAC screening?

Do not assume that cancelling the client request undoes server work. Keep the operation in an uncertain state until you recover it with the original key and body. A new key would represent a new check.

How should Go report an interrupted SDN comparison?

Keep the operation incomplete until its accepted result can be reconciled. A cancelled context describes the caller’s wait; it does not establish that no candidate existed or that remote screening never started.