Start

The AI contract

Munim's AI never types a number and never changes the books. Any frontend that shows AI output keeps those two promises by following this page.

1. Numbers arrive as references

AI prose carries no digits of its own. Every number, date or quotation is a reference token that the server already resolved against the business's own records:

  • ⟨snap:<snapshot-id>·<row>·<column>⟩ — one cell of a snapshot: a fixed, hashed query result minted by the core server.
  • ⟨cite:<kind>:<id>⟩, kind one of row, doc, msg, claim, snapshot — a cited record or a span of a document (·start-end).

The delimiters are U+27E8 ⟨, U+27E9 ⟩ and the middle dot U+00B7 ·. Each segment's refs array lists the bare ids it uses (snap:<id>, row:core.table:<id>).

An answer (ids shortened)
{
  "answer_id": "0192…",
  "segments": [
    {
      "text": "Paneer cost rose to ⟨snap:0192…·4·unit_cost⟩ last week.",
      "refs": ["snap:0192…"]
    }
  ],
  "abstained": false,
  "reason_code": null
}
// resolved_refs (beside the envelope in the response):
[{ "ref": "⟨snap:0192…·4·unit_cost⟩", "value": 41000 }]

2. Render references, not text

Rendering rule
for each segment:
  split segment.text on ref tokens  ⟨…⟩
  plain text  → render as text (never as HTML)
  ref token   → look it up in resolved_refs by the exact token
                found     → format the value (money in minor units, the
                            business's locale) inside a citation chip that
                            opens the snapshot rows
                not found → show the "Cannot verify" block instead
if envelope.abstained:
  show "Cannot verify" with your copy for envelope.reason_code

Show the value inside a citation chip that opens the rows behind it (GET /v1/snapshots/{id}). Model text is always rendered as plain text — never through a raw-HTML path.

3. "Cannot verify" is an answer

When the server cannot trace a claim — a missing file, a reference that does not resolve, a number written outside a reference — the envelope comes back with abstained: true and a reason_code. Show a calm "Cannot verify" block with your own copy for that code. It is not an error state and never shows the model's own words.

The reason codes are listed on the Errors page.

4. Changes are drafts until a person approves

  • Anything the AI proposes — a price, a purchase order, a roster, a listing — is a draft in the inbox, never applied by itself.
  • Each proposed number is a reference shown beside its value. A person may edit a value; the edit is labelled as theirs.
  • A named person with the authority approves (POST /v1/drafts/{id}:approve) or rejects with a reason. If the numbers behind the draft changed since it was written, it comes back stale and nothing runs.
  • Your UI must never offer an "auto-apply" state.

5. When AI is unavailable

Hide the AI surface or show a plain template. AI features degrade to hidden — never to an error screen.

Schemas

Rendered from the OpenAPI file.

AnswerEnvelopecontract answer-envelope
  • answer_idUuid7required
  • segmentsarray of objectrequired
    • textstringrequired
      min length 1max length 2000
    • refsarray of RefIdrequired
  • abstainedbooleanrequired
  • reason_codeReasonCode | nulloptional
  • follow_upenum (1) | nulloptional
    dig_deeper
FindingEnvelopecontract finding-envelope
  • finding_idUuid7required
  • segmentsarray of objectrequired
    • textstringrequired
      min length 1max length 2000
    • refsarray of RefIdrequired
  • impact_refstring | nullrequired
    pattern ^⟨snap:[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}·(?:0|[1-9][0-9]*)·[a-z_][a-z0-9_]*⟩$
  • abstainedbooleanrequired
  • reason_codeReasonCode | nulloptional
Snapshotcontract snapshot
  • snapshot_idUuid7required
  • template_idstringrequired
    pattern ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$
  • paramsmap of one of 4 | nullrequired
  • run_idUuid7 | nullrequired
  • minted_by_member_idUuid7 | nullrequired
  • minted_by_operator_idUuid7 | nullrequired
  • outlet_scopeone of 2required
  • columnsarray of objectrequired
    • namestringrequired
      pattern ^[a-z_][a-z0-9_]*$
    • typeenum (7)required
      integernumerictextdatetimestamptzbooleanuuid
    • numeric_sourceone of 2 | nulloptional
  • rowsarray of map of one of 3 | nullrequired
    max items 500
  • row_countintegerrequired
    ≥ 0≤ 500
  • sha256stringrequired
    pattern ^[0-9a-f]{64}$
  • created_atIsoDatetimerequired
V1AskResponsecontract v1.ask.response
  • thread_idUuid7required
  • turnobjectrequired
    • turn_idUuid7required
    • turn_nointegerrequired
      ≥ 1≤ 9007199254740991
    • addressed_toenum (8) | nullrequired
      cataloguekitcheninventory_purchasingfinancepeoplecustomerscompliancemarket
    • taintedbooleanrequired
  • answerobjectrequired
    • answer_idUuid7required
    • envelopeAnswerEnveloperequired
    • resolved_refsarray of objectrequired
      • refRefrequired
      • valueone of 3 | nullrequired
    • signatureobjectrequired
      • keyenum (2)required
        answer.signature.via_manageranswer.signature.manager
      • specialistone of 2required
      • name_keystringrequired
        min length 1