> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.thepennyinc.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.thepennyinc.com/_mcp/server.

# Reading Transactions & Ledger Entries

See [Transactions](/banking/transactions) first for what `type`, `direction`, and `status` mean. All requests go to Penny Banking.

## List a card's recent transactions

**`cURL`**

```bash title="cURL"
curl "https://sandbox.api.thepennyinc.com/transactions/?card_id=card_019375de-a2a0-7f31-884a-a2a0f3184abd&page_size=50" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**`Python`**

```python title="Python"
import requests

BANKING = "https://sandbox.api.thepennyinc.com"
headers = {"Authorization": f"Bearer {access_token}"}

transactions = requests.get(
    f"{BANKING}/transactions/",
    headers=headers,
    params={"card_id": "card_019375de-a2a0-7f31-884a-a2a0f3184abd", "page_size": 50},
).json()
```

**`JavaScript`**

```javascript title="JavaScript"
const BANKING = "https://sandbox.api.thepennyinc.com";
const headers = { Authorization: `Bearer ${accessToken}` };

const params = new URLSearchParams({ card_id: "card_019375de-a2a0-7f31-884a-a2a0f3184abd", page_size: "50" });
const transactions = await fetch(`${BANKING}/transactions/?${params}`, { headers }).then((res) =>
  res.json()
);
```

`GET /transactions/` accepts at most one scope filter per request: `account_id`, `card_id`, `cardholder_id`, `control_account_id`, or `program_id`. Sending more than one returns `422`. Omit the scope filter to list transactions across your whole business. Results are paginated with `next_page_token`:

**`Python`**

```python title="Python"
page = requests.get(f"{BANKING}/transactions/", headers=headers, params={"page_size": 50}).json()
while page.get("next_page_token"):
    page = requests.get(
        f"{BANKING}/transactions/",
        headers=headers,
        params={"page_size": 50, "next_page_token": page["next_page_token"]},
    ).json()
```

**`JavaScript`**

```javascript title="JavaScript"
let page = await fetch(`${BANKING}/transactions/?page_size=50`, { headers }).then((res) =>
  res.json()
);
while (page.next_page_token) {
  const params = new URLSearchParams({ page_size: "50", next_page_token: page.next_page_token });
  page = await fetch(`${BANKING}/transactions/?${params}`, { headers }).then((res) => res.json());
}
```

## Filter by date range

Transaction timestamps are filtered on `stage_time`: when the transaction last advanced through its lifecycle, not necessarily when it was created. You can add one scope filter such as `card_id` or `account_id` to a `stage_time` range, except `control_account_id`, which can't be combined with `stage_time` filters:

**`cURL`**

```bash title="cURL"
curl "https://sandbox.api.thepennyinc.com/transactions/?stage_time_greater_than_equal_to=2026-08-01T00:00:00Z&stage_time_less_than=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**`Python`**

```python title="Python"
requests.get(
    f"{BANKING}/transactions/",
    headers=headers,
    params={
        "stage_time_greater_than_equal_to": "2026-08-01T00:00:00Z",
        "stage_time_less_than": "2026-09-01T00:00:00Z",
    },
).json()
```

**`JavaScript`**

```javascript title="JavaScript"
const dateParams = new URLSearchParams({
  stage_time_greater_than_equal_to: "2026-08-01T00:00:00Z",
  stage_time_less_than: "2026-09-01T00:00:00Z",
});
await fetch(`${BANKING}/transactions/?${dateParams}`, { headers }).then((res) => res.json());
```

## Look up one transaction in detail

**`cURL`**

```bash title="cURL"
curl "https://sandbox.api.thepennyinc.com/transactions/$TRANSACTION_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
curl "https://sandbox.api.thepennyinc.com/transactions/$TRANSACTION_ID/events" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
curl "https://sandbox.api.thepennyinc.com/transactions/$TRANSACTION_ID/history" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**`Python`**

```python title="Python"
transaction = requests.get(f"{BANKING}/transactions/{transaction_id}", headers=headers).json()

# Every lifecycle event that produced this transaction's current state
events = requests.get(f"{BANKING}/transactions/{transaction_id}/events", headers=headers).json()

# The full transaction snapshot at each stage
history = requests.get(f"{BANKING}/transactions/{transaction_id}/history", headers=headers).json()
```

**`JavaScript`**

```javascript title="JavaScript"
const transaction = await fetch(`${BANKING}/transactions/${transactionId}`, { headers }).then(
  (res) => res.json()
);

// Every lifecycle event that produced this transaction's current state
const events = await fetch(`${BANKING}/transactions/${transactionId}/events`, { headers }).then(
  (res) => res.json()
);

// The full transaction snapshot at each stage
const history = await fetch(`${BANKING}/transactions/${transactionId}/history`, { headers }).then(
  (res) => res.json()
);
```

`GET /transactions/{transaction_id}` returns the latest state by default. Pass `stage` and `version` to read an earlier snapshot; `0` (the default for both) selects the latest.

Use `stage` (on the transaction itself) to tell whether a given event is still the latest one. See [Transactions](/banking/transactions#status-where-it-is-in-its-lifecycle).

## Find transactions by your own reference

If you set `client_reference` when you started a movement (for example, on `POST /accounts/capital/transfer`), look up the resulting transactions with it. References don't need to be unique, so the response is a paginated list:

**`cURL`**

```bash title="cURL"
curl "https://sandbox.api.thepennyinc.com/transactions/by-client-reference?client_reference=budget-2026-09-marketing" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**`Python`**

```python title="Python"
requests.get(
    f"{BANKING}/transactions/by-client-reference",
    headers=headers,
    params={"client_reference": "budget-2026-09-marketing"},
).json()
```

**`JavaScript`**

```javascript title="JavaScript"
const refParams = new URLSearchParams({ client_reference: "budget-2026-09-marketing" });
await fetch(`${BANKING}/transactions/by-client-reference?${refParams}`, { headers }).then((res) =>
  res.json()
);
```

A transfer carries its `client_reference` onto both of its transactions, so this returns the `outbound` and `inbound` sides together.

## Reading the ledger directly

Where a transaction is the record of an activity, a ledger entry is the resulting posting against a specific account's balance:

**`cURL`**

```bash title="cURL"
curl "https://sandbox.api.thepennyinc.com/accounts/capital/$ACCOUNT_ID/ledger" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**`Python`**

```python title="Python"
ledger_entries = requests.get(
    f"{BANKING}/accounts/capital/{account_id}/ledger", headers=headers
).json()
```

**`JavaScript`**

```javascript title="JavaScript"
const ledgerEntries = await fetch(`${BANKING}/accounts/capital/${accountId}/ledger`, {
  headers,
}).then((res) => res.json());
```

Prefer webhooks over polling for anything you need to react to in near-real-time — see [Subscribing to Webhooks](/platform/subscribing-to-webhooks).