Hold an organization's unpaid-plan escalation
The platform_billing.holds.set operation: PUT /api/v1/platform/organizations/{orgId}/billing_hold in the REST API and the platform_billing_holds_set tool in the MCP server.
/api/v1/platform/organizations/{orgId}/billing_holdHolds 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.
At a glance
- REST:
PUT /api/v1/platform/organizations/{orgId}/billing_hold - MCP tool:
platform_billing_holds_set - Operation ID:
platform_billing.holds.set - Scope: Platform
- Permission:
platform:admin - Risk tier:
write - Dry run: Supported (send
Ownrecord-Dry-Run: true) - Idempotency:
Idempotency-Keyoptional - Modes: Test and live
- Authentication: API key with the
platform:adminscope - Fresh sign-in: In the console, a sign-in within the last 15 minutes
- Since: M12
Request
Path parameters
| Field | Type | Description |
|---|---|---|
orgId | org_… ID | The organization (org_…). |
Body
A JSON object (Content-Type: application/json):
| Field | Type | Required | Description |
|---|---|---|---|
stage | one of active, restricted, suspended | Yes | The most severe stage while the hold applies. |
until | date-time | 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. |
Response
200 OK with a JSON object:
| Field | Type | Description |
|---|---|---|
object | "platform_billing_hold" | |
orgId | string | |
status | one of active, restricted, suspended, closing, closed | |
hold | object or null | |
unpaidSince | date-time or null | |
stage | one of active, restricted, suspended or null | |
next | object or null | |
billingOwnsStatus | boolean |
Examples
Keep an organization restricted, not suspended, until a bank transfer arrives
PUT /api/v1/platform/organizations/org_01jbz3m4k5n6p7q8r9s0t1v2w3/billing_hold
Authorization: Bearer or_test_…
Content-Type: application/json
{
"stage": "restricted",
"until": "2026-11-15T00:00:00.000Z",
"reason": "Paying by bank transfer; payment confirmed for 10 November"
}
Response (200):
{
"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
}
Errors
| Code | HTTP status | When | What to do |
|---|---|---|---|
validation_failed | 400 | The organization ID, stage, reason or date is missing or malformed. | Fix the fields listed in details.issues and send the request again. |
platform_billing_hold_invalid | 400 | The hold ends in the past, or the organization's subscription isn't unpaid. | Give a reason, a stage (active, restricted or suspended) and, to end it automatically, an until in the future. |
platform_billing_unavailable | 409 | Ownrecord's plans aren't set up on this installation yet. | A platform admin runs pnpm platform:bootstrap, which creates the plans, prices and features of data/platform/plans.yaml. Try again afterwards. |
resource_not_found | 404 | There is no such organization. | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode. |
permission_denied | 403 | Only a platform_admin holds an organization's escalation. | Ask an organization owner or admin for a role or scope that allows this action. |
fresh_auth_required | 403 | Your last sign-in is older than 15 minutes: sign in again first. | Sign in again, then repeat the action within 15 minutes. |
It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, idempotency_key_reused, idempotency_in_progress, rate_limited, internal_error.