Cloud service accounts
A service account is a machine identity for CI pipelines, integrations and
automation. It belongs to your billing account, is created from the Cloud
cabinet under Integrations → Service accounts, and consumes one unit of the
service-account allowance in your plan.
Use a service account when a program — not a person — has to work in a Space.
Do not hand a CI job an ordinary API token: an API token carries the
authority of the person who created it, including the ability to administer the
Space. A service account cannot.
What a service account can and cannot do
When you create a service account you bind it to one Space with a set of
permissions. Only these permissions can be granted:
| Permission | What it allows |
|---|---|
| Read the Space | See the Space itself |
| Read members | See who is in the Space |
| Read billing | See the Space’s billing state |
| Run work | Run tests, fuzzing and other work in the Space |
| Read shared projects | List and read shared projects |
| Change shared projects | Create, update and delete shared projects |
Run work and both shared-project permissions work only in a Team’s
Spaces. On other plans the cabinet offers just the three read permissions, and
the API refuses the others with service_account_team_required.
Administration is deliberately not on that list. A service account can never
be given the right to change roles, invite or remove members, or change billing,
because a machine credential with those rights is an administrator token with a
different name.
The binding is immutable
The Space binding and its permission set are written once. They cannot be edited
afterwards — not by you, not by an operator, and not by the service account
itself. This is the security property of the feature: a service account can
never widen its own permissions, and nothing can quietly promote one into an
administrator.
If the permissions need to change, revoke the service account and create a new
one. The revoked account and its binding stay in the audit history.
You can bind one service account to several Spaces, but each binding is
created with its own immutable permission set, and a service account still
consumes exactly one allowance unit no matter how many Spaces it is bound to.
To create, bind, issue or rotate a credential, or revoke a service account,
you must currently be an owner or admin of the affected Space. Binding to a
second Space requires that role in both Spaces. For a Team Space, your Team
membership and named Seat must also be active. If the Team or your access is
suspended, management is refused until the Team owner restores it.
Credentials
Creating a service account can issue its first credential when you choose
that option. A newly issued or
rotated credential is shown exactly once and is never recoverable. Store it in
your CI secret store immediately. The cabinet waits for an in-progress issue
or rotation to finish before letting you leave the page or switch Spaces. Once
the credential is visible, leaving requires confirmation so it is not hidden
by an accidental click.
If the account changes but the one-time credential does not arrive, do not
repeat Create: open the existing account and issue or rotate its credential.
A credential:
- has its own prefix,
msa_, and is not a Cloud API token; - is accepted only by the service-account endpoints — presenting it anywhere
else is refused by name; - cannot be read back from the service, so a lost credential is replaced,
never recovered; - can carry an expiry (0 days means no expiry);
- reports when it was last used, so you can find a credential nobody uses any
more.
A service account has at most one live credential. To replace it, use
Rotate credential: the previous credential is revoked and the new one is
issued in the same operation, so there is never a window with two working
secrets and never a window with none.
Revoking
Revoking a service account is terminal. In one operation it:
- revokes the live credential — it stops working immediately;
- marks the service account revoked;
- releases its service-account allowance.
The revocations cannot be undone; create a new service account instead. The
bindings and the credential history stay in the audit trail.
Asking a credential what it may do
A holder of the credential can read its own identity, its bindings, its
effective permissions and its last-used time:
curl -sS -H "Authorization: Bearer msa_…" \
https://cloud.example.com/api/v1/cloud/service-account/self
It can also check one permission in one bound Space, which is how an integration
reports a permission problem before it fails an operation:
curl -sS -H "Authorization: Bearer msa_…" \
"https://cloud.example.com/api/v1/cloud/service-account/spaces/<SPACE_ID>/authorize?capability=shared.project.write"
The answer names the Space as not_bound (404) rather than returning false,
so a wrongly configured Space is distinguishable from a missing permission.
Managing service accounts from the CLI
mockarty-cli cloud-service-accounts list <SPACE_ID>
mockarty-cli cloud-service-accounts get <SPACE_ID> <ACCOUNT_ID>
mockarty-cli cloud-service-accounts create <SPACE_ID> \
--name ci-runner \
--capability space.read --capability shared.project.write \
--issue-credential --credential-ttl-seconds 7776000 \
--idempotency-key "$(uuidgen)"
mockarty-cli cloud-service-accounts bind <SPACE_ID> <ACCOUNT_ID> \
--target-space <OTHER_SPACE_ID> --capability execute \
--idempotency-key "$(uuidgen)"
mockarty-cli cloud-service-accounts rotate-credential <SPACE_ID> <ACCOUNT_ID> \
--idempotency-key "$(uuidgen)"
mockarty-cli cloud-service-accounts revoke <SPACE_ID> <ACCOUNT_ID> \
--idempotency-key "$(uuidgen)"
Every mutating management call needs --idempotency-key: it is what makes a
retried request safe when the network drops the response. Create, bind,
credential and revoke operations also require the interactive
service_account step-up proof. A general mk_ API token cannot replace that
proof.
Using an msa_ credential
Pass the credential to the CLI as its token. The CLI recognises the msa_
prefix and sends it only as Authorization: Bearer:
mockarty-cli --token "$MOCKARTY_SERVICE_ACCOUNT_CREDENTIAL" cloud-service-account self
mockarty-cli --token "$MOCKARTY_SERVICE_ACCOUNT_CREDENTIAL" cloud-service-account authorize \
--space <SPACE_ID> --capability shared.project.write
For an active Team service account, Shared project reads and writes use the
dedicated machine route when --service-account is present. A machine mutation
uses an idempotency key; --request-id belongs to the human route and is
ignored in this mode:
mockarty-cli --token "$MOCKARTY_SERVICE_ACCOUNT_CREDENTIAL" cloud-shared-projects list \
--service-account --space <SPACE_ID>
mockarty-cli --token "$MOCKARTY_SERVICE_ACCOUNT_CREDENTIAL" cloud-shared-projects create \
--service-account --space <SPACE_ID> --name smoke-suite \
--body-file project.json --idempotency-key "$(uuidgen)"
An active Team credential with the run permissions can also dispatch, inspect
and cancel Shared runs. The Space that receives the run owns the quotas and
pays for the work. Reuse the same idempotency key only for the exact same
dispatch or cancel:
mockarty-cli --token "$MOCKARTY_SERVICE_ACCOUNT_CREDENTIAL" cloud-service-account runs dispatch \
--space <SPACE_ID> --project <PROJECT_ID> --capability execute --class batch \
--deadline 2026-09-21T20:00:00Z --payload-file run.json \
--idempotency-key "$(uuidgen)"
mockarty-cli --token "$MOCKARTY_SERVICE_ACCOUNT_CREDENTIAL" cloud-service-account runs get <RUN_ID> \
--space <SPACE_ID>
mockarty-cli --token "$MOCKARTY_SERVICE_ACCOUNT_CREDENTIAL" cloud-service-account runs cancel <RUN_ID> \
--space <SPACE_ID> --revision <REVISION> --idempotency-key "$(uuidgen)"
Do not put operation_id in a dispatch body. Cloud derives it from the
Idempotency-Key; a supplied value is accepted only when it exactly matches
that derivation.
SDKs
All supported SDKs keep human management separate from the machine workload:
| SDK | Human management | msa_ identity and workload |
|---|---|---|
| Go | client.CloudServiceAccounts() |
client.CloudServiceAccount() |
| Python | client.cloud_service_accounts |
client.cloud_service_account |
| Java | client.cloudServiceAccounts() |
client.cloudServiceAccount() |
Every admitted service account has self and authorize. An active Team
service account also has Shared project CRUD and run dispatch/get/cancel.
Dispatch and cancel require a caller-provided stable idempotency key. The SDK
run request deliberately has no operation_id field.
MCP for Team automation
An active Team service account can connect an MCP client to the dedicated
Streamable HTTP endpoint:
https://cloud.example.com/api/v1/cloud/service-account/mcp
Authorization: Bearer msa_…
This endpoint accepts only an active msa_ credential that belongs to an
active Team billing account. A personal service account, an mk_ API token and
a cabinet session are refused. It is separate from the general MCP server that
runs on a Mockarty node.
The Cloud MCP surface contains these tools:
| Workflow | Tools |
|---|---|
| Identity and permission check | cloud_service_account_self, cloud_service_account_authorize |
| Shared projects | cloud_shared_projects_list, cloud_shared_project_get, cloud_shared_project_create, cloud_shared_project_update, cloud_shared_project_delete |
| Shared runs | cloud_shared_run_dispatch, cloud_shared_run_get, cloud_shared_run_cancel |
Every tool except cloud_service_account_self names the target space_id.
Cloud then checks the service account’s immutable binding and capabilities for
that Space. The receiving Team Space owns the quotas and pays for the work, so
binding one service account to several Spaces does not move usage to its home
Space.
Project create, update and delete and run dispatch and cancel require
idempotency_key. Reuse a key only for the exact same mutation. The dispatch
tool does not accept operation_id; Cloud derives it from the key. Project JSON
is limited to 1 MiB and an optional run payload to 16 KiB.
The transport uses the same runtime proxy, capability checks and audit trail as
the service-account REST routes. It never returns the short-lived runtime token
used by Cloud to reach the Shared runtime.
Seats
A service account occupies the plan’s separate service-account allowance,
never a human Seat. Adding another Space binding does not consume a second
allowance, and freeing a human seat
(viewer, billing manager, or a member who has left) is unaffected by service
accounts. Revoking the service account frees its allowance slot in the same operation.
The allowance ladder: Free includes 1 service account, Pro 5, Pro+ 10.
A Team package includes up to one service account per purchased package Seat
(5, 10 or 20), while those machine principals do not occupy the human Seats.
Enterprise sits at the absolute ceiling of 64. The allowance belongs to the
billing account: creating more Spaces, bindings, tokens or devices does not
multiply it. When the allowance is spent, a new Create is refused with
429 quota_exceeded (metric: service_accounts).