# Ledger

Ledgers let you track the balance of any amount by posting transactions to them. A ledger can represent a bank account, a digital wallet, an inventory product — anything whose balance you need to keep track of.

Each LedgerTransaction moves balance in or out of a ledger and returns the resulting running balance, so your books always reflect the latest state. Rules let you cap a ledger's balance.

NOTE: Read [Core Concepts](https://docs.starkinfra.com/get-started/core-concepts.md) before continuing this guide.

**RESOURCE SUMMARY**

**Ledger**Track the balance of an amount.**Ledger Rule**Bound a ledger's balance with a minimum or maximum.**Ledger Transaction**Move balance in or out of a ledger; the response returns the new running balance.

## Available Languages

Code samples on this page are in Python. The same content is available with samples in:

- [Python](https://docs.starkinfra.com/get-started/ledger-python.md)
- [Node.js](https://docs.starkinfra.com/get-started/ledger-node.md)
- [PHP](https://docs.starkinfra.com/get-started/ledger-php.md)
- [Java](https://docs.starkinfra.com/get-started/ledger-java.md)
- [Ruby](https://docs.starkinfra.com/get-started/ledger-ruby.md)
- [Elixir](https://docs.starkinfra.com/get-started/ledger-elixir.md)
- [.NET](https://docs.starkinfra.com/get-started/ledger-dotnet.md)
- [Go](https://docs.starkinfra.com/get-started/ledger-go.md)
- [Clojure](https://docs.starkinfra.com/get-started/ledger-clojure.md)
- [cURL](https://docs.starkinfra.com/get-started/ledger-curl.md)

## Setup

For each environment (Sandbox or Production):

1. Create an account at Stark Bank.

2. Get in touch with your account manager to enable the Ledger product to your account.

## Ledger Overview

Here we show you how to create a ledger and keep its tags, metadata and rules up to date.

### Creating a Ledger

Create a ledger with a unique `externalId` of your own — use it to reference the ledger from your system. You can also attach `tags` for searching and a free-form `metadata` object.

You can create up to 100 ledgers in a single request.

**Request**

```python
import starkinfra

ledgers = starkinfra.ledger.create([
    starkinfra.Ledger(
        external_id="my-internal-id-123456",
        tags=["account/123", "savings"],
        metadata={"accountId": "123"}
    )
])

for ledger in ledgers:
    print(ledger)
```

**Response**

```python
[
    Ledger(
        id=5656565656565656,
        external_id=my-internal-id-123456,
        rules=[],
        tags=['account/123', 'savings'],
        metadata={'accountId': '123'},
        created=2022-01-01 00:00:00,
        updated=2022-01-01 00:00:00
    )
]
```

### Updating a Ledger

Update an existing ledger by its `id`. You can change its `tags`, `metadata` or `rules` — the example below replaces the ledger's tags.

**Request**

```python
import starkinfra

ledger = starkinfra.ledger.update(
    "5656565656565656",
    tags=["account/123", "closed"]
)

print(ledger)
```

**Response**

```python
Ledger(
    id=5656565656565656,
    external_id=my-internal-id-123456,
    rules=[],
    tags=['account/123', 'closed'],
    metadata={'accountId': '123'},
    created=2022-01-01 00:00:00,
    updated=2022-06-01 00:00:00
)
```

## Ledger Rule Overview

Rules bound the balance a ledger can reach and are validated on every transaction. Here we show you how to restrict a ledger's minimum and maximum balance.

### Restricting a ledger minimum balance

Add a `minimumBalance` rule to stop the balance from dropping below a floor. Setting it to `0`, for example, prevents the ledger from ever going negative.

The example below sets rules at creation time; you can also modify them later with an update.

**Request**

```python
import starkinfra

ledgers = starkinfra.ledger.create([
    starkinfra.Ledger(
        external_id="my-internal-id-123456",
        rules=[
            starkinfra.ledger.Rule(
                key="minimumBalance",
                value=0
            )
        ]
    )
])

for ledger in ledgers:
    print(ledger)
```

**Response**

```python
[
    Ledger(
        id=5656565656565656,
        external_id=my-internal-id-123456,
        rules=[
            Rule(
                key=minimumBalance,
                value=0
            )
        ],
        tags=[],
        metadata={},
        created=2022-01-01 00:00:00,
        updated=2022-01-01 00:00:00
    )
]
```

### Restricting a ledger maximum balance

Add a `maximumBalance` rule to cap how high the balance can go. Any transaction that would exceed the cap is rejected.

The example below adds the rule to an existing ledger with an update.

**Request**

```python
import starkinfra

ledger = starkinfra.ledger.update(
    "5656565656565656",
    rules=[
        starkinfra.ledger.Rule(
            key="maximumBalance",
            value=10000000
        )
    ]
)

print(ledger)
```

**Response**

```python
Ledger(
    id=5656565656565656,
    external_id=my-internal-id-123456,
    rules=[
        Rule(
            key=maximumBalance,
            value=10000000
        )
    ],
    tags=[],
    metadata={},
    created=2022-01-01 00:00:00,
    updated=2022-06-01 00:00:00
)
```

## Ledger Transaction Overview

A LedgerTransaction moves balance in or out of a ledger and returns the ledger's new running balance. Here we show you how to post transactions, move funds between ledgers, read balances, import history and override rules.

### Creating a transaction

Post a transaction to move balance into a ledger. Provide the `amount` (in cents), the `ledgerId` it belongs to, a unique `externalId` and a `source` describing where the movement came from.

The response returns the resulting `balance` of the ledger after the transaction is applied.

**Request**

```python
import starkinfra

transactions = starkinfra.ledgertransaction.create([
    starkinfra.LedgerTransaction(
        amount=11234,
        ledger_id="5656565656565656",
        external_id="my-internal-id-123456",
        source="bank-transfer/123"
    )
])

for transaction in transactions:
    print(transaction)
```

**Response**

```python
[
    LedgerTransaction(
        id=5656565656565656,
        ledger_id=5656565656565656,
        external_id=my-internal-id-123456,
        source=bank-transfer/123,
        amount=11234,
        fee=0,
        balance=11234,
        tags=[],
        metadata={},
        created=2022-01-01 00:00:00
    )
]
```

### Creating transactions in multiple ledgers

A single batch can carry transactions for different ledgers. This is the recommended way to move balance from one ledger to another while keeping your overall balance consistent: send one negative transaction debiting the source ledger and one positive transaction crediting the destination ledger, both with the same amount.

Because the two amounts net to zero, the total balance across your ledgers is preserved, and either both transactions are accepted or the whole request fails.

**Request**

```python
import starkinfra

transactions = starkinfra.ledgertransaction.create([
    starkinfra.LedgerTransaction(
        amount=-5000,
        ledger_id="5656565656565656",
        external_id="transfer-001-out",
        source="internal-transfer/789"
    ),
    starkinfra.LedgerTransaction(
        amount=5000,
        ledger_id="4545454545454545",
        external_id="transfer-001-in",
        source="internal-transfer/789"
    )
])

for transaction in transactions:
    print(transaction)
```

**Response**

```python
[
    LedgerTransaction(
        id=5656565656565656,
        ledger_id=5656565656565656,
        external_id=transfer-001-out,
        source=internal-transfer/789,
        amount=-5000,
        fee=0,
        balance=6234,
        tags=[],
        metadata={},
        created=2022-01-01 00:00:00
    ),
    LedgerTransaction(
        id=4545454545454545,
        ledger_id=4545454545454545,
        external_id=transfer-001-in,
        source=internal-transfer/789,
        amount=5000,
        fee=0,
        balance=5000,
        tags=[],
        metadata={},
        created=2022-01-01 00:00:00
    )
]
```

### Checking a ledger current balance

A ledger does not expose a balance field of its own — the current balance is simply the `balance` of its most recent transaction.

Query the transactions filtered by `ledgerId` with `limit=1` to retrieve the latest one, and read its `balance` to get the ledger's current balance.

**Request**

```python
import starkinfra

transactions = starkinfra.ledgertransaction.query(
    ledger_id="5656565656565656",
    limit=1
)

latest = next(transactions, None)
current_balance = latest.balance if latest else 0
print(current_balance)
```

**Response**

```python
6234
```

### Exporting transaction history

When migrating from a legacy system you can rebuild a ledger's history by posting past transactions with a `created` datetime.

`created` must be in ISO format and cannot be in the future. When sending several transactions in the same request, their `created` datetimes must be in chronological order so the running balance is computed correctly.

**Request**

```python
import starkinfra

transactions = starkinfra.ledgertransaction.create([
    starkinfra.LedgerTransaction(
        amount=10000,
        ledger_id="5656565656565656",
        external_id="legacy-001",
        source="legacy-import/2021",
        created="2021-01-01T03:00:00.000000+00:00"
    ),
    starkinfra.LedgerTransaction(
        amount=-2500,
        ledger_id="5656565656565656",
        external_id="legacy-002",
        source="legacy-import/2021",
        created="2021-02-01T03:00:00.000000+00:00"
    )
])

for transaction in transactions:
    print(transaction)
```

**Response**

```python
[
    LedgerTransaction(
        id=5656565656565656,
        ledger_id=5656565656565656,
        external_id=legacy-001,
        source=legacy-import/2021,
        amount=10000,
        fee=0,
        balance=10000,
        tags=[],
        metadata={},
        created=2021-01-01 03:00:00
    ),
    LedgerTransaction(
        id=4545454545454545,
        ledger_id=5656565656565656,
        external_id=legacy-002,
        source=legacy-import/2021,
        amount=-2500,
        fee=0,
        balance=7500,
        tags=[],
        metadata={},
        created=2021-02-01 03:00:00
    )
]
```

### Overwriting ledger rules

A transaction can carry its own `rules`, which **momentarily** overwrite the ledger's rules for that single transaction — the ledger's own rules stay unchanged for every other transaction.

This is handy when you need to force a one-off movement that the ledger's rules would otherwise forbid. For example, to push a balance negative even though the ledger has a `minimumBalance` of 0, override `minimumBalance` with a lower value on the transaction itself.

**Request**

```python
import starkinfra

transactions = starkinfra.ledgertransaction.create([
    starkinfra.LedgerTransaction(
        amount=-15000,
        ledger_id="5656565656565656",
        external_id="forced-debit-001",
        source="manual-adjustment/42",
        rules=[
            starkinfra.ledger.Rule(
                key="minimumBalance",
                value=-100000000
            )
        ]
    )
])

for transaction in transactions:
    print(transaction)
```

**Response**

```python
[
    LedgerTransaction(
        id=5656565656565656,
        ledger_id=5656565656565656,
        external_id=forced-debit-001,
        source=manual-adjustment/42,
        amount=-15000,
        fee=0,
        balance=-8766,
        tags=[],
        metadata={},
        created=2022-01-01 00:00:00
    )
]
```
