Relay — BYOK AI infrastructure
Your users bring their own OpenAI / Anthropic / Google API keys. You never see them. Relay stores keys encrypted and proxies AI calls on each user's behalf — so you can offer AI without paying for everyone's tokens.
Quickstart
Install the SDK and the official OpenAI client:
npm install @relayservice/sdk openai
Call AI on behalf of one of your users — the same code you already write with OpenAI:
import { Relay } from "@relayservice/sdk";
const relay = new Relay({ key: process.env.RELAY_KEY }); // your rly- key
const ai = relay.openai({ user: "jieun_123" });
const res = await ai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hello!" }],
});Get your rly- key from the Relay console.
SDK
The SDK has two parts: a small Relay-specific method for onboarding users, and a pre-configured OpenAI client for AI calls (so there's nothing new to learn).
const relay = new Relay({ key: "rly-..." });
// Relay-specific: issue a token so a user can connect their key
await relay.createRegistrationToken({ user: "jieun_123" });
// AI calls: a normal OpenAI client, bound to that user
const ai = relay.openai({ user: "jieun_123" });Registration tokens (your backend)
When a user wants to connect their key, ask Relay for a short-lived, single-use token. Omit provider to let the user pick (OpenAI / Anthropic / Google) in the widget, or set it to lock one provider.
const { registrationToken } = await relay.createRegistrationToken({
user: "jieun_123",
// provider: "openai", // optional
});Or call the HTTP API directly:
curl -X POST https://vault.relayservice.im/v1/registration-tokens \
-H "Authorization: Bearer rly-..." \
-H "Content-Type: application/json" \
-d '{"endUserLabel":"jieun_123"}'Key widget (your frontend)
Drop in the widget. The user pastes their key — it goes straight to Relay over TLS, never your server. The widget renders in a Shadow DOM (your CSS can't break it), is responsive, and themeable.
<div id="relay-widget"></div>
<script src="https://vault.relayservice.im/widget.js"></script>
<script>
Relay.mount('#relay-widget', {
registrationToken, // from your backend
theme: 'light', // 'light' | 'dark'
accentColor: '#635bff',
locale: 'en', // 'en' | 'ko'
onSuccess: (r) => console.log('connected', r.provider),
});
</script>On native apps, show a WebView pointing at a page that mounts the widget.
Calling AI
Once a user has connected a key, use the OpenAI client for that user. The model name picks the provider — gpt-*, claude-*, gemini-* — and Relay translates everything to OpenAI format.
const ai = relay.openai({ user: "jieun_123" });
// streaming works too
const stream = await ai.chat.completions.create({
model: "claude-haiku-4-5",
messages: [{ role: "user", content: "Write a haiku" }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}Embeddings
Same client. OpenAI (text-embedding-*) and Google (gemini-embedding-001) are supported.
const e = await ai.embeddings.create({
model: "text-embedding-3-small",
input: "search this",
});Errors & tracing
Every response carries a Relay request id — useful for support. The SDK's token methods throw RelayError (with .status and .requestId); AI calls throw the OpenAI client's errors.
const { data, response } = await ai.chat.completions
.create({ model: "gpt-4o-mini", messages })
.withResponse();
console.log(response.headers.get("x-relay-request-id"));API reference
| Method & path | Auth | Purpose |
|---|---|---|
| POST /v1/registration-tokens | Bearer rly- | Issue a key-connect token |
| POST /v1/chat/completions | Bearer rly- + X-Relay-User | Chat (OpenAI-compatible) |
| POST /v1/embeddings | Bearer rly- + X-Relay-User | Embeddings |
| GET /api/health | — | Service status |
Base URL: https://vault.relayservice.im. Set X-Relay-Paid: false to mark a free-tier user.