Record a transaction batch

  POST /ledger/v1/transactions
  

The request body is an array of 1–1000 transaction commands. Even a single transaction is sent as a one-element array.

Headers

HeaderRequiredNotes
X-Tenant-IDyesTenant the transactions belong to
X-Idempotency-KeyyesMissing key → 400 VALIDATION_ERROR
X-Correlation-IDnoOptional UUID

Request body

  [{
  "name": "Sales invoice #42",
  "description": "July invoice for cust-001",
  "documentReference": "INV-2026-0042",
  "transactionDate": "2026-07-01",
  "unit": "EUR",
  "entries": [
    { "accountReference": "1500-receivables", "entryType": "DEBIT",  "amountMinorUnits": 125000,
      "lineDescription": "Invoice total incl. VAT",
      "dimensions": { "CUSTOMER": "cust-001" } },
    { "accountReference": "3000-sales",       "entryType": "CREDIT", "amountMinorUnits": 100000 },
    { "accountReference": "2700-vat-out",     "entryType": "CREDIT", "amountMinorUnits": 25000 }
  ],
  "dimensions": [
    { "dimensionType": "PROJECT", "valueReference": "0b9f3c1e-…" }
  ],
  "applicationMetadata": { "source": "billing-service" }
}]
  
FieldTypeConstraints
namestringrequired, ≤ 200 chars
descriptionstring≤ 2000 chars
documentReferencestring≤ 100 chars
transactionDatedaterequired, yyyy-MM-dd
unitstringrequired, ≤ 20 chars — a currency (EUR, JPY, …) or other registered unit (kgCO2e, hours, …)
entriesarrayrequired, minimum 2 entries
entries[].accountReferencestringrequired, ≤ 50 chars
entries[].amountMinorUnitsintegerrequired; signed, scaled by the unit (Amounts and units) — negative means “reduce this side”
entries[].entryTypeenumrequired, DEBIT or CREDIT
entries[].lineDescriptionstring≤ 500 chars
entries[].dimensionsobjectmap of dimension type → value
entries[].quantitydecimaloptional, ≥ 0 (e.g. shares)
dimensionsarrayblock-level {dimensionType, valueReference}
applicationMetadataobjectfree-form
enforceCapturebooleanoptional; enforce dimension-capture validation

Every block must balance: the signed sum of DEBIT amounts must equal the signed sum of CREDIT amounts. See Negative amounts for what a negative entry means.

Response — 201 Created

Location: /ledger/v1/transactions/batch/{idempotencyKey}. The body is an array, one element per recorded transaction:

  [{
  "transactionId": "7d9e1a4c-90b2-4f7a-a9d3-2f6f6f1e0b7e",
  "tenantId": "org-84120557",
  "correlationId": "b2f1…",
  "name": "Sales invoice #42",
  "transactionDate": "2026-07-01",
  "unit": "EUR",
  "entryCount": 3,
  "totalDebitMinorUnits": 125000,
  "recordedAt": "2026-07-01T09:14:03.512Z"
}]
  

Errors

StatusCodeCause
400UNBALANCED_TRANSACTIONΣ debits ≠ Σ credits
400VALIDATION_ERRORMissing idempotency key, empty batch, field violations
409IDEMPOTENCY_CONFLICTKey already used; violations carries existingTransactionId=<uuid>
413BATCH_SIZE_EXCEEDEDMore than 1000 transactions (or oversized payload)

Correct a transaction

  POST /ledger/v1/transactions/{transactionId}/corrections
  

Records a compensating reversal of the original block: every debit becomes a credit and vice versa. The original stays untouched — both blocks remain in the ledger.

Headers are the same as above (X-Idempotency-Key required).

Request body

  {
  "reason": "Invoice #42 was issued to the wrong customer",
  "correctionDate": "2026-07-03",
  "applicationMetadata": { "requestedBy": "ops" }
}
  
FieldTypeConstraints
reasonstringrequired, ≤ 2000 chars
correctionDatedaterequired, yyyy-MM-dd
applicationMetadataobjectoptional

Response — 201 Created

Location: /ledger/v1/transactions/{correctionTransactionId}:

  {
  "correctionTransactionId": "4c1b…",
  "originalTransactionId": "7d9e1a4c-…",
  "tenantId": "org-84120557",
  "correlationId": "b2f1…",
  "reason": "Invoice #42 was issued to the wrong customer",
  "correctionDate": "2026-07-03",
  "unit": "EUR",
  "entryCount": 3,
  "totalDebits": 125000,
  "timestamp": "2026-07-03T08:02:44.100Z"
}
  

Errors

StatusCodeCause
400VALIDATION_ERRORMissing reason/date, missing idempotency key
404TRANSACTION_NOT_FOUNDUnknown transactionId
409IDEMPOTENCY_CONFLICTKey already used