Configuration
Everything the SDK needs is set once, at startup.
observra.configure({ serviceName: "my-app" });
That's the whole configuration for most applications. gatewayKey comes from
OBSERVRA_GATEWAY_KEY, and the gateway URL defaults to Observra's hosted gateway.
configure() returns the resolved config. You need the return value for exactly one
thing — grouping calls into a single trace
— and can ignore it otherwise.
Options
gatewayKey
Type: string · Falls back to: OBSERVRA_GATEWAY_KEY · Required
Your per-Application gateway key, starting with obs_. Generated in the
dashboard under Gateway Keys.
This is not a provider API key. The gateway reads it to identify which Application and Environment a request belongs to; your provider key travels separately, is forwarded to the provider, and is never stored.
Throws if neither the option nor the environment variable is set.
:::warning Keep it out of source A gateway key in a committed file is a leaked credential — anyone holding it can write observations into your account. Read it from the environment. :::
serviceName
Type: string · Default: "observra-app"
Names your service in trace data. Worth setting as soon as you run more than one application against the same gateway, so traces are attributable at a glance.
guardrailMode
Type: "warn" | "redact" | "block" · Default: "warn"
How to handle a prompt or response containing something that looks like PII or a secret. See Guardrails for the full behaviour and pattern list.
"warn"— log it, send the request unchanged"redact"— mask the match before sending"block"— throwGuardrailViolationand never send
gatewayUrl
Type: string · Falls back to: OBSERVRA_GATEWAY_URL, then
https://gateway.observra.in
Only needed if you self-host the gateway. On the hosted platform, leave it out.
// Self-hosted only
observra.configure({ gatewayUrl: "https://gateway.internal.example.com" });
Trailing slashes are stripped. A plaintext http:// URL is refused — over plain HTTP
your gateway key and prompts are readable by anything on the path.
insecure
Type: boolean · Default: false
Allows a plaintext http:// gateway URL. Without it, configure() throws rather than
send your gateway key and prompts over an unencrypted connection.
Intended for local development against a gateway on localhost. There is no legitimate
reason to set it in production — if you find yourself needing it there, the gateway is
reachable over plain HTTP by something other than you as well.
observra.configure({ gatewayUrl: "http://localhost:8787", insecure: true });
promptInjectionDetection
Type: boolean · Falls back to: OBSERVRA_PROMPT_INJECTION_DETECTION ·
Default: unset · Added in 0.2.0
Asks the gateway to scan this application's prompts for injection attempts.
Leaving it unset is not the same as false. The gateway reads three states:
| Value | Gateway behaviour |
|---|---|
| unset | Use whatever the Application is configured for in the dashboard |
true | Scan, and record that the SDK asked for it |
false | Skip the scan for this call |
So false is an active opt-out that overrides your dashboard setting, not a default.
Leave the option out entirely unless you specifically want one service to differ from
the Application's configuration.
Detection itself runs in the gateway; the SDK only sends the request. Findings appear in the dashboard, not in your process.
Environment variables
| Variable | Maps to |
|---|---|
OBSERVRA_GATEWAY_KEY | gatewayKey |
OBSERVRA_GATEWAY_URL | gatewayUrl — self-hosted only |
OBSERVRA_PROMPT_INJECTION_DETECTION | promptInjectionDetection — 1/true/yes/on are true, anything else false |
Explicit options win over environment variables, which win over the default.
instrument()
await observra.instrument();
Patches the global fetch so calls from provider SDKs and agent frameworks reroute to
your gateway and carry trace context. It also detects installed agent frameworks and
patches those it supports — currently LangChain, see
Agents and tools.
Three things worth knowing:
- It's async. Framework patching goes through dynamic
import(), soawaitit. Without theawait, your first calls can run before patching finishes. - It's idempotent. Calling it twice is a no-op.
- Order matters.
configure(), theninstrument(), then construct your clients.
You don't need instrument() if you only use the
SDK's own provider clients — those
talk to the gateway directly.
shutdown()
await observra.shutdown();
Flushes spans still sitting in the export buffer and closes the SDK's own connections
to the gateway. Safe to call more than once, safe to call before configure(), and it
never throws.
You rarely need it. A process that ends naturally flushes on its own — the SDK hooks
beforeExit. It matters for a short-lived script or CLI that would otherwise exit with
spans still buffered.
It is one-way: after it resolves, spans stop being exported and guardrail rules stop refreshing. A process that keeps running afterwards keeps working, silently untelemetered.
:::caution process.exit() on Windows
Node 24 on Windows aborts during teardown when a process calls process.exit() shortly
after any HTTP activity:
Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c
This is a Node/libuv issue, not an SDK one — two plain fetch calls to any server
reproduce it with no Observra code involved — but the SDK makes gateway requests, so a
script that ends in process.exit() will meet it. Awaiting shutdown() first does not
help.
Set process.exitCode and let the process end on its own instead:
await observra.shutdown();
process.exitCode = 0; // not process.exit(0)
:::
Startup order
observra.configure({ serviceName: "my-app" }); // 1
await observra.instrument(); // 2
const groq = new Groq({ apiKey: process.env.GROQ_API_KEY }); // 3
A client constructed before instrument() still works — the patch is on the global
fetch, not on the client — but this order avoids a class of edge cases with SDKs
that capture fetch at construction time.
What the SDK never does
- Never logs your provider key. It's moved into a dedicated header and forwarded; it never appears in a span attribute, an error message, or a log line.
- Never sets the global OpenTelemetry TracerProvider. The SDK builds a private one, so dropping it into an application running its own OpenTelemetry stack doesn't conflict.
- Never fails your request for telemetry reasons. If tracing or export fails, the LLM call still goes through.
Next
- Providers — which providers work on which path
- Guardrails —
guardrailModein detail - API reference — full type signatures