Dart & Flutter
Dart has no first-party OpenAI SDK. This page uses openai_dart, the
community-maintained client from the langchain.dart project. It takes a
baseUrl and an API key, and nothing else about your code changes. The provider key
stays in the vault.
Install
dart pub add openai_dart
Configure
import 'dart:io';
import 'package:openai_dart/openai_dart.dart';
final org = Platform.environment['VULNETIX_ORG_UUID']!;
final client = OpenAIClient(
config: OpenAIConfig(
authProvider: ApiKeyProvider(Platform.environment['VULNETIX_API_KEY']!), // Vulnetix key
baseUrl: 'https://guardrails.vulnetix.com/openai/$org/v1',
),
);
final response = await client.chat.completions.create(
ChatCompletionCreateRequest(
model: 'gpt-4o-mini',
messages: [ChatMessage.user('Hello')],
),
);
print(response.text);
Swap openai in the path for any other provider slug and pass that provider’s own
model string — the gateway forwards model verbatim.
openai_dart construct the client as
OpenAIClient(apiKey: …, baseUrl: …). The two settings are the same; only the
constructor shape moved.Streaming
Unchanged. The gateway relays the provider’s SSE stream.
final stream = client.chat.completions.createStream(
ChatCompletionCreateRequest(
model: 'gpt-4o-mini',
messages: [ChatMessage.user('Hello')],
),
);
await for (final delta in stream.textDeltas()) {
stdout.write(delta);
}
Handling a guardrail block
A request your policy refuses never reaches the provider. It returns an
OpenAI-shaped 403, which the client throws as an ApiException carrying the
status code and the response body.
try {
final response = await client.chat.completions.create(request);
} on ApiException catch (e) {
if (e.statusCode == 403) {
// The body is the firewall's error envelope:
// {"error": {"code": "request_blocked", "blocked_by": "<rule>", ...}}
final error = jsonDecode(e.message)['error'] as Map<String, dynamic>?;
final code = error?['code']; // request_blocked, model_denied, ...
final rule = error?['blocked_by']; // the rule that stopped it
throw StateError('AI firewall refused this request: $code (rule: $rule)');
}
rethrow;
}
code | Meaning |
|---|---|
request_blocked | A content guardrail matched. blocked_by names the rule; violations lists every rule that matched. |
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. |
If the exception in your version exposes the body under a different field, the envelope itself is unchanged — see curl & raw HTTP for its exact shape.
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, which includes the tool-result messages you append before the next call.
Gotchas
- Do not put the Vulnetix key in a Flutter app. Anything shipped to a device is extractable, and a leaked key lets an attacker spend your provider quota through your organisation’s policy. Call the gateway from a backend you control, and have the app talk to that backend.
api_keyis the Vulnetix key, not a provider key — the most common401.baseUrlmust include the/v1suffix.- This is a community package. If you use a different one (
dart_openai, for instance), check whether it appends/v1itself before you add it to the base URL: the request must land on…/{orgUuid}/v1/chat/completions.