Docs Cloud Spaces and Collaboration

Cloud Spaces and collaboration

A Space is the explicit collaboration context in Mockarty Cloud. The cabinet lists every Space you own or have joined and remembers your current selection in this browser. When several Spaces are available, choose one before opening team, billing, environment, Desktop, or audit controls; Mockarty does not guess the first owned Space.

Creating a Space

Your first Space is created together with your account — it is your personal one.
To set up another Personal collaborative Space, open Cloud cabinet → Overview, type a
name into New Space name and press Create Space. Nothing else is needed:
Mockarty derives the short identifier used in addresses from the name, including
for names written in a non-Latin alphabet.

A new Personal Space keeps its own members, invitations and data, while billing stays
shared with your Personal account. The cabinet switches to the new Space right away.
Paid Team Spaces use a separate Team account and the Create Team Space control
in Usage; see Organization overview.

How many Personal Spaces you can create depends on your Personal plan. Team
Spaces use the Team’s separate capacity. When your Personal slots are in use the
button is disabled and states how many the plan allows and how many are in use —
free one up, or move to a plan with more Spaces.

Reaching the Space limit does not block your account or remove existing Spaces.
The Overview shows a quota warning. If you downgrade with more Personal Spaces
than the new plan permits, the excess Spaces remain visible for reading and
export. In the Cloud cabinet’s Overview, use Active Personal Spaces to choose which Spaces can
receive changes. Your first Personal Space always stays active. Owned Spaces
and Spaces you joined have separate limits; the panel shows both. You cannot
create another Space or invite more people while your account exceeds its
Personal Space limit. Upgrading restores eligible existing Spaces without
deleting their data.
You can still leave a Space, remove a member, or revoke a pending invitation
while resolving the overage; renaming and other changes wait until the Space
is active again.

The cabinet saves this choice against the revision you saw. If another browser
tab changed it first, review the updated selection and save again. This
account-setting action is available with a signed-in Cloud session, not an API
token. Automations do not need to change it.
The change may briefly show Confirming limits while the Cloud and shared
runtime agree on the new access rules. Your previous selection stays in effect
until confirmation finishes; do not assume the new choice is active while it is
pending. If your plan or memberships changed meanwhile, review and save again.
The cabinet keeps your attempted checkboxes after a failed confirmation so you
can adjust the choice without starting over; no change is applied until saved.

GET /api/v1/cloud/personal/active-set
PUT /api/v1/cloud/personal/active-set
Content-Type: application/json

{"revision":7,"active_space_ids":["11111111-1111-4111-8111-111111111111"]}

The GET response lists Space names, ownership and writable status, plus
pending_transition_id while confirmation is in progress. PUT returns 202
with a transition ID. Repeat GET until that pending ID disappears, then match
the returned last_transition_id to your PUT response: only
last_transition_status: "committed" confirms that choice. "aborted" means
the plan or Spaces changed; review the saved draft and retry. A revision change
alone does not confirm your choice because another action can change it. The
PUT body must include the primary Space and no more
than the current owned and guest limits. A stale revision returns
409 active_set_stale; refresh the selection before trying again. SDK and CLI
deliberately omit this interactive owner choice.

Deleting a Space, and getting it back

Deleting a Space does not destroy it right away. It stays recoverable for
7 days, and the exact date is shown next to it in the cabinet, on the
Spaces screen under Recently deleted Spaces. Press Restore there and
the Space comes back with its members and its data as they were.

The slot it occupied is freed at once, so you can create another Space
immediately after deleting one — you do not have to wait out the window.

A day before the window closes, the Space owner receives an email saying which
Space is about to be removed and by when. If you deleted something by accident
and did not notice, that message is your last chance to act on it.

Once the window closes the Space’s content is removed and cannot be recovered,
and its name becomes available for a new Space. Restore is refused after that
point rather than returning an empty Space under the old name.

Two things to know before you delete:

  • A Team’s default Space cannot be deleted while it anchors the Team. Make
    another Space the default first.
  • While a deleted Space is still recoverable, its name stays reserved — that is
    what lets it come back under the name you know it by.

If you subscribe to webhooks, the space.deleted event carries
reversible: true and recoverable_until, so an integration can schedule its
own clean-up for after the window instead of acting on the deletion at once.

Roles

  • Owner can invite people, change roles, remove members, and manage billing.
  • Admin can invite and remove non-owner members.
  • Editor can operate resources and view the member list.
  • Viewer has read-only collaboration access.
  • Billing manager can view and manage billing without team administration.

The cabinet hides or disables actions that the current role cannot perform. The API enforces the same permissions; disabled controls are not a security boundary. Owners and admins can also change the user-visible Space name from Cloud cabinet -> Workspace. The stable slug and commercial identifiers do not change when the name changes.

For a Team Space, changing its name, invitations, members or ownership also requires an active Team, account, membership and assigned Seat. A suspended member cannot enter Team Spaces or use paid Team authority. Their dormant Space assignments are kept for a safe resume, and they can still see their own Team membership in the Team center to leave it.
The Team’s default Space cannot be deleted while it anchors the Team.

Invite a member

Open Cloud cabinet → Team, select the required Space, enter the recipient email, choose a role, and select Send invite. The recipient opens the link and sees the invitation card with the invited address, the role and the expiry. Someone who is not signed in yet lands on the registration form with that address already filled in — an invitation usually arrives before the account does — and can switch to sign-in with one click when the account already exists. On a phone the card sits below the form rather than over it, so both stay reachable.

The acceptance link contains a one-time token: share it with the intended recipient and do not put it in logs or tickets. Owners and admins can revoke an unused invite from the same page.

Free collaboration shows accepted people and pending invitations together with its current limit. A person can participate in up to three Free collaborative Spaces. Paid plans use the available paid-seat count. If admission reaches a limit, the response explains which limit must be freed or upgraded; a failed admission does not create a partial membership.

A Free Space’s monthly allowance — its requests, runner minutes and traffic — belongs to the Space owner. A member you invite can open the Space and its shared node, but their own requests to the Space’s shared projects and sync are refused with the message that the Free allowance belongs to the owner. To work on shared projects together, move to a Team: every Team member draws on the Team’s package.

Member and invite changes appear in other open cabinet sessions without a page refresh. The cabinet keeps one authenticated GET /api/v1/cloud/spaces/events event stream, then fully reloads the principal’s Space list and the selected team after a resync event. A reconnect also performs a full reload, so a temporary connection gap cannot leave stale membership on screen. If a change conflicts with a newer revision, the cabinet refreshes the current Space before the next attempt.

If the team list cannot be loaded, the cabinet shows Retry instead of keeping a loading indicator or presenting a partial list as complete. Switching Spaces does not let a delayed result or network error from the previous Space replace the new Space’s team.

If the active Space changes while you are confirming member removal, ownership transfer, SAML removal, or Desktop revocation, the cabinet cancels that action. Select the intended Space and start the action again.

The event stream is an invalidation transport for the browser cabinet, not a record feed. SDK and CLI automation should use the canonical list operations and their cursors; the stream is therefore not exposed as a curated SDK or CLI method.

Automation

Use an API token for the user whose Space role should authorize the operation. Every Space-scoped path contains an explicit space_id:

A new Space needs only a name — Mockarty derives the short identifier and adds a
suffix if that identifier is taken:

POST /api/v1/cloud/workspaces
Content-Type: application/json

{"name": "QA Team"}
GET /api/v1/cloud/spaces?limit=25
GET /api/v1/cloud/spaces/{space_id}
GET /api/v1/cloud/spaces/{space_id}/members?limit=25
GET /api/v1/cloud/spaces/{space_id}/invites?limit=25

List responses use items, has_more, and an opaque next_cursor. The top-level Space list also returns a monotonic collection_revision. Pass the cursor unchanged to the next request. A Space response carries a revision and an ETag such as "space-…-r7".

Cursors expire after 15 minutes and are bound to the authenticated user, collection, Space, and the relevant collaboration or accessible-Space collection revision. Do not decode, edit, or reuse a members cursor for invites or another Space. Restart pagination after cursor_expired or cursor_stale.

Every rename, invite, role, or removal mutation requires both headers:

If-Match: "space-…-r7"
Idempotency-Key: a-stable-key-for-this-exact-change

An invite recipient who is not a member yet can read the current precondition
from the ETag response header or etag response field of GET /api/v1/cloud/invites/{token}, then use
that exact value for POST /api/v1/cloud/invites/{token}/accept. Refresh the
preview and retry with a new idempotency key if the Space revision changed.

Reuse the same key, body, path, and ETag after an ambiguous timeout or server error. Mockarty returns the original successful response. Reusing the key for another request returns 409 idempotency_conflict; a stale ETag returns 412 space_revision_conflict. 429 and 503 responses include Retry-After and retryable.

The mutation paths are:

PATCH  /api/v1/cloud/spaces/{space_id}
POST   /api/v1/cloud/spaces/{space_id}/invites
DELETE /api/v1/cloud/spaces/{space_id}/invites/{invite_id}
POST   /api/v1/cloud/invites/{token}/accept
PATCH  /api/v1/cloud/spaces/{space_id}/members/{member_id}
DELETE /api/v1/cloud/spaces/{space_id}/members/{member_id}

The rename body contains only the new user-visible name:

{"name":"Platform engineering"}

The same explicit context and preconditions are available in every SDK:

space, _ := client.CloudSpaces().Get(ctx, spaceID)
etag := fmt.Sprintf("\"space-%s-r%d\"", space.ID, space.Revision)
renamed, err := client.CloudSpaces().Rename(ctx, space.ID,
    "Platform engineering", etag, "rename-platform-2026-08")
created, err := client.CloudSpaces().CreateInvite(ctx, space.ID,
    mockarty.CloudSpaceInviteRequest{Email: "teammate@example.com", Role: "editor"},
    etag, "invite-teammate-2026-08")
space = client.cloud_spaces.get(space_id)["space"]
etag = f'"space-{space["id"]}-r{space["revision"]}"'
renamed = client.cloud_spaces.rename(
    space["id"], "Platform engineering", etag, "rename-platform-2026-08"
)
created = client.cloud_spaces.create_invite(
    space["id"], "teammate@example.com", "editor", etag, "invite-teammate-2026-08"
)
Map<String, Object> envelope = client.cloudSpaces().get(spaceId);
Map<String, Object> space = (Map<String, Object>) envelope.get("space");
String etag = "\"space-" + space.get("id") + "-r" + space.get("revision") + "\"";
Map<String, Object> renamed = client.cloudSpaces().rename(
    spaceId, "Platform engineering", etag, "rename-platform-2026-08");
Map<String, Object> created = client.cloudSpaces().createInvite(
    spaceId, "teammate@example.com", "editor", 0, etag, "invite-teammate-2026-08");

For invite acceptance, call PreviewInvite / preview_invite / previewInvite first and pass its etag to the accept method. The CLI performs this preview automatically when --etag is omitted:

mockarty-cli cloud-spaces preview "$INVITE_TOKEN"
mockarty-cli cloud-spaces accept "$INVITE_TOKEN" --idempotency-key accept-invite-2026-08

The CLI exposes the same curated operations. Use --cursor on list, members, or invites, and keep --idempotency-key stable for an exact retry:

mockarty-cli cloud-spaces list --limit 25
mockarty-cli cloud-spaces rename --space "$SPACE_ID" --name 'Platform engineering' --etag "$ETAG" --idempotency-key rename-platform-2026-08
mockarty-cli cloud-spaces members --space "$SPACE_ID" --cursor "$NEXT_CURSOR"
mockarty-cli cloud-spaces invite --space "$SPACE_ID" --email teammate@example.com --role editor --etag "$ETAG" --idempotency-key invite-teammate-2026-08
mockarty-cli cloud-spaces role "$MEMBER_ID" --space "$SPACE_ID" --role viewer --etag "$ETAG" --idempotency-key role-change-2026-08

The Go, Python, and Java SDKs expose CloudSpaces/cloud_spaces/cloudSpaces; the CLI command is mockarty-cli cloud-spaces. The embedded MCP server intentionally does not mirror these endpoints because it cannot delegate a Cloud cabinet principal and therefore cannot prove the same Space role.

Space-scoped loyalty and support operations are documented in Cloud loyalty, support, and product operations.

Cancel or reactivate the plan

When the plan was bought with auto-renew, Subscription shows the date of the next charge and the payment provider (renewal in GET /api/v1/cloud/subscriptions); cancelling the subscription is how to stop the next charge. Without auto-renew the plan simply ends with the period.

If earlier account credit covers the full checkout price, no card is charged or saved. Automatic renewal cannot be enabled for this purchase; turn it off and buy another period after the current one ends. The same Pro or Pro+ plan cannot be bought again during an already-paid period (same_plan_period_active), so remaining paid days are not discarded. With only partial account credit, the remaining card charge can create a saved payment mandate.

The Space owner cancels the plan from Subscription in the cabinet (also POST /api/v1/cloud/subscriptions/cancel with X-Cloud-Space). Nothing more is charged and the paid period is not refunded: the plan stays active until the end of that period, then the Space moves to Free, and the owner receives the “Subscription cancelled” letter with that date.

Until the period ends the owner can reactivate from the same screen (POST /api/v1/cloud/subscriptions/reactivate) and keep everything as it is, including auto-renew: the saved payment method is restored and the next charge date shows again on Subscription. Once the period has ended the API refuses — 410 subscription_period_expired while the cancelled record is still visible, 404 no_active_subscription after the sweep has moved the Space to the Free plan — and the way back is to choose a plan again.

Enterprise and on-premise subscriptions are managed through their organization contract and deployment request. The retail cabinet does not start or resume an Enterprise checkout, Team package, package change, manual invoice, balance top-up, or automatic renewal. A request to buy the Enterprise plan returns contract_required; a new retail command for an account already managed by an Enterprise contract returns enterprise_contract_managed. Billing history stays visible. An exact retry of a retail operation saved before the Enterprise contract remains idempotent, and the owner can still cancel its automatic renewal. That unwinds an earlier retail obligation; it does not create new Enterprise billing authority.

When a paid period ends without a settled renewal, paid features stop at the paid-through date, even though the subscription first enters a three-day payment grace period. The owner receives a payment-needed e-mail and in-app notice naming the grace deadline. If payment does not arrive, the subscription expires; the Space stays on the Free plan and keeps its mocks, tests and data, and desktop licences bound to the Space are revoked. Other period endings also notify the owner:

Letter When Next step
Your trial has ended the trial period ran out choose a plan
Payment needed to restore your plan a paid period ended without renewal open billing before the grace deadline
Your plan has paused the grace period after a missed renewal ended without a payment choose the plan again
Your plan has ended a cancelled plan reached the end of its period choose a plan whenever you need it

Every letter is sent in the owner’s notification language, by e-mail and as an in-app notice.

Account balance, upgrades and cancellation

Cancelling never refunds. The plan stays active until the end of the paid period, then the Space moves to Free.

Account balance. When a paid Personal Pro period ends early because you join a Team, the unused part of that period is returned to your account balance. In your Personal Space, Subscription → Account balance shows the amount and a statement of what came in and what it paid for. The balance pays your next charges automatically, before the card: a Pro purchase, renewal or bank-transfer invoice takes whatever the balance holds, up to the price, and the card or transfer covers only the rest. If the balance covers the whole price, the plan is bought at once without the card and the letter says it was paid from the balance. A purchase with promotional bonus money spends only bonus money. The balance cannot be withdrawn. A Team member can also spend it on AI or infrastructure units for their Team (see Paid Team Spaces).

Refunds. If a paid period is fully refunded, the part paid by card goes back to the card, and the unused part of any earlier period that payment had credited becomes money on the account balance, which then offsets that account’s next charges. Refunds are issued by Mockarty support, not from the cabinet.

Upgrade a Team now. In Team billing, Upgrade now lists the larger packages. Each one shows three amounts: the package’s full price, the credit for the unused part of the current period, and what is left to pay. After the payment, the larger package and a new full period start at once. A smaller package starts from the next renewal: choose it under Package from the next renewal. If another person pays for the Team, the upgrade needs their approval, so choose the package for the next renewal instead.

Buy your own Team while you have Pro. If you pay for your own Team yourself while your Personal Pro period is still running, the unused part of that period is credited into the first Team payment. The card pays only the difference, and Personal Pro ends when the Team payment settles. If the unused part is worth more than the Team, it pays for the Team in full, no card is needed, and the rest goes to your account balance. If an organization pays for the Team, it pays the full price, and your unused Pro period goes to your account balance.

Which ceilings are refused, and which are only measured

A plan states ceilings for several axes, and they do not all behave the same way. The usage payload says which is which: GET /api/v1/cloud/quota returns, per metric, measured (whether this installation can count the axis at all) and enforced_by (who refuses a write past the ceiling, or none).

For mock stock and monthly flow, GET /api/v1/cloud/quota?space_id=<selected-space-id> shows the billing account that hosts the selected Space. Without space_id, these rows show your Personal Space. You must have access to a selected Space and an active named Seat for a Team Space; switching to a Team Space does not transfer its allowance into your personal account. A mock stored in both Cloud and the shared runtime during a move counts once. If the host’s inventory has not been reconciled, mock usage is marked unavailable instead of showing a partial count. The Personal Space count and creation limit always come from your Personal billing account, even when you select a Team Space. Team Spaces you own do not consume Personal slots.

Daily fuzzing and load-test starts use the same host account. Their day begins at 00:00 UTC. A start counts when Cloud admits it; a run that later fails or is stopped still uses that start. Repeating the same task request does not charge another start. Until the first complete UTC day of central accounting begins, the cabinet marks daily usage unavailable and the shared node does not admit these runs.

For scripts and CI, use a Cloud API token with workspace:read and select the Space explicitly when you need its host-account mock or flow allowance:

personal, err := client.CloudCustomer().GetQuota(ctx)
selected, err := client.CloudCustomer().GetQuota(ctx, spaceID)
personal = client.cloud_customer.get_quota()
selected = client.cloud_customer.get_quota(space_id)
Map<String, Object> personal = client.cloudCustomer().getQuota();
Map<String, Object> selected = client.cloudCustomer().getQuota(spaceId);
mockarty-cli cloud-customer quota
mockarty-cli cloud-customer quota --space "$SPACE_ID"
  • Refused at the boundary. Creating a Space stops at the number of Spaces your plan allows — the refusal names the ceiling and your current count — and adding a member stops at the seats your plan has, in a Space that is at its paid-seat ceiling. For those axes enforced_by names the gate.
  • Mock stock. Cloud sync counts mocks across a paid Team’s Spaces against one Team allowance and refuses a new mock when that allowance is full. Other Space mock counts can be displayed without a Cloud refusal; check enforced_by for the selected metric rather than treating every displayed ceiling as a write gate.
  • Monthly flow allowance. Requests, runner minutes and egress use the Space’s host billing account. A paid Team shares one pool across its Spaces, sized from the number of purchased Seats; a member’s personal allowance does not raise a foreign Space’s cap. Runtime reserves small blocks before use and refuses more work when the monthly pool is full. The displayed used value counts reserved blocks, so it can be slightly above completed work. Until the operator starts a full UTC month with this accounting active, these rows remain unavailable rather than showing a false zero.
  • Not measured. AI operations and cost remain marked unavailable with the reason until their own accounting authority is connected.

Organization overview

The Usage page reports a mock count only when it can read a current count for every Space you own. If that count is temporarily unavailable, the page says so instead of showing a misleading zero; try again later. Other usage figures that are not measured are also marked as unavailable rather than presented as remaining allowance.

If you own the billing account of the selected Space, the Usage page shows Your organization for that account. An active Team admin with an assigned Seat can also see the Team roster, pending invitations, shared Seat count and Spaces they can access. Only the Team owner sees Team activity in this view; Team billing remains limited to its billing roles. Switching between a Personal Space and a Team Space switches this view too. For ordinary accounts it shows every Space under the account, the seats bought and used in each, and the shared limits. A person’s Personal Space allowance follows their Personal account plan; joining a paid Team does not increase that Personal allowance. Team Spaces use the Team account’s shared Space allowance.

It answers “why can I not add my colleague”: you can see which Space ran out of seats, and whether the person has hit the per-person Space limit. A Space on an unlimited plan is shown as “unlimited” and does not count toward the purchased-seat total.

A free account also shows the shared free pool of members. A retail Team account instead shows its current members and their roles, plus one Team-wide seat count. It lists pending invitations with their recipient, role and expiry so the owner or Team admin can identify reserved places. The invitation secret is not shown. Pending invitations reserve places in the count; they do not give the invitee access until accepted. Other paid accounts use subscription seats rather than the free pool.

For a paid Team, Retained sync storage counts synchronized objects, changes and conflict records across its Team Spaces. The package provides 200 MiB per paid Seat in a shared account pool, with a ceiling of 1 GiB for any one Team Space. The organization view shows both the pool and per-Space ceiling. While existing records are being reconciled, the view says the available amount is unknown; it does not present a partial count as a balance. If storage is already over a limit, members can still read and delete records, while new growth waits until enough space is freed. Personal Spaces do not draw from the Team storage pool.

Promotional balance pays for one Team period only. It does not save a card for
future charges, so automatic renewal is unavailable for that payment source.
The cabinet turns off the renewal option when you choose the promotional balance;
switch to a card payment if you want automatic renewal. A purchase covered
entirely by an unused Pro period and/or account credit also saves no card for
renewal: auto_renew: true is refused with team_renewal_needs_card before any
money is spent. Turn it off and buy the next period after the paid period ends; a
partially credited checkout may charge the remainder to a card and save a mandate.

Buying a Team package creates a separate billing account and its first Team Space. In Usage, a Team owner or admin with an active Seat can create another Team Space within the Team’s shared Space allowance. Choose All Team members to give every active Seat holder access, or Only invited Team members to manage that Space’s access separately. Access to an All Team members Space follows the Team roster and cannot be added or removed with a separate Space invitation; use the Team member workflow to add or remove the person from the whole Team. A restricted Space still belongs to the same Team and shares its package allowance. Its Space invitation works only for an existing active Team member with a named Seat, and this is checked again when the link is accepted. An ordinary Space invitation never turns an outsider into a paid Team member, assigns a Seat or changes a personal subscription. A person may belong to only one paid Team at a time. Their Personal Spaces remain separate when they join or leave.

Start the paid Team from Subscription in a Personal Space. The cabinet offers published 5, 10 or 20 Seat packages and shows their full quoted price before payment. Choose whether an individual or an organization pays; an organization payer must enter its legal name and confirm authority. The Team activates only after payment settles. For an active Team, Usage → Team billing shows the payer, package, used and reserved Seats, invoices, renewal state, and shared unit balances. The owner or billing manager can view billing. For an organization payer it shows the protected legal name to those billing roles. The payer organization is a billing party, not a product member: it gets no Space access, Team license, or Seat. Every Team Space reads the plan and subscription status from the same Team billing account, so switching to a secondary Space does not show a fabricated Free plan. Payment history is account-wide and remains available only to the Team owner and billing manager. A larger package can start at once with a credit for the unused period; a smaller one is confirmed for the next renewal (see Account balance, upgrades and cancellation). The current package remains in force until the new purchase settles. Checkout, invoice, and unit purchases stay in the cabinet because they require interactive payment and payer authority; they are not SDK, CLI, or MCP automation operations.

Read-only Team package and billing reports are also available to scripts with a Cloud API token carrying team:read. Use GET /api/v1/cloud/team/packages?space_id={personal_space_id} as the verified owner of that Personal Space. Each quote has an expiry; reading it neither reserves the price nor starts a purchase. Use GET /api/v1/cloud/team/billing?space_id={team_space_id} as the current Team owner or billing manager. Its billing object contains the package, payer kind, Seat counts, recent invoices and shared unit balances. The Team member role is checked in addition to the token scope. Both reads require an explicit Space; a Personal Space does not grant access to another Team’s billing.

quotes, err := client.CloudSpaces().ListTeamPackages(ctx, personalSpaceID)
billing, err := client.CloudSpaces().GetTeamBilling(ctx, teamSpaceID)
quotes = client.cloud_spaces.list_team_packages(personal_space_id)
billing = client.cloud_spaces.get_team_billing(team_space_id)
List<Map<String, Object>> quotes = client.cloudSpaces().listTeamPackages(personalSpaceId);
Map<String, Object> billing = client.cloudSpaces().getTeamBilling(teamSpaceId);
mockarty-cli cloud-spaces team-packages --space "$PERSONAL_SPACE_ID"
mockarty-cli cloud-spaces team-billing --space "$TEAM_SPACE_ID"

If the Team owner requests a package whose next renewal costs more and the original payer is a different individual, the request goes to that payer for an exact-price decision. The payer sees the Team, current and proposed renewal amounts, package size and period in the cabinet and can accept or decline after confirming their identity. The higher package is scheduled only after acceptance. If the payer declines or the request expires, the existing renewal terms continue. A change of Team owner alone does not authorize a higher charge to the original payer.

An active named member with a current paid Team period can use their own account balance to buy AI credits or infrastructure units for that Team from Usage → Account balance for Team units. The screen shows the selected Team, unit rate and exact debit before confirmation. The purchase response includes a purchase receipt with the Team and Space recipient, currency, exact minor-unit debit, unit count and frozen price version; an exact retry returns the same receipt with replayed. The purchase debits only that member’s account balance and credits the Team account; those units stay with the Team if the member later leaves. It never draws from a Personal unit wallet automatically during Team work. An expired or fully refunded Team keeps its roster and Spaces for read access during the retained window, but cannot receive a new purchase from a member’s account balance. Provider-paid Team unit top-ups are centralized: only the Team owner or billing manager with a current named Seat can start them. A role granted only inside one Team Space does not grant Team payer authority. If a renewal charge or a current-period refund is already in progress, the other operation is deferred until the first reaches a terminal result, before any new provider request can start.

Canceling, expiry and a completed full refund discard any future package choice and unanswered payer request. The owner can restart the same Team account after the paid period ends: GET /api/v1/cloud/team/reactivation/offers?space_id={team_space_id} lists packages in the Team’s retained market, and POST /api/v1/cloud/team/{team_id}/reactivate starts the exact package payment. The package must cover all retained members, pending invitations and active or reserved Seats. The Team ID, roster, Spaces and data stay in place; paid access and the new capacity return only after provider-confirmed settlement. If the owner has a current Personal Pro period, its unused value is handled as on the first Team purchase: credited into the payment when the owner pays personally, otherwise returned to the account balance, or to the bonus wallet if it was paid with bonus money.

The same Team checkout is available during the three-day past_due grace period, not only after the subscription becomes expired. A payment still in progress does not restore paid access. Once it settles, the existing Team and Spaces receive one new full period starting at settlement; an exact retry returns that payment, not another charge. Purchases paid wholly from credits or promotional balance must be renewed manually.

When a Team paid period ends

The Team owner receives email and in-app reminders seven days and 24 hours before the paid period ends, when it ends, and seven days before read access closes. A successful payment also produces a restoration notice. A reminder does not charge a card; automatic renewal requires a separate existing payment mandate.

At paid_until, the Team enters past_due for three days. Current members can read existing data in Spaces they already have access to, but new work is paused. The next 30 days are retained_read with the same read-only access. At read_until (33 days after paid_until), the Team is archived: its working namespace closes, but its data is kept. Leaving or being removed from the Team ends that member’s access sooner. A settled renewal or reactivation restores the same Team and its Spaces; a pending payment does not.

Read-only also stops work delivery to connected runners and paired devices: polling for the next UI, voice, replay, or grid job cannot claim a queued job in that Team Space. Durable queued jobs remain available after paid access is restored; resend any live device command that expired while the Space was read-only.

In the Cloud cabinet, select the Team Space and open Subscription to see the paid and read-access deadlines. The Team owner sees the action to pay; other current members are directed to contact the owner. Billing records remain available only to the owner and billing manager. A current member can also read the access window without billing details through the authenticated Cloud API:

GET /api/v1/cloud/team/access?workspace_id={team_space_id}

The response gives mode (active, past_due, retained_read, or archived), paid_until, read_until, and can_pay. The selected Space ID is required. A Cloud API token needs team:read as well as current Team membership; an unknown Space or removed member receives 404. This read-only status is not a payment or billing-history endpoint.

The Team account ID appears as account_id in GET /api/v1/cloud/organization?space_id={team_space_id}. Use it as team_id to create another Team Space. Keep the same Idempotency-Key for an exact retry:

POST /api/v1/cloud/team/spaces
Idempotency-Key: create-team-space-2026-09
Content-Type: application/json

{"team_id":"<team-account-uuid>","name":"Release QA","audience":"team"}

The response contains space_id and team_id. For a restricted Space, set audience to restricted; an optional slug can be provided. The same operation is available in the SDKs and CLI:

created, err := client.CloudSpaces().CreateTeamSpace(ctx, mockarty.CloudTeamSpaceRequest{
    TeamID: teamID, Name: "Release QA", Audience: "team",
}, "create-team-space-2026-09")
created = client.cloud_spaces.create_team_space(
    team_id, "Release QA", "team", "create-team-space-2026-09"
)
Map<String, Object> created = client.cloudSpaces().createTeamSpace(
    teamId, "Release QA", "team", "", "create-team-space-2026-09");
mockarty-cli cloud-spaces team-space-create --team "$TEAM_ID" --name 'Release QA' --audience team --idempotency-key create-team-space-2026-09

To invite a new colleague, select a Space of the paid Team and use Subscription → Invite a Team member, or open Usage → Your organization → Invite to Team. The invitation form opens with the email field ready. Choose their Team role and send the invitation; they must accept it from email or the cabinet notification. In an All Team members Space, Space & team takes you to this Team form instead of offering a separate Space invitation.

On an active paid Team account, the owner or an admin with an assigned Seat can use Invite to Team in this view. Every Team role, including Billing Manager, reserves one paid Seat. The invitation expires after 72 hours; an existing account receives an in-app notice as well as email. The recipient opens the Team link, signs in or creates an account with the invited address, reviews the Team and role, then explicitly accepts. The signed-in recipient sees an estimated unused Personal Pro credit, if eligible, before accepting. The final amount is recalculated at acceptance. Their Personal Spaces stay theirs. If they have an eligible paid Personal Pro period, accepting replaces that subscription and credits its unused value to their personal wallet. An unsettled Personal Pro payment or an existing Team membership prevents acceptance.

Team invitations have their own API, separate from ordinary Space invitations. Creation requires an active Team owner or admin and an Idempotency-Key. It returns 202 with no invitation token; the link is delivered to the recipient. The public preview reveals the Team, role and expiry without showing the recipient address. The verified recipient can read their personal transition at GET /api/v1/cloud/team/invite-links/{token}/transition; it never returns another person’s credit. Acceptance requires a signed-in recipient with a verified matching address and its own Idempotency-Key:

POST /api/v1/cloud/team/invites
Idempotency-Key: <stable-request-key>
Content-Type: application/json

{"space_id":"<team-space-uuid>","email":"person@example.com","role":"member"}

GET /api/v1/cloud/team/invite-links/{token}
GET /api/v1/cloud/team/invite-links/{token}/transition
POST /api/v1/cloud/team/invite-links/{token}/accept
Idempotency-Key: <stable-accept-key>

To send that Team invitation from an SDK or the CLI, use the selected Team Space. These calls acknowledge delivery without returning the link or its token:

err := client.CloudSpaces().InviteTeamMember(ctx, mockarty.CloudTeamMemberInviteRequest{
    SpaceID: spaceID, Email: "person@example.com", Role: "member",
}, "invite-team-member-2026-09")
client.cloud_spaces.invite_team_member(
    space_id, "person@example.com", "member", "invite-team-member-2026-09"
)
client.cloudSpaces().inviteTeamMember(
    spaceId, "person@example.com", "member", "invite-team-member-2026-09");
mockarty-cli cloud-spaces team-invite --space "$SPACE_ID" --email person@example.com --role member --idempotency-key invite-team-member-2026-09

The invited person can inspect their own transition while signed in. A ready_credit response includes a minor-unit estimate, currency and credit_destination: personal_cash for a provider-funded Personal Pro period or personal_bonus when promotional balance paid for it. ready_free has no credit. A blocked response includes a reason. The amount can change before acceptance.

transition, err := client.CloudSpaces().PreviewTeamTransition(ctx, inviteToken)
transition = client.cloud_spaces.preview_team_transition(invite_token)
Map<String, Object> transition = client.cloudSpaces().previewTeamTransition(inviteToken);
mockarty-cli cloud-spaces team-transition "$INVITE_TOKEN"

After reviewing the transition, the recipient can explicitly accept the Team invitation. Keep the same idempotency key for an uncertain retry. Acceptance recalculates the unused Personal Pro value at the time of the transaction and ends that Personal Pro period in the same transaction. Value paid with money goes to the recipient’s account balance. Bonus-funded value returns to the original Personal Space bonus wallet and stays promotional money; no provider refund is created. Cash credit is available only when that recipient authorized the source Personal Pro payment; a payment authorized by another person blocks the transition instead of moving that payer’s value into the recipient’s wallet.

err := client.CloudSpaces().AcceptTeamInvite(ctx, inviteToken, "accept-team-2026-09", true)
client.cloud_spaces.accept_team_invite(invite_token, "accept-team-2026-09", confirm_transition=True)
client.cloudSpaces().acceptTeamInvite(inviteToken, "accept-team-2026-09", true);
mockarty-cli cloud-spaces team-accept "$INVITE_TOKEN" --confirm-transition --idempotency-key accept-team-2026-09

The Team owner or admin can select Revoke beside a pending invitation and confirm it. This releases that reserved Seat. An accepted member remains in the Team; this action only applies to invitations that are still pending.

The Team owner or admin can also select Remove from Team beside a permitted current member and confirm it. This releases the member’s Team Seat, access to all Team Spaces and their Team Desktop connection. Pending restricted-Space invitation links and unspent shared-node sign-in links for that Team are revoked in the same change. The member’s Personal Spaces and Personal credentials remain theirs. The owner must transfer Team ownership before leaving; the current owner cannot be removed.

Every non-owner Team member, including an admin, billing manager or suspended member, sees their own Team membership in the selected Team Space and can choose Leave Team. Leaving uses the same account-wide removal transaction: it releases the Seat, removes all Team Space access and revokes Team Desktop credentials. The cabinet then returns to an owned Personal Space. Personal Spaces, personal cash and personal unit balances remain with the user. A Team owner must transfer ownership first.

The Team owner, or an admin within their role boundary, can Suspend access and later Resume access for another member. Suspension is reversible: it keeps the named Seat occupied, preserves the one-Team identity and keeps the member’s existing Space assignments dormant, while Team Space and paid authority stay unavailable until resume. Existing Team Desktop credentials are revoked immediately; after access resumes, the member pairs that Desktop again. Use Remove from Team to reclaim the Seat. A member who owns a Team Space must transfer that Space first. These commands are audited and require an Idempotency-Key:

PATCH /api/v1/cloud/team/members/{member_id}/status?space_id={team_space_id}
Idempotency-Key: stable-retry-key
Content-Type: application/json

{"status":"suspended"}

The same reversible status change is available to Team administration scripts.
Use active to resume and suspended to pause access; neither value releases
the Seat:

err := client.CloudSpaces().SetTeamMemberStatus(ctx, spaceID, memberID, "suspended", "status-2026-09")
client.cloud_spaces.set_team_member_status(space_id, member_id, "suspended", "status-2026-09")
client.cloudSpaces().setTeamMemberStatus(spaceId, memberId, "suspended", "status-2026-09");
mockarty-cli cloud-spaces team-member-status "$MEMBER_ID" --space "$SPACE_ID" --status suspended --idempotency-key status-2026-09

The general MCP server does not mirror Team membership administration because
it cannot delegate the Cloud cabinet principal. The Team service-account MCP is
also excluded: a machine principal cannot suspend or resume a human member.

An individual Team Space can be handed only to another active Team member with an active named Seat. This changes that Space’s owner role; it does not change the Team owner, payer, wallet or subscription. A retail Personal Space cannot be reassigned to another user in place because its BillingAccount and commercial history belong to its owner. During beta, the legacy transfer request is refused with 409 workspace_transfer_migration_required; move content through a separate copy/import instead. An account governed by an Enterprise contract returns 409 enterprise_contract_managed and follows its organization agreement.

To transfer Team ownership, the current owner selects Make Team owner beside an active member and confirms. The former owner becomes a Team admin. This changes who manages the Team account; it does not change the payer or ownership of individual Spaces. Suspended members cannot receive ownership. An automation client can use POST /api/v1/cloud/team/owner-transfer with a stable Idempotency-Key and JSON body {"space_id":"<team-space-uuid>","successor_user_id":"<member-uuid>"}.

To automate these two actions, use a Team Space ID and keep the same idempotency key when retrying an uncertain response. Removing a member is also the way that member leaves the Team; the current owner must transfer ownership first.

err := client.CloudSpaces().RemoveTeamMember(ctx, spaceID, memberID, "remove-member-2026-09")
err = client.CloudSpaces().TransferTeamOwner(ctx, spaceID, successorID, "transfer-owner-2026-09")
client.cloud_spaces.remove_team_member(space_id, member_id, "remove-member-2026-09")
client.cloud_spaces.transfer_team_owner(space_id, successor_id, "transfer-owner-2026-09")
client.cloudSpaces().removeTeamMember(spaceId, memberId, "remove-member-2026-09");
client.cloudSpaces().transferTeamOwner(spaceId, successorId, "transfer-owner-2026-09");
mockarty-cli cloud-spaces team-remove-member "$MEMBER_ID" --space "$SPACE_ID" --idempotency-key remove-member-2026-09
mockarty-cli cloud-spaces team-transfer-owner "$SUCCESSOR_ID" --space "$SPACE_ID" --idempotency-key transfer-owner-2026-09

The Team owner and active Team admins can read the full member and pending-invitation list. Other current Team members receive only their own Team membership and the Team Spaces they can already access; this is enough to leave without exposing other members’ email addresses. The Team member list shows the owner the last activity observed in this Team’s Spaces or from a Desktop connected to them. It does not use the person’s activity in Personal or other Spaces. Missing activity is not proof that a member has stopped working: offline Desktop activity may arrive later.

Team audit

The cabinet’s Audit tab is different from Team audit: it shows the selected Space’s recent events to its owner or admin. You can filter the loaded events by action, actor and text. These filters do not search the entire history; the page states how many of the loaded events match. The integrity indicator always refers to the complete returned audit chain, not the filtered rows.

Usage → Team audit shows recent actions for the selected Team account and all its Team Spaces in one list, newest event first. It excludes Personal Space and global sign-in events. Only the Team owner with an assigned Seat can read member activity, including while Team billing is suspended. A billing manager can read private billing without receiving the activity log. Other members and outsiders do not receive the Team’s audit history.

The page also shows whether the Team account’s and Team Spaces’ audit chains verify. If it reports a broken chain, retain the displayed scope and sequence for support; a broken chain is not presented as a clean audit. The view includes action, time, actor ID, target and scope, without showing private event payloads.

Automation can read a bounded page using a selected Team Space ID:

GET /api/v1/cloud/team/audit?space_id={team_space_id}&limit=50
GET /api/v1/cloud/team/audit?space_id={team_space_id}&limit=50&before_id={next_before_id}

limit defaults to 50 and accepts 1–200. before_id is an exclusive event ID cursor; pass next_before_id unchanged only when has_more is true. The response contains team_id, records, has_more, next_before_id, chain_ok, broken_at and broken_scope_id. A failed integrity check returns chain_ok: false with the affected scope and sequence; a temporary audit dependency failure returns 503 instead of a partial page. A missing or unauthorized Team returns 404.

The full roster and pending invitations are visible to the Team owner and active Team admins. A current Team member can read only their own membership and accessible Team Spaces. An outsider receives 404 and cannot learn which account the Space belongs to. Private Team billing remains limited to the Team owner and billing manager; Team member activity and Team audit remain owner-only.

An authorized current Team member or Personal account owner can read the same view from automation by passing the selected Space ID explicitly:

GET /api/v1/cloud/organization?space_id={space_id}
organization, err := client.CloudSpaces().Organization(ctx, spaceID)
organization = client.cloud_spaces.organization(space_id)["organization"]
Map<String, Object> organization = client.cloudSpaces().organization(spaceId);
mockarty-cli cloud-spaces organization --space "$SPACE_ID"

The response reports seats_assigned, seats_reserved, seats_used, and seats_total, plus server-resolved can_manage_team, can_read_activity, can_transfer_owner, and can_leave_team capabilities. Each returned member also has can_remove, can_leave, owns_team_space, and an optional removal_blocked_reason. A person who owns a Team Space must transfer that Space before their Team Seat can be released; only the Team owner can remove another admin. Only a current Team member holds an assigned Seat; a pending unexpired invitation counts as reserved. The client must use these capabilities instead of inferring authority from a Space role. An outsider receives 404.

To release a pending Team invitation through automation, use its id from the organization response. Keep the same idempotency key for an exact retry. Team owners and admins may revoke; a foreign invitation or Space returns 404.

DELETE /api/v1/cloud/team/invites/{invite_id}?space_id={space_id}
Idempotency-Key: revoke-team-invite-2026-09
err := client.CloudSpaces().RevokeTeamInvite(ctx, spaceID, inviteID, "revoke-team-invite-2026-09")
client.cloud_spaces.revoke_team_invite(space_id, invite_id, "revoke-team-invite-2026-09")
client.cloudSpaces().revokeTeamInvite(spaceId, inviteId, "revoke-team-invite-2026-09");
mockarty-cli cloud-spaces team-revoke-invite "$INVITE_ID" --space "$SPACE_ID" --idempotency-key revoke-team-invite-2026-09

Shared Mockarty

Every Space has a namespace on the shared Mockarty and opens it from Instances → Open Mockarty with the cabinet identity. Module access follows both the Space’s current host plan and each member’s own licence. A Team’s purchased named Seats cover its Spaces once across the account. See Shared Mockarty.

Bringing your own runners

Your plan includes a number of runner minutes on our shared capacity. You can
also attach machines you operate yourself, and work that runs on them does
not consume those minutes — you are already paying for the hardware.

Open Runtime → Your runners in the cabinet and attach one:

  1. Give it a name, the region it runs in, and how many tasks it may run at
    once. The concurrency is your ceiling on your own machines.
  2. Confirm the action. The cabinet shows a connection string once, with a
    Copy connection string button. Save it where you keep credentials — it contains the
    runner’s secret and cannot be read again.
  3. Use the connection string on the machine that will do the work (see below).
    The runner is listed as Taking work as soon as it is attached.

Under each runner the cabinet shows whether a machine is connected right now:

  • Connected — taking work right now: a machine holding this runner’s
    connection string is online.
  • Not connected · last seen …: the runner is switched on, but no machine has
    checked in for a few minutes — for example, the laptop is closed. It picks up
    work again as soon as the machine is back.
  • Not connected yet: nobody has used the connection string so far.

After Rotate secret the runner shows as not connected until a machine
connects with the new string.

The connection string is one value that starts with mkrunner1. and carries
everything a runner needs: the Cloud address where it exchanges its secret, the
runtime address it takes work from, the runner’s identity and the secret
itself. There are two ways to use it.

On your own computer, with Mockarty Desktop. Open the Desktop control
panel, choose “Run Space jobs on this computer” and paste the string.

On a server or in CI, with mockarty-runner. Pass the string in one
environment variable:

MOCKARTY_RUNNER_CONNECTION='mkrunner1.…' ./mockarty-runner

MOCKARTY_RUNNER_CONNECTION cannot be combined with the separate provider
variables; set one or the other.

The same dialog also shows the bare bootstrap secret, for runners you
already configured with separate variables. If the cabinet says a connection
string could not be issued, the Cloud has no public runtime address a runner
can use; the bootstrap secret still works, and the Cloud operator can fix the
address.

If the connection string is lost or you suspect it leaked, press Rotate
secret
. The cabinet shows a new connection string; the previous one stops
working at once. Credentials already issued expire on their own within a few
minutes, so work in flight is not interrupted.

Detaching a runner

Press Detach next to a runner and confirm. Its status changes to
Paused, it gets no more work, and every connection string issued for it
stops working at its next exchange — including copies you gave to other people
or CI variables. Credentials already issued expire within a few minutes.
Detaching again is harmless.

A detached runner stays in the list under its name. To use it again, press
Reattach: the cabinet issues a new connection string and the runner takes
work again. Old connection strings stay invalid.

Two things to expect:

  • Attaching your own machines needs a paid plan for that Space. The free tier
    runs on our shared capacity only.
  • A runner attached from the cabinet takes the API tests, load tests and fuzz
    runs your plan includes. A runner can only be given work your plan includes;
    asking for anything else is refused with the list of what the plan does
    include, rather than accepted and then left silently idle.

When a Space has both its own runners and our shared capacity available, its
own machines are used first.

When the plan’s minutes run out

By default the plan’s runner minutes are a hard stop: once the month’s
allowance is used up, new runs on our shared capacity are refused until the
next month starts. Nothing is charged, and no invoice appears that you did not
choose.

On a paid plan you can choose to keep runs going instead. Open Usage →
Runner minutes past the plan
, tick Pay for extra minutes from the prepaid
balance
, set a Monthly ceiling (minutes) and save. Saving asks you to
confirm, as a spend limit does. The owner and billing managers of the Space can
change it.

How it works:

  • Only minutes past the plan are paid. A run that crosses the line pays for the
    minutes the plan did not cover, not for the whole run.
  • One minute costs one Infrastructure Unit, taken from the prepaid Infrastructure
    Units balance of the account. Nothing is taken on credit: when the balance
    cannot cover a run, the run is refused; top up the balance to continue.
  • The ceiling is a real ceiling. Past it runs are refused for the rest of the
    month, exactly as they would be without the switch.
  • The panel shows this month’s numbers next to the switch: minutes used out of
    the plan’s allowance, minutes already paid past it, and the prepaid balance.
  • On the Free plan the hard stop stays; attach your own runner or move to a
    paid plan to run past the allowance.

If the panel says paying for extra minutes is not open yet, the switch can be
saved but runs keep the hard stop until Infrastructure Units go on sale.

On-premise deployment

Cloud runs every Space on our shared infrastructure, so there is nothing to provision and nothing to keep running yourself. If you need Mockarty inside your own perimeter — your servers, your network, your security policy — that is an on-premise deployment, agreed and licensed under a separate contract. The Mockarty in the cloud tab in the cabinet carries a Contact sales link that starts that conversation.

Compatibility

Existing /workspaces, /members, and /invites clients remain supported. New integrations should use the canonical /spaces paths, explicit Space selection, keyset pagination, and revision preconditions.