TagPayTagPay

API Reference

Team & Roles

Manage team members and custom roles on the merchant account. Members are scoped to a single merchant; each member has either a built-in role (`admin`, `developer`, `finance`, `support`) or a merchant-defined **custom role** that bundles a subset of the platform permission catalog. The account `owner` is unique per merchant — transferred, never invited.

List team members

GET/v1/merchant/team

Returns every team member with status in active or pending. Revoked members are excluded. Sorted by role, then creation time. Authenticated with a JWT Bearer token and team:read permission.

When a member is assigned to a custom role, role is null and custom_role is populated. When assigned to a built-in role, the reverse: role carries the enum, custom_role is null.

Response

200
dataarray<TeamMember>

Team members.

idstring

Member user ID.

emailstring

Member email.

first_namestring

First name (empty for invite-only pending members).

last_namestring

Last name (empty for invite-only pending members).

roleenum<'owner' | 'admin' | 'developer' | 'finance' | 'support'> | null

Built-in role, or `null` when the member is on a custom role.

custom_roleobject | null

Custom role assignment, or `null` when on a built-in role.

idstring

Custom role ID.

namestring

Custom role name.

statusenum<'active' | 'pending'>

Membership status.

two_factor_enabledboolean

Whether the member has 2FA on.

invited_atstring | null

ISO-8601 of when the invite was issued.

last_login_atstring | null

ISO-8601 of the last successful login.

created_atstring

ISO-8601 of when the user row was created.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks the team:read permission.

Create a team member directly

POST/v1/merchant/team

Creates a pending team member with first_name and last_name already set, then emails an activation link (72h TTL) where the user picks a password. Authenticated with a JWT Bearer token and team:write permission.

Use this when the owner wants to set everything up front. The slimmer /v1/merchant/team/invite is the right choice when only the email is known.

Exactly one of role or custom_role_id must be supplied. role may not be owner — use /v1/merchant/team/transfer-ownership instead. You can only assign permissions you yourself hold.

Body parameters

emailstringrequired

Email address. Must not already belong to a user on any merchant.

first_namestringrequired

1–50 characters.

last_namestringrequired

1–50 characters.

roleenum<'admin' | 'developer' | 'finance' | 'support'>optional

Built-in role. Required when custom_role_id is not supplied.

custom_role_idstringoptional

Custom role ID. When set, role is ignored.

Response

200
idstring

Newly-created member ID.

emailstring

Member email.

first_namestring

First name.

last_namestring

Last name.

roleenum<'admin' | 'developer' | 'finance' | 'support'> | null

Built-in role, or `null` when on a custom role.

custom_roleobject | null

Custom role, or `null` when on a built-in role.

idstring

Custom role ID.

namestring

Custom role name.

statusenum<'pending'>

Always `pending` until the user accepts the invite.

invited_atstring

ISO-8601 of when the invite was sent.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks the team:write permission, or is granting a permission they do not hold.
EMAIL_TAKEN409A user with this email already exists.
INVALID_ROLE400Cannot create a user as owner. Use ownership transfer instead.
CUSTOM_ROLE_NOT_FOUND404No custom role with that ID on this merchant.

Invite a team member

POST/v1/merchant/team/invite

Creates a pending team member with blank first_name / last_name and emails an activation link (72h TTL). Authenticated with a JWT Bearer token and team:write permission.

The invitee fills in their name + password on the accept page. Exactly one of role or custom_role_id must be supplied — role may not be owner. You can only assign permissions you yourself hold.

Body parameters

emailstringrequired

Email address. Must not already belong to a user on any merchant.

roleenum<'admin' | 'developer' | 'finance' | 'support'>optional

Built-in role. Required when custom_role_id is not supplied.

custom_role_idstringoptional

Custom role ID. When set, role is ignored.

Response

200
idstring

Newly-created member ID.

emailstring

Invitee email.

roleenum<'admin' | 'developer' | 'finance' | 'support'> | null

Built-in role, or `null` when on a custom role.

custom_roleobject | null

Custom role assignment, or `null` when on a built-in role.

idstring

Custom role ID.

namestring

Custom role name.

statusenum<'pending'>

Always `pending` until accepted.

invite_urlstring

Activation URL the invitee was emailed.

invited_atstring

ISO-8601 of when the invite was sent.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks team:write, or is granting a permission they do not hold.
EMAIL_TAKEN409A user with this email already exists.
INVALID_ROLE400Cannot invite a user as owner. Use ownership transfer instead.
CUSTOM_ROLE_NOT_FOUND404No custom role with that ID on this merchant.

Preview an invite

GET/v1/merchant/team/invite/preview

Used by the accept page. Validates the invite token and returns the email, business name, and any pre-populated name fields so the form can render correctly (or skip name entry entirely if both names are already set).

No authentication required — the token in the query string is the authentication. Returns 401 if the token is invalid, expired, or the invite has already been used / revoked.

Query parameters

tokenstringrequired

JWT invite token (from the activation email).

Response

200
emailstring

Email the invite was sent to.

first_namestring

Pre-populated first name (empty if invite-only).

last_namestring

Pre-populated last name (empty if invite-only).

has_nameboolean

`true` when both names are pre-populated — the form can skip those inputs.

merchant_business_namestring

Business name to display on the accept page.

Errors

INVALID_TOKEN401Invite link is invalid or has expired.
ALREADY_USED401This invite has already been used or has been revoked.
TOKEN_REQUIRED400token query parameter is required.

Accept an invite

POST/v1/merchant/team/invite/accept

Consumes the invite token from the activation email, sets the password, flips status to active, and stamps email_verified=true. No prior authentication required — the token in the body is the authentication.

first_name and last_name are optional when the owner pre-populated them via POST /v1/merchant/team (direct create). If the invitee provides them, the supplied values overwrite the pre-populated ones.

Body parameters

tokenstringrequired

JWT invite token from the activation email.

first_namestringoptional

Required when the invite did not pre-populate names.

last_namestringoptional

Required when the invite did not pre-populate names.

passwordstringrequired

New account password (min 8 characters).

Response

200
idstring

Activated user ID.

emailstring

Member email.

rolestring

Built-in role enum (custom-role users carry a placeholder enum here too).

Errors

INVALID_TOKEN401Invite link is invalid or has expired.
ALREADY_USED401This invite has already been used or has been revoked.
NAME_REQUIRED400First name and last name are required.

Change a member's role

PATCH/v1/merchant/team/:user_id/role

Updates the target member's role and force-logs them out by bumping their token version. Authenticated with a JWT Bearer token and team:write permission.

Exactly one of role or custom_role_id must be supplied. You cannot change your own role, cannot demote the owner (use transfer-ownership first), and cannot grant a permission set you do not yourself hold.

Path parameters

user_idstringrequired

Target member ID.

Body parameters

roleenum<'admin' | 'developer' | 'finance' | 'support'>optional

New built-in role.

custom_role_idstringoptional

New custom role ID.

Response

200
idstring

Target member ID.

roleenum<'admin' | 'developer' | 'finance' | 'support'> | null

New built-in role, or `null` when moved to a custom role.

custom_roleobject | null

New custom role, or `null` when moved to a built-in role.

idstring

Custom role ID.

namestring

Custom role name.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks team:write, is granting a permission they do not hold, or is trying to demote the owner.
NOT_FOUND404Team member not found on this merchant.
INVALID_TARGET400You cannot change your own role.
INVALID_BODY400Provide exactly one of role or custom_role_id.

Re-send an invite

POST/v1/merchant/team/:user_id/resend-invite

Generates a new invite token (72h TTL), emails it, and bumps the invited_at timestamp on the user row. Authenticated with a JWT Bearer token and team:write permission.

Only works on members with status=pending. Active or revoked members get a 400.

Path parameters

user_idstringrequired

Pending member ID.

Response

200
idstring

Member ID.

emailstring

Member email.

invite_urlstring

Fresh activation URL.

invited_atstring

New ISO-8601 invitation timestamp.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks the team:write permission.
NOT_FOUND404Team member not found on this merchant.
NOT_PENDING400Only pending invitations can be re-sent.

Remove a team member

DELETE/v1/merchant/team/:user_id

Removes a team member. Authenticated with a JWT Bearer token and team:write permission.

Behavior depends on the target's status:

- `pending`: hard-deleted. The email is freed for re-invite. - `active`: soft-deleted — status flips to revoked, token version is bumped (force-logout), the row stays for audit-trail continuity.

You cannot remove yourself, and you cannot remove the owner (transfer ownership first).

Path parameters

user_idstringrequired

Member ID to remove.

Response

200
idstring

Removed member ID.

statusenum<'deleted' | 'revoked'>

`deleted` for pending invites; `revoked` for previously-active members.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks team:write, or is trying to remove the owner.
NOT_FOUND404Team member not found on this merchant.
INVALID_TARGET400You cannot remove yourself.

Transfer account ownership

POST/v1/merchant/team/transfer-ownership

Moves the owner role to another active team member. The previous owner is demoted to admin. Owner only — non-owners get 403. Authenticated with a JWT Bearer token and merchant_profile:write permission.

Requires re-authentication: current password plus a current totp_code. The owner must already have 2FA enabled (else 403). Both old and new owner are emailed a confirmation.

The target user must be on the same merchant and have status=active. Both users' token versions are bumped, forcing a fresh sign-in.

Body parameters

new_owner_user_idstringrequired

ID of the active team member who will become the new owner.

passwordstringrequired

Current password of the acting owner — re-verified.

totp_codestringrequired

Current 6-digit TOTP code from the acting owner. Required even if the 2FA policy is off — the owner must have 2FA enrolled to transfer.

Response

200
new_ownerobject

The promoted user.

idstring

User ID.

roleenum<'owner'>

Always 'owner'.

previous_ownerobject

The demoted user (the original caller).

idstring

User ID.

roleenum<'admin'>

Always 'admin' after transfer.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Only the account owner can transfer ownership, or the owner has not enabled 2FA.
INVALID_PASSWORD401Password does not match.
INVALID_TOTP401TOTP code did not verify.
NOT_FOUND404Target user not found on this merchant.
INVALID_TARGET400Target user must be active, and cannot be yourself.

List custom roles

GET/v1/merchant/roles

Lists every custom role on the merchant, oldest first. Each entry includes a member_count so the dashboard can flag in-use roles. Authenticated with a JWT Bearer token and team:read permission.

A merchant may have up to 25 custom roles. Built-in roles (owner, admin, developer, finance, support) are not returned here — they're implicit on every account.

Response

200
dataarray<CustomRole>

Custom roles.

idstring

Role ID.

namestring

Role name (unique per merchant, case-insensitive).

descriptionstring | null

Optional description.

permissionsarray<string>

Permission keys (see the permission catalog).

member_countinteger

Number of users currently assigned this role.

created_atstring

ISO-8601 creation timestamp.

created_byobject | null

The user who created the role.

idstring

Creator user ID.

first_namestring

Creator first name.

last_namestring

Creator last name.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks the team:read permission.

Create a custom role

POST/v1/merchant/roles

Creates a new custom role. Authenticated with a JWT Bearer token and team:write permission.

Permission keys must be drawn from the platform [permission catalog](#permission-catalog) — unknown keys return 400. You cannot create a role whose permissions exceed your own (e.g. an admin cannot create a role granting api_keys:write because admins don't hold that key). Names are unique per merchant (case-insensitive); the five built-in role names (owner, admin, developer, finance, support) are reserved. The merchant cap is 25 custom roles.

Body parameters

namestringrequired

2–40 characters. Unique per merchant, case-insensitive. Built-in role names are reserved.

descriptionstringoptional

Up to 200 characters.

permissionsarray<string>required

Permission keys from the catalog. 1–17 entries; must be unique. Caller must already hold every permission in the list.

Response

200
idstring

New role ID.

namestring

Role name.

descriptionstring | null

Description, or `null`.

permissionsarray<string>

Granted permission keys.

member_countinteger

Always `0` on create.

created_atstring

ISO-8601 timestamp.

created_byobject

The caller.

idstring

Caller user ID.

first_namestring

Caller first name.

last_namestring

Caller last name.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks team:write, or is granting a permission they do not hold.
UNKNOWN_PERMISSIONS400One or more permission keys are not in the catalog.
NAME_RESERVED409That name is reserved — try another.
NAME_TAKEN409A role with that name already exists on this merchant.
ROLE_LIMIT_REACHED409Role limit reached (25). Delete an unused role first.

Retrieve a custom role

GET/v1/merchant/roles/:id

Returns one custom role with its permission list and up to 50 active/pending assignees. Authenticated with a JWT Bearer token and team:read permission.

Path parameters

idstringrequired

Custom role ID.

Response

200
idstring

Role ID.

namestring

Role name.

descriptionstring | null

Description.

permissionsarray<string>

Permission keys.

member_countinteger

Total assignees (independent of the `assignees` sample).

created_atstring

ISO-8601 creation timestamp.

created_byobject | null

The user who created the role.

idstring

Creator user ID.

first_namestring

Creator first name.

last_namestring

Creator last name.

assigneesarray<Assignee>

Up to 50 active/pending team members on this role.

idstring

Assignee user ID.

emailstring

Assignee email.

first_namestring

First name.

last_namestring

Last name.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks the team:read permission.
NOT_FOUND404Role not found.

Update a custom role

PATCH/v1/merchant/roles/:id

Updates a custom role. Authenticated with a JWT Bearer token and team:write permission.

All fields are optional — supply just the ones you want to change. When permissions changes, every assignee's token version is bumped so they re-authenticate against the new set on their next request. You must hold every permission in both the existing role and the new role; you cannot touch a role that already grants something you lack.

Path parameters

idstringrequired

Custom role ID.

Body parameters

namestringoptional

New name (2–40 chars). Unique per merchant; built-in names reserved.

descriptionstringoptional

New description (up to 200 chars). Pass an empty string to clear.

permissionsarray<string>optional

New permission set (1–17 unique keys). Replaces the existing set.

Response

200
idstring

Role ID.

namestring

Updated name.

descriptionstring | null

Updated description.

permissionsarray<string>

Updated permission set.

member_countinteger

Total assignees.

created_atstring

ISO-8601 creation timestamp (unchanged).

assigneesarray<Assignee>

Up to 50 active/pending team members on this role.

idstring

Assignee user ID.

emailstring

Assignee email.

first_namestring

First name.

last_namestring

Last name.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks team:write, or is touching a role whose permissions exceed their own.
NOT_FOUND404Role not found.
UNKNOWN_PERMISSIONS400One or more permission keys are not in the catalog.
NAME_RESERVED409That name is reserved — try another.
NAME_TAKEN409A role with that name already exists on this merchant.

Delete a custom role

DELETE/v1/merchant/roles/:id

Deletes a custom role. Authenticated with a JWT Bearer token and team:write permission.

Blocked when any team member is still assigned — reassign them via PATCH /v1/merchant/team/:user_id/role first. You must hold every permission the role grants.

Path parameters

idstringrequired

Custom role ID.

Response

200
idstring

Deleted role ID.

deletedboolean

Always `true` on success.

Errors

INVALID_TOKEN401JWT missing, expired, or invalid.
FORBIDDEN403Caller lacks team:write, or the role grants a permission they do not hold.
NOT_FOUND404Role not found.
ROLE_IN_USE409Reassign the team members using this role before deleting it.

Permission catalog

Every permission key a custom role can include. Mirrored from the backend Permissions registry — changes there are mirrored here in the same release.

Transactions
transactions:readRead transactions and charges.
transactions:refundIssue refunds.
Customers
customers:readRead customer records.
customers:writeCreate and update customers.
Wallet
wallet:readRead the merchant wallet balance.
Transfers
transfers:readRead transfer history.
transfers:writeInitiate transfers.
Recipients
recipients:readRead the recipient registry.
recipients:writeCreate and update recipients.
Settlements
settlements:readRead settlement batches.
Settlement accounts
settlement_accounts:readRead settlement bank accounts.
settlement_accounts:writeAdd and remove settlement bank accounts.
Collection accounts
collection_accounts:readRead collection (virtual) accounts.
collection_accounts:writeProvision and manage collection accounts.
Webhooks
webhooks:readRead webhook endpoints and delivery logs.
webhooks:writeCreate, update, and delete webhook endpoints.
webhooks:replayReplay failed webhook deliveries.
API keys
api_keys:readView API keys.
api_keys:writeRegenerate API keys.
Compliance
compliance:readRead KYC status and compliance documents.
compliance:writeUpload and update compliance documents.
Checkout demo
checkout_demo:useUse the dashboard’s "Try a payment" tool.
Team
team:readList team members and custom roles.
team:writeInvite, remove, and re-role team members; manage custom roles.
Merchant profile
merchant_profile:writeEdit merchant profile, branding, 2FA policy, and transfer ownership.
Subaccounts & splits
subaccounts:readRead marketplace subaccounts.
subaccounts:writeCreate and update marketplace subaccounts.
splits:readRead split-payment configurations.
splits:writeCreate and update split-payment configurations.
Disputes
disputes:readRead dispute records.
disputes:respondRespond to disputes (upload evidence).