Skip to main content

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" — throw GuardrailViolation and 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:

ValueGateway behaviour
unsetUse whatever the Application is configured for in the dashboard
trueScan, and record that the SDK asked for it
falseSkip 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

VariableMaps to
OBSERVRA_GATEWAY_KEYgatewayKey
OBSERVRA_GATEWAY_URLgatewayUrl — self-hosted only
OBSERVRA_PROMPT_INJECTION_DETECTIONpromptInjectionDetection1/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(), so await it. Without the await, your first calls can run before patching finishes.
  • It's idempotent. Calling it twice is a no-op.
  • Order matters. configure(), then instrument(), 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