Dobby is freeeeeee — no key, no signup, no card. We don't read docs, we call the endpoint and publish what came back, including the 402s.

Free LLM API for agents: the /api/llm/up reference

Ask which free LLM endpoint answered recently, and get back the base URL, the model id, whether a key is needed and what a real call proved. No key, no signup. The list is ranked, so models[0] is the pick and the rest are your fallbacks.

Quick start

curl -s https://stillworks.supercapybara.com/api/llm/up

Any client that speaks the OpenAI chat-completions shape can use what comes back. Or get the three environment variables directly:

curl -s https://stillworks.supercapybara.com/api/llm/up?format=env

Walk the list until one answers

Keyless quotas are per IP, so the endpoint that answered us can refuse you. The pattern is a loop.

import json, urllib.request
from openai import OpenAI

shelf = json.load(urllib.request.urlopen(
    "https://stillworks.supercapybara.com/api/llm/up"))

for endpoint in shelf["models"]:            # already ranked, best first
    client = OpenAI(base_url=endpoint["openai_base_url"],
                    api_key="not-needed")
    try:
        reply = client.chat.completions.create(
            model=endpoint["model"],
            messages=[{"role": "user", "content": "hello"}],
        )
    except Exception:
        continue                           # your IP hit its quota — next
    print(endpoint["model"], reply.choices[0].message.content)
    break
import OpenAI from "openai";

const shelf = await fetch("https://stillworks.supercapybara.com/api/llm/up")
  .then((response) => response.json());

for (const endpoint of shelf.models) {      // already ranked, best first
  const client = new OpenAI({
    baseURL: endpoint.openai_base_url,
    apiKey: "not-needed",
  });
  try {
    const reply = await client.chat.completions.create({
      model: endpoint.model,
      messages: [{ role: "user", content: "hello" }],
    });
    console.log(endpoint.model, reply.choices[0].message.content);
    break;
  } catch { continue; }                    // your IP hit its quota — next
}

Parameters

ParameterValuesEffect
keynone (default), key, anynone returns only endpoints that answered with no Authorization header. key and any add providers that need your own key, each row labelled. auth is the older spelling and still works.
featuretools json_schema json_object vision image_out Repeat or comma-separate; all must hold. A feature is returned only where a real call proved it. A model never probed for it is not returned.
formatenvPlain text: OPENAI_BASE_URL, OPENAI_API_KEY, OPENAI_MODEL, taken from the top OpenAI-compatible row.

An unknown value is a 400 that names the valid ones, not an empty list: an empty list would read as "everything is down". At most 50 models are returned per call. Replies are Cache-Control: no-store and CORS is open, so a browser can call it too. GET /api/llm/<endpoint> returns one provider with its models and recent probes.

What each model carries

FieldMeaning
model, endpointThe model id to send, and the provider slug.
openai_base_urlWhat to give an OpenAI client as its base URL. Not always the provider's bare host.
authnone needs no Authorization header; bearer needs one of your own.
answered, recent_attempts, recent_successesHow many of the last seven days' chat probes answered.
last_ok, latency_msWhen it last answered, and how long that reply took.
proved[]Features a real call confirmed.
claimed_unproved[]Features the provider lists that we have not confirmed. Not a "no".
failed[]Features a call tried and did not get.
capabilitiesEach feature with the time it was checked and the error, where there was one.

A real row, as returned a moment ago

{
  "endpoint": "pollinations",
  "base_url": "https://text.pollinations.ai",
  "chat_path": "/openai",
  "auth_mode": "none",
  "model": "openai-fast",
  "tier": "anonymous",
  "chat_capable": true,
  "openai_compatible": true,
  "last_ok": "2026-10-12T07:46:10.272759Z",
  "latency_ms": 309,
  "capabilities": {
    "json_object": {
      "claimed": null,
      "supported": true,
      "checked_at": "2026-10-12T07:37:16.292697Z",
      "last_ok": "2026-10-12T07:37:16.292697Z"
    },
    "json_schema": {
      "claimed": null,
      "supported": true,
      "checked_at": "2026-10-12T07:36:45.926976Z",
      "last_ok": "2026-10-12T07:36:45.926976Z"
    },
    "tools": {
      "claimed": true,
      "supported": false,
      "checked_at": "2026-10-12T07:36:15.589728Z",
      "last_ok": null,
      "last_error": "answered without calling the tool"
    },
    "vision": {
      "claimed": false,
      "supported": null,
      "checked_at": "2026-10-12T07:37:46.589406Z",
      "last_ok": null,
      "last_error": "no colour named in: The API key used for this request has reached its budget. Please [raise the key budget](https://enter.pollinations.ai/edit-key?id=89idjaTSg2hI4YwZDuO8ZME5Ma6GrFps\u0026ref=agent_key_budget), then try again."
    }
  },
  "recent_attempts": 175,
  "recent_successes": 175,
  "openai_base_url": "https://text.pollinations.ai/openai",
  "auth": "none",
  "answered": "175/175",
  "proved": [
    "json_schema",
    "json_object"
  ],
  "claimed_unproved": [],
  "failed": [
    "tools"
  ]
}

The sponsored field

The response carries one more top-level key, sponsored: null when the slot is open, otherwise a paid placement with "sponsored": true, a label, a notice, the sponsor's provider, pitch and a tracked url, and a probe that is our real probe row for the sponsor's endpoint when it is on the shelf and null when it is not. It is never inside models and it never changes their order, so models[0] is still the pick. Ignore the key if you only want the ranked list. Sponsor the slot.

Errors and guarantees

An unknown key or feature answers 400, with the valid values in the body. An unknown endpoint slug answers 404. There is no guarantee: these are other people's free services, and an endpoint can add a key requirement or vanish between two probes. The payload says so in disclaimer, on every call.

Free LLM APIs with no key · The shelf · llms.txt