# Platform billing tools

> MCP tools for Platform billing: platform_billing_change_plan, platform_billing_get, platform_billing_holds_get, platform_billing_holds_lift, platform_billing_holds_set, platform_billing_plan_override_remove, platform_billing_plan_override_set, platform_billing_portal_sessions_create, platform_billing_settings_get, platform_billing_settings_update.

- `platform_billing_change_plan`: Change the organization's Ownrecord plan
- `platform_billing_get`: Get the organization's Ownrecord plan
- `platform_billing_holds_get`: Get an organization's unpaid-plan escalation
- `platform_billing_holds_lift`: Lift a hold on an organization's escalation
- `platform_billing_holds_set`: Hold an organization's unpaid-plan escalation
- `platform_billing_plan_override_remove`: Remove an organization's plan override
- `platform_billing_plan_override_set`: Override an organization's plan
- `platform_billing_portal_sessions_create`: Open the Ownrecord billing portal
- `platform_billing_settings_get`: Get the platform billing settings
- `platform_billing_settings_update`: Change the platform billing thresholds

## `platform_billing_change_plan`

**Change the organization's Ownrecord plan.** Operation [`platform_billing.change_plan`](https://ownrecord.io/docs/reference/platform-billing-change-plan.md) · Organization level · permission `platform_plan:admin` · risk tier `financial` · since M12

Moves the organization to another Ownrecord plan (`starter`, `growth` or `scale`; `sandbox` to go back to the
free plan) and billing interval (`month` or `year` where the plan has a yearly price):

- **Upgrade with a card on file:** applies now, invoiced at once with the unused time credited; the old plan
  stays until the payment succeeds.
- **Upgrade without a card** (for example from `sandbox`): the answer is `outcome: checkout` with `checkoutUrl`,
  Ownrecord's own checkout, where you save a card and pay the first invoice. The plan changes when it is paid.
- **Downgrade:** scheduled for the end of the current period (`outcome: scheduled`), with no credit.

Plans are priced in EUR, CHF and USD: pass `currency`, or leave it out to keep a running paid plan's currency
(or, from the free plan, take the suggestion for your country: CHF for Switzerland and Liechtenstein, EUR for
the EU, USD otherwise). A running paid plan keeps its currency. Every paid plan includes the volume fee on live
payments, billed monthly in arrears in the plan's currency. Invoices come from Ownrecord's entity, Etna Labs AG
in Switzerland, with tax (Swiss VAT for Swiss customers, reverse charge for businesses abroad with a valid VAT
ID). Only an org_owner or billing_admin can change the plan. Dry-run it to see what happens without changing
anything.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: false` · `openWorldHint: true`

### Arguments

| Argument          | Type                   | Required | Description                                                                                                                                                                                              |
| ----------------- | ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `plan`            | string                 | yes      | The plan key, e.g. `growth`.                                                                                                                                                                             |
| `interval`        | one of `month`, `year` | no       | The billing interval of the plan's fee. (default `"month"`)                                                                                                                                              |
| `currency`        | string                 | no       | The subscription's currency: EUR, CHF or USD (default: the running plan's, else your country's).                                                                                                         |
| `idempotency_key` | string                 | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean                | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The plan key, interval or currency is missing or malformed.
- [`platform_plan_unknown`](https://ownrecord.io/docs/reference/errors.md#platform_plan_unknown): There's no self-serve plan with this key, or it has no price for the interval or currency.
- [`platform_plan_currency_fixed`](https://ownrecord.io/docs/reference/errors.md#platform_plan_currency_fixed): A paid plan is running in another currency: it keeps its currency (move to the free plan first).
- [`platform_plan_unchanged`](https://ownrecord.io/docs/reference/errors.md#platform_plan_unchanged): The organization is already on this plan and interval.
- [`platform_plan_change_pending`](https://ownrecord.io/docs/reference/errors.md#platform_plan_change_pending): An upgrade is still waiting for its payment in a checkout.
- [`platform_billing_details_required`](https://ownrecord.io/docs/reference/errors.md#platform_billing_details_required): A paid plan needs the organization's VAT ID: add a legal entity, or billing details in the portal.
- [`platform_billing_unavailable`](https://ownrecord.io/docs/reference/errors.md#platform_billing_unavailable): Ownrecord's plans aren't set up on this installation yet.
- [`platform_customer_missing`](https://ownrecord.io/docs/reference/errors.md#platform_customer_missing): The organization's billing account is still being set up (dry runs).
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only an org_owner or billing_admin changes the plan.

### Examples

**Upgrade from Sandbox to Growth**

No card is on file, so the answer is Ownrecord's checkout.

Arguments:

```json
{
  "plan": "growth",
  "interval": "month",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "platform_plan_change",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "plan": "growth",
  "interval": "month",
  "currency": "EUR",
  "outcome": "checkout",
  "subscriptionId": "sub_01jbz3m4k5n6p7q8r9s0t1v2w6",
  "effectiveAt": null,
  "checkoutUrl": "https://ownrecord.io/c/ownrecord/cs_01jbz3m4k5n6p7q8r9s0t1v2w9?key=…"
}
```

**Upgrade a Swiss organization to Growth in Swiss francs**

Arguments:

```json
{
  "plan": "growth",
  "interval": "month",
  "currency": "CHF",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "platform_plan_change",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "plan": "growth",
  "interval": "month",
  "currency": "CHF",
  "outcome": "checkout",
  "subscriptionId": "sub_01jbz3m4k5n6p7q8r9s0t1v2w6",
  "effectiveAt": null,
  "checkoutUrl": "https://ownrecord.io/c/ownrecord/cs_01jbz3m4k5n6p7q8r9s0t1v2w9?key=…"
}
```

**Downgrade to Starter at the end of the period**

Arguments:

```json
{
  "plan": "starter",
  "interval": "month",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "platform_plan_change",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "plan": "starter",
  "interval": "month",
  "currency": "EUR",
  "outcome": "scheduled",
  "subscriptionId": "sub_01jbz3m4k5n6p7q8r9s0t1v2w5",
  "effectiveAt": "2026-11-01T00:00:00.000Z",
  "checkoutUrl": null
}
```

## `platform_billing_get`

**Get the organization's Ownrecord plan.** Operation [`platform_billing.get`](https://ownrecord.io/docs/reference/platform-billing-get.md) · Organization level · permission `platform_plan:read` · risk tier `read` · since M12

Returns the organization's own Ownrecord subscription, as Organization → Billing shows it: the current plan,
its status and period, the effective entitlements (an active override merged over the plan), usage against
each limit (`limits`, with `over` after a downgrade: nothing is deleted, creating more is blocked), the metered
volume of the current period with its volume fee so far, the cheapest plan for the last three months of volume,
the plans you can choose, and the escalation of an unpaid subscription (`escalation`: restricted after 14 days,
suspended after 30, unless a platform admin holds it).

Ownrecord bills organizations through its own platform brand: the plan is a subscription of the organization's
billing account there (`platformCustomerId`). Change the plan with `platform_billing.change_plan`; manage
payment methods, billing details and invoices in the billing portal (`platform_billing.portal_sessions.create`).

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

This tool takes no arguments.

- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only an org_owner, org_admin or billing_admin sees the organization's Ownrecord plan.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No such organization is visible to you.

### Example

**An organization on Growth**

Arguments:

```json
{}
```

Result `output`:

```json
{
  "object": "platform_billing",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "status": "active",
  "livemode": true,
  "provisioning": "ready",
  "platformCustomerId": "cus_01jbz3m4k5n6p7q8r9s0t1v2w4",
  "plan": {
    "key": "growth",
    "name": "Growth",
    "status": "active",
    "subscriptionId": "sub_01jbz3m4k5n6p7q8r9s0t1v2w5",
    "interval": "month",
    "currentPeriodEnd": "2026-11-01T00:00:00.000Z",
    "cancelAtPeriodEnd": false,
    "scheduledChange": null,
    "hasPaymentMethod": true
  },
  "override": null,
  "entitlements": {
    "api.rate_limit_rps": 100,
    "assistant.enabled": true,
    "assistant.messages_per_month": 1000,
    "audit.retention_days": 90,
    "brands.max": 3,
    "custom_domains.max": 2,
    "einvoicing.enabled": true,
    "entities.max": 2,
    "filing.reports": "full",
    "jurisdictions.registered.max": 10,
    "livemode": true,
    "members.max": 10,
    "processors.secondary": false,
    "sso.enabled": false,
    "subscriptions.enabled": true,
    "support.level": "email",
    "usage_billing.enabled": true
  },
  "limits": [
    {
      "feature": "entities.max",
      "limit": 2,
      "used": 1,
      "atLimit": false,
      "over": false
    },
    {
      "feature": "brands.max",
      "limit": 3,
      "used": 3,
      "atLimit": true,
      "over": false
    },
    {
      "feature": "members.max",
      "limit": 10,
      "used": 4,
      "atLimit": false,
      "over": false
    },
    {
      "feature": "jurisdictions.registered.max",
      "limit": 10,
      "used": 2,
      "atLimit": false,
      "over": false
    },
    {
      "feature": "custom_domains.max",
      "limit": 2,
      "used": 0,
      "atLimit": false,
      "over": false
    }
  ],
  "escalation": {
    "unpaidSince": null,
    "stage": null,
    "next": null,
    "hold": null,
    "thresholds": {
      "restrictAfterDays": 14,
      "suspendAfterDays": 30
    }
  },
  "usage": {
    "periodStart": "2026-10-01T00:00:00.000Z",
    "periodEnd": "2026-11-01T00:00:00.000Z",
    "volume": {
      "amount": "41250000000",
      "currency": "EUR"
    },
    "transactions": 1375,
    "estimatedVolumeFee": {
      "amount": "206250000",
      "currency": "EUR"
    }
  },
  "recommendation": {
    "cheapest": "growth",
    "averageMonthlyVolume": {
      "amount": "48000000000",
      "currency": "EUR"
    },
    "costs": [
      {
        "plan": "starter",
        "cost": {
          "amount": "480000000",
          "currency": "EUR"
        }
      },
      {
        "plan": "growth",
        "cost": {
          "amount": "389000000",
          "currency": "EUR"
        }
      },
      {
        "plan": "scale",
        "cost": {
          "amount": "643000000",
          "currency": "EUR"
        }
      }
    ]
  },
  "plans": [
    {
      "key": "growth",
      "name": "Growth",
      "highlighted": true,
      "contactSales": false,
      "current": true,
      "trialDays": 14,
      "prices": [
        {
          "lookupKey": "growth_monthly",
          "currency": "EUR",
          "interval": "month",
          "kind": "licensed",
          "amount": {
            "amount": "149000000",
            "currency": "EUR"
          },
          "ratePpm": null
        },
        {
          "lookupKey": "growth_yearly",
          "currency": "EUR",
          "interval": "year",
          "kind": "licensed",
          "amount": {
            "amount": "1430000000",
            "currency": "EUR"
          },
          "ratePpm": null
        },
        {
          "lookupKey": "growth_volume",
          "currency": "EUR",
          "interval": "month",
          "kind": "metered",
          "amount": null,
          "ratePpm": "5000"
        },
        {
          "lookupKey": "growth_monthly_chf",
          "currency": "CHF",
          "interval": "month",
          "kind": "licensed",
          "amount": {
            "amount": "149000000",
            "currency": "CHF"
          },
          "ratePpm": null
        },
        {
          "lookupKey": "growth_volume_chf",
          "currency": "CHF",
          "interval": "month",
          "kind": "metered",
          "amount": null,
          "ratePpm": "5000"
        }
      ],
      "entitlements": {
        "api.rate_limit_rps": 100,
        "assistant.enabled": true,
        "assistant.messages_per_month": 1000,
        "audit.retention_days": 90,
        "brands.max": 3,
        "custom_domains.max": 2,
        "einvoicing.enabled": true,
        "entities.max": 2,
        "filing.reports": "full",
        "jurisdictions.registered.max": 10,
        "livemode": true,
        "members.max": 10,
        "processors.secondary": false,
        "sso.enabled": false,
        "subscriptions.enabled": true,
        "support.level": "email",
        "usage_billing.enabled": true
      }
    }
  ],
  "currency": {
    "options": ["EUR", "CHF", "USD"],
    "current": "EUR",
    "suggested": "EUR",
    "changeable": false
  },
  "pendingUpgrade": null
}
```

## `platform_billing_holds_get`

**Get an organization's unpaid-plan escalation.** Operation [`platform_billing.holds.get`](https://ownrecord.io/docs/reference/platform-billing-holds-get.md) · Platform level · permission `platform:read` · risk tier `read` · since M12

Returns where an organization's unpaid-plan escalation stands (spec §37.5): its status, when its Ownrecord
subscription became unpaid, the stage the escalation set, the next change if it stays unpaid, and a platform
admin's hold. `billingOwnsStatus` is false while the organization is suspended for abuse or is closing or closed:
the escalation then changes nothing. Platform staff only.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument | Type         | Required | Description                 |
| -------- | ------------ | -------- | --------------------------- |
| `orgId`  | ID (`org_…`) | yes      | The organization (`org_…`). |

- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The organization ID is missing or malformed.
- [`platform_billing_unavailable`](https://ownrecord.io/docs/reference/errors.md#platform_billing_unavailable): Ownrecord's plans aren't set up on this installation yet.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): There is no such organization.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only platform staff read an organization's escalation.

### Example

**Restricted and held there until a bank transfer arrives**

Arguments:

```json
{
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3"
}
```

Result `output`:

```json
{
  "object": "platform_billing_hold",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "status": "restricted",
  "hold": {
    "stage": "restricted",
    "until": "2026-11-15T00:00:00.000Z",
    "reason": "Paying by bank transfer; payment confirmed for 10 November",
    "setBy": "usr_01jbz3m4k5n6p7q8r9s0t1v2w7"
  },
  "unpaidSince": "2026-10-05T09:12:00.000Z",
  "stage": "restricted",
  "next": {
    "stage": "suspended",
    "at": "2026-11-15T00:00:00.000Z"
  },
  "billingOwnsStatus": true
}
```

## `platform_billing_holds_lift`

**Lift a hold on an organization's escalation.** Operation [`platform_billing.holds.lift`](https://ownrecord.io/docs/reference/platform-billing-holds-lift.md) · Platform level · permission `platform:admin` · risk tier `write` · since M12

Ends a platform admin's hold (`platform_billing.holds.set`): the organization's status follows the time since
its Ownrecord payment first failed again, at once (restricted after 14 days, suspended after 30, by default).
Lifting an organization without a hold changes nothing. Platform admins only; recorded in the audit log.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type         | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orgId`           | ID (`org_…`) | yes      | The organization (`org_…`).                                                                                                                                                                              |
| `reason`          | string       | yes      | Why, for the audit log. (3–500 characters)                                                                                                                                                               |
| `idempotency_key` | string       | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean      | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The organization ID, stage, reason or date is missing or malformed.
- [`platform_billing_unavailable`](https://ownrecord.io/docs/reference/errors.md#platform_billing_unavailable): Ownrecord's plans aren't set up on this installation yet.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): There is no such organization.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only a platform_admin holds an organization's escalation.
- [`fresh_auth_required`](https://ownrecord.io/docs/reference/errors.md#fresh_auth_required): Your last sign-in is older than 15 minutes: sign in again first.

### Example

**Lift a hold**

Arguments:

```json
{
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "reason": "The promised transfer didn't arrive",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "platform_billing_hold",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "status": "suspended",
  "hold": null,
  "unpaidSince": "2026-10-05T09:12:00.000Z",
  "stage": "suspended",
  "next": null,
  "billingOwnsStatus": true
}
```

## `platform_billing_holds_set`

**Hold an organization's unpaid-plan escalation.** Operation [`platform_billing.holds.set`](https://ownrecord.io/docs/reference/platform-billing-holds-set.md) · Platform level · permission `platform:admin` · risk tier `write` · since M12

Holds the escalation of an organization whose Ownrecord subscription is unpaid (spec §37.5): while the hold
applies, the organization is at most at `stage`. Hold it where it is to stop it getting worse, hold it at
`active` to lift a restriction or suspension, or give `until` to extend the current stage to a date. The status
changes at once. The hold ends at `until`, when it is lifted (`platform_billing.holds.lift`), or when the
subscription is paid. Platform admins only; the reason is recorded in the organization's audit log.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type                                       | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orgId`           | ID (`org_…`)                               | yes      | The organization (`org_…`).                                                                                                                                                                              |
| `stage`           | one of `active`, `restricted`, `suspended` | yes      | The most severe stage while the hold applies.                                                                                                                                                            |
| `until`           | date-time (ISO 8601)                       | no       | When the hold ends (ISO 8601, in the future); left out, it lasts until lifted.                                                                                                                           |
| `reason`          | string                                     | yes      | Why, for the audit log and the support team. (3–500 characters)                                                                                                                                          |
| `idempotency_key` | string                                     | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean                                    | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The organization ID, stage, reason or date is missing or malformed.
- [`platform_billing_hold_invalid`](https://ownrecord.io/docs/reference/errors.md#platform_billing_hold_invalid): The hold ends in the past, or the organization's subscription isn't unpaid.
- [`platform_billing_unavailable`](https://ownrecord.io/docs/reference/errors.md#platform_billing_unavailable): Ownrecord's plans aren't set up on this installation yet.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): There is no such organization.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only a platform_admin holds an organization's escalation.
- [`fresh_auth_required`](https://ownrecord.io/docs/reference/errors.md#fresh_auth_required): Your last sign-in is older than 15 minutes: sign in again first.

### Example

**Keep an organization restricted, not suspended, until a bank transfer arrives**

Arguments:

```json
{
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "stage": "restricted",
  "until": "2026-11-15T00:00:00.000Z",
  "reason": "Paying by bank transfer; payment confirmed for 10 November",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "platform_billing_hold",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "status": "restricted",
  "hold": {
    "stage": "restricted",
    "until": "2026-11-15T00:00:00.000Z",
    "reason": "Paying by bank transfer; payment confirmed for 10 November",
    "setBy": "usr_01jbz3m4k5n6p7q8r9s0t1v2w7"
  },
  "unpaidSince": "2026-10-05T09:12:00.000Z",
  "stage": "restricted",
  "next": {
    "stage": "suspended",
    "at": "2026-11-15T00:00:00.000Z"
  },
  "billingOwnsStatus": true
}
```

## `platform_billing_plan_override_remove`

**Remove an organization's plan override.** Operation [`platform_billing.plan_override.remove`](https://ownrecord.io/docs/reference/platform-billing-plan-override-remove.md) · Platform level · permission `platform:admin` · risk tier `security` · since M13

Ends a platform admin's plan override (`platform_billing.plan_override.set`): the organization gets the
entitlements of the plan it pays for again, at once. Removing an organization without an override changes
nothing. Platform admins only; the reason is recorded in the organization's audit log.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type         | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orgId`           | ID (`org_…`) | yes      | The organization (`org_…`).                                                                                                                                                                              |
| `reason`          | string       | yes      | Why, for the audit log. (3–500 characters)                                                                                                                                                               |
| `idempotency_key` | string       | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean      | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`fresh_auth_required`](https://ownrecord.io/docs/reference/errors.md#fresh_auth_required): Your last sign-in is older than 15 minutes: sign in again first.
- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The organization ID or the reason is missing or malformed.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): There is no such organization.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The organization changed at the same time; retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only a platform_admin overrides an organization's plan.

### Example

**End a pilot's override**

Arguments:

```json
{
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "reason": "The pilot ended; the customer subscribed to Growth",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "platform_plan_override",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "override": null
}
```

## `platform_billing_plan_override_set`

**Override an organization's plan.** Operation [`platform_billing.plan_override.set`](https://ownrecord.io/docs/reference/platform-billing-plan-override-set.md) · Platform level · permission `platform:admin` · risk tier `security` · since M13

Gives an organization the entitlements of one of Ownrecord's plans (`plan`, a key of data/platform/plans.yaml
such as `growth` or `internal`) whatever it pays for, until `expiresAt` or until removed
(`platform_billing.plan_override.remove`). The override names its plan: an organization on `internal` isn't
metered. It replaces an earlier override. Its billing (the subscription, invoices, the unpaid-plan escalation)
doesn't change. Platform admins only; the reason is recorded in the organization's audit log.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type                 | Required | Description                                                                                                                                                                                              |
| ----------------- | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orgId`           | ID (`org_…`)         | yes      | The organization (`org_…`).                                                                                                                                                                              |
| `plan`            | string               | yes      | A plan key of data/platform/plans.yaml, e.g. `growth`. (1–40 characters)                                                                                                                                 |
| `expiresAt`       | date-time (ISO 8601) | no       | When the override ends (ISO 8601, in the future); left out, it lasts until removed.                                                                                                                      |
| `reason`          | string               | yes      | Why, for the audit log and the support team. (3–500 characters)                                                                                                                                          |
| `idempotency_key` | string               | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean              | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The plan isn't one of data/platform/plans.yaml, the end date is in the past, the reason is missing, or the organization is the platform's own.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): There is no such organization.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The organization changed at the same time; retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only a platform_admin overrides an organization's plan.
- [`fresh_auth_required`](https://ownrecord.io/docs/reference/errors.md#fresh_auth_required): Your last sign-in is older than 15 minutes: sign in again first.

### Example

**Growth for a design partner until the end of the pilot**

Arguments:

```json
{
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "plan": "growth",
  "expiresAt": "2027-03-31T00:00:00.000Z",
  "reason": "Design partner: Growth free until the end of the pilot",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "platform_plan_override",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "override": {
    "plan": "growth",
    "planName": "Growth",
    "entitlements": {
      "api.rate_limit_rps": 100,
      "assistant.enabled": true,
      "assistant.messages_per_month": 1000,
      "audit.retention_days": 90,
      "brands.max": 3,
      "custom_domains.max": 2,
      "einvoicing.enabled": true,
      "entities.max": 2,
      "filing.reports": "full",
      "jurisdictions.registered.max": 10,
      "livemode": true,
      "members.max": 10,
      "processors.secondary": false,
      "sso.enabled": false,
      "subscriptions.enabled": true,
      "support.level": "email",
      "usage_billing.enabled": true
    },
    "reason": "Design partner: Growth free until the end of the pilot",
    "setBy": "usr_01jbz3m4k5n6p7q8r9s0t1v2w7",
    "expiresAt": "2027-03-31T00:00:00.000Z",
    "active": true
  }
}
```

## `platform_billing_portal_sessions_create`

**Open the Ownrecord billing portal.** Operation [`platform_billing.portal_sessions.create`](https://ownrecord.io/docs/reference/platform-billing-portal-sessions-create.md) · Organization level · permission `platform_plan:admin` · risk tier `security` · since M12

Makes a one-time sign-in link (15 minutes) to Ownrecord's billing portal for the organization: payment methods,
billing details (the organization is a customer of Ownrecord's entity: legal name, address, VAT ID), invoices
from Ownrecord as PDF, and the upcoming invoice. It is the same customer portal subscribers give their own
customers. Only an org_owner or billing_admin can open it; opening it is recorded in the audit log.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: false` · `openWorldHint: false`

### Arguments

| Argument          | Type   | Required | Description                                                                                                                                                                                              |
| ----------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idempotency_key` | string | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** not supported. `dry_run: true` fails with `dry_run_not_supported`.

### Errors

- [`platform_billing_unavailable`](https://ownrecord.io/docs/reference/errors.md#platform_billing_unavailable): Ownrecord's plans aren't set up on this installation yet.
- [`platform_customer_missing`](https://ownrecord.io/docs/reference/errors.md#platform_customer_missing): The organization has no billing account (the platform organization isn't billed).
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only an org_owner or billing_admin opens the billing portal.

### Example

**A sign-in link to the billing portal**

Arguments:

```json
{
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "platform_portal_session",
  "orgId": "org_01jbz3m4k5n6p7q8r9s0t1v2w3",
  "url": "https://ownrecord.io/portal/ownrecord/login/verify?session=psess_…&token=…",
  "expiresAt": "2026-10-26T12:15:00.000Z"
}
```

## `platform_billing_settings_get`

**Get the platform billing settings.** Operation [`platform_billing.settings.get`](https://ownrecord.io/docs/reference/platform-billing-settings-get.md) · Platform level · permission `platform:read` · risk tier `read` · since M12

Returns how Ownrecord bills organizations: the days after a failed payment before an organization is
restricted and suspended (spec §37.5; 14 and 30 by default), the mode organizations are billed in (live in
production), and the default plan's price new organizations start on. Platform staff only.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

This tool takes no arguments.

- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A threshold is missing or not a whole number.
- [`platform_billing_unavailable`](https://ownrecord.io/docs/reference/errors.md#platform_billing_unavailable): The platform bootstrap hasn't run yet.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only platform staff read, and only a platform_admin changes, these settings.

### Example

**The defaults**

Arguments:

```json
{}
```

Result `output`:

```json
{
  "object": "platform_billing_settings",
  "restrictAfterDays": 14,
  "suspendAfterDays": 30,
  "livemode": true,
  "defaultPlanPriceId": "price_01jbz3m4k5n6p7q8r9s0t1v2w8"
}
```

## `platform_billing_settings_update`

**Change the platform billing thresholds.** Operation [`platform_billing.settings.update`](https://ownrecord.io/docs/reference/platform-billing-settings-update.md) · Platform level · permission `platform:admin` · risk tier `write` · since M12

Changes how many days after a failed Ownrecord payment an organization is restricted (`restrictAfterDays`,
default 14) and suspended (`suspendAfterDays`, default 30), from 1 to 60 days, restriction first. Organizations
move at the next escalation pass. The platform brand's own billing settings follow: payment retries and
entitlements last until the suspension. Platform admins only; audited.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument            | Type    | Required | Description                                                                                                                                                                                              |
| ------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `restrictAfterDays` | integer | no       | (1–60)                                                                                                                                                                                                   |
| `suspendAfterDays`  | integer | no       | (1–60)                                                                                                                                                                                                   |
| `idempotency_key`   | string  | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** not supported. `dry_run: true` fails with `dry_run_not_supported`.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A threshold is missing or not a whole number.
- [`platform_billing_thresholds_invalid`](https://ownrecord.io/docs/reference/errors.md#platform_billing_thresholds_invalid): The thresholds are outside 1–60 days or not in order.
- [`platform_billing_unavailable`](https://ownrecord.io/docs/reference/errors.md#platform_billing_unavailable): The platform bootstrap hasn't run yet.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): Only platform staff read, and only a platform_admin changes, these settings.
- [`fresh_auth_required`](https://ownrecord.io/docs/reference/errors.md#fresh_auth_required): Your last sign-in is older than 15 minutes: sign in again first.

### Example

**Suspend after 45 days**

Arguments:

```json
{
  "suspendAfterDays": 45,
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "platform_billing_settings",
  "restrictAfterDays": 14,
  "suspendAfterDays": 45,
  "livemode": true,
  "defaultPlanPriceId": "price_01jbz3m4k5n6p7q8r9s0t1v2w8"
}
```
