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

API reference

HTTP API

The elan1 HTTP API lets any system submit write intents and read proof records over /v1 endpoints. This reference covers authentication tokens, idempotency keys that make retries safe, the response envelopes for allows and refusals, and the endpoints for reading records and verifying the chain.

KernelPort v1 over HTTP, at /v1. For agents and services that do not speak MCP, and for elan1 Decide and elan1 Run tooling. Every write intent is evaluated against your rules and recorded, in Mode B.

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

Docs menu

Overview

The HTTP API carries the same six KernelPort calls, the same three outcomes and the same proof records as the SDK and the MCP server. It adds what a networked caller needs: tokens with scopes, idempotency keys, a response envelope you can branch on, and HMAC-authenticated webhooks for writes that wait on a person.

Base path
/v1 on your elan1 Govern endpoint.
Mode
Mode B · Attested advisory Evaluated against your rules and recorded. Your system holds the write credential.
Format
JSON request and response bodies; times in ISO 8601 with the time-zone offset.
Families
act, ro and ctl, each reached with its own kind of token.
Callers
Third-party agents and services; elan1 Work Attach; elan1 Decide, whose writes go to Govern as intents; elan1 Run tooling, which fails closed if elan1 is unreachable.
shA write intent over HTTPSH code: A write intent over HTTP
curl -X POST "https://<your elan1 Govern endpoint>/v1/assert" \
  -H "Authorization: Bearer $ELAN1_ACT_TOKEN" \
  -H "X-Tenant: tnt_specimen_01" \
  -H "Idempotency-Key: wr_specimen_7c3" \
  -H "Content-Type: application/json" \
  -d '{
    "kernelport": "v1",
    "call": "assert",
    "write_id": "wr_specimen_7c3",
    "write_type": "purchase_invoice.post",
    "declaration": { "id": "decl_gst_purchase", "version": 4 },
    "actor": { "id": "agt_specimen_ap01", "kind": "agent" },
    "record": { "id": "pinv_specimen_0418", "expected_version": 0 },
    "payload": { "vendor": "vnd_specimen_0091", "amount_inr": 482000, "hsn": "8483", "gst_rate_pct": 12 }
  }'
Illustrative specimen. Not customer data.

Payload fields are examples, not the published schema.

Endpoint families

Endpoints are grouped in three families. A token belongs to one family, so a credential leaked from one job cannot be used for another.

actSend intents, read outcomes

Send write intents up the Right Path, withdraw them, and read their outcomes. The only family that can lead to a change in a system of record.

roRead-only

Declarations, write types, obligations and proof records; chain verification; scope previews. Nothing in this family writes.

ctlControl plane

Register declaration versions and manage the integration. A ctl token cannot read any record.

Endpoints

The shape of the surface. Paths here are illustrative; the exact paths are in the KernelPort v1 reference. The family, the call behind each endpoint and its effect are as shown.

Endpoints by family (illustrative paths)
EndpointFamilyCallEffect
POST /v1/assertactassertSends a write intent. Answers committed, refused or escalated. Writes a proof record.
POST /v1/refuseactrefuseRecords a typed refusal: an approver's no, or a withdrawal. Metered at 0.4 of a governed action.
GET /v1/writes/{write_id}act—The outcome, sequence and digest of your own write intent.
GET /v1/obligations/{id}ro—A held write: who must decide, and when it expires.
GET /v1/records/{seq}ro—One proof record: the sixteen Seal v6 fields.
GET /v1/records?from_seq=&to_seq=ro—A stretch of chain, in seq order. Use it to fill a webhook gap.
POST /v1/verifyroverifyRecomputes digests and walks the chain. At L1, verification needs elan1.
GET /v1/scoperoscopeAdvisory blast radius and autonomy for an actor, write type and record.
GET /v1/declarations/{id}/versions/{v}ro—A declaration version: its seven blocks and digest.
POST /v1/definectldefineRegisters a declaration version. A governed write under dual control.
POST /v1/webhooksctl—Registers a webhook receiver and its HMAC secret.

seal has no endpoint: only the kernel seals. You see its result in every proof block.

Authentication and scopes

Every request carries a bearer token. A token is scoped to one tenant and one family, and the tenant is restated in the X-Tenant header so a misrouted request fails rather than reaching the wrong chain.

Token kinds
TokenFamilyMayMay not
Tenant token, act scopeactSend and withdraw write intents; answer an obligation when held by the named approver.Approve a request it made. An agent token can never approve.
Read-only tokenroRead records, declarations and obligations; verify; preview scope.Write anything.
Control-plane tokenctlRegister declaration versions; manage webhooks.Read any record.
  • Chains never cross tenants. A token for one tenant cannot read or write another’s, and a cross-domain query is not slow; it is impossible.
  • An actor without a grant resolves to A0 Observe.
  • Approval separation is checked on every answer: no approval where the approver is the requester, and none by an agent token (conformance test C11).

Versions and pinning

Three versions travel with every write, and all three end up in the record: the API version (kernelport/v1), the declaration version (F07) and the policy version (F08).

  • Pin in production. Production keys must name an explicit declaration version. “latest” is rejected as a contract error (conformance test C13).
  • Replay is deterministic. The same intent, versions and as-of time give the same decision on replay (C4).
  • Versions only move forward. A declaration version is never edited. Old records keep pointing at the rules that applied to them.

Idempotency by write_id

Every write intent carries a write_id, and the same value goes in the Idempotency-Key header. It is the identity of the intent everywhere: field F04 on the proof record, the key on the meter event, and the reference on an invoice line — an invoice line without a write_id cannot exist.

httpA retry returns the original answerHTTP code: A retry returns the original answer
# First attempt: the connection drops before the answer arrives.
POST /v1/assert   Idempotency-Key: wr_specimen_7c2   →   (timeout)

# Retry with the same write_id.
POST /v1/assert   Idempotency-Key: wr_specimen_7c2   →   200
{
  "outcome": "committed",
  "replayed": true,
  "proof": { "seq": 1042, "digest": "sha256:f2423aa3…5aca2c18" },
  "meter": { "governed_actions": 1 }      // the original event, not a second one
}
Illustrative specimen. Not customer data.
  1. Create it once.

    Generate the write_id when the intent is formed, and store it before you send anything.

  2. Retry with the same one.

    After a timeout or a dropped connection, send the same intent with the same write_id. Replaying returns the original outcome, sequence and digest, and no second meter event (C7).

  3. Never reuse it for a new intent.

    An outcome is final for its write_id. After a refusal, fix the cause and send a new intent with a new write_id.

How the API answers a reused write_id with a different payload is set out in the customer reference. Design as if it is refused.

Response envelope

Every call that writes answers with the same envelope. Branch on outcome, then on refusal.class. Show refusal.code and its plain words to a person; never parse them.

jsoncEnvelope shapeJSONC code: Envelope shape
{
  "outcome": "committed | refused | escalated",
  "rung": 0-9,                       // where it ended; "0b" is STOP
  "reason": "plain words",
  "refusal": {                       // present when outcome is "refused"
    "code": "<from the frozen register>",
    "class": "rule | platform",
    "reason": "plain words",
    "remedy": "what to fix",
    "contact": "who to ask"
  },
  "obligation": "OB-…",              // present when outcome is "escalated"
  "record": { "id": "…", "version": 0 },
  "proof": { "seal": "elan1.proof.seal.v6", "seq": 0, "digest": "sha256:…" },
  "meter": { "governed_actions": 0, "inr": 0 }      // 1 when committed; 0.4 when refused
}
Illustrative shape. Exact field names are in the KernelPort v1 reference.
Envelope fields
FieldWhenMeaning
outcomeAlwayscommitted, refused or escalated.
rungAlwaysThe rung where the intent ended, 0b STOP to 9 METERED.
refusalRefusedA typed code from the closed register, its class, and plain words: what happened, what to fix, who to ask. See Errors and refusals.
obligationEscalatedThe held write a named person must answer before it expires.
recordAlwaysThe target record and its version after the write. On a refusal, a zeroed id and version 0.
proofAlwaysThe seal, sequence and digest of the proof record this outcome wrote.
meterAlwaysGoverned actions counted: 1 when committed, 0.4 when refused, 0 while escalated. Beyond the allowance, a refusal costs 40% of your level's rate, for example ₹0.64 at L1.

Status codes

An outcome is an answer, not an error. All three outcomes come back as 200 with the envelope; a refusal is a successful call that said no. Status codes outside 200 mean the call itself did not produce an outcome.

Status code classes
StatusMeansProof recordBilled
200Committed Refused EscalatedWrittenA commit: one governed action. A refusal: 0.4 of one. An escalation: nothing while held.
4xxContract error: the request did not meet the contract — shape, token, tenant, version pin.NoneNo
5xxOur failure. Nothing is allowed. Where the chain writer is reachable, a platform refusal is recorded.When reachableNever
httpA contract errorHTTP code: A contract error
HTTP/1.1 422 Unprocessable Content

{
  "error": {
    "class": "contract",
    "reason": "declaration.version must be a number. \"latest\" is not accepted on a production key.",
    "field": "declaration.version"
  }
}

# No Right Path, no proof record, no meter event.
Illustrative specimen. Not customer data.

The precise 4xx and 5xx codes are in the customer reference. Calls under fusion.*/v1 in elan1 Run answer a refusal at the edge with 409, which costs 40% of one governed action; that is a different surface, described in fusion.*/v1 contracts.

Webhooks

A write that waits on a person resolves later. Rather than poll, register a receiver: elan1 sends an event when an obligation resolves and when a record is sealed for your integration. Webhooks are HMAC-authenticated and carry seq, so a gap in delivery is detectable.

httpWebhook deliveryHTTP code: Webhook delivery
POST https://<your receiver>/elan1/events
Content-Type: application/json
X-Signature: t=2027-07-14T10:42:08+05:30, v1=<hex HMAC-SHA256>

{
  "event": "obligation.resolved",
  "tenant_id": "tnt_specimen_01",
  "seq": 1042,
  "write_id": "wr_specimen_7c2",
  "obligation": "OB-specimen-0412",
  "outcome": "committed",
  "proof": { "seq": 1042, "digest": "sha256:f2423aa3…5aca2c18" }
}
Illustrative specimen. Not customer data.

Verify, then check the sequence

tsReceiver checkTS code: Receiver check
import { createHmac, timingSafeEqual } from "node:crypto";

// Illustrative. Exact header name, signed string and encoding are in the KernelPort v1 reference.
export function verifyWebhook(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=", 2)));
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1 ?? "", "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}

// Then check seq: each event's seq should follow the last one you stored.
// A jump means a missed delivery — read the gap back from the records endpoint.
Illustrative. Check names against the KernelPort v1 reference.
  • Reject a delivery whose signature does not match; it did not come from elan1.
  • Store the last seq you processed. If the next one jumps, read the missing stretch from GET /v1/records before acting.
  • Deliveries can repeat. Treat write_id as the key, and processing as idempotent.

Mode B semantics

Over HTTP, elan1 Govern evaluates and records; your system holds the credential and makes the write. We call that attested advisory and never say a write was blocked. The order of operations is what makes the record mean something.

  1. Ask first.

    Call assert before your system writes. A write made without asking leaves no record.

  2. Write only on committed.

    Your writer acts on committed and nothing else. Escalated means wait for the webhook; refused means do not write.

  3. Fail closed.

    No answer is not an allow. If elan1 is unreachable, do not write — as elan1 Run tooling does.

  4. Keep the write_id with the change.

    Carry it into your system of record so the change, the proof record and the meter event reconcile both ways (C10).

Where this is weak. Mode B records the write; it cannot stop it. Only Mode A, where elan1 holds the write credential through the in-process SDK, is enforced. A Mode B integration can move to Mode A where the credential can move: after 30 days of recorded traffic and a review.

What the customer reference covers

  • Paths, header names and the exact 4xx and 5xx codes.
  • Envelope field names beyond outcome, write_id and the Seal v6 fields.
  • Webhook event names, the signed string and the signature header.
  • Pagination and rate limits on the read-only family.
  • How a Mode B caller reports the resulting record version back for reconciliation.

All of 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.