Skip to content

Quick Start

Get from zero to your first governed AI call in under ten minutes.

Prerequisites


Part 1: Set up the panel

1. Create a project

A project groups everything that belongs together: API keys, provider credentials, routing, and policy. You might have one project per environment (production, staging) or one per product.

A default project is created when you register. To add another, go to Administration → Projects → New project.

2. Add a provider key

A provider key is your upstream LLM credential - the key Obstruo uses to call OpenAI, Anthropic, Google, or any other provider on your behalf.

Go to Connections → Provider keys → New provider key:

Field Description
Name A label, e.g. openai-prod
API type Your provider: OpenAI, Anthropic, Google AI Studio, OpenRouter, Ollama, etc.
API key Your upstream key - stored encrypted, masked after saving
API address Leave blank to use the provider's default endpoint. For self-hosted models point it at your own server WITH an explicit scheme, e.g. an Ollama box: http://my-gpu-host:11434/v1 (a bare address defaults to https://, which a plain-HTTP server rejects with an SSL error)

Use Test connection in the dialog to verify the credential before saving.

3. Allow models

Before routing can use a model, it must be registered under your project's model catalog. Go to Models → Models allowed → Add model:

Field Description
Provider The provider TYPE this model belongs to - must match the API type of the provider key that will serve it (e.g. Ollama for an Ollama key)
Model name Exact identifier as the provider knows it, e.g. gpt-4o, claude-3-5-sonnet-20241022, SpeakLeash/bielik-7b-instruct-v0.1-gguf:latest. A typo here surfaces later as a 404 from the upstream
Kind chat, embedding, or audio

Add one entry per model you want to route traffic to, and set its per-token price on the Pricing tab (used for cost tracking and budgets).

4. Enable the model on the provider key

The catalog says what exists; each provider key says what that credential may serve. Go back to Connections → Provider keys, edit your key, and in the Allowed models section tick the models this key should offer. Only ticked models appear as backing-model choices when you build a logical model.

Model missing from the list?

The Allowed models list shows catalog entries whose Provider matches the key's API type and whose kind fits. If it's empty, re-check step 3: the catalog entry was probably added under a different provider name.

5. Create a logical model

A logical model is the name your application will pass as model=. Obstruo resolves it to a real provider model at request time - so you can swap providers without changing application code.

Go to Connections → Logical models → New logical model:

Field Description
Name What your app uses, e.g. obstruo-chat
Kind chat, embedding, or audio
Routing Priority: use first available target; Round Robin: rotate; Cheapest: cost-optimised
Backing models Ordered list of (provider key, model) pairs - the first one is tried first. The choices here are the models you ticked on provider keys in step 4

Multiple backing models give you automatic failover at no extra cost.

6. Create an ObstruoKey

An ObstruoKey is the bearer token your application sends to Obstruo. Go to Endpoints → LLM keys → New obstruo key:

Field Description
Name A label, e.g. backend-prod
Country / region Determines the endpoint URL and where your data is processed
Mode Proxy (default): full pipeline + LLM call. Audit-only: redact and log, but don't call the LLM
Rate limit Max requests per minute - leave blank for unlimited
Monthly budget USD spending cap - leave blank for unlimited

Once created, find your endpoint URL in the ENDPOINT column on the Endpoints page. You can copy the key again at any time using the Copy key button next to each key row - it retrieves the decrypted value from the panel.

https://api.obstruo.ai/your-org/v1

Part 2: Your first call

Replace two things in your existing code: the base URL and the API key. The model value is the logical model name you created in step 5.

import openai

client = openai.OpenAI(
    api_key="your-obstruo-key",
    base_url="https://api.obstruo.ai/your-org/v1",
)

response = client.chat.completions.create(
    model="obstruo-chat",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "your-obstruo-key",
  baseURL: "https://api.obstruo.ai/your-org/v1",
});

const response = await client.chat.completions.create({
  model: "obstruo-chat",
  messages: [{ role: "user", content: "Hello!" }],
});
console.log(response.choices[0].message.content);
using OpenAI;
using OpenAI.Chat;

var client = new ChatClient(
    model: "obstruo-chat",
    credential: new ApiKeyCredential("your-obstruo-key"),
    options: new OpenAIClientOptions
    {
        Endpoint = new Uri("https://api.obstruo.ai/your-org/v1")
    }
);

var response = await client.CompleteChatAsync("Hello!");
Console.WriteLine(response.Value.Content[0].Text);
import (
    "github.com/openai/openai-go"
    "github.com/openai/openai-go/option"
)

client := openai.NewClient(
    option.WithAPIKey("your-obstruo-key"),
    option.WithBaseURL("https://api.obstruo.ai/your-org/v1"),
)

response, _ := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
    Model: openai.F("obstruo-chat"),
    Messages: openai.F([]openai.ChatCompletionMessageParamUnion{
        openai.UserMessage("Hello!"),
    }),
})
fmt.Println(response.Choices[0].Message.Content)

Verify it works

A successful response is identical to an OpenAI response, with an extra obstruo key containing gateway metadata:

{
  "id": "chatcmpl-abc123",
  "choices": [{ "message": { "role": "assistant", "content": "..." } }],
  "obstruo": {
    "redaction": {
      "total_entities": 0,
      "duration_ms": 4,
      "by_category": {}
    },
    "routing": {
      "resolved_model": "gpt-4o-mini",
      "provider_type": "OpenAI",
      "duration_ms": 312
    }
  }
}

Check the Audit log in the panel - your request should appear there within a few seconds, with redaction state, latency, cost, and guardrail verdict.

If a guardrail blocks the request, the response is still HTTP 200 but with finish_reason: "content_filter" and empty assistant content:

{
  "choices": [{
    "message": { "role": "assistant", "content": "" },
    "finish_reason": "content_filter"
  }]
}

Next steps