> 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.

# Accounts

Penny models money with three kinds of account. **Capital Accounts** hold real balances. **Control Accounts** hold no balance of their own; they scope how much can be spent against a Capital Account, and how fast. **Linked Accounts** are external bank accounts you register as sources of incoming funds. All three share the `account_` ID prefix, and every account response carries a `type` field (`capital`, `control`, or `linked`) that tells you which kind it is.

## Capital Accounts

A Capital Account holds an actual settled balance: this is your real money. It reports two balance fields: `available_balance` (what's free to move right now) and `total_balance` (including funds held against pending activity). Capital Accounts can be:

* **Nested**: every Capital Account you create sits under a `parent_account_id`, so one funding source can be split across departments or products. Your business's top-level Capital Account has `root: true`.
* **Auto-topped-up**: configure `auto_top_up` with a `source_account_id` (another Capital Account), a `threshold_amount`, and a `top_up_amount`, and Penny moves `top_up_amount` from the source whenever the balance drops below the threshold.
* **Alerted**: set `low_balance_notification.threshold_amount` (via `PATCH /accounts/capital/{account_id}/low_balance_alert`) and receive an `account.low_balance_alert` webhook when the balance drops below it.

**`A capital account`**

```json title="A capital account"
{
  "account_id": "account_019372e3-b8dc-7de2-80b6-b8dcde20b6c9",
  "type": "capital",
  "version": 3,
  "version_time": "2026-08-14T16:05:12Z",
  "alias": "operations-account",
  "description": "Primary operating account for day-to-day spend.",
  "program_id": "program_019377de-7f88-7f02-894f-7f88f0294fcb",
  "business_id": "business_01937b95-484e-75af-8c68-484e5afc68d3",
  "business_entity_id": "business_entity_01937c9d-3a1e-72b0-8881-3a1e2b088159",
  "status": "active",
  "active": true,
  "currency": "USD",
  "parent_account_id": "account_019372e1-4c2a-7b10-9d3e-4c2a7b109d3e",
  "root": false,
  "available_balance": "48200.00",
  "total_balance": "50200.00",
  "auto_top_up": {
    "source_account_id": "account_019372e1-4c2a-7b10-9d3e-4c2a7b109d3e",
    "source_account_type": "capital",
    "threshold_amount": "10000.00",
    "top_up_amount": "25000.00"
  },
  "low_balance_notification": { "threshold_amount": "5000.00" }
}
```

Keys whose value is `null` are omitted from responses. This account has no `spend_control_id`, so the key is absent.

## Control Accounts

A Control Account holds no balance. It is tied to one Capital Account (`capital_account_id`), and spend through it is drawn from that Capital Account's balance. It exists to answer one question: *how much is allowed to move through here, and how fast?* Control Accounts can nest under a `parent_account_id`, and each one can carry its own [Spend Control](/spend-controls) via `spend_control_id`.

This is the layer you attach to cards, cardholders, or business units when you want a shared limit across many of them. A card and its cardholder can point at the same Control Account and share one budget, backed by one Capital Account's balance.

When you create a Control Account (`POST /accounts/control/`), the request names the Capital Account as `source_capital_account_id` and an optional parent as `parent_control_account_id`. The response reports them as `capital_account_id` and `parent_account_id`:

**`A control account`**

```json title="A control account"
{
  "account_id": "account_019372e3-9f4a-7c1b-9a3d-9f4a7c1b9a3d",
  "type": "control",
  "version": 1,
  "version_time": "2026-08-14T16:10:44Z",
  "alias": "card-program-control",
  "program_id": "program_019377de-7f88-7f02-894f-7f88f0294fcb",
  "business_id": "business_01937b95-484e-75af-8c68-484e5afc68d3",
  "business_entity_id": "business_entity_01937c9d-3a1e-72b0-8881-3a1e2b088159",
  "status": "active",
  "active": true,
  "currency": "USD",
  "capital_account_id": "account_019372e3-b8dc-7de2-80b6-b8dcde20b6c9",
  "spend_control_id": "spend_control_019379d9-a170-796b-867a-a17096b67a21"
}
```

This Control Account sits directly under its Capital Account, so `parent_account_id` is `null` and omitted.

## Reading balances and history

Every Capital and Control Account exposes its own ledger and transaction history:

```http
GET /accounts/capital/{account_id}/ledger
GET /accounts/capital/{account_id}/transactions
GET /accounts/control/{account_id}/ledger
GET /accounts/control/{account_id}/transactions
```

See [Transactions](/banking/transactions) for what a transaction represents, and [Reading Transactions & Ledger Entries](/banking/reading-transactions) for a worked example.

## Moving money between accounts

`POST /accounts/capital/transfer` moves funds from one Capital Account to another.

| Field              | Required | Description                                                                      |
| ------------------ | -------- | -------------------------------------------------------------------------------- |
| `from_account_id`  | Yes      | The Capital Account the funds leave.                                             |
| `to_account_id`    | Yes      | The Capital Account the funds arrive in.                                         |
| `amount`           | Yes      | A positive decimal string, for example `"250.00"`.                               |
| `currency`         | No       | `"USD"` (the default and the only supported currency). Must match both accounts. |
| `description`      | No       | Up to 256 characters describing the transfer.                                    |
| `client_reference` | No       | Your own reference, copied onto both resulting transactions.                     |

Send an `Idempotency-Key` header with every transfer. It is optional, but strongly advised: if a request times out, retrying with the same key returns the original result instead of moving the money twice. See [Retries & Idempotency](/get-started/idempotency).

**`cURL`**

```bash title="cURL"
curl -X POST https://sandbox.api.thepennyinc.com/accounts/capital/transfer \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5f0c7a52-3b8e-4f7a-9d61-2c4e8b1a7f30" \
  -d '{
    "from_account_id": "account_019372e3-b8dc-7de2-80b6-b8dcde20b6c9",
    "to_account_id": "account_019372e4-0a61-7e5c-8f2b-0a617e5c8f2b",
    "amount": "250.00",
    "currency": "USD",
    "description": "Monthly marketing budget",
    "client_reference": "budget-2026-09-marketing"
  }'
```

The `201 Created` response contains both sides of the transfer: `source_transaction` (the `outbound` transaction on `from_account_id`) and `destination_transaction` (the `inbound` transaction on `to_account_id`). Both are `transfer`-type [Transactions](/banking/transactions) with an `internal` channel and a `transfer` counterparty naming the other account. Abridged response:

**`201 Created`**

```jsonc title="201 Created"
{
  "source_transaction": {
    "transaction_id": "transaction_01937a80-1c4d-7a2e-9b5f-1c4d7a2e9b5f",
    "account_id": "account_019372e3-b8dc-7de2-80b6-b8dcde20b6c9",
    "type": "transfer",
    "direction": "outbound",
    "base_amount": "250.00",
    "base_amount_currency": "USD",
    "client_reference": "budget-2026-09-marketing",
    "channel": { "type": "internal" },
    "counterparty": { "type": "transfer", "account_id": "account_019372e4-0a61-7e5c-8f2b-0a617e5c8f2b" }
    // …
  },
  "destination_transaction": {
    "transaction_id": "transaction_01937a80-1c4e-7b3f-8a6c-1c4e7b3f8a6c",
    "account_id": "account_019372e4-0a61-7e5c-8f2b-0a617e5c8f2b",
    "type": "transfer",
    "direction": "inbound",
    "base_amount": "250.00",
    "base_amount_currency": "USD",
    "client_reference": "budget-2026-09-marketing",
    "channel": { "type": "internal" },
    "counterparty": { "type": "transfer", "account_id": "account_019372e3-b8dc-7de2-80b6-b8dcde20b6c9" }
    // …
  }
}
```

## Account status and lifecycle

Every Capital and Control Account carries a `status`:

| Status        | Meaning                                                                          |
| ------------- | -------------------------------------------------------------------------------- |
| `active`      | The account can be used normally.                                                |
| `deactivated` | The account is temporarily disabled and can be reactivated.                      |
| `suspended`   | The account has been suspended.                                                  |
| `deleting`    | Deletion has been requested and is in progress; the account is no longer usable. |
| `deleted`     | The account has been permanently deleted.                                        |

```http
PATCH /accounts/capital/{account_id}/activate
PATCH /accounts/capital/{account_id}/deactivate
DELETE /accounts/capital/{account_id}
```

The same three operations exist under `/accounts/control/{account_id}` for Control Accounts. Linked Accounts have their own statuses; see [Linking an Account](/banking/linking-an-account).

Activating and deactivating an account both apply immediately and return the updated account. Deleting an account is asynchronous: the request returns `202 Accepted` with the account (`item`) and the Task that completes the deletion (`task`). Deleting an account whose deletion has already started is rejected. Abridged response:

**`202 Accepted from DELETE /accounts/capital/{account_id}`**

```jsonc title="202 Accepted from DELETE /accounts/capital/{account_id}"
{
  "item": {
    "account_id": "account_019372e3-b8dc-7de2-80b6-b8dcde20b6c9",
    "type": "capital",
    "status": "deleting"
    // … the rest of the capital account
  },
  "task": {
    "task_id": "task_0193749d-6d72-7481-83a4-6d724813a48a",
    "version": 1,
    "version_time": "2026-09-02T14:21:07Z",
    "business_id": "business_01937b95-484e-75af-8c68-484e5afc68d3",
    "status": "received"
  }
}
```

A task's `status` is one of `received`, `scheduled`, `pending`, `pending_subtasks`, `running`, `completed`, `failed`, or `cancelled`. The task also carries `start_time` once it starts running and `finished_time` once it finishes. To check on the deletion, poll `GET /tasks/{task_id}`, or re-fetch the account with `GET /accounts/capital/{account_id}` (or `GET /accounts/control/{account_id}`) and check its `status`.