应用:上架并让用户连接
为在 UberLLM 上构建应用的人准备的两件事。这两件事的工作方式与在 OpenRouter 上相同,因此已经这样做的应用只需更改 base URL 即可。
- 上架。 发送两个 headers,您的应用就会显示在 uberllm.dev/apps,并根据其发送的 tokens 进行排名。 模型页面会列出使用它们最多的应用。
- 让用户连接。 将用户发送到 UberLLM,他们同意后,您的应用 就会在他们的账户上获得一个 API key。他们支付自己的使用费用;您永远 不会处理他们的支付详情。
让你的应用登上排行榜
将以下请求头添加到你的请求中(聊天、消息或媒体,任何端点):
| 请求头 | 作用 |
|---|---|
HTTP-Referer |
你的应用地址,例如 https://myapp.com。必填:这是识别你应用的依据。Referer 也可以。 |
X-Title |
你的应用名称,即用户将看到的名称。X-OpenRouter-Title 也可以。 |
X-OpenRouter-Categories |
可选。最多 2 个以下类别,用逗号分隔。 |
X-OpenRouter-App-Visibility |
可选。hidden 可让新应用不显示在公开排行榜上。 |
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"}]}'
使用 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',
},
});
如何统计:
- 只保留地址:
https://myapp.com/some/page?user=…会被存储为https://myapp.com。路径、查询字符串和登录信息会被丢弃。GitHub、 GitLab、Codeberg、Bitbucket 或 Hugging Face 链接会保留其owner/repo,因此 开源应用会获得自己的条目。 - 名称会被截断至 60 个字符。隐藏字符和控制字符会被移除。
- 第一个为某个应用发送流量的账户会设置其名称和 类别。其他账户的流量仍会计入该应用,但无法更改其名称。
- 你本机或网络上的地址(
localhost、127.0.0.1、 私有 IP、*.local)绝不会公开显示。 - 来自浏览器的请求会自行发送
Referer,因此即使没有请求头,网页应用也会被统计。添加X-Title来选择其名称。 - 排行榜会统计在过去 24 小时、7 天或 30 天内,按 token 计数的已响应请求。它每隔几分钟刷新一次。
类别: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。未知类别会被忽略。一个应用最多保留 10 个类别。
排行榜也是一个 API:GET https://uberllm.dev/api/v1/public/apps?period=day|week|month&category=<slug>。
连接 UberLLM (OAuth PKCE)
您的应用将在用户自己的账户下获取一个以您的应用命名的密钥。用户在授权时可以为其设置消费限额,并可以随时在他们的密钥页面]上关闭它。
1. 生成代码验证器和挑战码
验证器是一个您需要保留的随机字符串(43到128个字符)。挑战码是其 SHA-256, base64url-encoded(无填充)的结果。
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. 将用户发送到 UberLLM
https://uberllm.dev/auth?callback_url=https://myapp.com/uberllm/callback&code_challenge=<CHALLENGE>&code_challenge_method=S256
| 参数 | |
|---|---|
callback_url |
用户返回的地址。必须是https://;http://localhost(任何端口)允许用于开发。如果省略,则会在屏幕上显示代码,供用户粘贴到您的应用中。 |
code_challenge |
来自步骤1。必需。 |
code_challenge_method |
S256。必需。不接受plain。 |
key_label |
密钥的可选名称(默认:您的应用名称)。 |
state |
可选。随代码原样返回,以便您可以检查回复是否属于您发起的请求。 |
用户登录(或注册)后,会看到“您的应用希望使用您的 UberLLM 额度”,并选择允许或取消。选择允许后,他们将被发送到:
https://myapp.com/uberllm/callback?code=<CODE>&state=<STATE>
选择取消后,不会发送任何内容。
3. 用代码换取密钥
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"}'
回复是{"key": "ull_…", "key_id": "…", "name": "My App"}。将密钥用作Authorization: Bearer在https://api.uberllm.dev/v1上。该 endpoint 响应来自任何站点的浏览器请求 (CORS),因此它适用于 Web 应用。
| 状态 | 代码 | 含义 |
|---|---|---|
| 400 | invalid_code_challenge_method |
使用S256。 |
| 403 | invalid_code |
错误的代码或code_verifier,或代码已被使用。 |
| 403 | code_expired |
代码在10分钟内有效。从步骤2重新开始。 |
| 405 | method_not_allowed |
使用POST。 |
| 429 | rate_limited |
来自同一地址的尝试次数过多;请等待一分钟。 |
一个代码只能使用一次。错误的code_verifier也会消耗它,因此泄露的代码无法被猜测利用。
与 OpenRouter 的区别
code_challenge是必需的,并且只接受S256(OpenRouter 也接受plain或不带挑战码)。如果没有它,任何在浏览器历史记录或日志中看到代码的人都可能获取密钥。workspace_id/required_workspace_id不使用:一个 UberLLM 账户只有一个余额。