> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lyceum.technology/llms.txt
> Use this file to discover all available pages before exploring further.

# Coding Agents

> Use Lyceum models in Claude Code, Cursor, Cline, opencode, Zed, aider and other AI coding tools

export const LyceumConfig = ({template, filename}) => {
  const EVENT = "lyceum-setup-change";
  const [state, setState] = useState({
    key: "",
    model: "z-ai/glm-5.2"
  });
  const [copied, setCopied] = useState(false);
  useEffect(() => {
    const read = () => {
      const store = window.__lyceumSetup;
      if (store) setState({
        key: store.key,
        model: store.model
      }); else {
        try {
          setState({
            key: localStorage.getItem("lyceum_key") || "",
            model: localStorage.getItem("lyceum_model") || "z-ai/glm-5.2"
          });
        } catch (e) {}
      }
    };
    read();
    window.addEventListener(EVENT, read);
    return () => window.removeEventListener(EVENT, read);
  }, []);
  const source = Array.isArray(template) ? template.join("\n") : template;
  const text = source.split("{{KEY}}").join(state.key || "lk_your_api_key").split("{{MODEL}}").join(state.model).split("{{BASE_URL}}").join("https://api.lyceum.technology/api/v2/external/serverless").split("{{ANTHROPIC_URL}}").join("https://api.lyceum.technology/api/v2/external/claude");
  const copy = () => {
    navigator.clipboard.writeText(text);
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };
  return <div style={{
    border: "1px solid rgba(128,128,128,0.35)",
    borderRadius: "10px",
    margin: "1.2rem 0",
    overflow: "hidden"
  }}>
      <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "6px 10px 6px 14px",
    borderBottom: "1px solid rgba(128,128,128,0.25)",
    fontSize: "12px",
    opacity: 0.8
  }}>
        <code>{filename || "config"}</code>
        <button style={{
    padding: "3px 10px",
    borderRadius: "6px",
    border: "1px solid rgba(128,128,128,0.4)",
    background: "transparent",
    color: "inherit",
    fontSize: "12px",
    cursor: "pointer"
  }} onClick={copy}>
          {copied ? "Copied" : "Copy"}
        </button>
      </div>
      <pre style={{
    margin: 0,
    padding: "14px",
    overflowX: "auto",
    fontSize: "12.5px",
    lineHeight: 1.55
  }}>
        <code>{text}</code>
      </pre>
    </div>;
};

export const LyceumSetup = ({showTest}) => {
  const BASE = "https://api.lyceum.technology/api/v2/external/serverless";
  const EVENT = "lyceum-setup-change";
  const DEFAULT_MODELS = ["z-ai/glm-5.2", "moonshotai/kimi-k2.7-code", "moonshotai/kimi-k3", "moonshotai/kimi-k2.6", "deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-flash-0731", "minimax/minimax-m3"];
  const [state, setState] = useState({
    key: "",
    model: "z-ai/glm-5.2"
  });
  const [models, setModels] = useState(DEFAULT_MODELS);
  const [status, setStatus] = useState(null);
  const [busy, setBusy] = useState(false);
  useEffect(() => {
    const store = window.__lyceumSetup || ({
      key: "",
      model: "z-ai/glm-5.2"
    });
    try {
      const k = localStorage.getItem("lyceum_key");
      const m = localStorage.getItem("lyceum_model");
      if (k) store.key = k;
      if (m) store.model = m;
    } catch (e) {}
    window.__lyceumSetup = store;
    setState({
      key: store.key,
      model: store.model
    });
    if (store.key.indexOf("lk_") === 0) fetchModels(store.key);
  }, []);
  const publish = next => {
    window.__lyceumSetup = next;
    try {
      localStorage.setItem("lyceum_key", next.key);
      localStorage.setItem("lyceum_model", next.model);
    } catch (e) {}
    setState(next);
    window.dispatchEvent(new CustomEvent(EVENT));
  };
  const fetchModels = async key => {
    try {
      const r = await fetch(BASE + "/models", {
        headers: {
          Authorization: "Bearer " + key
        }
      });
      if (!r.ok) return;
      const d = await r.json();
      const list = (d.data || d).map(m => m.id).sort();
      if (list.length) setModels(list);
    } catch (e) {}
  };
  const onKey = e => {
    const key = e.target.value.trim();
    publish({
      key: key,
      model: state.model
    });
    setStatus(null);
    if (key.indexOf("lk_") === 0) fetchModels(key);
  };
  const onModel = e => publish({
    key: state.key,
    model: e.target.value
  });
  const test = async () => {
    if (!state.key) {
      setStatus({
        ok: false,
        text: "Enter an API key first."
      });
      return;
    }
    setBusy(true);
    setStatus(null);
    const started = Date.now();
    try {
      const r = await fetch(BASE + "/chat/completions", {
        method: "POST",
        headers: {
          Authorization: "Bearer " + state.key,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          model: state.model,
          messages: [{
            role: "user",
            content: "Reply with a short one-sentence greeting."
          }],
          max_tokens: 512
        })
      });
      const d = await r.json();
      if (!r.ok) {
        setStatus({
          ok: false,
          text: d.detail || d.error && d.error.message || "HTTP " + r.status
        });
      } else {
        const choice = d.choices && d.choices[0];
        const msg = choice && choice.message && choice.message.content || "(empty response)";
        setStatus({
          ok: true,
          text: msg.trim(),
          meta: state.model + " · " + (Date.now() - started) + " ms · " + (d.usage && d.usage.total_tokens || "?") + " tokens"
        });
      }
    } catch (e) {
      setStatus({
        ok: false,
        text: "Request failed: " + e.message
      });
    }
    setBusy(false);
  };
  const field = {
    width: "100%",
    padding: "7px 10px",
    borderRadius: "6px",
    border: "1px solid rgba(128,128,128,0.4)",
    background: "transparent",
    color: "inherit",
    fontSize: "13px",
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace"
  };
  const label = {
    display: "block",
    fontSize: "11px",
    textTransform: "uppercase",
    letterSpacing: "0.06em",
    opacity: 0.65,
    marginBottom: "4px"
  };
  return <div style={{
    border: "1px solid rgba(128,128,128,0.35)",
    borderRadius: "10px",
    padding: "14px",
    margin: "1.2rem 0"
  }}>
      <div style={{
    display: "flex",
    gap: "12px",
    flexWrap: "wrap"
  }}>
        <div style={{
    flex: "2 1 260px"
  }}>
          <span style={label}>Lyceum API key</span>
          <input type="password" style={field} placeholder="lk_your_api_key" value={state.key} onChange={onKey} autoComplete="off" spellCheck="false" />
        </div>
        <div style={{
    flex: "1 1 200px"
  }}>
          <span style={label}>Model</span>
          <select style={field} value={state.model} onChange={onModel}>
            {models.map(m => <option key={m} value={m}>
                {m}
              </option>)}
          </select>
        </div>
        {showTest && <div style={{
    display: "flex",
    alignItems: "flex-end"
  }}>
          <button style={{
    padding: "7px 14px",
    borderRadius: "6px",
    border: "1px solid rgba(128,128,128,0.4)",
    background: "transparent",
    color: "inherit",
    fontSize: "13px",
    cursor: "pointer",
    whiteSpace: "nowrap"
  }} onClick={test} disabled={busy}>
            {busy ? "Testing…" : "Test connection"}
          </button>
        </div>}
      </div>

      <p style={{
    fontSize: "12px",
    opacity: 0.6,
    margin: "10px 0 0"
  }}>
        Your key stays in this browser (local storage) and is sent only to{" "}
        <code>api.lyceum.technology</code>.{" "}
        {showTest ? "Test connection sends one real, billed chat request." : "Every config block on this page fills in automatically."}{" "}
        Don't do this on a shared machine.
      </p>

      {status && <div style={{
    marginTop: "12px",
    padding: "10px 12px",
    borderRadius: "6px",
    border: "1px solid " + (status.ok ? "rgba(34,160,90,0.5)" : "rgba(210,70,70,0.5)"),
    fontSize: "13px"
  }}>
          <strong>{status.ok ? "Connected" : "Failed"}</strong>
          {status.meta && <span style={{
    opacity: 0.6,
    fontSize: "12px"
  }}> ({status.meta})</span>}
          <div style={{
    marginTop: "6px",
    whiteSpace: "pre-wrap"
  }}>{status.text}</div>
        </div>}
    </div>;
};

Every AI coding tool that speaks the OpenAI or Anthropic API works with Lyceum. You point it at our endpoint, give it your API key, and pick a model.

## Set up once

Enter your key and pick a model below. Every config block across these pages fills itself in with your real values, and the button runs a live request so you know the key works before you paste anything into an editor.

<LyceumSetup />

Get a key from the [dashboard](https://dashboard.lyceum.technology). See [API keys](/docs/configuration/api-keys) for scopes and rotation.

## Which endpoint

Two endpoints, depending on what the tool speaks:

|                          | Endpoint                                                   | Auth header                  |
| ------------------------ | ---------------------------------------------------------- | ---------------------------- |
| **OpenAI-compatible**    | `https://api.lyceum.technology/api/v2/external/serverless` | `Authorization: Bearer lk_…` |
| **Anthropic-compatible** | `https://api.lyceum.technology/api/v2/external/claude`     | `x-api-key: lk_…`            |

Most tools want the first. Claude Code wants the second.

## Pick your tool

<CardGroup cols={3}>
  <Card title="Claude Code" href="/docs/inference/claude-code">
    CLI and VS Code extension
  </Card>

  <Card title="Cursor" href="/docs/inference/cursor">
    Model override in settings
  </Card>

  <Card title="opencode" href="/docs/agents/opencode">
    Custom provider block
  </Card>

  <Card title="GitHub Copilot" href="/docs/agents/github-copilot">
    Custom endpoint in VS Code
  </Card>

  <Card title="Cline" href="/docs/agents/cline">
    VS Code agent
  </Card>

  <Card title="Roo Code" href="/docs/agents/roo-code">
    VS Code agent
  </Card>

  <Card title="Kilo Code" href="/docs/agents/kilo-code">
    VS Code agent
  </Card>

  <Card title="Zed" href="/docs/agents/zed">
    Editor with built-in agent
  </Card>

  <Card title="aider" href="/docs/agents/aider">
    Terminal pair programmer
  </Card>

  <Card title="goose" href="/docs/agents/goose">
    Block's terminal agent
  </Card>

  <Card title="Vercel AI SDK" href="/docs/agents/vercel-ai-sdk">
    Build your own agent
  </Card>
</CardGroup>

## Verify before you configure

If a tool misbehaves, check the API directly first. This is the same request every tool above makes:

<LyceumConfig
  filename="curl"
  template={[
"curl {{BASE_URL}}/chat/completions \\",
"  -H \"Authorization: Bearer {{KEY}}\" \\",
"  -H \"Content-Type: application/json\" \\",
"  -d '{",
'    "model": "{{MODEL}}",',
'    "messages": [{"role": "user", "content": "Say hello"}]',
"  }'",
]}
/>

List every model your key can reach:

<LyceumConfig
  filename="curl"
  template={[
"curl {{BASE_URL}}/models \\",
'  -H "Authorization: Bearer {{KEY}}"',
]}
/>

## Choosing a model

| Model                             | Good for                         |
| --------------------------------- | -------------------------------- |
| `moonshotai/kimi-k2.7-code`       | Agentic coding, tool-heavy loops |
| `z-ai/glm-5.2`                    | Strong general reasoning         |
| `moonshotai/kimi-k3`              | Long-context work                |
| `deepseek/deepseek-v4-pro`        | Reasoning at lower cost          |
| `deepseek/deepseek-v4-flash-0731` | Fast edits, autocomplete         |
| `minimax/minimax-m3`              | Cheap, high throughput           |

Full list at [active models](/docs/inference/active-models), or call `/models` above.

<Note>
  Model IDs are case-insensitive. Tools that reject `/` in model names accept the slash-free form, so `z-ai-glm-5.2` resolves to `z-ai/glm-5.2`.
</Note>

## Context windows

The `/models` response does not report context window sizes. Tools that ask you to set **Context Window** or **Max Output Tokens** by hand (Cline, Roo Code, Kilo Code, Zed) need those numbers from the [active models](/docs/inference/active-models) page.
