All four platforms available
Reference · all four platforms availableDocs v1.0 · KernelPort v1 call names and shapes are stable

API reference

Errors and refusals

A refusal is elan1's typed "no" to a write intent, and it is different from an error. The refusal envelope names the rung that refused and a reason code your code can act on. Errors mean the request could not be processed. This page lists both and shows how to handle each safely.

A refusal is an answer, not a failure. It returns a typed code, still writes a proof record, and is metered at 40% of one governed action. This page sets out the three ways elan1 Govern can say no, the envelope each comes in, and how your code should handle them.

Docs v1.0Examples are illustrative specimens, not customer data

Docs menu

Overview

Callers meet three kinds of “no”. A rule refusal means a rung of the Right Path, or a named person, said no. A platform refusal means something on our side failed, so nothing was allowed. A contract error means the request itself did not meet the contract and never reached the Right Path. Each has a different envelope, a different effect on the chain and the meter, and a different right response.

Codes on this page are illustrative. Real refusal codes come from the closed register in KernelPort v1, shared with customers in the full reference. The format and the handling rules below are what to build against; the code strings here are examples.

Three kinds of no

class: ruleRule refusal

A rung said no — STOP, identity, write door, a pre-gate, the versioned rules, the guardrail — or a person did, or a hold expired.

Typed code · proof record written · 0.4 governed action

class: platformPlatform refusal

A dependency failed. elan1 fails closed: if elan1 is down, allows stop; a dependency outage produces a platform refusal, never an allow.

Typed code · recorded where the chain is reachable · never billed

class: contractContract error

The request did not meet the contract: its shape, its token, its tenant, or a version pin such as “latest” on a production key.

4xx · no Right Path · no record · no meter

The three classes side by side
QuestionRule refusalPlatform refusalContract error
Who decidedYour rules, or a named personNobody: elan1 could not decideNobody: the request was malformed
Reached the Right PathYesYes, until the failureNo
Proof recordWrittenWritten where the chain writer is reachableNone
Meter0.4 of a governed actionNever billedNothing
Final for its write_idYesYesNo — nothing was recorded
What to doShow a person; fix the cause; send a new intentDo not write; try later as a new intentFix the request; resend

The refusal envelope

A refusal comes back in the same envelope as every other outcome, with a refusal block added. The block is built for two readers: your code, which branches on class, and a person, who reads the plain words.

httpRule refusal · G4 at rung 3HTTP code: Rule refusal · G4 at rung 3
HTTP/1.1 200 OK

{
  "outcome": "refused",
  "rung": 3,
  "write_id": "wr_specimen_7c3",
  "refusal": {
    "code": "G4_GST_RATE_MISMATCH",
    "class": "rule",
    "reason": "The invoice charges 12% GST; the rate table says 18% for HSN 8483.",
    "remedy": "Correct the rate on the invoice, or update the rate table under dual control.",
    "contact": "usr_specimen_controller"
  },
  "record": { "id": "00000000-0000-0000-0000-000000000000", "version": 0 },
  "proof": { "seal": "elan1.proof.seal.v6", "seq": 1043, "digest": "sha256:4be190c7…" },
  "meter": { "governed_actions": 0.4, "inr": 0.64 }
}

# Metered at 0.4 of a governed action: ₹0.64 at L1, beyond the allowance.
Illustrative specimen. Not customer data.
httpPlatform refusalHTTP code: Platform refusal
HTTP/1.1 503 Service Unavailable

{
  "outcome": "refused",
  "write_id": "wr_specimen_7c4",
  "refusal": {
    "code": "<platform code from the frozen register>",
    "class": "platform",
    "reason": "A dependency of the gate check did not answer. Nothing was allowed."
  },
  "meter": { "governed_actions": 0, "inr": 0 }
}

# Ours, not a rule. Never billed. Never an allow.
Illustrative specimen. Not customer data.
Refusal envelope fields
FieldMeaningProof record
outcomeAlways refused.—
rungThe rung where the intent stopped: 0b STOP to 7 ACT.Bound to the record
refusal.codeA typed code from the closed register. An opaque string: compare it, never parse it.Bound to the record
refusal.classrule or platform. Branch on this.—
refusal.reasonWhat happened, in plain words, from the declared policy — never from a model.—
refusal.remedyWhat to fix.—
refusal.contactWho to ask.—
recordA zeroed record_id and record_version 0, because nothing changed.F12 · F13
proofThe seal, sequence and digest of the refusal's own proof record.F02 · digest
meter0.4 of a governed action, held there by a database check. Within the allowance, it uses 0.4 of an included action; beyond it, it costs 40% of your level's rate, for example ₹0.64 at L1.—

Field names are illustrative; the exact names are in the KernelPort v1 reference. Conformance test C8 checks the shape: a register code, the rung, a zeroed record_id, record_version 0 and a meter quantity of 0.4.

Refusal codes

Every refusal code comes from one closed register. A code that is not in the register cannot be emitted, so a caller can always tell a known refusal from anything else. The register, its codes and the rung each code belongs to are part of KernelPort v1 and are shared with customers; this site uses a handful of illustrative codes, always marked so.

Illustrative codes used on this site
Code (illustrative)ClassRungWhere you will see it
G4_GST_RATE_MISMATCHrule3 PRE-GATESThe GST gate (G4) finds an invoice rate that does not match the rate table.
GATE.PRECONDITION_FAILEDrule3 PRE-GATESThe precondition gate (G9) fails against your data.
APPROVER_REFUSEDrule6 AUTONOMYThe named approver says no.
APPROVAL_EXPIREDrule6 AUTONOMYNobody answers before the hold expires. Expiry is a refusal.

Rules for callers

  • Treat a code as an opaque, case-sensitive string. The two styles above — underscored and dotted — are both illustrative; do not depend on either.
  • Branch on outcome and refusal.class. Use the code for display, logging and support, not for control flow you cannot change later.
  • Never invent a code, and never map an unknown code to an allow. An unknown code is still a refusal.

Where refusals come from

Any rung can refuse. The rung tells a person where to look; the code tells them exactly what failed.

What each rung can refuse
RungRefuses whenModel involvedCode
0b STOPWrites are stopped for this tenant.NoFrom the frozen register
1 IDENTITYThe asker cannot be identified, or is acting outside its scope.NoFrom the frozen register
2 WRITE DOORThe write type is not on the declaration's list of allowed write types.NoFrom the frozen register
3 PRE-GATESOne of the nine pre-gates, G1–G9, fails against your own data. Six can never be skipped.NoG4_GST_RATE_MISMATCH, GATE.PRECONDITION_FAILED (illustrative)
4 evaluate()The versioned rules say no.NoFrom the frozen register
5 GUARDRAILThe blast radius is beyond the declared limit.NoFrom the frozen register
6 AUTONOMYThe named approver refuses, or the hold expires.NoAPPROVER_REFUSED, APPROVAL_EXPIRED (illustrative)
7 ACTIn Mode A, the single writer cannot make the change as approved.NoFrom the frozen register

Rungs 8 PROOF and 9 METERED do not refuse a write on its merits. If the chain writer cannot seal, nothing is allowed and the intent ends as a platform refusal. Rungs 3 to 8 cannot reach a model client: a model may produce a write intent, never an allow or a refusal reason.

Handling each case

What your integration should do
You receiveYour system shouldShow a personRetry
CommittedStore proof.seq and proof.digest with the change. In Mode B, make the write now.Nothing, or a receipt—
EscalatedNot write. Wait for the obligation to resolve by webhook, or read it.Who decides, and by whenNo. Wait.
Refused · ruleNot write. Keep the code, rung and proof sequence with the intent.The code, in plain words; what to fix; who to askNot with the same write_id. Fix the cause, then send a new intent.
Refused · platformNot write. Fail closed.That nothing was written, and whyLater, as a new intent with a new write_id.
Contract errorFix the request. Nothing was recorded.Usually nobody: this is a defect in the callerAfter fixing it.
No answerNot write. Treat silence as no.That the change is waitingYes, with the same write_id; you get the original outcome.
Never treat a timeout as an allow. If elan1 is unreachable, allows stop. elan1 Run tooling fails closed in exactly this case, and your integration should too.

A handler, end to end

The same logic as code. It persists the write_id before sending, retries only with the same write_id, fails closed on silence, and writes only on committed.

tsOutcome handlerTS code: Outcome handler
// Illustrative. Exact field names are in the KernelPort v1 reference.
type Outcome = "committed" | "refused" | "escalated";

async function propose(intent: WriteIntent): Promise<void> {
  await store.saveIntent(intent);                       // persist write_id first

  let res: Response;
  try {
    res = await sendWithRetry(intent);                  // same write_id on every retry
  } catch {
    return hold(intent, "elan1 did not answer. Nothing was written.");   // fail closed
  }

  if (res.status >= 400 && res.status < 500) {
    const { error } = await res.json();
    return fixRequest(intent, error);                   // contract error: no record exists
  }

  const body = await res.json();
  switch (body.outcome as Outcome) {
    case "committed":
      await writer.apply(intent, body.proof);           // Mode B: your system writes now
      return;
    case "escalated":
      return waitFor(body.obligation);                  // webhook resolves it; expiry is a refusal
    case "refused":
      if (body.refusal.class === "platform") {
        return hold(intent, body.refusal.reason);       // ours; never billed; try later as a new intent
      }
      return showRefusal(intent, body.refusal, body.rung); // a rule or a person said no; 0.4 of a governed action
  }
}
Illustrative. Check names against the KernelPort v1 reference.
httpContract error · no recordHTTP code: Contract error · no record
HTTP/1.1 400 Bad Request

{
  "error": {
    "class": "contract",
    "reason": "write_type 'journal_entry.repost' is not a write type this API knows.",
    "field": "write_type"
  }
}

# The request never reached the Right Path. No record.
Illustrative specimen. Not customer data.

Showing a refusal to a person

A refusal is only useful if the person who must act on it can read it. elan1 Work’s refusal card is the pattern to follow: a typed code in plain words, what to fix, and who to ask.

Heading
REFUSED, never an error colour alone: always the word.
Code
The typed code in a monospace face, exactly as received.
Where
The rung where it stopped, for example 3 PRE-GATES.
Why
refusal.reason in plain words.
Next
refusal.remedy and refusal.contact.
Evidence
“Recorded · 0.4 governed action”, with the proof sequence so anyone can find the record.

Testing your handling

Four conformance tests from the C1–C14 suite exercise exactly this behaviour. A third-party agent makes no claim to run on elan1 Govern until all fourteen pass.

Conformance tests that cover errors and refusals
TestChecks
C5 Fail-closed matrixEvery dependency outage produces a platform refusal, never an allow.
C7 IdempotencyReplaying an intent returns the original outcome, sequence and digest, and no second meter event.
C8 Refusal shapeA refusal returns a register code, the rung, a zeroed record_id, record_version 0 and a meter quantity of 0.4.
C11 Approval separationNo approval where the approver is the requester, and none by an agent token.

See Conformance for the whole suite and how to run it.

What the customer reference covers

  • The refusal register: every code, its class and its rung. Codes on this site are illustrative.
  • The names of the refusal fields, including remedy and contact.
  • The exact HTTP status codes for contract errors and platform refusals.
  • How a reused write_id with a different payload is answered. Design as if it is refused.

These are fixed in KernelPort v1 and the record shape, and are listed in the full reference customers receive with their workspace.

// BUILD WITH US

Get the full reference for your integration.

Customers receive the full KernelPort v1 reference with their workspace. Ten early-adopter design partners also help shape what comes next.