NewPoint any agent at UberLLM — one key, every model, list price or less.

Apps: get listed and let users connect

Two things for people who build apps on UberLLM. Both work the same way as on OpenRouter, so an app that already does them only changes the base URL.

  1. Get listed. Send two headers and your app shows up on uberllm.dev/apps, ranked by the tokens it sends. Model pages list the apps that use them most.
  2. Let users connect. Send users to UberLLM, they say yes, and your app gets an API key on their account. They pay for their own usage; you never handle their payment details.

Get listed on the apps ranking

Add these headers to your requests (chat, messages or media, any endpoint):

Header What it does
HTTP-Referer Your app's address, e.g. https://myapp.com. Required: it is what identifies your app. Referer works too.
X-Title Your app's name as people should see it. X-OpenRouter-Title works too.
X-OpenRouter-Categories Optional. Up to 2 of the categories below, comma-separated.
X-OpenRouter-App-Visibility Optional. hidden keeps a new app off the public ranking.
curl https://api.uberllm.dev/v1/chat/completions \
  -H "Authorization: Bearer $UBERLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "HTTP-Referer: https://myapp.com" \
  -H "X-Title: My App" \
  -H "X-OpenRouter-Categories: cli-agent" \
  -d '{"model":"deepseek/deepseek-v4-flash","messages":[{"role":"user","content":"Hello"}]}'

With the OpenAI SDK:

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://api.uberllm.dev/v1',
  apiKey: process.env.UBERLLM_API_KEY,
  defaultHeaders: {
    'HTTP-Referer': 'https://myapp.com',
    'X-Title': 'My App',
  },
});

How it is counted:

  • Only the address is kept: https://myapp.com/some/page?user=… is stored as https://myapp.com. Paths, query strings and logins are dropped. A GitHub, GitLab, Codeberg, Bitbucket or Hugging Face link keeps its owner/repo, so open-source apps get their own entry.
  • The name is cut to 60 characters. Hidden and control characters are removed.
  • The first account that sends traffic for an app sets its name and categories. Other accounts' traffic still counts toward the app, but cannot rename it.
  • Addresses on your own machine or network (localhost, 127.0.0.1, private IPs, *.local) are never shown publicly.
  • Requests from browsers send Referer on their own, so a web app is counted even without the headers. Add X-Title to choose its name.
  • The ranking counts requests that were answered, by tokens, over the last 24 hours, 7 days or 30 days. It refreshes every few minutes.

Categories: cli-agent, ide-extension, cloud-agent, programming-app, native-app-builder, creative-writing, video-gen, image-gen, audio-gen, writing-assistant, general-chat, personal-agent, legal, roleplay, game. Unknown ones are ignored. An app keeps up to 10.

The ranking is also an API: GET https://uberllm.dev/api/v1/public/apps?period=day|week|month&category=<slug>.

Connect UberLLM (OAuth PKCE)

Your app gets a key on the user's own account, named after your app. The user can set a spending limit for it while allowing it, and can turn it off any time on their keys page.

1. Make a code verifier and challenge

The verifier is a random string you keep (43 to 128 characters). The challenge is its SHA-256, base64url-encoded without padding.

function base64url(bytes) {
  return btoa(String.fromCharCode(...new Uint8Array(bytes)))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

const codeVerifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const codeChallenge = base64url(
  await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier)),
);
sessionStorage.setItem('uberllm_verifier', codeVerifier);

2. Send the user to UberLLM

https://uberllm.dev/auth?callback_url=https://myapp.com/uberllm/callback&code_challenge=<CHALLENGE>&code_challenge_method=S256
Parameter
callback_url Where the user comes back to. Must be https://; http://localhost (any port) is allowed for development. Leave it out to show the code on screen for the user to paste into your app.
code_challenge From step 1. Required.
code_challenge_method S256. Required. plain is not accepted.
key_label Optional name for the key (default: your app's name).
state Optional. Sent back unchanged with the code, so you can check the reply belongs to the request you started.

The user signs in (or signs up), sees "Your app wants to use your UberLLM credit", and chooses Allow or Cancel. On Allow they are sent to:

https://myapp.com/uberllm/callback?code=<CODE>&state=<STATE>

On Cancel nothing is sent anywhere.

3. Trade the code for a key

const code = new URLSearchParams(location.search).get('code');
const res = await fetch('https://uberllm.dev/api/v1/auth/keys', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    code,
    code_verifier: sessionStorage.getItem('uberllm_verifier'),
    code_challenge_method: 'S256',
  }),
});
const { key } = await res.json(); // ull_…  Store it like a password.
curl https://uberllm.dev/api/v1/auth/keys \
  -H "Content-Type: application/json" \
  -d '{"code":"<CODE>","code_verifier":"<VERIFIER>","code_challenge_method":"S256"}'

The reply is {"key": "ull_…", "key_id": "…", "name": "My App"}. Use the key as Authorization: Bearer on https://api.uberllm.dev/v1. The endpoint answers browser requests from any site (CORS), so it works from a web app.

Status Code Meaning
400 invalid_code_challenge_method Use S256.
403 invalid_code Wrong code or code_verifier, or the code was already used.
403 code_expired Codes work for 10 minutes. Start again from step 2.
405 method_not_allowed Use POST.
429 rate_limited Too many tries from one address; wait a minute.

A code works once. A wrong code_verifier uses it up too, so a code that leaked cannot be guessed against.

Differences from OpenRouter

  • code_challenge is required and only S256 is accepted (OpenRouter also takes plain or no challenge). Without it, anyone who sees the code in a browser history or log could take the key.
  • workspace_id / required_workspace_id are not used: an UberLLM account has one balance.