# Create a manual ledger adjustment

> The ledger.adjustments.create operation: POST /api/v1/ledger/adjustments in the REST API and the ledger_adjustments_create tool in the MCP server.

Posts a manual adjustment to the legal entity's ledger: a transaction of kind `adjustment` that corrects
balances, e.g. to record an opening bank balance. Posted transactions are never changed; to undo an
adjustment, post the reverse one.

Send either two accounts and an amount (`debitAccount`, `creditAccount`, `amount` as
`{ "amount": "<integer micros>", "currency": "EUR" }`), or `entries` (2–50, each
`{ account, side, amount, currency }`) that balance in every currency. Every adjustment needs a `memo`: the
reason, shown in the ledger, the audit log and to approvers.

- Customer wallet accounts can't be adjusted (credit grants move them).
- Accounts are codes from the chart of accounts, in the entry's currency. An adjustment may open
  entity-wide, bank (this entity's) and brand accounts (of this entity's brands); accounts that name a
  processor connection, product or customer must already exist.
- `brandId` makes the transaction the brand's (default: the one brand whose accounts it moves).

Financial tier: the finance role (or an owner or admin) with a sign-in in the last 15 minutes. If the
organization's approval policy lists this operation, a call above the limit of its currency (or in several
currencies) answers `approval_required`: it runs once a second person approves it, as you sent it. Dry-run
it first (`Ownrecord-Dry-Run: true`, `dry_run: true`) to see the entries it would post.

## At a glance

- **REST:** `POST /api/v1/ledger/adjustments`
- **MCP tool:** `ledger_adjustments_create`
- **Operation ID:** `ledger.adjustments.create`
- **Scope:** Legal entity
- **Permission:** `ledger:adjust`
- **Risk tier:** `financial`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** API key with the `ledger:adjust` scope
- **Fresh sign-in:** In the console, a sign-in within the last 15 minutes
- **Since:** M2

## Request

### Body

A JSON object (`Content-Type: application/json`):

| Field                | Type                     | Required | Description                                                                          |
| -------------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------ |
| `debitAccount`       | string                   | No       | Two-account form: the account code to debit, e.g. `bank:ent_…:EUR`.                  |
| `creditAccount`      | string                   | No       | Two-account form: the account code to credit, e.g. `opening_balances:EUR`.           |
| `amount`             | object                   | No       | Two-account form: the amount, integer micros as a string, and its currency.          |
| `amount.amount`      | string                   | Yes      |                                                                                      |
| `amount.currency`    | string                   | Yes      |                                                                                      |
| `entries`            | array of object          | No       | Entries form: 2 to 50 entries that balance in every currency.                        |
| `entries[].account`  | string                   | Yes      | An account code from the chart of accounts, e.g. `rounding:EUR`.                     |
| `entries[].side`     | one of `debit`, `credit` | Yes      | `debit` or `credit`.                                                                 |
| `entries[].amount`   | string                   | Yes      | Integer micros as a string, more than zero.                                          |
| `entries[].currency` | string                   | Yes      | The account's currency.                                                              |
| `brandId`            | `brd_…` ID               | No       | The brand the adjustment belongs to. Default: the one brand whose accounts it moves. |
| `memo`               | string                   | Yes      | The reason: shown in the ledger, the audit log and to approvers.                     |

## Response

`201 Created` with a JSON object:

| Field                    | Type                                                                          | Description                                                                              |
| ------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `id`                     | `txn_…` ID                                                                    |                                                                                          |
| `object`                 | `"ledger_transaction"`                                                        |                                                                                          |
| `entityId`               | `ent_…` ID                                                                    |                                                                                          |
| `livemode`               | boolean                                                                       |                                                                                          |
| `brandId`                | `brd_…` ID or null                                                            |                                                                                          |
| `kind`                   | one of 19 values, e.g. `topup`, `sale`, `grant` (all in the OpenAPI document) |                                                                                          |
| `status`                 | one of `pending`, `posted`, `voided`                                          |                                                                                          |
| `entries`                | array of object                                                               |                                                                                          |
| `entries[].accountId`    | `acct_…` ID                                                                   |                                                                                          |
| `entries[].accountCode`  | string                                                                        | The account's code from the chart of accounts.                                           |
| `entries[].side`         | one of `debit`, `credit`                                                      |                                                                                          |
| `entries[].amount`       | string                                                                        | Integer micros, more than zero.                                                          |
| `entries[].currency`     | string                                                                        |                                                                                          |
| `totals`                 | array of object                                                               | Debit and credit totals per currency, always equal.                                      |
| `totals[].currency`      | string                                                                        |                                                                                          |
| `totals[].debits`        | string                                                                        |                                                                                          |
| `totals[].credits`       | string                                                                        |                                                                                          |
| `effectiveAt`            | date-time                                                                     | The business date of the movement.                                                       |
| `postedAt`               | date-time or null                                                             |                                                                                          |
| `voidedAt`               | date-time or null                                                             |                                                                                          |
| `reversalOf`             | `txn_…` ID or null                                                            | The posted transaction this one reverses.                                                |
| `links`                  | object                                                                        | Objects the transaction is about, by ID.                                                 |
| `memo`                   | string                                                                        | The description; for a manual adjustment, its reason.                                    |
| `actor`                  | object                                                                        | Who posted it. `approvalId` names the approval request when a second person approved it. |
| `actor.type`             | one of `user`, `api_key`, `agent`, `app`, `system`, `public`, `customer`      |                                                                                          |
| `actor.id`               | string                                                                        |                                                                                          |
| `actor.via`              | one of `console`, `api`, `mcp`, `assistant`, `portal`, `job` or null          |                                                                                          |
| `actor.approvalId`       | string or null                                                                |                                                                                          |
| `actor.onBehalfOfUserId` | string or null                                                                |                                                                                          |
| `actor.clientId`         | string or null                                                                |                                                                                          |
| `actor.supportGrantId`   | string or null                                                                |                                                                                          |
| `createdAt`              | date-time                                                                     |                                                                                          |

## Examples

### Record an opening bank balance

Two accounts and an amount: debit the bank account, credit opening balances.

```http
POST /api/v1/ledger/adjustments
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "debitAccount": "bank:ent_01m4tx5nj8emwbyf7y9vcf47x2:EUR",
  "creditAccount": "opening_balances:EUR",
  "amount": {
    "amount": "1500000000",
    "currency": "EUR"
  },
  "memo": "Opening bank balance from the 2026-09-30 statement"
}
```

Response (`201`):

```json
{
  "id": "txn_01m4tx5nj8emwbyf7y9vcf47xg",
  "object": "ledger_transaction",
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "livemode": true,
  "brandId": null,
  "kind": "adjustment",
  "status": "posted",
  "entries": [
    {
      "accountId": "acct_01m4tx5nj8emwbyf7y9vcf47xc",
      "accountCode": "bank:ent_01m4tx5nj8emwbyf7y9vcf47x2:EUR",
      "side": "debit",
      "amount": "1500000000",
      "currency": "EUR"
    },
    {
      "accountId": "acct_01m4tx5nj8emwbyf7y9vcf47xd",
      "accountCode": "opening_balances:EUR",
      "side": "credit",
      "amount": "1500000000",
      "currency": "EUR"
    }
  ],
  "totals": [
    {
      "currency": "EUR",
      "debits": "1500000000",
      "credits": "1500000000"
    }
  ],
  "effectiveAt": "2026-10-14T09:30:00.000Z",
  "postedAt": "2026-10-14T09:30:00.000Z",
  "voidedAt": null,
  "reversalOf": null,
  "links": {},
  "memo": "Opening bank balance from the 2026-09-30 statement",
  "actor": {
    "type": "user",
    "id": "usr_01m4tx5nj8emwbyf7y9vcf47xa",
    "via": "console",
    "approvalId": "apr_01m4tx5nj8emwbyf7y9vcf47xj",
    "onBehalfOfUserId": null,
    "clientId": null,
    "supportGrantId": null
  },
  "createdAt": "2026-10-14T09:30:00.000Z"
}
```

### Move usage revenue of one brand, with entries

```http
POST /api/v1/ledger/adjustments
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "entries": [
    {
      "account": "revenue:brd_01m4tx5nj8emwbyf7y9vcf47x4:usage:api.calls:EUR",
      "side": "debit",
      "amount": "2500000",
      "currency": "EUR"
    },
    {
      "account": "rounding:EUR",
      "side": "credit",
      "amount": "2500000",
      "currency": "EUR"
    }
  ],
  "memo": "Reclassify a rounding difference booked as usage revenue"
}
```

Response (`201`):

```json
{
  "id": "txn_01m4tx5nj8emwbyf7y9vcf47xh",
  "object": "ledger_transaction",
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "livemode": true,
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "kind": "adjustment",
  "status": "posted",
  "entries": [
    {
      "accountId": "acct_01m4tx5nj8emwbyf7y9vcf47xe",
      "accountCode": "revenue:brd_01m4tx5nj8emwbyf7y9vcf47x4:usage:api.calls:EUR",
      "side": "debit",
      "amount": "2500000",
      "currency": "EUR"
    },
    {
      "accountId": "acct_01m4tx5nj8emwbyf7y9vcf47xf",
      "accountCode": "rounding:EUR",
      "side": "credit",
      "amount": "2500000",
      "currency": "EUR"
    }
  ],
  "totals": [
    {
      "currency": "EUR",
      "debits": "2500000",
      "credits": "2500000"
    }
  ],
  "effectiveAt": "2026-10-14T09:30:00.000Z",
  "postedAt": "2026-10-14T09:30:00.000Z",
  "voidedAt": null,
  "reversalOf": null,
  "links": {},
  "memo": "Reclassify a rounding difference booked as usage revenue",
  "actor": {
    "type": "user",
    "id": "usr_01m4tx5nj8emwbyf7y9vcf47xa",
    "via": "api",
    "approvalId": null,
    "onBehalfOfUserId": null,
    "clientId": null,
    "supportGrantId": null
  },
  "createdAt": "2026-10-14T09:30:00.000Z"
}
```

## Errors

| Code                                                                                                           | HTTP status | When                                                                                                                                                       | What to do                                                                                                                                                                                                                                                                            |
| -------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)                         | 400         | A field is missing or malformed, an entry's currency isn't its account's, both input forms were sent, or an account can't be used here (`details.issues`). | Fix the fields listed in `details.issues` and send the request again.                                                                                                                                                                                                                 |
| [`ledger_unbalanced`](https://ownrecord.io/docs/reference/errors.md#ledger_unbalanced)                         | 400         | Debits and credits differ in a currency (`details.imbalances`).                                                                                            | Make the debits equal the credits in every currency (`details.imbalances` lists each currency's totals) and post again.                                                                                                                                                               |
| [`ledger_account_not_adjustable`](https://ownrecord.io/docs/reference/errors.md#ledger_account_not_adjustable) | 400         | An entry posts to a customer wallet account.                                                                                                               | Change wallet balances with a credit grant, an expiry or a refund instead, so the wallet's credit grants stay in step with its balance.                                                                                                                                               |
| [`insufficient_funds`](https://ownrecord.io/docs/reference/errors.md#insufficient_funds)                       | 402         | The adjustment would take a customer's credit balance below zero.                                                                                          | Reserve or capture a smaller amount, or top up the wallet first. `details.requested` and `details.available` are in integer micros.                                                                                                                                                   |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)                       | 404         | `brandId` isn't a brand of this legal entity.                                                                                                              | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                                                                                                                                                                  |
| [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused)               | 409         | The idempotency key was used before for another adjustment.                                                                                                | Use a new Idempotency-Key for a different request, or resend the original request unchanged.                                                                                                                                                                                          |
| [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required)                         | 202         | The approval policy needs a second person for this amount; nothing was posted yet.                                                                         | Nothing ran yet. An approver reviews `approval.summary` and the dry-run result in the console (`approval.approveUrl`). Poll `GET /approvals/{id}` or watch for the `approval.resolved` event; sending the same request again with the same Idempotency-Key returns the same approval. |
| [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected)                         | 409         | The same request was sent again after it was rejected.                                                                                                     | Read the reason in `details.approval.rejection`. To try again, send a new request with a new Idempotency-Key.                                                                                                                                                                         |
| [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired)                           | 409         | The same request was sent again after its approval request expired.                                                                                        | Send the request again with a new Idempotency-Key to create a new approval request. Requests expire after 72 hours.                                                                                                                                                                   |
| [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval)                               | 409         | The approved request could no longer run as requested.                                                                                                     | Check the current state (`details.approval.failure` says what failed), then send a new request with a new Idempotency-Key.                                                                                                                                                            |
| [`fresh_auth_required`](https://ownrecord.io/docs/reference/errors.md#fresh_auth_required)                     | 403         | Your last sign-in is older than 15 minutes.                                                                                                                | Sign in again, then repeat the action within 15 minutes.                                                                                                                                                                                                                              |
| [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed)                   | 403         | You have no live-mode access, so you can't adjust the live ledger.                                                                                         | Use test mode, or credentials for the target's mode. Developers need live access granted by an organization owner or admin.                                                                                                                                                           |
| [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required)                               | 400         | The request names no legal entity (an org key without `Ownrecord-Entity`).                                                                                 | Call the operation with credentials or a target at its level: an organization, a legal entity, a brand or the platform (`details.required_scope`).                                                                                                                                    |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)                         | 403         | You can't adjust this entity's ledger (finance, owners and admins can).                                                                                    | Ask an organization owner or admin for a role or scope that allows this action.                                                                                                                                                                                                       |
| [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict)                           | 409         | Another posting changed the same accounts at the same moment; retry.                                                                                       | Fetch the object again and retry the change.                                                                                                                                                                                                                                          |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
