Keys & security

Which key belongs in which runtime, and what the SDK refuses to let you do.

Browser or server Both — that is the point of this page dev · https://api.dev.oprag.ai

Two key kinds, one rule: an embed key is public and belongs in the browser; an integration key is a workspace secret and belongs only on a server.

RuntimeKey kindPrefixEnv varRule
BrowserEmbed keyembed_test_… / embed_live_…PUBLIC_OPRAG_EMBED_KEYShips in the bundle. Locked to the origins you allowlist on the project.
ServerIntegration keysk_test_… / sk_live_…OPRAG_SECRET_KEYRead from the process environment. Never sent to, or through, a browser.

The SDK enforces it

createClient and mountWidget both refuse an sk_* key when they detect a browser, throwing OpragConfigError before any request goes out. This is a guard rail, not a security boundary — the boundary is that you never put the key there.

TypeScript
import { createClient, OpragConfigError } from "@oprag/sdk";

try {
  createClient({ apiUrl: "https://api.dev.oprag.ai", apiKey: "sk_live_..." }); // in the browser
} catch (err) {
  if (err instanceof OpragConfigError) {
    // Use an embed key here, and keep sk_* behind your own API.
  }
  throw err;
}

Embed keys need an Origin

An embed key request with no Origin header is refused with 403 — there is no server-side fallback. That is deliberate: it means a leaked embed key cannot be replayed with curl. Server-to-server calls use an integration key instead.

The origin allowlist is per project, and an empty allowlist rejects everything. Full rules are on CORS & origins.

Naming that carries the boundary

.env Shell
# Ships to the browser. Public by construction.
PUBLIC_OPRAG_EMBED_KEY=embed_live_...

# Server only. Never referenced from client code.
OPRAG_SECRET_KEY=sk_live_...

Bundlers expose variables by prefix. Naming the browser key ${KEY_ENV_VARS.browser} and the secret ${KEY_ENV_VARS.server} means the build tooling itself refuses to leak the secret, and a reviewer can see a mistake without knowing the values.

Deciding at runtime

TypeScript
import { createClient, isBrowser } from "@oprag/sdk";

// `isBrowser()` treats a Node process with a DOM shim as *not* a browser,
// because it checks process.versions.node as well as window.
const oprag = createClient({
  apiUrl: "https://api.dev.oprag.ai",
  apiKey: isBrowser()
    ? import.meta.env.PUBLIC_OPRAG_EMBED_KEY
    : process.env.OPRAG_SECRET_KEY!,
});

Ready to ship?

Get started free