Version

Team Members API

Team members are the humans who collaborate inside a team: the creator who owns it and the people they invited to help run its projects.

Team members are the humans who collaborate inside a team: the creator who owns it and the people they invited to help run its projects. They are distinct from a project's members, the subscribers who buy plans, who are modelled separately and reached through the Members endpoints.

A collaborator joins by invitation to an email address with a role code, holds one role at a time, and leaves either by removal or by having an unaccepted invitation withdrawn; both take the same ability, because both revoke the same prospective access. The team's owner appears as the first row of the roster but is not a membership: ownership cannot be re-roled or removed here. Reading who is on a team and reading how to reach them are separate asks, so email is present only when the token carries team-member:view and is otherwise absent from the row altogether.

The list is not paginated, since a team's collaborator list is small by construction. Every change announces itself as a team.* event.

Background

What the API refuses

The teams package enforces these itself; the API restates them rather than letting a plainly-invalid request surface as a 500.

AttemptResult
Re-role or remove the owner404: ownership is not a membership row
Invite someone already in the team422
Name a role code the team does not have422
Address a user who is not in the team404
Address a team the caller cannot see404

A team is visible only when the caller owns it or is a member. Tokens scoped to a different team get 404 RESOURCE_NOT_FOUND on every route here; existence never leaks.

Endpoints

List a team's members

GET
curl https://api.subscriby.net/v1/teams/$TEAM_ID/members \  -H "Authorization: Bearer $SUBSCRIBY_TOKEN"

The team's owner as the first row followed by every member. Not paginated: a team's collaborator list is small by construction.

email is omitted entirely unless the token carries team-member:view. Listing who is on a team and reading how to reach them are different asks, and the fine-grained ability is what separates them. A roster-only token gets every other field and no email key at all: not null, absent.

The same team-member:view-any permission gates the dashboard's Settings → Teams → Manage Members page: only team owners and members holding the permission via their role can load it. Free-tier teams auto-assign everyone as owner, so this only matters on Growth-tier teams with multiple collaborators.

GET
/v1/teams/{team}/members

Requires ability

The token must hold this ability, or the call is refused with 403.

Runs the same action from an agent, behind the same ability.

Authorization

bearerToken
AuthorizationBearer <token>

A personal access token minted on the dashboard under Settings, then Tokens, sent as Authorization: Bearer sbt_live_…. The token carries the abilities each endpoint lists under Requires ability and is frozen to one team.

In: header

Path Parameters

team*string

The team, resolved by the route binder.

Formatuuid

Responses

200OK

Array of TeamMemberResource

401Unauthorized

The request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an sbt_test_ token on production).

403Forbidden

The token is valid but does not carry the ability this endpoint requires; error.context.required_ability names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers FORBIDDEN, TEAM_TIER_REQUIRED or CONNECTOR_TIER_REQUIRED with the same status, and says so in its own description.

404Not found

An id in the path names nothing the token can see. TENANT_MISMATCH: the project sits outside the token's scope:project: allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.

429Too many requests

The token has spent its 300 requests a minute or 10,000 an hour; Retry-After says when the next one is accepted.

Invite a team member

POST
curl -X POST https://api.subscriby.net/v1/teams/$TEAM_ID/members \  -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \  -H "Idempotency-Key: $(uuidgen)" \  -H "Content-Type: application/json" \  -d '{"email": "[email protected]", "role": "support-agent"}'

Addressed by email, not user id: the invitee may not have a Subscriby account yet, which is what separates inviting from adding. They receive an invitation message with a link; the membership row appears when they accept.

role is a role code on that team, such as support-agent, not a role id. Create it first with Roles if it does not exist.

Answers 201 with the invitation summary. Emits team.member.invited, then team.member.joined when they accept.

POST
/v1/teams/{team}/members

Requires ability

The token must hold this ability, or the call is refused with 403.

Fires one event

Delivered to every endpoint subscribed to it once the change is made.

Runs the same action from an agent, behind the same ability.

Idempotent

Send the header on every call; the same key replays the original response for 24 hours.

Authorization

bearerToken
AuthorizationBearer <token>

A personal access token minted on the dashboard under Settings, then Tokens, sent as Authorization: Bearer sbt_live_…. The token carries the abilities each endpoint lists under Requires ability and is frozen to one team.

In: header

Path Parameters

team*string

The team, resolved by the route binder.

Formatuuid

Header Parameters

Idempotency-Key*string

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.

Formatuuid

Request body

JSONWhat the request carries

Who to invite and as what. An invitation names an email rather than a user, because the invitee may not have an account yet.

Responses

201Created

201 with the invitation summary.

400Bad request

Every write needs an Idempotency-Key header. Send a fresh UUID per distinct operation.

401Unauthorized

The request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an sbt_test_ token on production).

403Forbidden

The token is valid but does not carry the ability this endpoint requires; error.context.required_ability names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers FORBIDDEN, TEAM_TIER_REQUIRED or CONNECTOR_TIER_REQUIRED with the same status, and says so in its own description. On this endpoint: TEAM_TIER_REQUIRED: below the Growth tier; nothing changes.

404Not found

An id in the path names nothing the token can see. TENANT_MISMATCH: the project sits outside the token's scope:project: allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.

409Conflict

The key was already used in the last 24 hours with a different request body.

422Validation failed

The payload broke a rule, and error.fields maps each offending key to its messages. A refusal from the domain, such as a plan that cannot go on sale or a member who cannot be removed, uses the same code with error.message saying why and no fields. On this endpoint: VALIDATION_FAILED: when the role code is not one of the team's, or the invitee is already in the team.

425Too early

The first request with this key is still running; retry in a few seconds and the original response is replayed.

429Too many requests

The token has spent its 300 requests a minute or 10,000 an hour; Retry-After says when the next one is accepted.

Get a team member

GET
curl https://api.subscriby.net/v1/teams/$TEAM_ID/members/$USER_ID \  -H "Authorization: Bearer $SUBSCRIBY_TOKEN"

One collaborator, with their email, which this route always includes because it requires team-member:view by definition. 404 RESOURCE_NOT_FOUND if the user is not on the team or the team is not in the caller's visibility set.

  • role_id points at a Role row on the same team, or null for the owner pseudo-row.
  • is_owner is true only for the single team creator; the owner is returned alongside the members but bypasses the membership table entirely.
GET
/v1/teams/{team}/members/{member}

Requires ability

The token must hold this ability, or the call is refused with 403.

Authorization

bearerToken
AuthorizationBearer <token>

A personal access token minted on the dashboard under Settings, then Tokens, sent as Authorization: Bearer sbt_live_…. The token carries the abilities each endpoint lists under Requires ability and is frozen to one team.

In: header

Path Parameters

team*string

The team, resolved by the route binder.

Formatuuid
member*string

The person's place on the team, resolved by the route binder.

Responses

200OK

The membership.

401Unauthorized

The request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an sbt_test_ token on production).

403Forbidden

The token is valid but does not carry the ability this endpoint requires; error.context.required_ability names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers FORBIDDEN, TEAM_TIER_REQUIRED or CONNECTOR_TIER_REQUIRED with the same status, and says so in its own description.

404Not found

An id in the path names nothing the token can see. TENANT_MISMATCH: the project sits outside the token's scope:project: allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.

429Too many requests

The token has spent its 300 requests a minute or 10,000 an hour; Retry-After says when the next one is accepted.

Remove a team member

DELETE
curl -X DELETE https://api.subscriby.net/v1/teams/$TEAM_ID/members/$USER_ID \  -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \  -H "Idempotency-Key: $(uuidgen)"

Returns 204 No Content. They lose access to every project and setting scoped to the team immediately; their own Subscriby account is untouched. Not gated by tier. Emits team.member.removed.

DELETE
/v1/teams/{team}/members/{member}

Requires ability

The token must hold this ability, or the call is refused with 403.

Fires one event

Delivered to every endpoint subscribed to it once the change is made.

Runs the same action from an agent, behind the same ability.

Idempotent

Send the header on every call; the same key replays the original response for 24 hours.

Authorization

bearerToken
AuthorizationBearer <token>

A personal access token minted on the dashboard under Settings, then Tokens, sent as Authorization: Bearer sbt_live_…. The token carries the abilities each endpoint lists under Requires ability and is frozen to one team.

In: header

Path Parameters

team*string

The team, resolved by the route binder.

Formatuuid
member*string

The membership, resolved by the route binder.

Header Parameters

Idempotency-Key*string

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.

Formatuuid

Responses

204No content

No content

400Bad request

Every write needs an Idempotency-Key header. Send a fresh UUID per distinct operation.

401Unauthorized

The request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an sbt_test_ token on production).

403Forbidden

The token is valid but does not carry the ability this endpoint requires; error.context.required_ability names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers FORBIDDEN, TEAM_TIER_REQUIRED or CONNECTOR_TIER_REQUIRED with the same status, and says so in its own description.

404Not found

An id in the path names nothing the token can see. TENANT_MISMATCH: the project sits outside the token's scope:project: allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred. On this endpoint: RESOURCE_NOT_FOUND: for the owner, who cannot be removed.

409Conflict

The key was already used in the last 24 hours with a different request body.

425Too early

The first request with this key is still running; retry in a few seconds and the original response is replayed.

429Too many requests

The token has spent its 300 requests a minute or 10,000 an hour; Retry-After says when the next one is accepted.

PATCH
curl -X PATCH https://api.subscriby.net/v1/teams/$TEAM_ID/members/$USER_ID/role \  -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \  -H "Idempotency-Key: $(uuidgen)" \  -H "Content-Type: application/json" \  -d '{"role": "manager"}'

Moves a collaborator onto another of the team's roles. The team owner cannot be re-roled: ownership is not a membership row and carries everything unconditionally. Answers 200 with the membership and emits team.member.role_changed.

Changing a member's role has no dashboard equivalent: the settings page offers invite, remove and cancel-invitation only, so this endpoint is the only way to move someone between roles without removing and re-inviting them.

PATCH
/v1/teams/{team}/members/{member}/role

Requires ability

The token must hold this ability, or the call is refused with 403.

Delivered to every endpoint subscribed to it once the change is made.

Runs the same action from an agent, behind the same ability.

Idempotent

Send the header on every call; the same key replays the original response for 24 hours.

Authorization

bearerToken
AuthorizationBearer <token>

A personal access token minted on the dashboard under Settings, then Tokens, sent as Authorization: Bearer sbt_live_…. The token carries the abilities each endpoint lists under Requires ability and is frozen to one team.

In: header

Path Parameters

team*string

The team, resolved by the route binder.

Formatuuid
member*string

The membership, resolved by the route binder.

Header Parameters

Idempotency-Key*string

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.

Formatuuid

Request body

JSONWhat the request carries

The role to move a collaborator onto.

Responses

200OK

The membership after the change.

400Bad request

Every write needs an Idempotency-Key header. Send a fresh UUID per distinct operation.

401Unauthorized

The request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an sbt_test_ token on production).

403Forbidden

The token is valid but does not carry the ability this endpoint requires; error.context.required_ability names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers FORBIDDEN, TEAM_TIER_REQUIRED or CONNECTOR_TIER_REQUIRED with the same status, and says so in its own description. On this endpoint: TEAM_TIER_REQUIRED: below the Growth tier; nothing changes.

404Not found

An id in the path names nothing the token can see. TENANT_MISMATCH: the project sits outside the token's scope:project: allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred. On this endpoint: RESOURCE_NOT_FOUND: for the owner, who has no role to change, or for someone who is not in the team.

409Conflict

The key was already used in the last 24 hours with a different request body.

422Validation failed

The payload broke a rule, and error.fields maps each offending key to its messages. A refusal from the domain, such as a plan that cannot go on sale or a member who cannot be removed, uses the same code with error.message saying why and no fields. On this endpoint: VALIDATION_FAILED: when the role code is not one of the team's.

425Too early

The first request with this key is still running; retry in a few seconds and the original response is replayed.

429Too many requests

The token has spent its 300 requests a minute or 10,000 an hour; Retry-After says when the next one is accepted.

Withdraw an invitation

DELETE

Requires team-member:remove, the same ability as removing a member, because withdrawing an invitation and removing a member revoke the same prospective access.

curl -X DELETE https://api.subscriby.net/v1/teams/$TEAM_ID/invitations/$INVITATION_ID \  -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \  -H "Idempotency-Key: $(uuidgen)"

An invitee is not a member. Someone who has not accepted has no membership row to remove, only a pending invitation. That is why this is a separate route: removing a member and withdrawing an invitation fail on different things, and using the wrong one returns 404 rather than doing something surprising.

Returns 204 No Content and the invitation link stops working.

DELETE
/v1/teams/{team}/invitations/{invitation}

Requires ability

The token must hold this ability, or the call is refused with 403.

Runs the same action from an agent, behind the same ability.

Idempotent

Send the header on every call; the same key replays the original response for 24 hours.

Authorization

bearerToken
AuthorizationBearer <token>

A personal access token minted on the dashboard under Settings, then Tokens, sent as Authorization: Bearer sbt_live_…. The token carries the abilities each endpoint lists under Requires ability and is frozen to one team.

In: header

Path Parameters

team*string

The team, resolved by the route binder.

Formatuuid
invitation*string

The pending invitation, resolved within the team by the route binder.

Formatuuid

Header Parameters

Idempotency-Key*string

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.

Formatuuid

Responses

204No content

No content

400Bad request

Every write needs an Idempotency-Key header. Send a fresh UUID per distinct operation.

401Unauthorized

The request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an sbt_test_ token on production).

403Forbidden

The token is valid but does not carry the ability this endpoint requires; error.context.required_ability names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers FORBIDDEN, TEAM_TIER_REQUIRED or CONNECTOR_TIER_REQUIRED with the same status, and says so in its own description.

404Not found

An id in the path names nothing the token can see. TENANT_MISMATCH: the project sits outside the token's scope:project: allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.

409Conflict

The key was already used in the last 24 hours with a different request body.

425Too early

The first request with this key is still running; retry in a few seconds and the original response is replayed.

429Too many requests

The token has spent its 300 requests a minute or 10,000 an hour; Retry-After says when the next one is accepted.

How is this guide?

Version

On this page

Subscriby is a product
designed by you — for you.
No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request