FACTUM

For integrators

Your AI never parses our sentences.

Every answer is a code it can act on. Factum speaks to a client in closed, typed vocabularies, never in prose. Every verdict, refusal and gap is a registered code, and every refusal carries a remedy class. The prose is for humans. A client never needs it.

4 remedy classes

A client holds the whole decision table. The vocabularies are disjoint and enumerable: 4 remedy classes over a closed list of codes.

Remedy classWhat the client doesIntake codesCertify codes
retryA fault, not the caller's doing. Send it again.01
declareRe-declare the named member and resubmit.156
different_inputThe request or the document must change.2445
not_curableNothing the caller can send will work.14
Every code maps to exactly one class.4056

Closed vocabularies

Every one of the 40 document-intake codes and every one of the 56 certify-path codes carries a remedy class. Each table's totality is held when the engine is built, in both directions: a missing row and a stranger row both stop the build, and there is no default branch.

The claim covers these two registered vocabularies. It is not a claim about every refusal the service can return.

The 56 certify-path codes, across 7 stages
CodeStatusStageRemedyDeclaration neededCure fields
unknown_report_type404cataloguedifferent_inputavailableReportTypes
unknown_member_report_type404cataloguedifferent_inputavailableReportTypes
report_type_declares_no_questions422report_typedifferent_input
cross_references_need_a_segmented_document422report_typedeclareReportType.statementPoints[].inSegment
certificate_surface_unhonorable422report_typedeclareReportType.surface
member_report_type_kind_ambiguous422report_typedifferent_input
source_kind_mismatch422documentdifferent_input
document_unreadable422documentdifferent_input
pairing_required422parametersdeclarepair
no_declared_pairing422parametersdifferent_input
revision_pair_required422parametersdeclarerevision
report_type_compares_no_revision422report_typedifferent_input
revision_baseline_unreadable422documentdifferent_input
revision_declaration_malformed422parametersdifferent_input
revision_roles_name_one_document422parametersdifferent_input
revision_documents_do_not_match_the_declaration422parametersdifferent_input
as_of_malformed422parametersdifferent_input
as_of_without_history422parametersdifferent_input
as_of_entity_resolution_declared422parametersnot_curable
as_of_after_issue422parametersdifferent_input
portfolio_declaration_malformed422parametersdifferent_input
member_document_empty422parametersdifferent_input
portfolio_declares_no_documents422parametersdifferent_input
no_document_submitted422parametersdifferent_input
document_not_in_the_declared_portfolio422parametersdifferent_input
citation_corpus_not_available422corpusdeclareregisterCitationCorpus
citation_corpus_mismatch422corpusdeclareregisterCitationCorpus
citation_corpus_loading422corpusretry
citation_corpus_load_refused422corpusnot_curable
member_certificate_refused422membernot_curable
reconciliation_claim_refused422accountingnot_curable
fill_malformed422parametersdifferent_input
fill_schema_unknown422parametersdifferent_input
fill_key_not_scope422parametersdifferent_input
fill_mode_required422parametersdifferent_input
fill_column_invalid422parametersdifferent_input
fill_source_unknown422parametersdifferent_input
fill_source_duplicated422parametersdifferent_input
fill_workbook_unreadable422documentdifferent_input
fill_sheet_unknown422documentdifferent_input
fill_source_unreadable422documentdifferent_input
fill_nothing_to_fill422documentdifferent_input
composition_malformed422parametersdifferent_input
composition_schema_unknown422parametersdifferent_input
composition_key_not_scope422parametersdifferent_input
composition_id_invalid422parametersdifferent_input
composition_with_report_type422parametersdifferent_input
composition_base_unknown404cataloguedifferent_inputavailableReportTypes
composition_item_unknown422parametersdifferent_input
composition_item_duplicated422parametersdifferent_input
composition_input_not_taken422parametersdifferent_input
composition_mechanism_not_composable422parametersdifferent_input
composition_settings_conflict422parametersdifferent_input
composition_boundary_conflict422parametersdifferent_input
composition_takes_nothing422parametersdifferent_input
composition_not_registrable422report_typedifferent_input
The 40 document-intake codes
CodeRemedyDeclaration needed
format_not_in_uniondeclareformat
both_bytes_and_htmldifferent_input
no_payloaddifferent_input
content_type_unreadabledeclarecontentType
content_type_disagreesdeclarecontentType
format_disagrees_with_htmldeclareformat
filename_disagrees_with_htmldeclarefilename
not_html_contentdifferent_input
format_disagrees_with_xlsxdeclareformat
filename_disagrees_with_xlsxdeclarefilename
format_disagrees_with_docxdeclareformat
filename_disagrees_with_docxdeclarefilename
docx_unreadabledifferent_input
format_disagrees_with_jsondeclareformat
filename_disagrees_with_jsondeclarefilename
json_unreadabledifferent_input
json_not_an_arraydifferent_input
json_empty_arraydifferent_input
json_element_not_objectdifferent_input
json_nested_valuedifferent_input
json_inconsistent_keysdifferent_input
json_duplicate_keydifferent_input
json_number_not_representabledifferent_input
container_not_inspectabledifferent_input
container_not_a_spreadsheetdifferent_input
bytes_unrecogniseddifferent_input
filename_disagrees_with_pdfdeclarefilename
pdf_format_undeclareddeclareformat
format_disagrees_with_pdfdeclareformat
pdf_is_a_scannot_curable
pdf_has_no_pagesdifferent_input
workbook_unreadabledifferent_input
workbook_multi_sheetdifferent_input
sheet_no_header_blockdifferent_input
sheet_header_cell_emptydifferent_input
csv_encoding_undetermineddifferent_input
csv_dialects_too_closedifferent_input
csv_no_dialect_above_floordifferent_input
csv_emptydifferent_input
format_disagrees_with_csvdeclareformat

Verdict and code

The same code at refused says fix the input. At fault it says retry. The verdict separates them, so a client never resubmits a fault forever.

VerdictWhat it says
refusedThe engine will not do this as asked. The remedy class says what to change.
gapA declared item has no answer. It is named, with its code.
faultSomething went wrong on the service side. Retry.
mixedMore than one of the above in a single answer, each counted.

A refusal is a formal statement

Every refusal is RFC 9457 application/problem+json, so a generic client works too. Below, a PDF is sent to intake with no declared format. Two PDF formats cannot be told apart from the bytes, and the wrong reader returns confident nonsense. Factum refuses, names the member to declare, and lists the values it accepts.

Recorded outputA refusal returned by the engine on 2026-11-05. Status 422, application/problem+json; charset=utf-8.
POST /api/tenants/{tenant}/documents?scopeId={scope} · Content-Type: application/pdf · 903 bytes
{
  "requestId": "94835c8a-4ff5-4e1d-8305-1ce09bbc9f1e",
  "type": "/problems/intake/pdf_format_undeclared",
  "title": "Pdf format undeclared",
  "status": 422,
  "detail": "a PDF was supplied with no declared `format` - `pdf_table` and `pdf_form` are the same file type and cannot be told apart from the bytes. Running the …",
  "verdict": "refused",
  "echo": {
    "contentType": "application/pdf",
    "bytes": 903
  },
  "stage": "intake",
  "decisions": [
    {
      "code": "pdf_format_undeclared",
      "reason": "a PDF was supplied with no declared `format` - `pdf_table` and `pdf_form` are the same file type and cannot be told apart from the bytes. Running the …",
      "remedy": "declare",
      "cure": {
        "acceptedFormats": [
          "pdf_table",
          "pdf_form"
        ]
      },
      "declarationNeeded": "format"
    }
  ],
  "ok": false,
  "code": "pdf_format_undeclared",
  "reason": "a PDF was supplied with no declared `format` - `pdf_table` and `pdf_form` are the same file type and cannot be told apart from the bytes. Running the …",
  … 2 more fields: two that carry wire identifiers, printed in the API reference
}

The proof is a test, not a claim. Factum's own suite hands a client a refusal body with ok, code, reason, detail and title deleted, and requires it to still decide correctly. A client that fell back to the prose would not pass.

Recorded outputFour refusals returned by the engine, and what the envelope alone tells a client to do.
What was sentStatusCodeStageRemedyWhat the client does
The corpus invoice, under a report type id that is not registered.404refusedunknown_report_typecataloguedifferent_inputChange the input. The body lists 16 availableReportTypes.
64 random bytes to the certify door, typed application/octet-stream.415refusedpayloadtransportdifferent_inputChange the input. The body lists 6 acceptedContentTypes.
A one-page PDF to document intake, with no declared format.422refusedpdf_format_undeclaredintakedeclareDeclare format: one of pdf_table, pdf_form, and resubmit.
64 random bytes to document intake.422refusedbytes_unrecognisedintakedifferent_inputChange the input. These bytes will not be read.

A certificate is data

One REST call returns the certificate as JSON. The blocks below are whole objects from the recorded certificate on the demonstration page, at the paths named. Receipts and rule references are deterministic ids: the same input, run again, yields the same ones.

Recorded outputFrom run-1.json, issued 2026-11-05T23:56:23.275Z. An ellipsis marks an elision: the producer prefix of a rule id.
POST /api/tenants/{tenant}/certify?reportType=fmc-demurrage-detention-invoice-completeness&filename=syn-inv-01-complete-import.html · Authorization: Bearer {key} · Content-Type: text/html

certificate.answers[0], a located answer

{
  "dataPointId": "dp-bol-number",
  "label": "the bill of lading number",
  "status": "resolved",
  "slotId": "S1",
  "factId": "fact-81627aff4e85c7ca",
  "value": "PBLU2026034771",
  "subject": "Bill of Lading Number",
  "field": "Value",
  "at": {
    "kind": "grid",
    "table": 1,
    "row": 2,
    "col": 2
  }
}

certificate.document

{
  "name": "syn-inv-01-complete-import.html",
  "kind": "html",
  "bytes": 3442,
  "sha256": "409c2b8b6b3a9e9731ee590dd552e4b4179c284d5aecfbdcee144a78b2720104"
}

certificate.signature

{
  "alg": "ed25519",
  "canonical": "VD-CANON-1",
  "keyId": "vd-418b7ced1cea5f8e",
  "signature": "rQUj3NgAFK1qzsTCT2RgTpYuUi+iIENMiqya5Yw22/6IbSb3HJWpic6/nx7vbPsSDflW2U3mknKQAmrv/I6NBw=="
}

certificate.answers[10], an absent answer

{
  "dataPointId": "dp-earliest-return",
  "label": "the earliest return date",
  "status": "gap",
  "code": "no_fact_answers_this_point",
  "derivation": {
    "entity": "drv:071ed9039ffbfd065d19f8a9e46c640d",
    "wasGeneratedBy": "…/certify/data-point-resolution@1",
    "wasDerivedFrom": [
      "rule:data-point/fmc-demurrage-detention-invoice-completeness%2Fdp-earliest-return",
      "input:document/syn-inv-01-complete-import.html"
    ]
  },
  "decision": {
    "code": "no_fact_answers_this_point",
    "reason": "the earliest return date: no_fact_answers_this_point",
    "remedy": "not_curable",
    "evidence": {
      "family": "answers",
      "itemId": "dp-earliest-return",
      "label": "the earliest return date"
    }
  }
}

certificate.arithmetic[1], a computed check

{
  "arithmeticPointId": "ap-arith-charge-total",
  "label": "Does the number of dates charged multiplied by the daily rate equal the total amount due?",
  "status": "satisfied",
  "statement": "5 day(s) charged at 275.00 is 1375.00; the invoice states 1375.00.",
  "rounding": null,
  "cite": "§ 541.6(c)(1)–(3)",
  "derivation": {
    "entity": "drv:12250656e4fd1dc4dc741516fc95e746",
    "wasGeneratedBy": "…/arithmetic/count-times-rate-equals-total@1",
    "wasDerivedFrom": [
      "rule:arithmetic-point/fmc-demurrage-detention-invoice-completeness%2Fap-arith-charge-total",
      "node:fact-1f2875fc61c25ccc#value",
      "node:fact-d6ee3650e96769fd#value",
      "node:fact-34fe5c129f430c4f#value",
      "node:fact-f6fe79882ec59943#value"
    ]
  }
}

The full signed bytes: run-1.json. Check them with the Factum Verifier.

Canonical form and signature

A certificate is signed with Ed25519 over a published canonical form. The rule is short enough to implement from its statement, and the verifier carries a frozen known-answer test of it.

Retired public keys stay published, and the certificate's own key id selects among them. A certificate issued before a rotation still verifies.

The canonical rule, as the engine states it

VD-CANON-1: UTF-8 JSON, no insignificant whitespace, object keys sorted ascending by UTF-16 code unit, undefined object values dropped and undefined array elements emitted as null (both matching JSON.stringify, so an in-memory certificate and a parsed one canonicalize identically), strings and numbers emitted by JSON.stringify (ECMAScript QuoteJSONString and Number::toString, as RFC 8785 specifies), -0 normalised to 0, and a REFUSAL on a non-finite number, on any value that is not JSON data, and on a key named "signature" at any depth.

The same doors as the Console

The Console is a reference client. It calls the doors a program calls, and every screen prints the call it rendered. Nothing exists only in the interface.

A composed checklist rides on the certify call as one header that carries the declaration. A two-source report names its declared pairing on the same call. A workbook fill, a revision comparison and document intake are doors of the same shape, with the same refusal envelope.

An AI engine is registered per tenant: a model, a key and a transport. Two vendors' own interfaces are spoken, and so is the common chat-completions interface, at an address the tenant gives.

Registration is not admission. An engine is admitted by measurement on the duties it will perform, and an engine that has not been measured writes nothing. How admission is measured

Each door is shown on a recorded run, request line included: worked examples.

Arguments, as a graph

The comparison report type serializes its verdicts as an argument graph in the Argument Interchange Format. The graph attests structure. An argument that is "in" has not thereby "won".

The API reference, with every wire identifier printed exactly, is provided with access.