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.

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.
| Requirement | Value or action |
|---|---|
| Runtime | Use 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 file | Save the client as Screen.swift |
| 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 | Mira Calder: invented subject; sandbox@1: synthetic records |
{
"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 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
swiftc -parse-as-library Screen.swift -o screen
./screen request.jsonConfirm 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.
| Check | Required evidence |
|---|---|
| GET /api/v1/sources | Production key with sources:read; response data array |
| id | ofac-sdn |
| 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 ofac-sdn in the production request
| Setting | Production request |
|---|---|
| Key | Separate production credential with screenings:write |
| Coverage | sources: ["ofac-sdn"]; 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": "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.
| Fields in data.matches[] | Review use |
|---|---|
| record.sourceId and record.id | Keep the SDN source and stable record reference attached to the candidate. |
| record.designations[].programs and legalReferences | Show the listed program and legal references; do not turn every non-SDN restriction into an asset freeze. |
| record.names, identifiers and birthDates | Compare 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
| 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 |
- 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
Authority and technical sources
SanctionsKit documentation
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.