POST /v1/admin/billing/org/{org_id}/suspend
Automated billing-driven suspension (master-key auth; payment-worker only).
What this is
Sets organizations.active = 0 for the target org. Called by payment-worker on Stripe webhook events: subscription unpaid after dunning exhaustion, paused, customer.subscription.deleted, full charge.refunded within the 14-day grace window, or charge.dispute.created. Idempotent — set-twice is a no-op.
Distinct from POST /v1/admin/org/{org_id}/suspend which is the manual admin-dashboard path (different audit reason code, different undo policy).
Authentication
X-API-Key: $MASTER_ADMIN_KEY_BILLING_SUSPEND — scope-specific secret. payment-worker holds; admin-worker does not. Defense-in-depth invariant per D15.
Idempotency
Idempotency-Key header required by convention (use Stripe event.id). Composite PK (endpoint, idempotency_key) prevents cross-endpoint replay.
Reactivation path
Reactivation does NOT use /v1/admin/billing/org/{org_id}/reactivate. The reactivation flow mints a NEW key via POST /v1/admin/billing/org/{org_id}/rotate — the old key was permanently revoked at suspension time and is unrecoverable.
Rate limiting
- Layer 1 (per-IP, 60/60s): enforced.
- Layer 2 (per-key, 1000/60s): bypassed for master-key admin endpoints.
Request
curl -X POST https://api.cohesionauth.com/v1/admin/billing/org/org_acme_2026_example/suspend \
-H "X-API-Key: $MASTER_ADMIN_KEY_BILLING_SUSPEND" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: example-billing-suspend-key" \
-d '{}'
Response
{
"suspended": true,
"org_id": "org_acme_2026_example",
"suspended_at": "2026-05-02T20:00:00.000Z",
"note": "Organization is now inactive. Reactivate via /v1/admin/billing/org/:id/rotate (mints a new key).",
"request_id": "req_01H...",
"timestamp": "2026-05-02T20:00:00.000Z"
}
Errors
| Status | Meaning |
|---|---|
401 | Master-key missing or wrong scope |
404 | Org not found |
422 | Malformed org_id path parameter |