Swift
Swift has no first-party OpenAI SDK. Most projects use MacPaw’s OpenAI
package, which is community-maintained; the rest call the HTTP API directly
with URLSession. Either way you change the same two things: the host you talk to
and the key you send. Every ChatQuery you already build stays exactly as it is.
Install
// Package.swift
.package(url: "https://github.com/MacPaw/OpenAI.git", from: "0.4.0")
URLSession is in Foundation.Configure
The package builds its URLs from a Configuration, and appends /v1/chat/completions
to the basePath itself. So the gateway goes in as a host plus a base path — and
your queries do not change at all.
import OpenAI
let org = ProcessInfo.processInfo.environment["VULNETIX_ORG_UUID"]!
let configuration = OpenAI.Configuration(
token: ProcessInfo.processInfo.environment["VULNETIX_API_KEY"]!, // Vulnetix key
host: "guardrails.vulnetix.com",
basePath: "/openai/\(org)"
)
let openAI = OpenAI(configuration: configuration)
// Every call you already make is unchanged:
let result = try await openAI.chats(query: query)
The final URL must come out as
https://guardrails.vulnetix.com/openai/{orgUuid}/v1/chat/completions. If your
version of the package composes paths differently, adjust basePath until it does —
that URL is the contract, not the field.
import Foundation
let org = ProcessInfo.processInfo.environment["VULNETIX_ORG_UUID"]!
let key = ProcessInfo.processInfo.environment["VULNETIX_API_KEY"]!
var request = URLRequest(
url: URL(string: "https://guardrails.vulnetix.com/openai/\(org)/v1/chat/completions")!
)
request.httpMethod = "POST"
request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"model": "gpt-4o-mini",
"messages": [["role": "user", "content": "Hello"]],
])
let (data, response) = try await URLSession.shared.data(for: request)
The token is your Vulnetix API key, not a provider key. Swap openai in the
path for any other provider slug; model names pass through verbatim.
Streaming
Unchanged. The gateway relays the provider’s SSE stream — with MacPaw’s package,
openAI.chatsStream(query:) works exactly as before.
Handling a guardrail block
A request your policy refuses never reaches the provider. It returns an
OpenAI-shaped 403, and the two fields worth reading are code and
blocked_by.
struct FirewallErrorEnvelope: Decodable {
struct FirewallError: Decodable {
let code: String?
let message: String?
let blocked_by: String?
}
let error: FirewallError
}
let (data, response) = try await URLSession.shared.data(for: request)
if let http = response as? HTTPURLResponse, http.statusCode == 403 {
let envelope = try JSONDecoder().decode(FirewallErrorEnvelope.self, from: data)
switch envelope.error.code {
case "request_blocked":
throw AppError.firewallBlocked(rule: envelope.error.blocked_by ?? "unknown")
default:
// provider_denied, model_denied, model_not_allowed, provider_key_missing
throw AppError.policyRefused(code: envelope.error.code ?? "unknown")
}
}
code | Meaning |
|---|---|
request_blocked | A content guardrail matched. blocked_by names the rule. |
provider_denied | Policy does not allow this provider. |
model_denied / model_not_allowed | Policy does not allow this model. |
provider_key_missing | No provider key in the vault for this provider. |
With MacPaw’s package the failure surfaces as its own API error type, which models
the standard OpenAI error fields — code is there, blocked_by is not, because it
is a Vulnetix addition the package does not know about. The rule name is also in the
message text; decode the body yourself, as above, if you want it structured.
200. A pii_redact rule rewrites the matched
spans in your prompt to the literal [REDACTED] and forwards the request. There is
no error to catch; the completion simply talks about redacted text.Tool / function calling
Tools are forwarded untouched. Guardrails inspect the request you send, which includes the tool-result messages you send back on the next turn.
Gotchas
- Do not ship the Vulnetix key in an iOS or macOS app. A key in a client binary is a key you have published, and the firewall cannot help with a credential an attacker already holds. Call the gateway from your own backend and let the app talk to that.
- MacPaw’s
Configurationsplits the URL intohost+basePathand adds/v1/…itself — so unlike most SDKs you do not put/v1in what you configure. - The
tokenis the Vulnetix key. A provider key here is the most common401.