POST
/transactionsCreate a new transaction (direct entries or trancode invocation)
Two mutually exclusive modes. Direct: supply entries[]. Template: supply transaction_code and transaction_code_params; the server validates params against the code's params_schema and materialises its entries. Sending both returns 422. Posting is asynchronous: the 202 body carries the id; GET /transactions/{id} returns the posted record.
Request bodyrequired
| Field | Type | Description |
|---|---|---|
effective_daterequired | string date | Transaction effective date |
descriptionrequired | string | |
reference | string | External reference number |
layer | string enum: settled | pending | encumbrance · default: "settled" | Case-insensitive on the request side; values like Settled or SETTLED are normalised to canonical lowercase before validation. Responses always render the canonical lowercase form. |
journal_id | string uuid · nullable | |
correlation_id | string nullable | Group related transactions. If omitted but correlation_source_id is given, defaults to the source transaction's group handle (its correlation_id, else its id). |
correlation_type | string max length: 50 · nullable | Correlation classifier (e.g. invoice, refund, reschedule). Paired with correlation_source_id — supply both or neither. |
correlation_source_id | string uuid · nullable | Optional predecessor transaction (same ledger) to link to. Creates a Correlation pair row (source = this id, target = the new transaction) classified by correlation_type — e.g. a reschedule replacement pointing at its voided original. |
idempotency_keyrequired | string | Unique key to prevent duplicate transactions |
metadata | object nullable | |
attributes | object nullable | Optional searchable business dimensions — a flat map of key => value string pairs (e.g. {"customer_id": "C-123", "loan_id": "L-42"}). Applies to both posting modes. Keys must be lowercase snake_case (max 64 chars) and not shadow a reserved field; values are strings (max 128 chars). Subject to per-transaction (default 20) and per-tenant distinct-key caps, and, when the tenant runs strict mode, must be declared in the attribute catalog first — a violation returns 422 with an ATTRIBUTE_* code. Written atomically with the transaction and immutable thereafter. |
entries | object[] | Mode 1 only. Double-entry transaction entries (must balance after server-side resolution). Mutually exclusive with transaction_code. |
entries[].account_code | string | Account code (alternative to account_id) |
entries[].account_id | string uuid | Account UUID (alternative to account_code) |
entries[].typerequired | string enum: debit | credit | |
entries[].amountrequired | string | Amount in major units (e.g., dollars) |
entries[].description | string nullable | |
entries[].metadata | object nullable | |
transaction_code | string max length: 50 | Mode 2 only. Code of an active trancode in this ledger. Must be paired with transaction_code_params. Mutually exclusive with entries. |
transaction_code_params | object | Mode 2 only. Parameter map keyed by parameter name as declared in the trancode's params_schema. Values are substituted into the template (account selectors and amount expressions) at materialisation time. |
Responses
202Transaction accepted and processing
{
"success": true,
"data": {
"id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"status": "pending",
"idempotency_key": "string",
"correlation_id": "string",
"message": "string"
}
} 400Validation error
{
"success": false,
"message": "Validation failed",
"code": "VALIDATION_ERROR"
} 401Unauthenticated
{
"success": false,
"message": "Validation failed",
"code": "VALIDATION_ERROR"
} 403Forbidden
{
"success": false,
"message": "Validation failed",
"code": "VALIDATION_ERROR"
} 409Idempotency key conflict
{
"success": false,
"message": "Validation failed",
"code": "VALIDATION_ERROR"
} 503Idempotency service unavailable (code SERVICE_UNAVAILABLE) — fail-closed with a Retry-After header; nothing is written
{
"success": false,
"message": "Validation failed",
"code": "VALIDATION_ERROR"
} Examples
curl -X POST https://ledga.io/api/v1/transactions \
-H "Authorization: Bearer sk_xxxxxxxx_your_secret" \
-H "Content-Type: application/json" \
-d '{
"effective_date": "2026-01-15",
"description": "Invoice INV-001 paid",
"reference": "INV-001",
"idempotency_key": "inv-001-payment",
"entries": [
{
"account_code": "1000",
"type": "debit",
"amount": "500.00"
},
{
"account_code": "4000",
"type": "credit",
"amount": "500.00"
}
]
}' use Ledga\Api\LedgaClient;
$ledga = new LedgaClient('sk_xxxxxxxx_your_secret');
$ack = $ledga->transactions->create([
'effective_date' => '2026-01-15',
'description' => 'Invoice INV-001 paid',
'idempotency_key' => 'inv-001-payment',
'entries' => [
['account_code' => '1000', 'type' => 'debit', 'amount' => '500.00'],
['account_code' => '4000', 'type' => 'credit', 'amount' => '500.00'],
],
]);
$transaction = $ledga->transactions->get($ack->id);