Skip to main content

    Migrating from OpenAI

    Behest is OpenAI-SDK compatible. For most apps, switching is a one-line change: swap baseURL and hand the client a Behest JWT instead of your OpenAI key. Everything else — streaming, tool calls, structured outputs, the entire chat.completions surface — works unchanged.


    Step 1: move your OpenAI key to Behest

    Dashboard → Project → ProvidersOpenAI → paste your sk-.... Behest encrypts it at rest and forwards requests on your behalf. You stop sending the raw OpenAI key from your servers.

    Already have multiple provider keys (Anthropic, Google, etc.)? Add them too — Behest handles model routing for you.


    Step 2: swap the SDK config

    Node / TypeScript

    Before:

    ts
    import OpenAI from "openai";
    const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

    Important — Behest is not a one-header swap. OpenAI accepts a long-lived sk-... as the Bearer on /v1/chat/completions. Behest does not: Kong requires a short-lived JWT (minted per end-user). The behest_sk_live_... API key authenticates only the mint endpoint. Dropping your API key into new OpenAI({ apiKey: BEHEST_KEY, baseURL }) and calling chat.completions.create will return 401. Use the v1.5 SDK (which mints for you) or mint manually.

    After — recommended: v1.5 Behest SDK

    ts
    import { Behest } from "@behest/client-ts";
     
    const behest = new Behest({
      key: process.env.BEHEST_KEY, // behest_sk_live_...
      baseUrl: `https://${process.env.BEHEST_SLUG}.behest.app`,
    });
     
    // The SDK calls /v1/auth/mint under the hood per request, attaches the JWT
    // as Authorization, injects X-Session-Id, and streams the response.
    await behest.chat.completions.create({ messages, user_id: userId });

    After — if you want to keep using the raw OpenAI SDK: do the mint yourself on the server, pass the resulting JWT (not your API key) to OpenAI's client.

    ts
    import OpenAI from "openai";
     
    // 1) Mint a per-user JWT on your backend.
    const r = await fetch(
      `https://${process.env.BEHEST_SLUG}.behest.app/v1/auth/mint`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${process.env.BEHEST_KEY}`,
        },
        body: JSON.stringify({ user_id: userId, session_id: crypto.randomUUID() }),
      }
    );
    const { access_token: behestJwt, session_id: sessionId } = await r.json();
     
    // 2) Hand the MINTED JWT to the OpenAI SDK as apiKey. Do not pass BEHEST_KEY here.
    const client = new OpenAI({
      apiKey: behestJwt,
      baseURL: `https://${process.env.BEHEST_SLUG}.behest.app/v1`,
      defaultHeaders: { "X-Session-Id": sessionId },
    });
    await client.chat.completions.create({ messages, model: "gpt-4o-mini" });

    Python

    python
    from behest import Behest
    behest = Behest()  # reads BEHEST_KEY + BEHEST_BASE_URL from env
    # Mints a JWT per call, scoped to user_id.
    behest.chat.completions.create(messages=..., model="gpt-4o-mini", user_id=user_id)

    Raw OpenAI Python works too — same rule: mint first, pass the JWT.

    python
    import os, uuid, requests
    from openai import OpenAI
     
    session_id = str(uuid.uuid4())
    mint = requests.post(
        f"https://{os.environ['BEHEST_SLUG']}.behest.app/v1/auth/mint",
        headers={"Authorization": f"Bearer {os.environ['BEHEST_KEY']}"},
        json={"user_id": user_id, "session_id": session_id},
    ).json()
     
    client = OpenAI(
        api_key=mint["access_token"],                                         # JWT, NOT BEHEST_KEY
        base_url=f"https://{os.environ['BEHEST_SLUG']}.behest.app/v1",
        default_headers={"X-Session-Id": session_id},
    )

    cURL

    diff
    - curl https://api.openai.com/v1/chat/completions \
    -   -H "Authorization: Bearer $OPENAI_KEY" \
    + # 1) Mint a JWT first. BEHEST_KEY only authenticates the mint endpoint.
    + ACCESS_TOKEN=$(curl -sS -X POST https://amber-fox-042.behest.app/v1/auth/mint \
    +   -H "Authorization: Bearer $BEHEST_KEY" \
    +   -H "Content-Type: application/json" \
    +   -d '{"user_id":"user_123","session_id":"'$(uuidgen)'"}' | jq -r '.access_token')
    +
    + # 2) Call chat with the minted JWT.
    + curl https://amber-fox-042.behest.app/v1/chat/completions \
    +   -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'

    Step 3: nothing else changes

    All of these work identically:

    • chat.completions.create (streaming and non-streaming)
    • tool_calls / function calling
    • response_format: { type: "json_schema", ... } structured outputs
    • Vision / multimodal messages
    • embeddings.create
    • moderations.create

    If something OpenAI-compatible doesn't work, it's a bug — open an issue.


    What you get for free

    • Per-user rate limits and usage — pass user: userId or mint a per-user JWT; no more "top customer blows the key's RPM".
    • No key in the browser — mint short-lived JWTs server-side; frontend gets a 15-min token.
    • Cost visibility — dashboard breaks down cost by model, user, and day.
    • Guardrails — PII redaction and prompt-injection detection in front of OpenAI.
    • BYOK + fallback — keep using your OpenAI key; add Anthropic/Google for failover.
    • Persistent threads — Behest stores conversation history for you (X-Thread-Id).

    Gotchas

    • Model names: default pass-through. If you want Behest to pick a model based on capability or cost, set a project default and omit model in the request.
    • Organization header: OpenAI's OpenAI-Organization header is ignored — Behest uses project-scoped routing.
    • Assistants API: not yet supported. If you rely on it, stay on OpenAI directly and wire Behest for chat.completions paths. Follow the roadmap.
    • Rate limit headers: Behest sets its own x-ratelimit-* headers reflecting your tier, not OpenAI's org-level limits.

    Adding per-user auth incrementally

    You don't have to rewrite everything at once. Start with the drop-in baseURL swap above (server-to-server, API key in header). Later, move browsers to JWTs by:

    1. Add a /api/token endpoint (Node / Next.js / Python).
    2. Update your frontend to fetch a JWT and call Behest directly.
    3. Delete the server-side proxy.

    Net effect: lower latency, full streaming, no bandwidth through your server.


    See also

    Enterprise Token FinOps: Enforce hard budgets and attribute costs per session.

    Learn more