Kotlin
There is no first-party Kotlin SDK, but the official openai-java client works
cleanly from Kotlin and is the recommended option on the JVM. If you need Kotlin
Multiplatform, the community openai-kotlin client is the usual choice. Either
way, two settings change — the base URL and the API key — and nothing else does.
Install
implementation("com.openai:openai-java:LATEST")
implementation("com.aallam.openai:openai-client:LATEST")
Configure
import com.openai.client.okhttp.OpenAIOkHttpClient
import com.openai.models.ChatModel
import com.openai.models.chat.completions.ChatCompletionCreateParams
val org = System.getenv("VULNETIX_ORG_UUID")
val client = OpenAIOkHttpClient.builder()
.baseUrl("https://guardrails.vulnetix.com/openai/$org/v1")
.apiKey(System.getenv("VULNETIX_API_KEY")) // Vulnetix key, not an OpenAI key
.build()
val params = ChatCompletionCreateParams.builder()
.model(ChatModel.GPT_4O_MINI)
.addUserMessage("Hello")
.build()
val completion = client.chat().completions().create(params)
println(completion.choices()[0].message().content().orElse(""))
For a provider whose model has no ChatModel constant, build one from the string
the provider itself uses — ChatModel.of("llama-3.3-70b-versatile"). Model names
pass through the gateway verbatim.
import com.aallam.openai.client.OpenAI
import com.aallam.openai.client.OpenAIConfig
import com.aallam.openai.client.OpenAIHost
import com.aallam.openai.api.chat.ChatCompletionRequest
import com.aallam.openai.api.chat.ChatMessage
import com.aallam.openai.api.chat.ChatRole
import com.aallam.openai.api.model.ModelId
val org = System.getenv("VULNETIX_ORG_UUID")
val openAI = OpenAI(
OpenAIConfig(
host = OpenAIHost(baseUrl = "https://guardrails.vulnetix.com/openai/$org/v1/"),
token = System.getenv("VULNETIX_API_KEY"), // Vulnetix key, not an OpenAI key
)
)
val completion = openAI.chatCompletion(
ChatCompletionRequest(
model = ModelId("gpt-4o-mini"),
messages = listOf(ChatMessage(role = ChatRole.User, content = "Hello")),
)
)
println(completion.choices.first().message.content)
OpenAIHost(baseUrl = …) needs a trailing slash. Its URL resolution drops the
last path segment without one, so …/{orgUuid}/v1 silently becomes …/{orgUuid}/
and your requests miss the gateway route.Streaming
Unchanged — the gateway relays the provider’s SSE stream. With openai-java:
client.chat().completions().createStreaming(params).use { stream ->
stream.stream().forEach { chunk ->
chunk.choices().forEach { choice ->
choice.delta().content().ifPresent(::print)
}
}
}
With openai-kotlin, openAI.chatCompletions(request) returns a Flow of chunks.
Handling a guardrail block
A request your policy refuses never reaches the provider. It returns an
OpenAI-shaped 403. With openai-java, that is a
com.openai.errors.PermissionDeniedException, whose body() carries the error
object — including Vulnetix’s blocked_by.
import com.openai.errors.PermissionDeniedException
try {
client.chat().completions().create(params)
} catch (e: PermissionDeniedException) { // HTTP 403
// e.statusCode() == 403
// e.body() carries: code, message, blocked_by, violations
error("AI firewall refused this request: ${e.body()}")
}
Branch on the code field to tell your organisation refused this apart from the
provider refused this:
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 openai-kotlin, API failures arrive as OpenAIAPIException subclasses; check
the status code and read the code field from the error it carries.
pii_redact rule rewrites the
matched spans in your prompt to the literal [REDACTED] and forwards the request.
You get an ordinary completion back, about redacted text.Tool / function calling
Tools are forwarded untouched. Guardrails inspect the request you send, including the tool-result messages you add before the next call — so a tool that returns a secret into the conversation is a request the firewall can block or redact.
Gotchas
- The API key is your Vulnetix key. A provider key here is the most common
401. openai-javafalls back toOPENAI_API_KEYfrom the environment when you omit.apiKey(...). Be explicit, so a leftover provider key on the machine cannot win.- Base URL suffix:
/v1foropenai-java,/v1/(trailing slash) foropenai-kotlin. - On Android, keep the Vulnetix key off the device — a key shipped in an app is a key you have published. Call the gateway from your own backend.