# AI SDK

The AI SDK's provider factories read `OPENAI_API_KEY` from the environment by
default, and take an explicit `apiKey` and `baseURL` when you want to override
them. Both shapes are one line.

> **Note:** **Start here if seekrit is new:** [three commands](/docs/guides/frameworks#get-running-in-five-minutes) put the keys in an environment, mint a token bound to it, and export `SEEKRIT_TOKEN`. A token reads [everything in its environment](/docs/guides/frameworks#which-secrets-does-the-agent-get), so there is no per-key or per-framework setup to do before any of the below.

## 1. Wrap the process

```bash
seekrit run -- next dev
seekrit run -- node server.js
```

Nothing to change in the app. The default `openai(...)` provider picks up the
injected `OPENAI_API_KEY`.

## 2. Resolve in code

Serverless and edge runtimes are the reason this shape exists: there is no
process to wrap and no ambient environment, so resolve inside the handler.

```ts
import { createOpenAI } from "@ai-sdk/openai";
import { generateText } from "ai";
import { Seekrit } from "@seekrit/sdk";

export async function POST(req: Request) {
  const secrets = await new Seekrit().resolve();
  const openai = createOpenAI({ apiKey: secrets.OPENAI_API_KEY });

  const { text } = await generateText({
    model: openai("gpt-5.6-terra"),
    prompt: await req.text(),
  });
  return Response.json({ text });
}
```

On Cloudflare Workers there is no `process.env`, so pass the token from the
Worker's own env — the one variable your platform holds:

```ts
const secrets = await new Seekrit({ token: env.SEEKRIT_TOKEN }).resolve();
```

The SDK is pure WebCrypto and global `fetch`, so it runs unchanged on Node, Bun,
Deno, browsers, and Workers.

## 3. Never hold the key

```ts
import { createOpenAI } from "@ai-sdk/openai";

const openai = createOpenAI({
  baseURL: "http://127.0.0.1:8080/openai/v1",
  apiKey: "{{seekrit:OPENAI_API_KEY}}",
});
```

```toml
# seekrit-proxy.toml
listen = "127.0.0.1:8080"

[[route]]
prefix = "/openai"
upstream = "https://api.openai.com"
allow = ["OPENAI_API_KEY"]
methods = ["POST"]
paths = ["/v1/chat/completions", "/v1/embeddings"]
```

For a provider without a first-party package, `createOpenAICompatible` takes the
same `baseURL` and `apiKey`.

### Without running the proxy

`createOpenAI` takes a custom `fetch`, which is all the substitution needs:

```ts
import { seekritFetch } from "@seekrit/sdk/fetch";

const openai = createOpenAI({
  apiKey: "{{seekrit:OPENAI_API_KEY}}",
  fetch: seekritFetch({ allow: { "api.openai.com": ["OPENAI_API_KEY"] } }),
});
```

Same placeholder, same allowlist, no sidecar — and a weaker boundary, since it
runs in your process. [In-process injection](/docs/guides/agent-proxy/in-process)
sets out the trade-off.

## Gotchas

- **`@ai-sdk/openai` calls `/v1/responses`, not `/v1/chat/completions`.** Current
  versions default to the Responses API, so a route or allowlist pinned to
  `/v1/chat/completions` refuses every request. Use `/v1/**`, or pin
  `/v1/responses` on purpose.

- **Resolve once per request, not once per token.** `resolve()` is a network
  round trip and a decrypt; calling it inside a streaming loop turns every chunk
  into an API call. Resolve at the top of the handler.
- **A browser bundle must never hold a service token.** The SDK runs in a browser
  because Workers and Deno need the same code path, not because a token belongs
  in client JavaScript. Keep resolution server-side.
- **Tool calls are where a leak becomes expensive.** `generateText` with `tools`
  runs your functions with whatever credentials they close over. If those are
  third-party keys, that's the case for shape 3.
