Docs

Call it like OpenAI or Anthropic.

Swap the base URL and key in any OpenAI-, Anthropic- or Responses-compatible client. Keys start with sk-.

01

Create an account

  1. Create an account or sign in.
  2. Open API keys and mint a key.
  3. Send it as Authorization: Bearer sk-…, or as x-api-key on the Anthropic endpoint.
02

Base URL

https://relay.169.58.242.32.nip.io/v1

Anthropic SDKs take the host without /v1; they add it themselves.

03

Endpoints

EndpointSpeaksAuth header
POST /v1/chat/completionsOpenAI Chat CompletionsAuthorization: Bearer
POST /v1/messagesAnthropic Messagesx-api-key or Authorization: Bearer
POST /v1/responsesOpenAI ResponsesAuthorization: Bearer
GET /v1/modelsModel listAuthorization: Bearer

Every model works on every endpoint: a Claude model can be called through the OpenAI or Responses API and the reply comes back in the shape that API expects.

04

List models

curl https://relay.169.58.242.32.nip.io/v1/models \
  -H "Authorization: Bearer sk-YOUR_KEY"

Use the ids exactly as listed, including the r/ prefix. All of them are on the models page. An unknown id returns 404.

05

Chat completions

curl https://relay.169.58.242.32.nip.io/v1/chat/completions \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "r/claude-sonnet-5-5",
    "messages": [{"role":"user","content":"hello"}]
  }'
06

Anthropic Messages

curl https://relay.169.58.242.32.nip.io/v1/messages \
  -H "x-api-key: sk-YOUR_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "r/claude-sonnet-5-5",
    "max_tokens": 1024,
    "messages": [{"role":"user","content":"hello"}]
  }'

max_tokens is required here, as in Anthropic's own API.

07

Responses API

curl https://relay.169.58.242.32.nip.io/v1/responses \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "r/claude-sonnet-5-5",
    "input": "hello",
    "max_output_tokens": 1024
  }'
08

SDKs

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.RELAYSTATION_KEY,
  baseURL: "https://relay.169.58.242.32.nip.io/v1",
});

const reply = await client.chat.completions.create({
  model: "r/claude-sonnet-5-5",
  messages: [{ role: "user", content: "hello" }],
});
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: process.env.RELAYSTATION_KEY,
  baseURL: "https://relay.169.58.242.32.nip.io",
});

// Long generations: stream them (see "Long requests").
const stream = client.messages.stream({
  model: "r/claude-sonnet-5-5",
  max_tokens: 32000,
  messages: [{ role: "user", content: "Write a long report." }],
});
const message = await stream.finalMessage();
09

Streaming

Set "stream": true. A request without stream gets one JSON reply, as in OpenAI's and Anthropic's own APIs.

curl -N https://relay.169.58.242.32.nip.io/v1/chat/completions \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "r/claude-sonnet-5-5",
    "stream": true,
    "stream_options": {"include_usage": true},
    "messages": [{"role":"user","content":"hello"}]
  }'
  • Chat and Responses streams use data: events and end with data: [DONE]. Messages streams use Anthropic's named events (message_start … message_stop).
  • While the model is busy thinking, the stream sends a keep-alive every 15 seconds: an event: ping on Messages, a : keep-alive comment elsewhere. Standard parsers ignore both.
  • Pass stream_options.include_usage to get the token counts in the last chunk.
  • A reply the model refuses to give ends with finish_reason: "content_filter" (stop_reason: "refusal" on Messages) and a short explanation in the text, never an empty reply.
10

Long requests

Long generations (extended thinking, large outputs) can run for many minutes. Two things keep them alive end to end:

  • Prefer streaming for anything over a minute. A stream is never idle, so no proxy or load balancer between you and us closes it, and you see output as it is produced. It also lets your client notice and recover from a dropped connection.
  • Plain JSON requests are supported too. If one is still running after about 20 seconds, the server answers 200 right away and sends a blank line every 10 seconds until the result is ready. Leading whitespace is valid JSON, so every client parses the body as usual. We have run non-streaming calls of about 400 seconds this way.

The status line goes out first on a long request, so a failure after that point cannot change it. It is reported two ways at once: an error body in your API's own shape (for example {"type":"error","error":{...}} on Messages), then the connection is closed abruptly. Official SDKs treat that as a network error: the Python SDKs retry it automatically, and the Node SDKs raise it. A bare HTTP client sees status 200 followed by a broken connection, so it should treat an incomplete body as a failure. Failures in the first 20 seconds, such as a bad key or an invalid request, return their normal status codes.

Streaming requests behave the same way for a slow start: if nothing has been produced after about 50 seconds, the stream is opened and kept alive with pings until the first real event, so a long wait before the first token no longer risks a proxy timeout.

If your client disconnects, generation stops and nothing further is billed.

11

Token counts

Usage matches what the model provider reports, counted once. prompt_tokens (input_tokens on Responses) includes cached tokens; the cached part is shown separately.

{
  "usage": {
    "prompt_tokens": 8432,
    "completion_tokens": 4,
    "total_tokens": 8436,
    "prompt_tokens_details": { "cached_tokens": 8423 }
  }
}
  • On /v1/messages the Anthropic layout is kept: input_tokens, cache_read_input_tokens and cache_creation_input_tokens are separate fields.
  • Thinking tokens count as output and are reported under reasoning_tokens.
  • Streaming usage arrives at the end of the stream. If you disconnect early, the prompt is charged and the output is an estimate.
  • A reply that comes back completely empty is not charged to your quota, and the server retries it on another account before giving up.
12

Errors

StatusMeaning
400The request could not be processed. Check the body and parameters.
401Missing or invalid API key.
403Your account is disabled, has no active plan, or the model needs a pay-as-you-go plan.
404Unknown model id.
429Rate limit or quota reached. Free accounts can watch an ad for more tokens.
499The client closed the connection.
502 / 503Upstream capacity is unavailable right now. Retry after a moment.
13

Limits

Each account has a plan with monthly tokens, monthly requests, and a per-minute rate limit. Some plans also carry a separate allowance per model family. Free accounts are never blocked: watch an ad to add more tokens. See the plans and your usage page.

14

Contributors

Donors start at /contribute, then connect API-key or OAuth accounts once an operator grants the Contributor role. Connected accounts fuel the shared pool, and each donor gets a personal API key scoped to the accounts they connected.