---
title: "Convert Workspace To Paid"
method: POST
path: "/api/v1/internal/workspaces/{workspace_id}/billing-account"
tags: ["internal", "workspaces"]
---

# Convert Workspace To Paid

`POST /api/v1/internal/workspaces/{workspace_id}/billing-account`

Put an existing workspace onto a paid plan (audited).

The workspace's type is deliberately never touched; the account's plan
alone is what makes it paid, and a workspace that holds the free plan's
account is converted in place. ``origin`` is untouched for the same reason
and a stronger one — it is the acquisition fact, and converting a workspace
does not change which door it came through.

The service reads the rate card itself, inside the transaction that opens
the account, so the unpriced-usage refusal answers on the stored card
rather than on one this handler chose. Until staff save once, that stored
card is the shipped one. It reads the approved included allowance in the
same place and for the same reason, so a request that names no amount
grants what staff approved rather than what a client remembered.

## Path parameters

- `workspace_id` string, uuid, required

## Request body

- BillingAccountConvertRequest — What staff choose about a conversion — which is the period, not the plan. The plan a workspace lands on is a server constant every activation path shares, so it is deliberately not a field here: a client that could name it is what put one product under two names.
  - `anchor_day` integer, required
  - `included_granted_usd` union
    - number
    - string

## Response `200`

Successful Response

- BillingAccountResponse — One workspace's billing account, as the internal console reads it. Carries the domain's own free/paid verdict rather than leaving the console to rebuild it from ``plan_key``: the plan's spelling is a value the client shares no symbol with, so a console that compares the string is a consumer a rename cannot reach.
  - `workspace_id` string, uuid, required
  - `plan_key` string, required
  - `plan_version` string, required
  - `on_free_plan` boolean, required
  - `status` 'pending' | 'active' | 'past_due' | 'unpaid' | 'cancelled', required — Subscription state as MAIA understands it, not as Stripe reports it. ``PAST_DUE`` is the retry window: full access with a visible warning. ``UNPAID`` is that window exhausted — paid work refuses until payment succeeds, but the subscription has not ended, so purchased usage is kept. ``CANCELLED`` is paid access ended. Purchased usage is kept there too — no status takes it — but only a status that keeps paid access may spend it.
  - `anchor_day` integer, required
  - `period_start` string, date, required
  - `period_end` string, date, required
  - `included_granted_usd` string, required
  - `included_consumed_usd` string, required
  - `purchased_balance_usd` string, required
  - `given_balance_usd` string, required — The part of purchased_balance_usd staff gave for free.
  - `activated_at` string, date-time, nullable, required

## Other responses

- `422` — Validation Error

## Changes

- **2026-09-19** `8fb358001d37` — 1 info
  - added the required property `given_balance_usd` to the response with the `200` status
- **2026-09-15** `499d0b94aa94` — 2 warning, 3 info
  - removed the request property `plan_key`
  - removed the request property `plan_version`
  - the request property `included_granted_usd` became optional
  - request property `included_granted_usd` list-of-types was widened by adding types `null` to media type `application/json`
  - …1 more
- **2026-09-12** `604ea84d0ff7` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/maia-analytics/apis/maia-api/changes/api/v1/internal/workspaces/:workspace_id/billing-account/post.md)

---

[API](https://skmtc.dev/maia-analytics/apis/maia-api.md) · [All operations](https://skmtc.dev/maia-analytics/apis/maia-api/llms.txt) · [OpenAPI document](https://skmtc.dev/maia-analytics/apis/maia-api/revisions/bb508e935174?raw)
