Tokens

Mint a token

curl -X POST https://api.subscriby.net/v1/tokens \
  -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Nightly reconciliation",
        "team_id": "a83f0d51-4c92-4b7e-8615-2fd9e70a3c86",
        "abilities": ["project-subscription:view-any", "payment:view-any"]
      }'

token sits alongside data, not inside it, the same shape webhook endpoints use for their one-time signing secret. Store it before you do anything else; there is no endpoint that will hand it back. Minting is deliberately not offered as an MCP tool.

team_id is required here, and only here. Everywhere else in v1 the team comes from the calling token's frozen scope and a team_id in the body is ignored. Minting is the exception: a token is created for a team, and you may belong to several. The minted token gets scope:team:<team_id> automatically. It cannot be widened later.

A token can never mint a stronger token. The abilities you request must be a subset of the abilities the calling token holds, and token:create is withheld unconditionally from anything minted this way.

Minting used to be dashboard-only precisely so a leaked automation token could not spin up a higher-privilege one. That concern is real, and attenuation is the answer to it rather than a retreat from it: a compromised credential can produce nothing its holder could not already do directly. token:create is withheld on top of the subset rule for a separate reason: inherited, it would leave an unbounded ladder of tokens descending from one leak, with no single revocation that ends the chain. A human in the dashboard is not attenuated. They authenticated with a password and a second factor, not with a bearer string, so they can grant anything in the catalog.

If the calling token carries a deprecated coarse ability, it may still mint the precise abilities that ability covers; the split does not shrink what an existing token can delegate.

Asking for more than you hold returns 422; context.abilities echoes back what you asked for, not what was refused, and the refused ones are named in message:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "A token cannot grant abilities it does not hold: coupon:delete, project:delete.",
    "context": {
      "abilities": ["coupon:delete", "project:delete", "project:view-any"]
    }
  }
}

A retired ability keeps working on tokens that already carry it, but no new token can be given one. This is a field-level failure, so every offending entry in the array is reported at once under fields, and the message names the replacements; an ability that is simply unknown gets a different message pointing at the catalog, because a typo and a retirement need different fixes:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "webhook-endpoint:manage is retired and cannot be granted to a new token. Use webhook-delivery:retry, webhook-delivery:view-any, webhook-endpoint:create, webhook-endpoint:delete, webhook-endpoint:update, webhook-endpoint:view, webhook-endpoint:view-any instead.",
    "fields": {
      "abilities.0": [
        "webhook-endpoint:manage is retired and cannot be granted to a new token. Use webhook-delivery:retry, webhook-delivery:view-any, webhook-endpoint:create, webhook-endpoint:delete, webhook-endpoint:update, webhook-endpoint:view, webhook-endpoint:view-any instead."
      ]
    }
  }
}
post/v1/tokens

Request

  • Base URL: https://api.subscriby.net
  • URL: https://api.subscriby.net/v1/tokens
  • Auth: HTTP bearer

Headers

Idempotency-Keystring uuid required

A key unique to this operation, such as a fresh UUID. The same key replays the original 2xx response for 24 hours (with Idempotent-Replay: true), so a retry after a timeout never repeats the write; the same key with a different body is refused with 409.

Request body

namestring required

A label for the token, up to 255 characters, shown on Settings → API Tokens.

abilitiesstring[] required

The abilities the token holds, at least one. Each must be one the calling token holds itself (attenuation), and token:create is withheld unconditionally.

team_idstring uuid required

The team the token is minted for; it receives scope:team:<team_id> automatically and cannot be widened later. Must be a team the caller owns or belongs to. Required here, unlike everywhere else in v1 where the team comes from the calling token's scope.

Response

The token resource plus the plaintext, as a 201.

tokenstring required

The plaintext token, sbt_<env>_<id>_<secret>, returned exactly once. Store it before anything else.

Example response

{
  "data": {
    "id": "1284",
    "name": "Zapier production",
    "abilities": [
      "project:view-any"
    ],
    "scopes": {
      "team_id": "a83f0d51-4c92-4b7e-8615-2fd9e70a3c86",
      "project_ids": [
        "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13"
      ]
    },
    "abilities_count": 2,
    "last_used_at": "2026-05-18T09:15:00Z",
    "created_at": "2026-05-01T10:05:00Z"
  },
  "token": "sbt_live_1902_9f2c…"
}

Changes

No recorded changes to this operation across all 1 revision of this API.