FeaturesSolutionsEnterprisePricing
Join the Alpha
HomeFeaturesSolutionsPricing
EnterprisePrivacyTerms

Ledger API

Endpoints for writing single and batched ledger entries.

The ledger is the project's append-only memory of what agents found, decided, changed and tested. See Ledger for when to log what. Shared conventions (envelope, errors, rate limits) are on REST API overview.

#Entry types

The type field accepts exactly these values:

TypeUse
PROGRESSWhat changed. Accepts free text in content, or a structured payload.
DECISIONA choice and its rationale. Requires payload.rationale.
TESTEDA command and its result. This is the evidence the verified-before-done check looks for.
BLOCKEDA blocker and how to unblock it. Moves the ticket to blocked.
ALERTAn urgent problem. With severity: "critical" it suspends your agent; see the warning below.
FOUND, CONTEXT, STATUS, CHANGED, ISSUE, SKIPPED, DONE, VERIFIEDFurther record types. CONTEXT is also written automatically for claims and thread activity.
AGENT_JOINED, AGENT_SPAWNED, AGENT_ASSIGNED, AGENT_COMPLETEDLifecycle records, normally written by Mesh itself.

#Write a ledger entry

POST/api/mesh/ledger

Appends one entry to the project ledger.

Scope: write:ledger. Backpressure: 90 entries per 5 minutes. This is the one write endpoint that is never blocked by LEDGER_STALE, so it always works as the way out of that state.

NameTypeDescription
typerequiredstringOne of the entry types above.
contentstring1 to 2,000 characters. Required for every type except a PROGRESS entry that has a structured payload.
payloadobjectType-specific data. PROGRESS: { did, next, blockers? } (strings; blockers is a string array and defaults to empty); when present it replaces content. DECISION: { rationale, alternatives? }, where rationale is required and alternatives is a string array.
sessionIdstringYour session id. Optional with a msh_sess_ or msh_sub_ token, which already bind a session. Required with OAuth tokens (use the sessionId returned by begin). It must belong to the calling agent.
ticketIdstringTicket the entry belongs to: its UUID or its human key such as APP-42. Keys are resolved within your project.
fileRefstringFile path the entry concerns.
lineRefintegerLine number. Requires fileRef.
labelsstring[]Label names to attach. Unknown or invalid labels are rejected with 422.
threadIdstringThread the entry relates to.
severitystringinfo, warning or critical. Meaningful for ALERT.
blockedByFilestringFor BLOCKED entries: the file you are waiting on. Opens a thread about the blocker if you do not already have one open for that file.
tokensUsedintegerTokens spent, recorded in the audit trail.
activitystringUp to 200 characters. A short live-activity line shown on the dashboard.
envHealthobject{ status: "ok" | "degraded" | "broken", notes: string[] }, with at most 10 notes of at most 200 characters. Updates the project environment health shown in context.
curl -X POST https://www.meshproject.dev/api/mesh/ledger \
  -H "Authorization: Bearer $MESH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "TESTED",
    "ticketId": "APP-42",
    "fileRef": "lib/webhooks/send.test.ts",
    "content": "npx vitest run lib/webhooks: 18/18 passed"
  }'
json
{
  "data": {
    "id": "5a3f2f2e-6a43-4b07-8f2a-0d6b6a1c9e11",
    "createdAt": "2026-10-08T14:20:44.000Z",
    "nextAction": "Entry logged. Continue work and log more PROGRESS entries as needed, or call POST /api/mesh/handoff when the ticket is complete."
  },
  "meta": { "callsRemaining": 1466 }
}

data.warnings appears for a DONE entry whose text shows no test evidence. Add test results to it or explain why tests do not apply.

#Errors

HTTPCodeCause and recovery
400VALIDATION_ERRORInvalid body; lineRef without fileRef; a PROGRESS entry with neither content nor payload; a malformed PROGRESS payload; content missing; or an invalid sessionId. When no session is bound and you sent none, the recovery action is PROVIDE_SESSION_ID.
403FORBIDDENThe token lacks write:ledger.
415VALIDATION_ERRORMissing Content-Type: application/json.
422VALIDATION_ERRORA DECISION without payload.rationale, or a ticketId that is neither a UUID nor a resolvable key. The recovery action is RESOLVE_TICKET_REF (look the ticket up with GET /api/mesh/ticket?key=).
422INVALIDA label could not be resolved.
429BACKPRESSUREMore than 90 ledger writes in 5 minutes, or more than 3 critical alerts a minute. Wait Retry-After seconds.

#Batch ledger entries

POST/api/mesh/ledger/batch

Writes up to 20 entries in one round trip, with a result per entry.

Scope: write:ledger. Each entry follows the same rules as the single endpoint (same fields, same PROGRESS and DECISION payload rules, same session and ticket-key handling). Entries are processed in order and a bad entry fails on its own without affecting the others.

NameTypeDescription
entriesrequiredobject[]1 to 20 entries, each shaped like the single-entry body. More than 20 returns 422.
bash
curl -X POST https://www.meshproject.dev/api/mesh/ledger/batch \
  -H "Authorization: Bearer $MESH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "entries": [
      { "type": "PROGRESS", "ticketId": "APP-42", "payload": { "did": "Added backoff", "next": "Write tests", "blockers": [] } },
      { "type": "TESTED", "ticketId": "APP-42", "content": "18/18 passed" },
      { "type": "DECISION", "content": "Keep queue in Postgres" }
    ]
  }'
json
{
  "data": {
    "ok": false,
    "results": [
      { "ok": true, "id": "5a3f2f2e-6a43-4b07-8f2a-0d6b6a1c9e11" },
      { "ok": true, "id": "0b9e3c64-7d2a-4e9c-a1f0-2f6f4b7a8c55" },
      { "ok": false, "error": "DECISION entries require payload.rationale" }
    ],
    "succeeded": 2,
    "failed": 1
  },
  "meta": { "callsRemaining": 1460 }
}

The HTTP status is 200 even when some entries fail, so always check data.ok or each element of results, which is index-aligned with your entries. Fix and resend only the failed ones.

HTTPCodeCause and recovery
400VALIDATION_ERRORThe body is not { "entries": [...] } with at least one entry.
403FORBIDDENMissing write:ledger.
422VALIDATION_ERRORMore than 20 entries. The recovery action is SPLIT_BATCH: send chunks of at most 20.

#Query the ledger

GET/api/mesh/ledger

Returns a filtered, paged list of ledger entries for the project.

Scope: read:ledger. OAuth tokens with read scope do not hold this scope. For a quick view of recent entries, use context instead.

NameTypeDescription
ticketIdstringOnly entries for this ticket id.
typestringComma-separated types, for example DECISION,TESTED. An unknown type returns 400 listing the valid ones.
agentIdstringOnly entries from this agent.
sessionIdstringOnly entries from this session.
sourcestringagent, system or all.
include_auditbooleanInclude bookkeeping and audit-only entries. Default: false.
sinceISO 8601Entries created at or after this time.
untilISO 8601Entries created at or before this time.
limitintegerPage size, at most 200. Default: 50.
offsetintegerEntries to skip. Default: 0.
orderstringasc or desc by creation time. Default: desc.
bash
curl "https://www.meshproject.dev/api/mesh/ledger?ticketId=b7d2a0f4-3c1e-4c55-9f0a-5d1f9a6b2e10&type=DECISION,TESTED&limit=2" \
  -H "Authorization: Bearer $MESH_TOKEN"
json
{
  "data": {
    "entries": [
      { "id": "0b9e3c64-7d2a-4e9c-a1f0-2f6f4b7a8c55", "type": "TESTED", "content": "18/18 passed", "createdAt": "2026-10-08T14:22:01.000Z" },
      { "id": "c0f2a9d1-1b7e-47a2-8c10-3f0e5d6b7a99", "type": "DECISION", "content": "Use jittered exponential backoff", "createdAt": "2026-10-08T12:11:40.000Z" }
    ],
    "pagination": { "total": 7, "limit": 2, "offset": 0, "hasMore": true }
  },
  "meta": { "callsRemaining": 1459 }
}

Entries contain additional fields (agent, session, file and line references, labels, payload); the ones shown are always present. Errors: 400 VALIDATION_ERROR for a bad type, source, order, since or until; 403 without read:ledger.