Skip to the text
Et Al.
Vol. 1 · No. 1 · November 2026 API reference p. 5.2

API reference

Six endpoints under https://api.etal-labs.com/v1. Requests and responses are JSON.

Basics

Authentication

Send your key in the Authorization header as a bearer token. x-api-key is accepted too. Keys are created and revoked in the console, and a revoked key stops working at once.

Listing 1. http
POST /v1/detect HTTP/1.1
Host: api.etal-labs.com
Authorization: Bearer etal_...
Content-Type: application/json

Text format

All text is Markdown. Write math as $…$, citations as [@key] and footnotes as [^1]; QED is trained to carry them through unchanged. Separate paragraphs with a blank line.

Models and versions

A model string names a fixed checkpoint and does not change: qed-2026b, sic-2026a. The aliases qed and sic point at the current default of each family and move when a new version is released. Pin the dated string if you need results to stay comparable.

Limits

Each account has a rate limit in requests per minute, a cap on concurrent requests and a monthly allowance in US dollars at list price. A request that would exceed one of them is refused with status 429 and an error type that says which. Retry-After is set when waiting will help. Models and limits has the defaults.

What is stored

For each request we keep the endpoint, model, sizes, cost, latency and status. We do not keep the text you send or the text returned.

Write

POST/v1/write

QED writes a passage from notes (compose) or edits a draft (polish, revoice). It is not streamed; a 200-word passage takes about three seconds.

modelstringqed or qed-2026b. Defaults to qed.
taskstringcompose, polish or revoice. Defaults to compose when brief is present, otherwise polish.
briefstring, composeThe points to cover, one per line. At most 2,000 words. Lines are treated as separate points; a single block of prose is split into sentences.
textstring, polish and revoiceThe draft to edit. At most 2,000 words; edits are reliable up to about 400. Longer drafts are edited lightly, and less towards the end; a note in the response gives the share changed at the start and at the end.
titlestring, optionalThe article's title. It helps QED pitch the passage.
target_wordsinteger, optionalA guide to the length of a composed passage, 30 to 1,500. QED writes what the notes support and does not pad, so sparse notes give a short passage whatever the target; a note in the response says when the result is well short.
voice.exemplarsarray of strings, optionalUp to five passages by the author, 1,500 words in total; two or three of 150 to 350 words work best. QED writes in their voice. They shape compose and revoice; polish makes light edits whatever the voice.
documentobject, optional, composeWrite the passage as one section of a longer document. Fields: thesis (the document's overall point), outline (one line per section, saying what it does), position (which section this is, from 1), so_far (the text already written; the last 1,200 words are used) and heading (optional heading line).
temperaturenumber, optional0 to 1.2. Defaults to 0.7.

The response:

textThe passage, in Markdown.
words, truncatedLength, and whether the output hit the length limit.
changed_ratio, unchangedFor polish and revoice: the share of words that differ from the draft, 0 to 1. If QED returns the draft as it was, unchanged is true, a note says so, and the call is not charged.
guardWhat the output guard did: samples (1 means the first output passed), repaired, and the problems seen (loop, markup, repetition, exponent, fragment, structure, unchanged, truncated, empty). Resamples are metered.
usageinput_tokens, output_tokens and cost_usd at list price.
notesPlain-language cautions about this output, if any.
Listing 2. json
{
  "id": "wr_d5c89318-…", "object": "writing", "model": "qed-2026b", "task": "compose",
  "text": "Vitamin D did not reduce fractures (HR 0.98, 95% CI 0.89 to 1.07, 25,871 participants, median follow-up 5.3 y), even in the subgroup with low baseline levels.",
  "words": 27, "truncated": false,
  "guard": {"samples": 1, "repaired": false, "problems": []},
  "usage": {"input_tokens": 155, "output_tokens": 60, "cost_usd": 0.00067},
  "notes": []
}

Detect

POST/v1/detect

Sic scores every sentence of a text. It takes about two seconds per 1,000 words.

modelstringsic or sic-2026a. Defaults to sic.
textstringEnglish scientific prose, at most 10,000 words. Blank lines separate paragraphs.
include_textboolean, optionalSet to false to omit each sentence's text from the response and keep only offsets.
include_error_tableboolean, optionalSet to true to add the full error-rate table as error_table. It is also on GET /v1/models/sic-2026a.

The response:

sentencesOne entry per sentence: start and end, text, score (0 to 1) and label. Offsets index your text in Unicode code points, as Python does; in JavaScript, convert with Array.from(text) if the text may contain characters outside the basic plane.
scoreNot a probability. Only the two thresholds are calibrated: a sentence at 0.91 is below both and carries no finding. Scores also reflect the surrounding passage, so Sic locates machine-written paragraphs, not single sentences.
labelabove_strict, above_sensitive or below_thresholds. A label describes the score and nothing else.
thresholdsstrict (0.9946) and sensitive (0.9544), fixed on calibration data.
documentmax_score, above_strict, above_sensitive, and counts of sentences above each threshold.
error_ratesFor each threshold, the share of human-written research papers and commentary that have a sentence above it. The full table, with counts and detection rates, is Table 1 on the Sic page and GET /v1/models/sic-2026a.
usagewords and cost_usd.

Compare

POST/v1/compare

Checks a model's output against its source. Use it on every QED output.

sourcestringThe original text, or the notes. At most 4,000 words.
outputstringThe text to check. At most 4,000 words.
kindstringrewrite (default) or draft. For a draft from notes the length, hedging and negation checks are skipped, since notes are not prose.
judgebooleanAdd a claim-by-claim reading by a language model. Defaults to false. Costs about $0.005 and takes a few seconds.

The response has warnings, each with a type and a severity, and pass, which is true when no warning is an error.

errorSomething checkable went missing or appeared. Fix these.
numbers_missing, numbers_addedNumbers in the source that are nowhere in the output, and the reverse. items lists them. 0.05\% and 0.05% are the same number.
citations_…, math_…, footnotes_missingThe same for citation keys, math spans and footnote marks. A key cited twice is not reported.
claims_added, claims_missing, claims_changedWith judge: counts of claims the judge found. The claims, with the passages quoted, are in judge.
reviewA heuristic that often fires on faithful text. Each carries where, the sentences it fired on. Read them and decide.
hedging_reducedThe output has fewer hedging words (may, suggests, likely) than the source.
negation_count_changedThe number of negations differs; added and removed name the words.
causal_terms_addedThe output uses causal or certainty verbs the source did not.
length_ratioThe output is under 0.75 or over 1.33 times the length of the source.

The judge is a language model and can be wrong. It quotes the passages it relies on, so each finding can be checked.

Disclosure

POST/v1/disclosure

Drafts an AI-use disclosure from your account's own recent requests. It counts calls; it has no access to the text.

stylestringgeneric (default), elsevier, icmje or springer.
since_hoursintegerHow far back to count. Defaults to 72; at most 90 days.
sectionsstring, optionalWhere the model was used, for example "the Introduction and Discussion".

The response has statement, placement (where that style of journal expects it), based_on (the counts used) and authors_must_confirm. The statement reports only what your request log shows. Its closing sentence, that the authors reviewed and edited the output, is yours to make true.

Models and usage

GET/v1/models

Lists the models with their limits, list prices and status, and the models that are named but not released. A checkpoint that has been replaced has status superseded and stays callable by its dated string.

GET/v1/models/{id}

One model. Aliases resolve to the model they point at.

GET/v1/usage

This month's allowance, the amount used, your rate limits, and counts per endpoint.

Errors

Errors come back with a matching HTTP status and one shape:

Listing 3. json
{"error": {"type": "input_too_long", "message": "`text` has 2500 words; the limit is 2000. Split the text by section and send the parts separately.", "param": "text"}}
400 invalid_request, invalid_json, input_too_longThe request is malformed. param names the field.
401 authentication_required, invalid_api_keyNo key, or a key that is wrong or revoked.
403 account_suspendedWrite to hello@etal-labs.com.
404 model_not_found, not_foundUnknown model string or path.
413 request_too_largeThe body is over 2 MB.
429 rate_limited, too_many_concurrent_requestsSlow down. Retry-After says for how long.
429 allowance_exhaustedThe request could take the account past its monthly allowance.
502 upstream_errorA model backend did not answer. Nothing was charged; retry.
503 capacity_pausedThe service has reached its daily capacity. It resets at 00:00 UTC.