API reference

Base URL: https://api.jepela.com (the API of your key's region; see Regions). Every /v1 route except sign-up needs Authorization: Bearer <key>, and every POST Content-Type: application/json. A POST body may be compressed with Content-Encoding: gzip (or x-gzip) or zstd, one layer, as TypeSafe's newer SDKs send it. Bodies are capped at 1 MB (16 MB for /v1/batches and /v1/golden); a compressed body counts once decompressed. Duplicate JSON keys are rejected, and a body nested more than 100 levels deep (arrays and objects; in a JSON Lines batch, each line) is refused with a 400. Connections stay open between requests (HTTP/1.1).

POST /v1/systemone

Request:

FieldTypeMeaning
statestring, object or array (required)what to evaluate; see State for how much of it the engine reads
questionsobject of question id to question (required)at least one
modelstringa name from /v1/models, or this tenant's fine-tuned jepela-ft-*; default jepela
subjectstring, 1 to 36 of A-Za-z0-9_-use this subject's memory and rules
memoryobjectuse (default true with a subject), top_k (1 to 20, default 6), min_share (0.05 to 1, default 0.2), compare (default false), placebo (default false), names (default true: the subject's name and aliases are searched with the state), question_words (default true: the questions' instructions are searched too), focus (default false: keep only lines that share a word with the state beyond those names), explain (default false: per memory line, the answer's move when that line alone is left out; one engine pass per line, at most 8). meaning is internal, not a supported option: it is accepted as true or false and is off unless sent, but it may change or be refused without notice, so do not rely on it
quality"fast" (default), "balanced" or "best"speed or accuracy: above fast, the answers the engine is least sure of, those whose state it could not read in full and every answer of a decision with memory or computed facts are answered again by a larger, slower second tier; best sends more of them (Request options)
method"auto" (default), "engine" or "match"how choice questions are answered: auto matches choices with more than 20 options by vectors and asks the engine the rest; engine reads every option; match matches every choice (Choice)
robusttrue or {"orders": 1-5}ask each choice of 2 to 20 options in several option orders and average them; true is 3. A choice of more than 20 options is matched (or, with method: "engine", answered in two rounds) and is not reordered
windowstrue or {"combine": {"question_id": "max" | "min" | "mean"}}read a state that is cut in overlapping windows (32 tokens shared), at most 16
deriveobject, at most 20 namesfacts computed in code: {"name": "expression"} or {"name": {"when": "expression", "fact": "text", "else": "text"}}; a name starts with a letter and has at most 40 letters, digits or underscores; an expression has at most 300 characters, a fact at most 500
valuesobject, at most 50 fieldsnumbers and text that derive and rules may use, read before a field of the state with the same name; today sets the date days_since counts from
cachebooleanan identical request that does not use a subject's memory returns the stored answers: from the same engine build, at most 24 hours old, removed by POST /v1/decisions/delete; a repeat is answered while the engine is briefly away (Request options)

Questions: {"type": "choice", "instructions", "criteria": {name: description}} with 1 to 255 options (more than 20 are matched by default; see method); {"type": "score", "instructions", "criteria": [level, ...]} with 2 to 10 levels, lowest first; {"type": "noul", "instructions", "criteria"?: {"true", "false"}}. instructions and descriptions may be strings, objects or arrays. A question whose depends_on is set to anything but null or an empty value is refused with a 422: every question is answered on its own, so ask the questions together and apply the condition in your code. A request may need at most 400 engine questions, counting every option order, every group of a two-round choice and its final round; matched choices are not engine questions. context is not accepted: Jepela builds it from the subject's memory and derive.

Fields Jepela does not read: assets and images (pictures, which Jev takes), in the request or in a question, are refused with a 422 when they hold anything, before anything is asked or charged, because Jepela reads text only and an answer from the text alone would look as if the pictures had been seen; describe what they show in the state. Empty ones pass. Any other field Jepela does not read, at the top of the request, in memory or in a question, is answered as usual, without that field, and the answer carries an unknown_field warning that names it and the closest field Jepela reads (a question's warning has question): a misspelt subjet gives "unknown field subjet; did you mean subject?" on a decision made without memory, and memory.comapre gives no comparison and "did you mean memory.compare?"; a noul's critera is a question without its descriptions and "did you mean questions.refund.criteria?". A question's fields are those TypeSafe's API reference documents (type, instructions, criteria; and depends_on, above), so code written for Jev keeps working, and a typo is seen. A request refused for a missing field names the misspelt one: "choice needs 1 to 255 named candidates (unknown field questions.route.criterias; did you mean questions.route.criteria?)". A name that looks like a key or a token is never repeated in a warning. A request with an unknown field is billed like the same request without it.

Response:

FieldMeaning
decision_iddec_ and 24 hex digits; send it back with /v1/feedback. The state is never stored
modelthe name asked for, aliases resolved
enginename, checkpoint (the one that answered), runtime (the engine's metadata: version, the Jepela build id, which is the hash of the engine's code and changes whenever the engine does; device; timings)
answersper question, each with type, confidence (1 minus the normalised entropy of the probabilities for a choice or score; the larger of yes and no for a noul), answer_confidence (the probability of the answer given, on one scale for every type: the chosen option's, the most likely level's, or the larger of yes and no; always the top value of the answer's own probabilities, after a tenant temperature too), method (engine or match; matched answers have similarity) and, on an answer the engine read (method engine), the engine's action (not on matched answers or on answers the second tier gave, nor in their first; do not decide on it; see below the table). choice: choice, probabilities; rounds and groups above 20 options; orders and agreement with robust. score: score (probability-weighted level), probabilities, legend. noul: noul (probability of yes). With a tenant temperature: calibration and the raw_ values before it. With windows: window. With quality balanced or best: right_probability (the estimate that the engine's answer is right), and on an answer the second tier gave tier (2) and first (the engine's own answer)
warningsknown traps in this request: {code, message, question?}; see Warnings
readingper question: state_tokens, read, room, chars_read, truncated (counted on the state with memory and computed lines in front; chars_read counts from the start, or for an array, which is cut from its start, the last characters; with windows, read and chars_read count the tokens and characters of the state the windows covered: from its start, or for an array too long for the windows, which are then placed at its end, up to its end); context_lines_read when such lines were placed; turns_dropped when Jepela left out that many of an array's oldest elements so that the lines placed before it were read (then truncated is true, and read and chars_read count what was sent, the lines and the newest elements, read whole); windows when windows read the whole state; for a matched choice, what the matching model read, with method: "match" and counted with its tokens (see Choice)
derivedthe value of every derive name
memorywith a subject: subject, lines_used (fired rules and recalled lines sent to the engine), lines_seen (how many of them it read: the first ones, or in an array, which is cut from its start, the last ones), lines_dropped (left out of its 64 context lines), hits and rules_fired (each line with read: whether the engine read it, as many true as lines_seen), rules_broken (rules not checked: id, error, and missing, the field, when the request had no field the rule reads), recall_ms, without_memory and changes (with compare, otherwise null: the same call without the memory and its rules, on the same elements of an array when Jepela left out its oldest, and per question what changed; a choice's entry has excluded when exclusion rules removed options), placebo (with placebo: lines, answers, content_effect, memory_effect, memory_distance, placebo_distance, presence_only; where the second tier gave the answer, measured_on_first lists the questions whose effects are measured on the engine's own answer first: see Memory); when they apply: pinned (ids of pinned rules sent), excluded (per question, the options removed by exclusion rules and why), exclusions_skipped, explain (a list of {line, kind, read, effect}; where the second tier gave the answer, measured_on_first lists the questions whose effect is the move of the engine's own answer first: see Memory)
qualitywith quality: level; above fast also below (the right_probability under which an answer is answered again), asked_again (question ids), why (per question: unsure, cut or memory) and, when the second tier gave no answer in time or failed, fallback (questions that kept the engine's own answer, marked tier: 1, and the reason)
windowswhen the state was read in windows: count, covered_all (whether the windows reached the end of the state), state_tokens, tokens_per_window; each answer's window names the window it came from (null with mean)
usagequestions, answers, answers_billed, engine_answers, input_characters, input_tokens (the engine's count, what you pay for), output_tokens (always 0), state_tokens, state_tokens_read, state_truncated (over the questions the engine read; a matched choice's own reading stays in reading), memory_lines_used, memory_lines_seen, match_questions (when some were matched), tier2_questions and tier2_input_tokens (when the second tier answered), cached, engine_ms, gateway_ms, cost, currency, balance. answers_billed is the sum over questions of the option orders used (1 without robust) × windows; the compare, explain and placebo passes are not billed

action is {"act_probability": <number>}, an output of the engine that Jepela passes through as it comes and reads nothing from; it is not one of the answer's probabilities. On all 40 answers recorded on 2026-09-23 (Examples) it was 1.0: for choice, score and noul questions alike, with and without memory, at confidences from 0.016 to 1.0. So it tells you nothing about an answer: do not decide on it. Decide from probabilities and confidence (Confidence), or, once you have labels, from the thresholds GET /v1/quality gives in automate_at (Quality).

Memory

RouteBody or queryResponse
POST /v1/memory/remembersubject, text (one fact per line, each at most 2,000 characters; a subject holds at most 10,000 lines)lines_added (the lines it did not hold yet: a line the subject holds is not stored again), lines_stored, warnings, usage
POST /v1/memory/forgetsubject, concept, preview (true: nothing is removed and the answer says what would go, with "preview": true, no usage and no verified_forgotten; not billed, no ledger line)lines_removed, removed_lines (their text), rules_removed, removed_rules (id and text of each), matched_unsure (lines and rules removed although the case does not show the name there: the first word of a sentence, a list item or a table cell, a word or a heading in capitals or title case, or a name of several parts in another case; or a word of the concept matched only with other accents or none, "José" for "Jose", "Jose" for "José", or with a letter such as ø, æ or ł written without it, "Søren" for "Soren", "Kjaer" for "Kjær"), related (lines and rules kept that may be about a name: they hold the concept with a name in another case, "Billing is per month." for "Per", or a part of a name of several parts, "Ann prefers email." for "Ann Lindqvist"), verified_forgotten, leaks, usage, warnings; a line or rule goes when it holds every content word of the concept as a whole word, in any script ("Ann" is not in "annual"; "MÜLLER" is "Müller"): a word written in lowercase with case ignored, also as a part of a hyphenated word that holds a letter ("dairy" in "dairy-free", not "2026" in "2026-10-16"), a word written with a capital letter (a name) only where the line has a capital too ("Per" is not in "per month"), and also in its possessive and genitive forms ("Ann's", "Åsas") and as a part of a hyphenated name ("Smith" in "Smith-Jones"), with the apostrophes inside it, however typed ("O'Brien" is "OʼBrien", not "Brien")
POST /v1/memory/deletesubjectlines_removed (the lines the subject held), batch_requests_deleted (the subject's requests taken out of your batches, with their results), usage, warnings; the lines and rules on disk go too. Any other field with a value is refused (422) and nothing is deleted
GET /v1/memorysubject, lines=truelines_stored, rules; with lines=true also lines and rule_list (id, when, text, expires_at), word for word
POST /v1/memory/rulessubject, when (at most 300 characters; optional with pin or exclude, then always), text (at most 500), pin (true: the line is sent at every decision, first), exclude (1 to 20 option keys removed from choice questions in code when the rule holds; its text is never sent to the engine), question (limit an exclusion to one question id; refused without exclude), expires (ISO date or time) or ttl_seconds (at most ten years); at most 100 rules per subjectrule (with expires_at), rules (how many), usage, warnings; billed as one memory line
POST /v1/memory/aliasessubject, aliases (at most 20 names, 1 to 80 characters each; an empty list clears them)aliases, searched_as (the words recall adds to the state), warnings
POST /v1/memory/aliases/deletesubject, aliases (the names to take out, at most 20, matched in any case; required, an empty list removes nothing)removed (the aliases that went, as stored), not_found (the names the subject did not hold), aliases (those left), searched_as, warnings; adds no name; not billed
GET /v1/memory/rulessubjectrules
POST /v1/memory/rules/deletesubject, rule_iddeleted, rules, warnings

Fields a memory route does not read get the policy of a decision: the request is carried out without the field, and warnings has an unknown_field warning naming it and the closest field the route reads, or the route that reads it: a rule with ttl_secs is stored without an expiry and "unknown field ttl_secs; did you mean ttl_seconds?" says so; text sent to forget is "read by POST /v1/memory/remember and POST /v1/memory/rules, not here". A field that would change what is done and cannot be honoured is refused with a 422 before anything is stored or deleted, and nothing is charged: remember with when, pin, exclude, question, expires or ttl_seconds (or a name close to one: a remembered line is recalled by its words, always and until forgotten; send it as a rule), delete with any field but subject (it deletes everything about the subject, more than such a field asks for), forget with a name close to preview in any case or spelling (Preview, PREVIEW, preview_only), dry_run, dry run, simulate, simulation, what_if or noop, or an object that holds one ("options": {"preview": true}), or with any query string (POST /v1/memory/forget?preview=true; the caller asked only to look; send "preview": true in the body), and a rule's question without exclude. Every route that deletes reads only its JSON body and has no preview but forget's: a query string on forget, delete, rules/delete, /v1/golden/delete, /v1/decisions/delete or /v1/finetune/delete (?dry_run=true) is refused with a 422, and so is a field that asks rules/delete, /v1/golden/delete or /v1/finetune/delete only to look (a name like preview or dry_run, as for forget, or an object holding one); nothing is deleted or charged. A field whose value is null, false or empty asks for nothing and is only named. customer, the older name of subject, is read everywhere. A 422 for a missing field names the misspelt one the body carried: "concept must not be empty (unknown field concpt; did you mean concept?)".

Removing data never waits for money: forget (and its preview), delete, rules/delete and aliases/delete work when the balance is zero or below, and while the account is suspended (a deletion request suspends it until it is erased), as do the golden, decision and fine-tuned model deletes below. A suspended account can also read its data on every GET route (GET /v1/memory with lines=true, the rules, golden cases, batches, fine-tuning jobs and usage), so it can still answer a data subject's request for access; it cannot store, change or decide anything. An erased account or a revoked key can do none of this. A forget or delete is charged by the prices while the balance covers it; when it does not, it is carried out all the same, its ledger line says cost 0, and usage.unbilled says what it would have cost, so removing data never takes the balance below zero.

Labels and quality

RouteBody or queryResponse
POST /v1/feedbackdecision_id, question, correct (an option, a level, or true/false)label, correct, labels_for_this_question; calibration_refit at 50, 100, 200 … labels
GET /v1/qualityquestion, days (default 30), model, source (feedback or golden)per question, model and tier (2 for the second tier's answers): labels, accuracy, calibration_error, brier, log_loss, bands, automate_at
POST /v1/calibration/fitmodel, question, source, min_labels (at least 20, default 50)results: per question, and per group of questions with the same type and number of options, the fitted temperature and whether it was kept
GET /v1/calibrationtemperatures in use
POST /v1/goldencases: {id, state, questions, expected, subject?, values?, derive?, memory?, robust?, windows?, method?}; id is 1 to 64 characters, and a case with an existing id replaces it; customer, the older name, is stored as subject (subject wins when both are sent); context, assets and images are refused as in a decisionadded, cases (at most 5,000 per tenant), warnings (an unknown_field warning, with case, for each field a case is not kept with)
GET /v1/goldencount, cases (ids, questions, expected), runs
POST /v1/golden/runmodel (default jepela)run_id, cases, accuracy, questions, changed_since_last_run (null on a model's first run), failed (each case that failed or was not run, with the reason); billed like decisions; a case that fails, or is not run because the credit ran out, is left out of the accuracy and not billed
POST /v1/golden/delete{"ids": [...]}, or {} for every case (an empty list deletes nothing); without ids, any other field with a value is refused (422) and nothing is deleteddeleted, warnings
POST /v1/finetunebase (english default, multilingual, typed-decisions); trains on your golden cases, at least 20the job: job, model, cases, held_out, state; 409 while another of your jobs runs or the engine is off. See Fine-tuning
GET /v1/finetunejobs, min_cases, bases (the bases the engine serves now)
GET /v1/finetune/<job>state (running, saving, done, failed with error), result (won, base_accuracy, tuned_accuracy, held_out_questions, log losses, training_seconds), usable
POST /v1/finetune/deletemodeldeleted; its files and its name are gone
POST /v1/decisions/delete{}; a field with a value is refused (422) and nothing is deleteddecisions_deleted (question records), labels_deleted, batches_deleted, warnings: every stored decision record and label of the tenant, and every batch with its requests and results (a running one stops); the answers the cache keeps for the tenant go too

Decision records (the decision id, question ids and a hash of each question, the probabilities before and after calibration; never the state or the question text) are kept 30 days, then deleted automatically. Batches are kept 30 days from the upload, then deleted with their requests and results. Answers kept by cache (probabilities and option names, in the gateway's memory) are served for at most 24 hours.

Batches

RouteBodyResponse
POST /v1/batches{"requests": [...]}, or JSON Lines with Content-Type: application/x-ndjson; each request a /v1/systemone body with an optional custom_id; at most 5,000the job: id, status, total
GET /v1/batchesbatches
GET /v1/batches/<id>status (queued, running, done, stopped: no credit left, stopped: account suspended, stopped: key revoked, or failed with error when the job itself broke), total, done, succeeded, failed, cost, expires (when it is deleted), removed (requests taken out when their subject's memory was deleted)
GET /v1/batches/<id>/resultsJSON Lines: custom_id, status, response or error, in the order sent

Account

RouteResponse
GET /v1/modelsmodels (name, engine, checkpoint, description, available, pinned: runtime, "engine" and the Jepela build id, and the loaded checkpoints; this tenant's fine-tuned jepela-ft-* models also carry fine_tuned: true), default, aliases
GET /v1/usagecredited, spent, debited (credit taken back by refunds and disputes), balance, requests, rate_limit_per_minute, prices, totals, recent (last 20 requests), keys
POST /v1/signup (no key)email, invite_code when the gateway asks for one → tenant, key (shown once), key_id, credit, currency, note. Off unless the owner turns it on; by default 3 sign-ups per address a day, and the new key gets 60 requests per minute

No key needed

GET /health (whether the gateway, the memory service and the engine are up, the region, and the engine's code hash; nothing about tenants or subjects), GET /v1/regions (the regions, their API addresses and status; see Regions), GET / (a how-to), GET /playground, GET /docs, GET /docs/<page>.md, GET /llms.txt, GET /robots.txt (crawlers may read these pages; the /v1 routes need a key).

Errors

{"error": {"type": "...", "message": "..."}}

StatusTypeWhen
400invalid_request_errora Content-Length that is not a number, a compressed body that does not decompress (or holds more than 64 gzip members), or JSON nested more than 100 levels deep
401authentication_errorno key, unknown key, revoked key
402payment_requiredthe tenant's balance is zero or below; never on a route that removes data: POST /v1/memory/forget (and its preview), /v1/memory/delete, /v1/memory/rules/delete, /v1/memory/aliases/delete, /v1/golden/delete, /v1/decisions/delete and /v1/finetune/delete
403signup_disabled, invite_required, account_suspendedsign-up is off, or needs an invite code; or the account is suspended (a suspended account may still read its data on every GET route and call the routes that remove data, listed under 402) or erased
404not_foundunknown route, page, batch, or a decision id of another tenant
421wrong_regionthe key (or the sign-up) belongs to another region; the message and the Location header give its API address
409conflicta limit: 5,000 golden cases for a tenant, 10,000 memory lines or 100 rules for a subject, nothing was stored; or a fine-tuning job that cannot start now (another of yours runs, or the engine is off)
413invalid_request_errorbody over the cap (a compressed body counts once decompressed)
415invalid_request_errornot application/json (or JSON Lines for batches); or a Content-Encoding other than gzip, x-gzip or zstd, or more than one
422invalid_request_errorthe reason is in the message: bad question, unknown model (a Jev name such as jev-latest is told to set the model to jepela), bad subject id, a derive or when expression that is not allowed
429rate_limit_errorover the account's requests per minute (all its keys together), or too many sign-ups; Retry-After in seconds
500server_errorthe gateway failed; the message names only the error type, and nothing was charged
502engine_errorthe engine or the memory service behind the gateway is down or answered an error; when the engine is switched off the message says the engine is off. Nothing was charged (a proxy in front of the gateway may answer 503 for the same)

Retry 429 after Retry-After, and 502 or 503 with backoff. The SDKs do both, and never send a decision again once it may have arrived.