Lumen
Lumen 2.2.0 has been released. For up-to-date information check our Teams Channel - LLMs as a service

API Reference

Developer note: This guide is for people writing code or integrating with Lumen programmatically. If you just want to use the chat interface, you don't need this page.

Lumen exposes an OpenAI-compatible REST API at /v1/. Any tool or library that works with OpenAI can be pointed at your Lumen instance with minimal changes.

Base URL and Authentication

Replace https://lumen.ncsa.illinois.edu with your institution's Lumen URL. All requests require an Authorization header:

Authorization: Bearer sk_your_api_key_here

See Profile → API Keys to create a key.

Endpoints

Method Path Description
GET /v1/models List available models
GET /v1/models/<id> Retrieve details for a single model
POST /v1/models/<id>/acknowledge Acknowledge a model that requires consent
GET /v1/usage Get cumulative usage for the bearer API key
POST /v1/chat/completions Send a chat message and receive a reply
POST /v1/completions Legacy text-completion endpoint (prefer /v1/chat/completions)
POST /v1/audio/transcriptions Transcribe audio to text (speech-to-text)
POST /v1/audio/translations Translate audio into English text

API Key Usage

curl https://lumen.ncsa.illinois.edu/v1/usage \
  -H "Authorization: Bearer sk_your_api_key_here"

Returns cumulative usage for the specific key in the Authorization header, including requests, input and output tokens, their total, audio seconds, coins spent (cost), coins still available (coins_available), and the UTC time the key was last used:

{
  "requests": 12,
  "input_tokens": 2400,
  "output_tokens": 600,
  "total_tokens": 3000,
  "audio_seconds": 0,
  "cost": 0.012345,
  "coins_available": 83.25,
  "last_used_at": "2026-09-28T14:00:00Z"
}

last_used_at is null for a key that has never been used. coins_available is the coin balance of the account that owns the key, so it is shared with that account's other keys and browser chat; -2 means the account has an unlimited coin pool and null that the account has no coin pool (none configured for it, its groups or the global default) or a blocked one (Max Coins 0). This endpoint does not add to the request count. It does not include usage from other keys, browser chat, or earlier periods before the key was created.


List Models

curl https://lumen.ncsa.illinois.edu/v1/models \
  -H "Authorization: Bearer sk_your_api_key_here"

Returns a list of model IDs you can use in chat completion requests. Aliases of renamed models are listed as additional IDs whose parent field names the canonical model; standalone models have parent: null.

Each model may carry consent-state fields for the calling account:

Field Meaning
tags The acknowledgment requirements still open for your account: needs_ack, early_access, both, or []. A non-empty tags means you cannot use the model until you acknowledge it (see below).
acknowledged_at Timestamp of your most recent acknowledgment, null while any requirement is still open.
notice The model's acknowledgment / early-access notice text, present only while a requirement is open so you can read it before acknowledging.

Models you are blocked from (owned by someone else without a grant, disabled, or expired) are omitted entirely.

By default the list also omits models you cannot use until you acknowledge them (any needs_ack or early_access requirement still open), so OpenAI-compatible clients only see models they can call. Every listed model then has tags: [].

Add ?include_unacknowledged=true to also list those models, each with its open tags and its notice. The parameter also accepts 1 or yes (case-insensitive); any other value, including an empty one, keeps them hidden.

curl "https://lumen.ncsa.illinois.edu/v1/models?include_unacknowledged=true" \
  -H "Authorization: Bearer sk_your_api_key_here"

Retrieve a Model

curl https://lumen.ncsa.illinois.edu/v1/models/qwen-coder-cc \
  -H "Authorization: Bearer sk_your_api_key_here"

Returns the same metadata as the list, for one model. A model specified by alias returns its metadata under the requested alias with parent naming the canonical model.

GET /v1/models/<id> returns 404 for blocked, disabled, expired, deleted, or unknown models. A model that merely awaits your acknowledgment is returned so you can read its notice before acknowledging.


Acknowledge a Model

Models that require a one-time acknowledgment (needs_ack) or early-access acknowledgment (early_access) block use until you consent. Over the API, acknowledge once per model:

curl -X POST https://lumen.ncsa.illinois.edu/v1/models/qwen-coder-cc/acknowledge \
  -H "Authorization: Bearer sk_your_api_key_here"

<id> may be an alias or the canonical name (including names containing slashes, such as org/model). Consent is recorded per account (shared across all of its API keys, matching the web UI), and one call satisfies every currently-open requirement — needs_ack and/or early_access. The endpoint is idempotent: repeating it returns the same 200 shape, so callers can safely retry after a timeout, even when two retries land at once. Acking a blocked, disabled, expired, deleted, or unknown model returns 404.

Response (200):

{
  "id": "qwen-coder-cc",
  "tags": [],
  "acknowledged_at": "2026-10-05T16:00:00Z"
}
  • tags is [] after a successful acknowledgment. If the model later gains a new requirement (for example it becomes early access), tags reports it and you must acknowledge once more.
  • notice is included only while tags is non-empty, matching GET /v1/models/<id>; read the notice there before acknowledging. A model with no requirements returns tags: [] as a no-op.
  • GET /v1/models/<id>/acknowledge returns 405 with Allow: POST. The /acknowledge suffix is reserved: config validation rejects a model name or alias ending in /acknowledge, so POST /v1/models/<id>/acknowledge always means this endpoint.
  • If your account cannot use the model, an un-acknowledged consume call above returns 403 with code: "consent_required" and a message pointing at this endpoint.

Chat Completions

Basic request (curl)

curl https://lumen.ncsa.illinois.edu/v1/chat/completions \
  -H "Authorization: Bearer sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o",
    "messages": [
      {"role": "user", "content": "Explain quantum entanglement in plain English"}
    ]
  }'

Python (openai SDK)

Install the library once: pip install openai

from openai import OpenAI

client = OpenAI(
    api_key="sk_your_api_key_here",
    base_url="https://lumen.ncsa.illinois.edu/v1"
)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "Explain quantum entanglement in plain English"}
    ]
)

print(response.choices[0].message.content)

Python — multi-turn conversation

from openai import OpenAI

client = OpenAI(
    api_key="sk_your_api_key_here",
    base_url="https://lumen.ncsa.illinois.edu/v1"
)

history = []

def chat(user_message):
    history.append({"role": "user", "content": user_message})
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=history
    )
    reply = response.choices[0].message.content
    history.append({"role": "assistant", "content": reply})
    return reply

print(chat("What is a transformer model?"))
print(chat("How does the attention mechanism work?"))

Python — streaming responses

from openai import OpenAI

client = OpenAI(
    api_key="sk_your_api_key_here",
    base_url="https://lumen.ncsa.illinois.edu/v1"
)

with client.chat.completions.stream(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Write a short poem about data science"}]
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
print()

Python — system prompt

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "You are a helpful research assistant who always cites sources."},
        {"role": "user", "content": "Summarize recent advances in protein folding"}
    ]
)
print(response.choices[0].message.content)

Node.js (openai SDK)

Install the library once: npm install openai

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "sk_your_api_key_here",
  baseURL: "https://lumen.ncsa.illinois.edu/v1",
});

const response = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [
    { role: "user", content: "Explain quantum entanglement in plain English" }
  ],
});

console.log(response.choices[0].message.content);

Node.js — streaming

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "sk_your_api_key_here",
  baseURL: "https://lumen.ncsa.illinois.edu/v1",
});

const stream = await client.chat.completions.stream({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Write a haiku about machine learning" }],
});

for await (const chunk of stream) {
  const text = chunk.choices[0]?.delta?.content ?? "";
  process.stdout.write(text);
}

Using Lumen as a drop-in replacement for OpenAI

If you have existing code that uses the OpenAI API, you can redirect it to Lumen by changing two values:

# Before (standard OpenAI)
client = OpenAI(api_key="sk-...")

# After (Lumen)
client = OpenAI(
    api_key="sk_your_lumen_key",
    base_url="https://lumen.ncsa.illinois.edu/v1"
)

Everything else — model names, message format, streaming, tool calls — works identically as long as the model you request is available in your Lumen instance.


Audio Transcriptions and Translations

Lumen proxies OpenAI-compatible speech-to-text endpoints for backends that support them (e.g. Qwen3-ASR). transcriptions returns text in the spoken language; translations returns English text. Both accept a multipart/form-data upload, not JSON.

Form field Required Applies to Description
file yes both The audio file to transcribe/translate
model yes both A Lumen model name backed by a speech-to-text endpoint
language no transcriptions ISO-639-1 code of the input language (improves accuracy)
prompt no both Optional text to guide the model's style or continue prior audio
response_format no both json (default), verbose_json, text, srt, or vtt
temperature no both Sampling temperature

curl

curl https://lumen.ncsa.illinois.edu/v1/audio/transcriptions \
  -H "Authorization: Bearer sk_your_api_key_here" \
  -F file=@speech.flac \
  -F model=qwen3-asr \
  -F response_format=verbose_json

Python (openai SDK)

from openai import OpenAI

client = OpenAI(base_url="https://lumen.ncsa.illinois.edu/v1", api_key="sk_your_api_key_here")

with open("speech.flac", "rb") as f:
    result = client.audio.transcriptions.create(model="qwen3-asr", file=f)
print(result.text)

Billing

Speech-to-text models are billed per hour of audio. Lumen reads the upstream usage object:

  • {"type": "duration", "seconds": N} → cost = N / 3600 × audio_cost_per_hour (configured per model).
  • {"type": "tokens", ...} (e.g. gpt-4o-transcribe-style models) → billed via the usual per-token pricing.
  • No usage reported → the request succeeds at zero cost and a warning is logged.

Using Lumen in Third-Party Chat Tools

Many desktop and web chat applications support custom OpenAI-compatible endpoints. Look for a setting labelled API Base URL, Custom endpoint, or OpenAI-compatible server and enter:

https://lumen.ncsa.illinois.edu/v1

Then paste your sk_... key as the API key. Common tools that support this pattern include Jan, Open WebUI, Msty, and most AI IDE extensions.


Token Usage in Responses

Every response includes a usage field with exact token counts:

{
  "choices": [...],
  "usage": {
    "prompt_tokens": 42,
    "completion_tokens": 183,
    "total_tokens": 225
  }
}

These counts drive the coin deduction on your account. You can retrieve the same numbers from the Usage page after the fact.


Rate Limits

If you send too many requests too quickly, the API returns:

HTTP 429 Too Many Requests

Wait a moment and retry. The Usage page shows your recent request volume so you can gauge how close you are to the limit. Listing models (GET /v1/models and GET /v1/models/{id}) does not count against the limit, so a client that lists models before every completion is not penalised for it.

A 429 is also returned when your coin budget is exhausted, which needs the opposite reaction — retrying shortly will not help until the budget refills. The two are told apart by the error code:

code Meaning What to do
rate_limit_exceeded Too many requests too quickly Retry after a short pause
insufficient_quota Your coin budget is spent Wait for the refill, or ask for a larger budget

Both carry a Retry-After header, in seconds, whenever the wait is knowable — the OpenAI SDK honours it. A coin budget that never refills automatically sends no Retry-After, because there is no time to give.


CORS (browser apps)

Every /v1 endpoint allows cross-origin requests, so a web page on any origin can call the API directly with fetch or the OpenAI JS SDK (dangerouslyAllowBrowser: true). Each /v1 response, including errors and streaming responses, carries:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, OpenAI-Organization, OpenAI-Project, OpenAI-Beta,
    X-Stainless-Lang, X-Stainless-Package-Version, X-Stainless-OS, X-Stainless-Arch, X-Stainless-Runtime,
    X-Stainless-Runtime-Version, X-Stainless-Retry-Count, X-Stainless-Timeout, X-Stainless-Helper-Method
Access-Control-Expose-Headers: Retry-After
Access-Control-Max-Age: 600

(Beyond Authorization and Content-Type, these are the headers the OpenAI JS SDK sends.) Preflight OPTIONS requests are answered 204 without an API key and do not count against your rate limit. Credentials are never allowed: the browser sends no cookies, and the page must attach the Authorization: Bearer header itself. Any script on that page can read the key, so treat a key held in the browser like any other secret and revoke it on your Profile if it leaks.

Retry-After is exposed so browser code can honour it on a 429 (see Rate Limits).

Reverse proxies: if your Lumen sits behind an ingress or proxy, make sure it passes OPTIONS requests through to Lumen and does not add its own CORS headers. A proxy that answers preflights itself can drop the allowed headers, and one that adds a second Access-Control-Allow-Origin makes browsers reject every response.


Error Responses

HTTP Status Meaning
401 Invalid or missing API key
403 Your account does not have access to the requested model
404 Model not found
429 Rate limit exceeded, or coin budget exhausted (see Rate Limits)
503 Model backend is currently unavailable

A 403 on a consume endpoint either means the model is blocked for your account or — for a model that requires acknowledgment you have not given — that you must acknowledge it first. The two need opposite reactions, so they are told apart by code:

{
  "error": {
    "message": "This model requires acknowledgment before use. Acknowledge it via POST /v1/models/qwen-coder-cc/acknowledge.",
    "type": "invalid_request_error",
    "code": "consent_required"
  }
}

A code: "consent_required" means the model merely awaits your acknowledgment (see Acknowledge a Model); a 403 without a code means the model is blocked for your account. This is the same code-disambiguation pattern used for 429, where insufficient_quota (stop until the budget refills) is distinguished from a plain rate limit.

National Center for Supercomputing Applications

Lumen

Illinois Chat Lumen LLMHub LLMflux