A lightweight, cache-optimized LLM registry that auto-updates from Artificial Analysis API daily and runs on Cloudflare Workers.
-
Build command: None
Deploy command:
bunx wrangler deployRoot directory:
/ -
Go to Storage & Databases > Workers KV
Copy the Namespace ID
-
Use your custom domain or delete that line to use
your-subdomain.workers.devChange the kv_namespace id to your created Namespace ID
-
See Environment Variables below
-
bunx wrangler deploy
bun run seed
wrangler kv key put --binding REGISTRY manifest --path ./data/manifest-preview.json
bun add @rttnd/llmimport { createRegistry } from '@rttnd/llm'
const registry = createRegistry({
baseUrl: 'https://llm.your-subdomain.workers.dev',
})
// Get all models
const { data: models } = await registry.getModels()
// Search for models
const { data: visionModels } = await registry.searchModels({
capability: 'vision',
minIq: 3,
})
// Get specific model details
const { data: model } = await registry.getModel('openai', 'gpt-5')See the Client Library Documentation for more details.
When your app already runs on Cloudflare Workers you can skip HTTP requests entirely and read the manifest straight from the shared KV namespace:
import { createKVRegistry } from '@rttnd/llm'
export default {
async fetch(request, env) {
const registry = createKVRegistry({ kv: env.REGISTRY })
const { data: models, error } = await registry.getModels()
if (error)
throw error
return Response.json(models)
},
}- Works inside Server Functions with meta frameworks that use Nitro.
- Override
manifestKeyif you store multiple manifests in the same namespace. - KV namespace IDs are safe to keep in your repo; they only work inside accounts that bind them via Cloudflare dashboard.
The registry includes some official overrides (e.g. Azure, Groq, Cerebras) on the server side, but you can also add your own providers and models in your app.
- Server-side (this repo): official overrides live in
packages/server/src/customand are merged into the manifest during the transform step. - Client-side (your app): you can take the manifest and apply your own overrides at runtime using the shared helper.
Example: add OpenRouter as a custom provider, and a couple of OpenRouter models, on the client:
import type { ModelOverride, ProviderOverride } from '@rttnd/llm'
import { applyOverrides, createRegistry } from '@rttnd/llm'
const registry = createRegistry({ baseUrl: 'https://llm.your-subdomain.workers.dev' })
const openRouterProvider: ProviderOverride = {
value: 'openrouter',
name: 'OpenRouter',
website: 'https://openrouter.ai/',
status: 'latest',
}
const openRouterModels: ModelOverride[] = [
{
provider: 'openrouter',
value: 'openai/gpt-5-pro',
inheritFrom: { provider: 'openai', value: 'gpt-5-1' }, // reuse AA metrics/capabilities
},
{
provider: 'openrouter',
value: 'openai/gpt-oss-20b:free',
inheritFrom: { provider: 'openai', value: 'gpt-oss-20b' },
pricing: { input: 0, output: 0, blended: 0 }, // mark as free on OpenRouter
},
]
const { data: manifest } = await registry.getManifest()
const { providers, models } = applyOverrides(manifest.providers, manifest.models, {
providers: [openRouterProvider],
models: openRouterModels,
})Returns the complete manifest with all providers and models.
Response:
{
"version": "v1.5c3e2c7d9f8a",
"etag": "\"5c3e2c7d9f8a6b4c1d0e9f8a6b4c1d0e\"",
"generatedAt": "2025-01-15T02:00:00.000Z",
"providers": [/* ... */],
"models": [/* ... */]
}Returns list of all providers.
Response:
[
{
"value": "openai",
"name": "OpenAI",
"keyPlaceholder": "sk-...",
"website": "https://platform.openai.com/api-keys",
"status": "latest"
}
]Returns models for a specific provider.
Response:
[
{
"id": "...",
"value": "gpt-5",
"provider": "openai",
"name": "GPT-5",
"alias": "GPT-5",
"iq": 5,
"speed": 3,
"pricing": {
"input": 1.25,
"output": 10,
"blended": 3.44
}
}
]Search models with query parameters rather than downloading the full manifest.
Query Parameters:
name- Filter by partial match across name, value, or alias. Accepts a single string or an array of stringsprovider- Restrict results to one or more provider slugscapability- Require a capability (text,vision,reasoning,toolUse,json,audio)minIq- Minimum IQ score (0-5)minSpeed- Minimum speed score (0-5)status- Filter by one or more statuses (latest,preview,all)
Response:
[
{
"id": "...",
"value": "gpt-5",
"provider": "openai",
"name": "GPT-5",
"iq": 5,
"speed": 3,
"capabilities": {
"vision": true,
"text": true
}
}
]Returns version info for update checks.
Response:
{
"version": "v1.5c3e2c7d9f8a",
"etag": "\"5c3e2c7d9f8a6b4c1d0e9f8a6b4c1d0e\"",
"generatedAt": "2025-01-15T02:00:00.000Z"
}Health check endpoint.
Response:
{
"status": "ok"
}Set these in Cloudflare dashboard or via wrangler secret:
AA_API_KEY- Your Artificial Analysis API key (https://artificialanalysis.ai/documentation)ALLOWED_ORIGINS- Comma-separated CORS origins (e.g.,https://app.com,https://*.example.com), use*to allow all origins.
The default cron schedule is daily at 2 AM UTC in wrangler.toml:
[triggers]
crons = [ "0 2 * * *" ]- Cloudflare Cron fetches from Artificial Analysis API daily
- Transform converts AA data to normalized model format
- Store saves manifest to Workers KV
- Serve manifest from Worker
- GitHub Action publishes human-readable snapshot
- Me: I add the missing data to the registry that's missing from the AA API
IQ Score (0-5):
- 5: Intelligence Index ≥ 65
- 4: Intelligence Index ≥ 55
- 3: Intelligence Index ≥ 45
- 2: Intelligence Index ≥ 35
- 1: Intelligence Index ≥ 25
- 0: < 25
Speed Score (0-5):
- 5: ≥ 300 tokens/sec
- 4: ≥ 200 tokens/sec
- 3: ≥ 100 tokens/sec
- 2: ≥ 50 tokens/sec
- 1: ≥ 25 tokens/sec
- 0: < 25
MIT
Data from Artificial Analysis