Spec0docs
PlatformRecipes

Issue a Service Account Token (SAT) for a team

Create a team-scoped, non-interactive token for use by CI pipelines, AI agents, or any caller that's not a human at a terminal. Pick scopes deliberately and copy the token once — it's only shown at creation.

Create a team-scoped, non-interactive token for use by CI pipelines, AI agents, or any caller that's not a human at a terminal.

A Service Account Token (SAT) is bound to a team, not a person. It survives team-member turnover, carries the team's permissions intersected with the scopes you grant at issuance, and is the right credential for any automation that publishes specs, approves subscriptions, or pulls from the registry on a schedule.

Prerequisites

  • A team you can administer. If you haven't created one yet, see Create a team first.
  • You're signed in as a Team Admin for that team, or as an Org Admin / Org Owner (who can mint tokens for any team in the org).
  • A clear answer to two questions before you start, because they affect what the token can do:
    1. Which scopes does this token need? Pick the minimum set. See Pick scopes below for the menu.
    2. Should it expire? CI tokens that rotate quarterly are healthier than tokens that live forever. Default: pick a date 90 days out.

Steps

1. Open the team's tokens page

Sign in, then navigate to Settings → Teams → [team] → Service Account Tokens (/settings/teams/[teamId]/service-account-tokens). You'll see existing tokens for that team — name, scopes, last_used_at, expiry. Brand-new teams show an empty list.

If the page is missing or the Create token button is hidden, you don't have the team:manage permission on this team. See Common failures.

2. Click Create token

The form asks for:

  • Name — a short label that tells you (and future-you) what this token is for. Examples: ci-publish, agent-mcp, nightly-changelog. Names are not unique; pick something searchable.
  • Description (optional) — one sentence on what consumes the token. Useful when revoking later.
  • Scopes — a multi-select of what the token can do. See below.
  • Expires at (optional) — pick a date or leave blank. Tokens without an expiry never auto-revoke; you'll have to remember to rotate. Recommended: 90 days.

3. Pick scopes

A scope is <level>:<resource>. Levels are read-only or write. Grant the minimum set — the token's effective permissions are team's permissions ∩ token's scopes, so adding scopes the team itself doesn't have grants nothing.

ScopeWhat it permits
read:apisList APIs the team owns; read API metadata.
write:apisCreate, edit, or delete APIs owned by the team.
admin:apisReserved for ownership transfers. Rarely needed in CI.
read:specsFetch spec versions of the team's APIs; needed for spec0 diff.
write:specsPublish new spec versions; needed for spec0 push / spec0 publish.
write:public-apisPromote an API to the public registry (spec0 publish --visibility published). Separate from write:apis — public publishing is a deliberate escalation.
read:subscribers / write:subscribersList or approve subscriptions on the team's APIs.
read:mocks / write:mocksRead or configure the team's mock servers.
admin:teamManage the team itself — mint tokens, add/remove members. Don't grant this to a CI token; it's a key-to-the-kingdom for the team.

Common recipes:

  • CI that publishes specs: read:apis read:specs write:apis write:specs. Add write:public-apis only if the same job promotes the API to the public registry.
  • CI that only checks breaking changes: read:apis read:specs — read-only, can't accidentally publish.
  • MCP / AI agent: start with the read set; add write scopes only for the specific tasks the agent owns. An agent that "drafts spec changes for human approval" probably doesn't need write:specs — it can return a diff for the human to apply.

What you cannot grant via a team-scoped SAT: admin:org (org-wide admin). That's reserved; if you need it, the request fails at issuance.

4. Create and copy the token

Submit the form. The token appears once — a long opaque string starting with spec0_sat_. Copy it immediately into:

  • The GitHub Actions Environment secret it belongs to (SPEC0_TOKEN is the canonical name).
  • Your local password manager, if a human needs to use it.
  • Wherever the consumer reads tokens from.

If you close the modal without copying, the token is gone and you'll need to create a new one. Revoking the un-copied one is good hygiene but not security-critical — without a copy, no one (including you) can use it.

Use the token

In CI (GitHub Actions example):

env:
  SPEC0_TOKEN: ${{ secrets.SPEC0_SAT_CI_PUBLISH }}
  SPEC0_ORG_ID: ${{ vars.SPEC0_ORG_ID }}
steps:
  - name: Publish spec
    run: |
      npm i -g @spec0/cli
      spec0 publish openapi.yaml --semver

From a local script (less common; prefer PATs for human use):

export SPEC0_TOKEN="spec0_sat_..."
export SPEC0_ORG_ID="..."
spec0 api list

In an AI agent over MCP, the same token works — see the agents page.

Verify

In the tokens list:

  1. Your new token appears with the name you gave it.
  2. The scopes match what you selected.
  3. last_used_at is empty until the first request lands.
  4. Once you've fired a request from CI, last_used_at updates within a few seconds.

A quick smoke from your terminal:

SPEC0_TOKEN="spec0_sat_..." SPEC0_ORG_ID="..." spec0 api list

200 OK and a list of the team's APIs means the token is wired correctly.

Rotate or revoke

Each row in the tokens table has a Revoke button. Revoking is instant — the next request fails 401. To rotate without downtime:

  1. Create a new token with the same scopes.
  2. Update the CI secret / agent config to use the new token.
  3. Wait one cycle (or watch last_used_at on the old token stop advancing).
  4. Revoke the old token.

There's no "edit scopes on existing token" — rotate instead. This is by design; a token's scopes are part of its identity.

Common failures

SymptomCauseFix
Create token button missingYou're a Team Member, not Team Admin. SATs need team:manage.Ask a Team Admin or Org Admin to mint it, or to promote you.
403 Forbidden — User org ≠ Resource orgThe token's org differs from the resource you're acting on.Confirm SPEC0_ORG_ID matches the token's org. Tokens are not cross-org.
403 Forbidden — User is not in same team as the API resourceThe token is for Team A; you're acting on Team B's API.Mint a separate token from Team B, or have an Org Admin perform the action.
403 Forbidden — User lacks required permission: write:specsThe scope was missing at issuance.Create a new token with write:specs included. Scopes can't be added after issuance.
Token works locally but fails in CICI environment is missing SPEC0_ORG_ID or SPEC0_TOKEN, or the secret is empty.Confirm both are set on the workflow's environment: block, not at repo level (unless that's where you put them).
401 Unauthorized — token expiredPast the expiry date you set.Create a new token; update the consumer; revoke the expired one.

Next

On this page