> For the complete documentation index, see [llms.txt](https://vendex.gitbook.io/ledgerise/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://vendex.gitbook.io/ledgerise/12-reference/01-transaction-schema.md).

# transaction schema

The canonical transaction schema is the internal contract between every inbound adapter and the journal engine. Every inbound adapter must produce records in this format. The engine only reads this format and has no knowledge of source-specific data structures.

Schema identifier: `transaction.schema.json v1.0`

Breaking changes to the schema increment the major version. Additive changes (new optional fields) increment the minor version. The engine rejects records whose schema version it does not support.

***

## required fields

| Field            | Type                      | Description                                                                                                      |
| ---------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `id`             | UUID v4                   | Ledgerise-generated record ID. Generated by the adapter at normalization time.                                   |
| `source.adapter` | string                    | Kebab-case name of the inbound adapter that produced this record. e.g. `paystack-webhook`, `generic-csv`         |
| `source.system`  | string                    | The upstream platform or internal system this transaction came from. e.g. `paystack`, `backoffice`               |
| `occurred_at`    | ISO 8601 datetime         | When the transaction happened in the source system. Used as the journal entry date.                              |
| `completed_at`   | ISO 8601 datetime or null | When the transaction was finalized. Null if still pending. The engine only posts transactions where this is set. |
| `status`         | enum                      | Transaction status. One of: `pending`, `completed`, `failed`, `reversed`, `disputed`                             |
| `type`           | string                    | Transaction type in dot notation. → See [transaction types](/ledgerise/12-reference/02-transaction-types.md)     |
| `direction`      | enum                      | `debit` or `credit` from the operator's perspective.                                                             |
| `amount`         | positive integer          | Transaction amount in the smallest currency unit (kobo for NGN, cents for USD/KES). Always positive.             |
| `currency`       | ISO 4217 string           | Three-letter currency code. e.g. `NGN`, `KES`, `GHS`                                                             |
| `principal.id`   | string                    | Stable internal identifier for the customer, merchant, or agent involved. Must not contain PII.                  |
| `channel`        | enum                      | Originating channel. One of: `web`, `mobile`, `ussd`, `pos`, `api`, `agent`, `qr`, `nfc`, `internal`             |
| `product.line`   | string                    | The operator's product line. Primary key for mapping rule resolution.                                            |
| `metadata`       | object                    | Adapter-specific key-value pairs. Not read by the engine for mapping decisions.                                  |

***

## optional fields

| Field                     | Type              | Description                                                                                                                                                                                                                                     |
| ------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source_id`               | string            | Original transaction ID from the source system. Used for deduplication — the engine skips records with a `source_id` it has already processed. If the source system provides no stable ID, set to null and document this in the adapter README. |
| `source.environment`      | enum              | `live` or `test`. Defaults to `live`. Test-environment transactions are stored but never posted to the accounting system.                                                                                                                       |
| `processed_at`            | ISO 8601 datetime | When Ledgerise normalized this record. Set by the adapter to the current UTC timestamp.                                                                                                                                                         |
| `fee.platform_fee`        | positive integer  | Fee charged to the customer. Operator revenue. In smallest currency unit.                                                                                                                                                                       |
| `fee.processing_fee`      | positive integer  | Fee charged by the aggregator to the operator. Operator cost. In smallest currency unit.                                                                                                                                                        |
| `fee.net_fee`             | integer           | `platform_fee - processing_fee`. Operator margin. May be negative if processing fees exceed platform fees.                                                                                                                                      |
| `principal.type`          | enum              | The role of the principal in this transaction. One of: `customer`, `merchant`, `agent`, `internal`                                                                                                                                              |
| `principal.reference`     | string            | Masked human-readable identifier for the principal. Phone numbers: last 4 digits only. Card numbers: last 4 digits only. BVN and NIN must never appear here.                                                                                    |
| `product.biller`          | string            | Exact biller identifier. Second priority for mapping rule resolution after `product.line`.                                                                                                                                                      |
| `product.biller_category` | string            | Biller category grouping. Third priority for mapping rule resolution.                                                                                                                                                                           |
| `float.aggregator`        | string            | The aggregator whose float was used to settle this transaction.                                                                                                                                                                                 |
| `float.account_ref`       | string            | The operator's float account reference with the aggregator.                                                                                                                                                                                     |
| `float.balance_before`    | integer           | Float balance before this transaction. In smallest currency unit.                                                                                                                                                                               |
| `float.balance_after`     | integer           | Float balance after this transaction. In smallest currency unit.                                                                                                                                                                                |
| `reversal_of`             | UUID or null      | If this record is a reversal, the `id` of the original transaction being reversed. Links reversals in the Transactions page and Journal Log.                                                                                                    |

***

## design principles

**Direction by field, not by sign.** Amounts are always positive integers. The `direction` field captures which way money moved relative to the operator. This avoids sign convention errors across currencies and systems.

**Amounts in smallest currency unit.** All monetary values are integers in the smallest indivisible unit of the currency: kobo for NGN, cents for USD and KES, pesewas for GHS. This eliminates floating-point rounding errors in journal arithmetic.

**Currency on every record.** There is no concept of a base currency in the schema. Each record carries its own currency code. The outbound adapter handles any conversion at the output layer.

**Product context drives mapping.** The engine resolves COA accounts using `product.line`, `product.biller`, and `product.biller_category` in that priority order. Adapters must populate these fields accurately — incorrect or missing values lead to wrong mapping or suspense posting.

**Metadata is a safety valve.** The `metadata` object carries anything that does not fit the canonical schema. The engine does not read `metadata` for mapping decisions. Adapters must not use `metadata` as a substitute for canonical fields.

**Source ID is the deduplication key.** The engine deduplicates using `source_id` before posting. An adapter that omits `source_id` loses deduplication protection — if the source system retries a delivery, duplicate journal entries may result.

***

## where the schema is enforced

**Inbound adapters** produce canonical records at normalization time. Every adapter is required to conform to this schema on every record it emits.

**The journal engine** reads canonical records when generating journal entries. It uses `type`, `product.line`, `product.biller`, `product.biller_category`, `amount`, `currency`, and `direction` for mapping. It does not read `metadata`.

**The Transactions page** displays canonical records. Fields shown in the table include `status`, `type`, `channel`, `direction`, `amount`, `currency`, and `principal.id`. The detail drawer shows all fields.

**Outbound adapters** receive journal entries (not raw canonical records) when posting to the accounting system. Journal entries reference `source_id` and `occurred_at` from the originating canonical record for traceability.
