자체 API 키 사용(BYOK)
사용자마다 provider 크레딧 비용을 부담하고 싶지는 않을 것입니다. 사용자가 자체 API 키를 사용하도록 합니다.
키는 브라우저에 보관되므로 서버에 사용자 키를 저장할 걱정을 하지 않아도 됩니다.
릴레이는 키를 한 번의 호출에 사용한 후 잊습니다. 전송할 때마다 키는 JSON 본문이 아니라 x-byok-* 헤더에 담깁니다.
다음 네 단계를 수행합니다.
defineByok로 스토어를 생성합니다.byok.update로 UI에서 키를 저장합니다.- 같은 스토어를
useChat에 전달합니다. getByokKey로 릴레이에서 키를 읽습니다.
1. 스토어 생성
import { defineByok, defaultByokStorage } from "@tanstack/ai-client/byok";
export const byok = defineByok({
storage: defaultByokStorage(),
});
브라우저가 passkey를 지원하면 defaultByokStorage()가 passkey를 사용합니다. 지원하지 않으면 키가 이 탭의 메모리에만 보관됩니다.
2. 키 저장
자체 UI에서 byok.update("openai", value)를 호출합니다. 라이브러리는 대화상자를 제공하지 않습니다.
useByok(byok)는 저장된 키의 상태를 제공합니다.
import { useState } from "react";
import { useByok } from "@tanstack/ai-react";
import { byok } from "./byok";
export function KeyForm() {
const snapshot = useByok(byok);
const [value, setValue] = useState("");
const [error, setError] = useState("");
const status = snapshot.status.openai;
const last4 = status && "masked" in status ? status.masked : "";
return (
<form
onSubmit={(event) => {
event.preventDefault();
const next = value.trim();
if (!next) return;
void byok
.update("openai", next)
.then(() => {
setValue("");
setError("");
})
.catch((caught: unknown) => {
setError(
caught instanceof Error ? caught.message : "Could not save key",
);
});
}}
>
<input
type="password"
autoComplete="off"
value={value}
onChange={(event) => setValue(event.target.value)}
placeholder={last4 ? `Saved ${last4}` : "Paste a key"}
/>
<button type="submit" disabled={!value.trim()}>
Save
</button>
{error ? <p>{error}</p> : null}
</form>
);
}
ts-react-chat 예제에는 복사해서 사용할 수 있는 키 아이콘 팝업이 있습니다.
3. useChat으로 전송
같은 스토어를 전달합니다. forwardedProps.provider를 "openai"로 설정합니다. 그러면 클라이언트가 해당 키만 전송합니다.
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
import { byok } from "./byok";
export function Chat() {
const { sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
byok,
forwardedProps: { provider: "openai", model: "gpt-5.6" },
});
return (
<button
type="button"
disabled={isLoading}
onClick={() => {
void sendMessage("Hello");
}}
>
Send
</button>
);
}
provider를 설정하지 않으면 전송 시 오류가 발생합니다. 클라이언트는 저장된 모든 키를 첨부하지 않습니다.
내장 fetch 및 XHR 어댑터는 헤더를 POST 요청에 복사합니다.
사용자 지정 connect를 작성한다면 runContext.headers를 직접 복사합니다. 연결 어댑터를 참고합니다.
4. 릴레이에서 키 읽기
모든 API 라우트에서 다음과 같이 사용합니다.
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { createOpenaiChat } from "@tanstack/ai-openai";
import { openaiByok } from "@tanstack/ai-openai/byok";
import { byokMissing, getByokKey } from "@tanstack/ai/byok/server";
export async function POST(request: Request) {
const params = await chatParamsFromRequest(request);
const apiKey = getByokKey(request, openaiByok);
if (!apiKey) return byokMissing(openaiByok);
const stream = chat({
adapter: createOpenaiChat("gpt-5.6", apiKey),
messages: params.messages,
threadId: params.threadId,
runId: params.runId,
});
return toServerSentEventsResponse(stream);
}
openaiByok는 어댑터 기본 진입점이 아니라 @tanstack/ai-openai/byok에서 가져옵니다. /byok 파일은 브라우저에서 안전합니다. 기본 진입점은 provider SDK를 가져옵니다.
헤더가 우선합니다. 헤더가 비어 있으면 getByokKey가 환경에서 OPENAI_API_KEY를 읽습니다. 둘 다 비어 있으면 byokMissing이 401을 반환합니다.
주의: 원본 키를 로그에 기록하지 않습니다. 오류 문자열에는 maskKey를 사용합니다.
키를 붙여 넣고 메시지를 전송하면 릴레이가 해당 키로 OpenAI를 호출합니다.
릴레이에 이미 환경 키가 있는 경우
기본적으로 브라우저 키가 없으면 전송하지 않습니다. 릴레이에 환경 키가 있다면 byok.setServerCoverage(true)를 호출합니다.
byok.setServerCoverage(true);
그러면 붙여 넣은 키가 없어도 전송합니다. 릴레이는 환경 키를 사용합니다. 환경 키도 비어 있으면 릴레이가 byokMissing(401)을 반환합니다. 클라이언트는 snapshot.prompt를 설정합니다.
이미지, 오디오 및 OpenRouter
그 외의 경우는 다음과 같습니다.
- 이미지 및 오디오 POST는 같은 스토어를 사용합니다. 생성 훅을 참고합니다.
- OpenRouter는 OAuth로 키를 발급할 수 있습니다. OpenRouter로 로그인을 참고합니다.
- Lovable은
@tanstack/ai-lovable/byok의lovableByok를 사용합니다. Lovable AI Gateway를 참고합니다.