Relay

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 & pathAuthPurpose
POST /v1/registration-tokensBearer rly-Issue a key-connect token
POST /v1/chat/completionsBearer rly- + X-Relay-UserChat (OpenAI-compatible)
POST /v1/embeddingsBearer rly- + X-Relay-UserEmbeddings
GET /api/healthService status

Base URL: https://vault.relayservice.im. Set X-Relay-Paid: false to mark a free-tier user.