Learner LabsLearner 1.0Foundation models that continually learn

API

Give it a document. Ask it questions. It answers from what it learned.

Authenticate with a bearer token, create a learner, teach it a source, and ask. The chat call is the shape most clients already speak, so an existing client usually works unchanged.

The facts

Base URL
https://api.learnerlabs.ai
Authentication
Authorization: Bearer YOUR_API_KEY
Your learner
learner-1.0:<learner_id>
The base model
learner-1.0-base
Context window
8,192 tokens
Before you are charged
Send a teaching call with confirm set to false to see the price with nothing trained. Sent without that field, it trains immediately

Point the same call at your learner or at the base model to ask both the same question and compare the two answers. That comparison is the fastest way to see whether teaching did anything.

How a learner answers. A taught learner answers from what actually reached it during teaching. When taught material conflicts — with the model's general knowledge, or with other taught statements — the version it encountered more often tends to win. A confident answer is not by itself evidence that a fact was taught or retained: verify with the comparison above, or with the per-fact rows in the training report.

Two practical notes. Requests need a real User-Agent header. The default Python urllib agent string is refused at our edge. And on a shared machine, keep the key out of the process list: put the header line in a file and pass it with curl -H @auth.txt instead of writing the token into the command.

Your keys

Mint, review and revoke the keys a machine uses to call this API. The secret is shown once, at mint. The server keeps only a hash of it, so there is nothing to show a second time.

Loading…

The path through it

Six calls, in this order, once. After that you are just asking questions.

  1. Get a key

    POST/v1/keys while signed in as a person. The secret is shown once and never again; only a hash of it is stored. A machine key cannot mint another key, so an agent has to be handed one.

  2. Create a learner

    POST/v1/learners with a name. Keep the learner_id that comes back, because almost every later call takes it.

  3. Open a session, then activate it

    POST/v1/sessions with the learner id, then POST/v1/sessions/{id}/activate with no body. Compute scales to zero between uses, so a cold learner's first answer includes the whole start-up wait. That wait is billed. Three cold questions took 497, 576 and 582 seconds and cost $0.68, $0.79 and $0.80 at the $0.0013658 per GPU-second /v1/billing reports. A warm learner answers in seconds to tens of seconds, so its questions cost cents at the same rate. Activating does not skip that wait, but it opens the sitting and gives you compute.stage to poll, so the wait renders as a visible stage instead of an unexplained stall. (The learner-create response also carries a session_id; that one is account-scoped bookkeeping, so open a learner session here for asking.)

  4. Add a source: quote first, then confirm

    POST/v1/sources with a file, a URL or a repository. Send it with "confirm": false and it trains nothing. It returns an estimate: how much material it found, roughly what it will cost, and roughly how long. Send the same call with the field dropped, or set to true, and it starts immediately. Add cost_cap_usd to any teaching call and it refuses to start if the estimate exceeds your cap.

  5. Watch the job

    GET/v1/jobs/{id} for the state, or GET/v1/jobs/{id}/events for a live stream of it. When it finishes, the report tells you how much the learner gained on the material you gave it.

  6. Ask it something

    POST/v1/chat/completions, shaped like the chat call most clients already speak. When the learner is cold it answers 202 with a pending job instead of an answer. The job's field is id; poll GET /v1/chat/completions/jobs/{id} until the answer arrives. With streaming turned on you get events as the text arrives.

    Thinking mode works on taught learners, and it is not yet optimized for them. Send enable_thinking: true on a learner and the server prepends one fixed sentence to the thinking phase, asking the model to recall what it was taught before it answers; the response carries the trace in choices[0].message.thinking. On our handbook learner this recovers 11 to 12 of 16 taught answers against 14 of 16 with thinking off, and the trace names the taught value in 13 of 16 cases. On a learner taught counterfactual facts, the trace can carry a taught value into an unrelated question. Send think_phase_cap to change the default cap of 300 thinking tokens, or your own think_primer to replace the sentence. The base model honours enable_thinking with no primer.

Three kinds of input

Choose by what you have. The same API key works for all three.

You haveCallWhat the learner gets
A document, a web page or a code repositoryPOST /v1/sourcesWith "extract_facts": true (the default), the document is learned and the facts found in it are stored as rows you can list with GET /v1/facts and taught with it. With "extract_facts": false, the document is learned as it is and no facts are extracted: use this for style, terminology or procedure. A document is not turned into question-and-answer pairs.
Individual facts you write yourselfPOST /v1/facts
POST /v1/facts/train
Each fact is stored as a row. Nothing is trained until you call /v1/facts/train, which trains the facts not yet in the model.
Examples of a task: an input and the output you want/learning/v1/…Supervised training on your rows, in sessions that produce checkpoints you can evaluate and query. See Training on your own examples.

Documents

kind is one of file, url or github_repo. For file, send text inline in content (up to 8 MiB), or upload a file to POST /v1/sources/upload (up to 8 MiB): plain-text formats such as .txt, .md, .csv, .json, .html and source code, PDF with a text layer (up to 2,000 pages) and .docx. A legacy .doc is refused. For url, the address must be public http or https and return text, HTML, JSON, XML or YAML (a PDF link is refused, upload the file instead), up to 16 MiB. For github_repo, give the https address. For a private repository, add your own read token in deploy_token. Every source is quoted first with "confirm": false, which trains nothing and charges nothing.

curl https://api.learnerlabs.ai/v1/sources \
  -H "Authorization: Bearer $LEARNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"learner_id": "YOUR_LEARNER_ID", "kind": "file", "name": "house style",
       "content": "…your text…", "extract_facts": false, "confirm": false}'

Facts

POST/v1/facts takes learner_id and fact_text, one statement such as "The Zorvanex relay operates at 14.2 kHz." A fact that contradicts one already stored is not written. The answer is 409 with a notice naming the conflict. That check runs on the learner's serving capacity: while it is starting, the call answers 503 pod_cold with retry_after_s, and the fact is not written. Retry after that many seconds. Edit a fact with PATCH/v1/facts/{id}, which starts the training run that brings the model in line. POST/v1/facts/train with the learner_id starts a training job for the facts not yet in the model and answers 409 nothing_pending when there are none. It has no price quote: the charge appears on the job. The facts are in the model only when that job finishes. Poll GET /v1/jobs/{id}. Training facts currently needs at least one document taught to the same learner first: without one the call answers 409 no_stored_source.

Training on your own examples

The Learning API is at https://api.learnerlabs.ai/learning/v1. GET /learning/v1/health and GET /learning/v1/openapi.json (the full schema, with examples) need no key. Every other call takes the same API key as the rest of the API. It is enabled per account: a valid key on an account that is not enabled gets 403 learning_not_enabled, and you can ask for it through the request-access form.

Training data is JSON Lines, one object per line with exactly row_id, prompt and answer, all non-empty strings, row_id unique. The prompt is context and is not trained on. The answer, plus an end-of-sequence token, is what training fits. Nothing is inserted between the two, so put your own separator in the text, and ask later with the same prompt wording. Raw documents are refused here.

{"row_id": "a-001", "prompt": "Q: What is the capital of Ruvelia?\nA:", "answer": " Tarn"}
{"row_id": "a-002", "prompt": "Q: What river runs through Tarn?\nA:", "answer": " The Oskel"}

Each session also takes monitor panels: small fixed sets of rows the service scores during and after training to track retention. They are not a held-out test. For that, upload a separate evaluation set of {"id", "prompt", "expected"} rows. An evaluation is refused with 409 eval_overlap if any of its prompts matches a training or monitor prompt after lower-casing, collapsing whitespace and removing trailing punctuation. Once a session has used a panel, every later panel set for that learner must contain it unchanged.

Limits: up to 24,000 training rows per file, prompts up to 1,024 tokens, answers up to 1,024 tokens including the end token, 2,000,000 prompt-and-answer tokens and 1,200,000 answer tokens per file, 12 panels of up to 256 rows, 4,000 evaluation rows. Format and row counts are checked when you upload. Token limits are checked when a session starts, on the training worker before the model loads. A row over a limit refuses the session with limits_exceeded and nothing is truncated. Learning jobs are not currently deducted from your account credit, so a refused session costs you nothing. The replication repository's validate_learning_inputs.py checks your files locally for free.

# base: https://api.learnerlabs.ai/learning/v1   header: Authorization: Bearer $LEARNER_API_KEY
POST /learners                                    {"name": "my learner"}      # → learner_id, initial_checkpoint_id
POST /learners/{id}/datasets        (application/x-ndjson, training rows)       # → dataset_id
POST /learners/{id}/panels          {"schema": "pumod_monitor_v1", "panels": {"a": [{"id", "prompt", "answer"}]}}
POST /learners/{id}/eval-datasets   (application/x-ndjson, {"id", "prompt", "expected"} rows)
POST /learners/{id}/evaluations     {"checkpoint_id": initial_checkpoint_id, "dataset_id": …, "idempotency_key": "base-a"}
POST /learners/{id}/sessions        {"dataset_id": …, "panel_set_id": …, "idempotency_key": "teach-a"}   # → job
GET  /learners/{id}/jobs/{job_id}   # poll until not queued or running; the job names the new checkpoint
POST /learners/{id}/evaluations     {"checkpoint_id": …, "dataset_id": …, "idempotency_key": "after-a"}
POST /learners/{id}/checkpoints/{checkpoint_id}/inferences   {"prompts": [{"id": "p1", "prompt": "…"}], "idempotency_key": "ask-1"}
GET  /learners/{id}/jobs/{job_id}/answers      # verbatim answers, paged

To teach a second skill, upload its rows, upload a panel set with the first skill's panel unchanged plus a new one, and start another session: it continues from the learner's latest checkpoint. Retrying any call with the same idempotency_key returns the same job and never starts a second one. Jobs are batch work: a training or inference job takes roughly 10 to 30 minutes before its first output, so poll rather than hold a connection open. The replication repository has a standard-library Python client and a step-by-step runbook.

One example, start to finish

Teach a page and ask about it. Two calls to teach (the first prices it, the second starts it) and one to ask.

# 1 · create a learner
curl https://api.learnerlabs.ai/v1/learners \
  -H "Authorization: Bearer $LEARNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "product docs"}'

# 2 · price the source. confirm:false trains nothing and charges nothing
curl https://api.learnerlabs.ai/v1/sources \
  -H "Authorization: Bearer $LEARNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"learner_id": "YOUR_LEARNER_ID", "kind": "url", "confirm": false,
       "uri": "https://example.com/docs/changelog"}'

# → { "confirmed": false, "quote": { "tokens": 21504, "est_usd": 0.31,
#     "est_seconds": 180 } }   send it again with "confirm": true to start
# (to teach raw text instead of a URL: "kind": "file", "content": "…your text…")

# 3 · ask it, once the job has finished
curl https://api.learnerlabs.ai/v1/chat/completions \
  -H "Authorization: Bearer $LEARNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "learner-1.0:YOUR_LEARNER_ID",
       "messages": [{"role": "user", "content": "What changed in v4?"}],
       "max_tokens": 256, "temperature": 0}'

# A warm learner answers in seconds to tens of seconds. A cold one hands back
# 202 { "id": "…", "object": "chat.completion.pending", "retry_after_s": … }
# instead — not an error. Poll with its "id" until choices arrive:
curl https://api.learnerlabs.ai/v1/chat/completions/jobs/THE_PENDING_ID \
  -H "Authorization: Bearer $LEARNER_API_KEY"

For agents

A hosted tool endpoint so an agent can teach a document, ask a learner and replay any of the published demonstrations without you writing a client. One line to install:

claude mcp add --transport http learner https://mcp.learnerlabs.ai/mcp \
  --header "Authorization: Bearer sk-your-key"

The demonstrations page shows the same install and what to say to your agent once it is connected.

To run the ten-skill study through the API, see the API replication guide.

Endpoint reference

Keys

MethodPathWhat it does
POST/v1/keysMint a key. Signed in as a person only
GET/v1/keysList your keys by prefix. Never the secret. Signed in as a person only
DELETE/v1/keys/{id}Revoke one. Signed in as a person only

Listing, minting and revoking keys all need a signed-in person. A key on its own gets 403 human session required on each of the three. So an agent cannot revoke its own key if that key is compromised. A person has to.

Learners

MethodPathWhat it does
POST/v1/learnersCreate one
GET/v1/learnersList them
GET/v1/learners/{id}One learner and what it holds
DELETE/v1/learners/{id}Delete it and everything it learned
GET/v1/seedsThe prepared starting points a learner can be created from

A prepared learner is one created from a published starting point instead of from nothing, so you can run the teach-in-sequence demonstration without teaching a document first. Pass a name from GET /v1/seeds as seed on POST /v1/learners. The copy takes a few minutes (4 minutes 9 seconds) and until it finishes the learner answers 409 preparing the requested starting point.

Sessions

MethodPathWhat it does
POST/v1/sessionsOpen a session for a learner
POST/v1/sessions/{id}/activateOpen the sitting and get a stage to poll. No GPU is held until your first message
GET/v1/sessions/{id}Watch it come up

Teaching

MethodPathWhat it does
POST/v1/sourcesPrice a source with confirm false. Start it with the field dropped or true
POST/v1/sources/uploadUpload a file directly
GET/v1/sourcesEverything this learner has been taught
GET/v1/jobs/{id}Job state
GET/v1/jobs/{id}/eventsLive stream of the job
GET/v1/jobs/{id}/reportWhat it gained on the material

Individual facts

MethodPathWhat it does
POST/v1/factsWrite one fact directly, for a statement the extractor declined from a document
GET/v1/factsEverything this learner holds as individual facts, each with the row_id the edit route takes
PATCH/v1/facts/{id}Edit one by its row_id. The retrain that republishes it is dispatched for you
POST/v1/facts/trainTrain any still-pending facts into the served weights

Asking

MethodPathWhat it does
POST/v1/chat/completionsAsk. The familiar chat shape
GET/v1/chat/completions/jobs/{id}Collect an answer that came back as a job
POST/v1/chat/streamAsk, streamed

Conversations

MethodPathWhat it does
POST/conversationsStart a conversation with a learner
GET/conversationsList them
POST/conversations/{id}/messagesSend a message
GET/conversations/{id}/abThe same question to the learner and to the base model
PATCH/conversations/{id}Rename it
DELETE/conversations/{id}Delete it

Restore points

MethodPathWhat it does
GET/v1/checkpointsEvery point you can go back to
POST/v1/checkpoints/{id}/restoreRoll a learner back to one
POST/v1/checkpoints/{id}/forkBranch this learner from that point. Its next teaching builds on the fork point
POST/v1/checkpoints/{id}/preloadWarm it before you need it
GET/v1/checkpoints/{id}/exportTake it away

Billing and records

MethodPathWhat it does
GET/v1/billingBalance and usage
POST/v1/billing/checkoutAdd credit
GET/v1/auditSessions, teaches, fact writes, keys and seeds, in order. Asks are not recorded here
GET/v1/reportOne learner's summary. Pass its learner_id
API · Learner Labs