Documentation

One key, any OpenAI client.

LLMJob speaks the OpenAI chat-completions protocol. Point any SDK you already use at our base URL, pass your key as a bearer token, and your requests run on the GPUs in your own cluster — or on our shared hosted models while you get your nodes online.

Quickstart

Create a key on the dashboard, then make a request. The base URL is https://llmjob-production.up.railway.app/v1.

bash
curl https://llmjob-production.up.railway.app/v1/chat/completions \
  -H "Authorization: Bearer $LLMJOB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      { "role": "user", "content": "Explain PPLNS in two sentences." }
    ]
  }'

The response is an ordinary OpenAI chat.completion object:

json
{
  "id": "chatcmpl-8f1c…",
  "object": "chat.completion",
  "created": 1775001600,
  "model": "qwen/qwen3.8-27b",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "PPLNS pays…" },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 12, "completion_tokens": 48, "total_tokens": 60 }
}

Your API key

Keys are created on the dashboard under API. They look like lj-live-… and are shown once, at creation — we only ever store a hash, so a lost key has to be revoked and replaced rather than recovered.

Send it as a bearer token on every request:

http
Authorization: Bearer lj-live-7c2f4a91b8e3d05f6a1c9e2b4d7f8031

Most OpenAI SDKs take this as the api_key argument and set the header for you — there is nothing LLMJob-specific to install.

Keep keys server-side. A key in browser or mobile code can be read by anyone who opens the page, and it spends your cluster's time. Revoke a leaked key from the dashboard — it stops working immediately.

Every request is logged against the key that made it: model, node, tokens in and out, speed, and finish reason all show up under Logs on the dashboard.

Models

Two kinds of model sit behind the same endpoint, chosen by the model field in your request.

ModelRuns onAvailable to
qwen/qwen3.8-27b LLMJob's hosted backend public keys
Gemma-4-E4B-it-Q4_K_M The node network — a GPU running LLMJob Earn any key

The Qwen model is the one the free Chat page serves. It is available through the API so you can build against a working endpoint today, before your own nodes are up. It draws on a shared free allowance — see Limits.

The split is a hardware one. Gemma-4-E4B is small enough to load on the range of consumer GPUs that actually run LLMJob Earn, which is what makes it the network model. A 27B model is not, so Qwen is hosted rather than mined — a node serving it would have nothing left to earn with.

Anything else falls through to the node network. Omit model entirely, or send gpt-4, or send whatever string your framework hard-codes: the request becomes an inference job and the next available node in your cluster runs it against the model it has loaded. The model field of the response always names what actually ran, never what you asked for.

Call GET /v1/models with your key to see exactly which models that key can reach.

Public & private keys

Every key carries a routing setting, toggled on the dashboard:

  • Public — requests may be served by any node on the network, and may use the hosted models above. This is the default.
  • Private — requests only ever run on nodes in your account. Nothing leaves your hardware, which also means the hosted models are off-limits: asking for one returns 403 rather than quietly sending your prompt elsewhere.

Flip a key to private when the prompts matter more than the throughput, and keep a separate public key for hosted-model work.

Chat completions

POST /v1/chat/completions

Request body

FieldTypeDescription
messagesrequired array The conversation, as {role, content} objects. Roles are system, user and assistant; anything else is treated as a user turn. content is either a string or OpenAI's array of text and image_url parts — send an image as a data: URL to a node running a vision model. A conversation over the prompt budget is trimmed from the oldest turn, so the question you just asked always survives.
model string A hosted model id, or anything else to use the node network. Defaults to the network.
stream boolean true streams the answer back as server-sent events. Defaults to false.
max_tokens integer Completion ceiling, clamped to the server limit. See Limits.
temperature number Passed through to the model.

Response

A standard chat.completion. Two details are worth knowing:

  • finish_reason is "length" when the completion budget ran out. On a reasoning model that is the difference between a short answer and a truncated one.
  • message.reasoning_content appears when the model produced a chain of thought. It explains an empty content — the budget was spent thinking. Clients that don't know the field ignore it.

Headers

HeaderDirectionDescription
X-LLMJob-Node request Pin the request to one node id, to check that it serves and how fast. Fails fast with 404 if that node is offline or unknown, and with 400 if you also asked for a hosted model (no node runs those). Most OpenAI SDKs let you add default headers.
X-LLMJob-Served-By response Which node served the request — or openrouter for a hosted model. Non-streaming responses only: a stream sends its headers before any node has the job.

Streaming

Set "stream": true to receive chat.completion.chunk events over SSE, terminated by data: [DONE] — the same shape and the same terminator whichever backend served you.

bash
curl -N https://llmjob-production.up.railway.app/v1/chat/completions \
  -H "Authorization: Bearer $LLMJOB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "stream": true,
    "messages": [{ "role": "user", "content": "Count to five." }]
  }'

data: {"id":"chatcmpl-8f1c…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-8f1c…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"One"},"finish_reason":null}]}
data: {"id":"chatcmpl-8f1c…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
If a node drops a job mid-answer, the network hands it to another node and the reply restarts from the beginning. SSE can't retract text already sent, and a visible restart beats an answer that stops mid-sentence and never resumes.

Listing models

GET /v1/models returns the models your key can reach, in OpenAI's list shape. A private key sees only the network model, because that is all it may use.

bash
curl https://llmjob-production.up.railway.app/v1/models \
  -H "Authorization: Bearer $LLMJOB_API_KEY"

{
  "object": "list",
  "data": [
    { "id": "qwen/qwen3.8-27b",      "object": "model", "owned_by": "llmjob-hosted"  },
    { "id": "Gemma-4-E4B-it-Q4_K_M", "object": "model", "owned_by": "llmjob-network" }
  ]
}

Treat it as a guide rather than a whitelist: an unlisted model id is not an error, it just goes to the node network.

Limits

LimitNetwork modelsHosted models
Prompt size 24,000 characters 24,000 characters
Images per request 4, up to 4 MB each
Request body 20 MB 20 MB
Completion (max_tokens) 6,400 2,048
Request timeout 280 seconds Upstream
Cost Your own hardware Shared free allowance

Prompts over the character budget are trimmed rather than rejected, and an oversized max_tokens is clamped to the ceiling — your request still runs.

The hosted models draw on one shared, capped allowance across everybody's API traffic and the free web chat. Once it is spent they return 402 until it is topped up; the node network is unaffected, so a request with no model keeps working.

Errors

Errors use the standard OpenAI envelope, so any SDK will parse them:

json
{
  "error": {
    "message": "Node rig-02 took the job but produced no output within 280s.",
    "type": "timeout_error",
    "code": null,
    "job_id": "job_4b1e…",
    "served_by": "rig-02",
    "job_status": "assigned"
  }
}

Failures from the node network carry three extra fields — job_id, served_by and job_status — so a timeout or a node error is attributable instead of anonymous. Strict clients ignore them; yours can log them.

StatusTypeWhat happened
400invalid_request_error No usable messages, or a hosted model pinned to a node.
401 Missing, malformed or revoked API key.
402quota_exhausted The hosted models' free allowance is spent. Use the node network.
403permission_error A private key asked for a hosted model. Switch the key to public, or drop the model field.
404target_node_error The node named in X-LLMJob-Node is offline or unknown.
502node_error / upstream_error The node failed the job, or the hosted backend returned an error.
503not_configured Hosted models aren't configured on this deployment.
504timeout_error No node finished the job in time. The message says whether the fleet was empty, a node went quiet, or generation was still running.

A 504 with no served_by means nothing picked the job up: check that a node is online and serving on your dashboard. Still stuck? Ask in Discord.