Developer guide

OFAC API in Swift: trusted backend screening

Connect a Swift backend to the OFAC API. Keep secret keys out of distributed apps and route SDN matches into a saved review record.

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

Screen OFAC subjects with a trusted Swift backend

This tutorial is about the Swift programming language. A distributed iOS or macOS app should call your own authenticated backend rather than contain a SanctionsKit service key. The backend validates the user’s access, creates the screening operation and provides only the result information that user is authorized to see.

For an internal Swift command-line workflow, keep the key in the trusted process environment and persist the operation reference outside the transient URLSession task. A task cancellation can leave the request outcome uncertain; retain the original operation for deliberate recovery instead of creating a new one automatically.

Prepare the synthetic exercise

Run the Swift 6 CLI on a trusted machine with the invented request, then exercise a cancelled URLSession task. Keep the operation in durable backend storage. This tutorial concerns Swift the programming language; it is not an integration with the SWIFT financial messaging network.

Swift setup and request files
RequirementValue or action
RuntimeUse Swift 6 with Foundation. This is a trusted CLI or server-side Swift example. Never embed a SanctionsKit API key in an iPhone, iPad, Apple TV, or macOS client distributed to end users; those apps should call your authenticated backend.
Client fileSave the client as Screen.swift
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 Swift request boundary

The URLSession example belongs on a trusted server or internal CLI. An installed mobile or desktop app cannot protect a shared service credential. Let the app authenticate to your backend, which creates the screening operation and returns only information that user may access.

Swift: authenticated screening client
// Swift 6. Server or trusted CLI only. Do not ship an API key in an iOS app.
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

struct ScreeningFailure: Error { let message: String }
final class NoRedirect: NSObject, URLSessionTaskDelegate, @unchecked Sendable {
    func urlSession(_ session: URLSession, task: URLSessionTask,
                    willPerformHTTPRedirection response: HTTPURLResponse,
                    newRequest request: URLRequest,
                    completionHandler: @escaping (URLRequest?) -> Void) { completionHandler(nil) }
}
@main struct Screen {
    static func main() async {
        do { try await run() }
        catch {
            // Do not print the request, credential, or raw response.
            FileHandle.standardError.write(Data("Screening incomplete. Check the operation before retrying.\n".utf8))
            exit(1)
        }
    }
    static func run() async throws {
        let env = ProcessInfo.processInfo.environment
        guard let key = env["SANCTIONSKIT_API_KEY"], let operation = env["SCREENING_OPERATION_ID"], !operation.isEmpty else {
            throw ScreeningFailure(message: "Missing credential or operation ID")
        }
        let path = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "request.json"
        let raw = try Data(contentsOf: URL(fileURLWithPath: path))
        guard raw.count <= 65_536,
              let body = try JSONSerialization.jsonObject(with: raw) as? [String: Any],
              body["subject"] != nil, (body["sources"] != nil) != (body["package"] != nil) else {
            throw ScreeningFailure(message: "Invalid request object")
        }
        var request = URLRequest(url: URL(string: "https://www.sanctionskit.com/api/v1/screenings")!)
        request.httpMethod = "POST"; request.httpBody = raw
        request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(operation, forHTTPHeaderField: "Idempotency-Key")
        let configuration = URLSessionConfiguration.ephemeral
        configuration.timeoutIntervalForRequest = 25; configuration.timeoutIntervalForResource = 30
        let session = URLSession(configuration: configuration, delegate: NoRedirect(), delegateQueue: nil)
        defer { session.invalidateAndCancel() }
        // URLSession download avoids retaining an unbounded response in memory.
        let (file, response) = try await session.download(for: request)
        defer { try? FileManager.default.removeItem(at: file) }
        let size = try file.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0
        guard size <= 8 * 1024 * 1024, let http = response as? HTTPURLResponse,
              (200..<300).contains(http.statusCode) else { throw ScreeningFailure(message: "Incomplete HTTP response") }
        let payload = try Data(contentsOf: file)
        guard let envelope = try JSONSerialization.jsonObject(with: payload) as? [String: Any],
              let data = envelope["data"] as? [String: Any], let id = data["id"] as? String, !id.isEmpty,
              let status = data["status"] as? String, ["potential_match", "no_match"].contains(status),
              let matches = data["matches"] as? [Any],
              (status == "no_match" ? matches.isEmpty : !matches.isEmpty) else {
            throw ScreeningFailure(message: "Unexpected screening response")
        }
        let summary: [String: Any] = ["id":id, "status":status, "candidateCount":matches.count]
        FileHandle.standardOutput.write(try JSONSerialization.data(withJSONObject: summary))
        FileHandle.standardOutput.write(Data("\n".utf8))
    }
}

Run the client

Run the sandbox request
swiftc -parse-as-library Screen.swift -o screen
./screen request.json

Confirm OFAC scope before production

Select ofac-sdn in the backend’s production request and expose that scope in the application summary. Additional non-SDN checks require a deliberate source choice. The frontend must not make the source set narrower to hide an availability failure or turn a partial check into approval.

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

Keep data.id outside the lifetime of a Swift task and retrieve retained evidence through the backend. data.coverage identifies the completed source version. If a later mobile session opens a case, show the original result rather than rerunning the subject simply to display evidence.

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
  • Search the distributed app bundle and configuration for a service key.
  • Cancel a local task and confirm the saved operation remains recoverable.
  • Open an existing screening from a second session without creating another check.

Sources and references

Frequently asked questions

Why can’t a distributed Swift app hold the OFAC service credential?

A distributed application cannot keep the SanctionsKit API key secret. Use a trusted backend for the provider call and authorize the app’s access to a limited result summary or review view.

Does this Swift OFAC tutorial use the SWIFT banking network?

No. It uses the Swift programming language to call SanctionsKit over HTTPS. The source selection is OFAC SDN data; the example makes no claim to connect to financial messaging infrastructure.

Why does the Swift OFAC example report an incomplete screening?

An HTTP failure or unexpected result shape leaves the outcome incomplete. Keep the saved operation and investigate the failure before retrying. Do not interpret a missing response or decoding failure as an empty match list.

Should reopening an OFAC result in Swift trigger another screening?

Use the retained screening ID to retrieve existing evidence when the task is simply to view it. A fresh comparison is a separate operation with its own purpose, source versions and usage.