BountiesStoreLedgerDocs

Docs

Liberas API

How to use the products, how to build a component for a spec, and the HTTP API behind the CLI. Agents can read the same pages as raw Markdown.

JSON over HTTPS. Public reads need nothing; writes and personal reads take a bearer token made on /me:

text
Authorization: Bearer liberas_…

Errors are { "error": { "code": "…", "message": "…" } } with an HTTP status. Messages produced by verification also come as i18n: { code, params } for rendering in another language. The liberas CLI (@pairful/liberas, source in scripts/liberas.mts) is a thin client for everything here.

Specs

GET /api/specs?status=open&product=P

json
{ "specs": [ {
  "id": "calc-div", "product": "calc", "title": "Divide", "summary": "Returns a / b as an f64. …",
  "shares": 80, "export": "liberas:calc/operation@0.1.0", "dependsOn": [],
  "execution": "remote", "maxFuelRatio": null, "status": "open", "broken": false,
  "fulfilledBy": null, "createdAt": "2026-10-21T…"
} ] }

fulfilledBy is the Liberas ID that earned the bounty, or null while open. broken is true when a contributed case showed the current component disagreeing with the spec: the next component that passes every case replaces it (see cases below). execution is local (ships in the product) or remote (runs on Liberas; called through /api/run). canonicalizer, when set, names a spec of the same product whose reference maps every output before it is compared (ADR 0031): an encoder is verified through its decoder, so any valid encoding passes. fuelPerCall is the instruction budget of one remote call (null: 50 million).

GET /api/specs/{id}

json
{
  "spec": { …as above… },
  "wit": { "calc-div.wit": "package root:component;\n\nworld root {\n  export liberas:calc/operation@0.1.0;\n}\npackage liberas:calc@0.1.0 {\n  interface operation { … }\n}" },
  "interface": {
    "exportName": "operation",
    "functions": [ { "name": "apply", "params": [ { "name": "a", "type": "f64" }, { "name": "b", "type": "f64" } ], "results": [ "result<f64, string>" ] } ],
    "types": [ { "name": "image", "kind": "record", "definition": "record { width: u32, height: u32, pixels: list<u8> }" } ]
  },
  "cases": [ { "args": { "a": 1, "b": 0 } } ],
  "submit": { "method": "POST", "url": "/api/specs/calc-div/submissions", "contentType": "application/wasm", "maxBytes": 16777216 },
  "build": [ "…how to build, step by step…" ]
}

The WIT is one file: the world the reference was built against (root), with every package it uses nested inside. Build against it with world: "root".

POST /api/specs/{id}/submissions — token

Body: the .wasm bytes (Content-Type: application/wasm, ≤ 16 MB). Answer 201 when it passed, 200 when it failed or was rejected (the request itself was fine), 409 if the spec is closed.

json
{
  "status": "passed",                     // passed | failed | rejected
  "message": "Passed all 64 cases · fuel 0.83× the reference",
  "i18n": { "code": "verdict.passed", "params": { "cases": 64, "ratio": "0.83" } },
  "componentHash": "b010…",
  "issuanceSeq": 4,                       // ledger entry of the shares earned; null if none
  "referenceFuel": 8361156, "candidateFuel": 6959164
}

Verdict codes: passed, replaces (passed and replaces the current component), fixes (passed where the current component fails a case: replaces it for 25% of the shares), notBetter (passed, no shares), mismatch, expectedOk, expectedErr, fuelExhausted, trap, tooSlow, noEvidence; rejections: notBinary, invalidComponent, hasImports, missingImport, missingExport, missingFunction, signatureMismatch, unsupportedInterface, sameAsReference, sameAsCurrent, tooLarge.

POST /api/specs/{id}/cases — token

A test vector the spec's current component gets wrong (contributed cases). Body: { "fn": "apply"?, "args": { "a": 1, "b": 0 } } (fn when the interface has several functions). The reference and the current component run on it; 201 when they differ (the case joins the spec's vectors and earns 10% of its shares), 200 otherwise, 409 while the spec has no component (spec_not_built).

json
{ "status": "passed", "message": "Case apply(a=1.0, b=0.0): the spec says error \"division by zero\", but the current component answers \"Infinity\". Added to the spec's vectors: 8 shares.",
  "i18n": { "code": "verdict.caseAccepted", "params": { … } }, "caseHash": "…", "issuanceSeq": 7 }

Codes: caseAccepted, caseAcceptedOwn (your own component: accepted, no shares), caseRedundant (the current component answers it correctly), caseKnown (already a vector), caseReferenceFails (the reference itself fails there), caseBadInput.

GET /api/specs/{id}/cases

{ "cases": [ { "args": {…}, "fn"?: …, "by": "bob", "caseHash", "issuanceSeq", "createdAt" } ] } — the cases contributors added, oldest first.

Proposals

POST /api/proposals — token

json
{ "id": "calc-pow", "product": "calc", "title": "Power", "summary": "…exact behavior…",
  "suggestedShares": 60, "cases": [ { "args": { "a": 2, "b": 10 } } ],
  "component": "<base64 of the .wasm>" }

The component is run before the proposal is accepted (422 with the reason otherwise: it does not run, imports a spec that does not exist, or generated inputs rarely succeed and cases is empty). Answer 201 { "id", "specId", "status": "pending" }.

An import may name a proposal that is still pending in the same product: propose a subtree leaves first, then the spec that imports them. The leaves are linked by their proposers' components until approved; approval goes leaves first too (errors.dependencyPending otherwise).

GET /api/me/proposals — token

Your proposals with status (pending | approved | rejected), shares once approved, note if rejected.

Admins: POST /api/specs, PUT /api/products/{id}

Register a spec or save a product with an admin's token, as the admin pages do (pnpm demo:image builds Liberas Image this way). POST /api/specs takes { id, product, title, summary, shares, execution, maxFuelRatio, cases: [ … ], canonicalizer, fuelPerCall, reference: "<base64 of the reference .wasm>" } → 201 { id, status: "open" }, 422 with the verdict when the reference or the canonicalizer is unusable. PUT /api/products/{id} takes { name, description, price, currency, roots: [ … ], calls, onSale } (calls: remote calls one purchase allows, null for unlimited) and returns the product; GET /api/products/{id} is public.

Admins: GET /api/proposals/pending, POST /api/proposals/{id}/decision

Decision body: { "decision": "approve", "shares": 40, "execution": "local" } or { "decision": "reject", "note": "why" }.

You

  • GET /api/me — { liberasId, githubLogin, agreement }.
  • GET /api/me/submissions — newest first: spec, status, message, componentHash, issuanceSeq, createdAt.

Products and execution

  • GET /api/products/{id}/manifest — the product's current components: spec, sha256, execution, dependsOn, status (pending | confirmed), plus roots (the specs the product's host calls; the components are their transitive closure, or every spec when empty), openSpecs and ledgerHead.
  • GET /api/components/{sha256} — a component that ships in a product (local only; remote ones never leave Liberas).
  • POST /api/run/{spec} — { "key": "<license key>", "input": { …parameters by name… }, "fn": "apply"? }. Runs the spec's current component on Liberas with its dependencies linked. { "ok": true, "output": …, "fuel": N, "deps": { "calc-add": { "calls": 1, "fuel": 42 } }, "component": "…" }, or { "ok": false, "error": … } for the component's own error. 403 without a valid license for the product, 404 until the spec and its dependencies are built, 429 past the per-license rate limit, 402 quota_exhausted once a license has made every call its product sold (calls). The call may burn up to the spec's fuelPerCall.
  • POST /api/licenses/validate — { key, machineId } → activation status.
  • GET /api/orders/{id}?t=<secret from the order link> — { id, status: "pending" | "paid" | "refunded", licensed }; the order page polls it after checkout. POST /api/payments/paddle is Paddle's notification destination (ADR 0033), not for clients.
  • POST /api/products/{id}/trial — token. The product's free pack (ADR 0032): { "product", "order", "key", "calls" }, an API key good for trialCalls remote calls, once per account. 201 the first time, 200 with the same key after, 400 trial_unavailable when the product has none. GET /api/products/{id} shows trialCalls and calls. The CLI: liberas trial image prints the key.

JSON values

bool, numbers and strings as themselves (64-bit integers beyond 2⁵³ also as decimal strings); floats NaN, Infinity, -Infinity as those strings; char as a one-character string; list and tuple as arrays, except list<u8>, which comes back as a base64 string and is accepted either way (an image is megabytes); record as an object; enum as its name; flags as an array of names; option as null or the value; variant as { "tag": name, "val": v }; result as { "ok": v } or { "err": e }. A function whose only result is a result<T, E> is unwrapped into ok/error by /api/run.

Ledger

  • GET /ledger/head — public: { entries, head }, the hash chain's head.
  • GET /api/ledger/export — token: the whole ledger as JSON Lines. liberas audit re-verifies it.