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

openai-java (official)
implementation("com.openai:openai-java:LATEST")
openai-kotlin (community)
implementation("com.aallam.openai:openai-client:LATEST")

Configure

openai-java (official)
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.

openai-kotlin (community)
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)
Warning 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:

codeMeaning
request_blockedA content guardrail matched. blocked_by names the rule.
provider_deniedPolicy does not allow this provider.
model_denied / model_not_allowedPolicy does not allow this model.
provider_key_missingNo 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.

Note Redaction is silent — nothing is thrown. A 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-java falls back to OPENAI_API_KEY from the environment when you omit .apiKey(...). Be explicit, so a leftover provider key on the machine cannot win.
  • Base URL suffix: /v1 for openai-java, /v1/ (trailing slash) for openai-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.