Teams

All routes require x-api-key; none requires an API-key permission flag. Most team-management routes accept a valid session, x-profile-id plus x-team-id, or a team/profile-scoped API key (allowHeaderValidation). Session-only routes require Authorization: Bearer <session> and do not accept header-only context.

API-key failures return 403, rate limits return 429, and required-session failures return 401. Error bodies use the standard error envelope.

Roles used by invitation and role-update schemas: admin, member, viewer, developer, analyst, blocked, api. Admin is required for mutations unless noted otherwise.

Endpoints#

MethodPathContextAdminSuccess
GET/v1/teams/banksSession, headers, or scoped keyNo200
POST/v1/teams/countriesSession, headers, or scoped keyYes200
DELETE/v1/teams/countriesSession, headers, or scoped keyYes200
PATCH/v1/teams/default-receiver-walletSessionYes200
GET/v1/teams/invitationsSession, headers, or scoped keyYes200
POST/v1/teams/invitationsSession, headers, or scoped keyYes201
DELETE/v1/teams/invitations/{id}SessionYes200
GET/v1/teams/membersSession, headers, or scoped keyNo200
DELETE/v1/teams/members/{profileId}Session, headers, or scoped keyYes200
PATCH/v1/teams/members/{profileId}/roleSession, headers, or scoped keyYes200
PATCH/v1/teams/metadataSession, headers, or scoped keyYes200
PATCH/v1/teams/notification-emailSession, headers, or scoped keyYes200
POST/v1/teams/tax-infoSession, headers, or scoped keyYes200
PATCH/v1/teams/tax-info/defaultSession, headers, or scoped keyYes200
GET/v1/teams/{teamId}/activitySessionMembership200
POST/v1/teams/{teamId}/image/upload-urlSessionYes200
POST/v1/teams/{teamId}/imageSessionYes200

GET /v1/teams/members#

Lists members for the active team. Any team member with valid context may call this route.

Headers: x-api-key plus session, both x-profile-id and x-team-id, or a team/profile-scoped key.

{
	"success": true,
	"data": [
		{
			"profileId": "profile-uuid",
			"role": "admin",
			"joinedAt": "2026-07-28T17:00:00.000Z",
			"addedBy": "profile-uuid",
			"profile": {
				"id": "profile-uuid",
				"nickname": "johndoe",
				"metadata": {},
				"createdAt": "2026-07-28T17:00:00.000Z",
				"updatedAt": "2026-07-28T17:00:00.000Z"
			},
			"user": {
				"id": "user-uuid",
				"username": "johndoe",
				"email": "john@example.com",
				"firstName": "John",
				"lastName": "Doe",
				"country": "US",
				"language": "en"
			},
			"imageUrl": "/profile/profile-uuid/image-hash"
		}
	]
}

imageUrl is omitted when the member has no profile image. Missing context returns 400 with APP_V1_TEAMS_MEMBERS_ROUTE_01 (Missing profile or team). Lookup failures return 500.

PATCH /v1/teams/members/{profileId}/role#

Admin only. Path {profileId} is the member to update.

FieldTypeRequiredConstraints
roleenumYesadmin, member, viewer, developer, analyst, blocked, api
{
	"success": true,
	"data": { "profileId": "profile-uuid", "role": "developer" }
}
StatusCodeMessage
400APP_V1_TEAMS_MEMBERS_PROFILEID_ROLE_ROUTE_01Missing profile or team
400APP_V1_TEAMS_MEMBERS_PROFILEID_ROLE_ROUTE_02Missing profile ID
403APP_V1_TEAMS_MEMBERS_PROFILEID_ROLE_ROUTE_01Insufficient permissions
400body parser codeInvalid body
403propagatedRole update rejected (for example last-admin protection)

DELETE /v1/teams/members/{profileId}#

Admin only. Blocks the member by setting role to blocked. There is no request body.

{
	"success": true,
	"data": { "profileId": "profile-uuid", "blocked": true }
}
StatusCodeMessage
400APP_V1_TEAMS_MEMBERS_PROFILEID_ROUTE_01Missing profile or team
400APP_V1_TEAMS_MEMBERS_PROFILEID_ROUTE_02Missing profile ID
403APP_V1_TEAMS_MEMBERS_PROFILEID_ROUTE_01Insufficient permissions
403propagatedBlock rejected

POST /v1/teams/invitations#

Admin only. Creates a pending invitation. Invitation email is sent only when the calling API key includes the EMAIL permission; invitation creation still succeeds if email sending fails.

FieldTypeRequiredConstraints
emailstringYesValid email.
roleenumNoDefaults to member; same role enum as role updates.
{
	"success": true,
	"data": {
		"id": "invitation-uuid",
		"teamId": "team-uuid",
		"email": "user@example.com",
		"role": "member",
		"invitedBy": "profile-uuid",
		"status": "pending",
		"expiresAt": "2026-08-04T17:00:00.000Z",
		"createdAt": "2026-07-28T17:00:00.000Z",
		"updatedAt": "2026-07-28T17:00:00.000Z"
	}
}
StatusCodeMessage
400APP_V1_TEAMS_INVITATIONS_ROUTE_01Missing profile or team
403APP_V1_TEAMS_INVITATIONS_ROUTE_01Insufficient permissions
409APP_V1_TEAMS_INVITATIONS_ROUTE_02User already in team
400body parser codeInvalid body
500propagatedInvitation creation failed

The 409 path triggers when the email already has one or more profiles for the same app (not only membership in this team).

GET /v1/teams/invitations#

Admin only. Returns pending invitations for the active team.

{
	"success": true,
	"data": [
		{
			"id": "invitation-uuid",
			"teamId": "team-uuid",
			"email": "user@example.com",
			"role": "member",
			"invitedBy": "profile-uuid",
			"status": "pending",
			"expiresAt": "2026-08-04T17:00:00.000Z",
			"createdAt": "2026-07-28T17:00:00.000Z",
			"updatedAt": "2026-07-28T17:00:00.000Z"
		}
	]
}
StatusCodeMessage
400APP_V1_TEAMS_INVITATIONS_ROUTE_01Missing profile or team
403APP_V1_TEAMS_INVITATIONS_ROUTE_03Insufficient permissions
500propagatedInvitation list failed

DELETE /v1/teams/invitations/{id}#

Session required. Admin only. Cancels by setting invitation status to rejected.

{
	"success": true,
	"data": {
		"id": "invitation-uuid",
		"status": "rejected",
		"updatedAt": "2026-07-28T17:00:00.000Z"
	}
}

The success payload is the full updated invitation row returned by the status update.

StatusCodeMessage
400APP_V1_TEAMS_INVITATIONS_ID_ROUTE_01Missing profile or team
403APP_V1_TEAMS_INVITATIONS_ID_ROUTE_02Insufficient permissions
400APP_V1_TEAMS_INVITATIONS_ID_ROUTE_03Missing invitation ID
404APP_V1_TEAMS_INVITATIONS_ID_ROUTE_04Invitation not found
403APP_V1_TEAMS_INVITATIONS_ID_ROUTE_05Invitation doesn’t belong to team
500propagatedLookup or update failed

POST /v1/teams/countries#

Admin only. Adds a supported ISO alpha-2 country to the team.

FieldTypeRequiredConstraints
countrystringYesExactly two characters after trim; uppercased and validated against supported country codes.

Returns 200 with the updated team row (including countries and timestamps). Duplicate countries are treated as success and return the existing team.

StatusCodeMessage
400APP_V1_TEAMS_COUNTRIES_01Missing profile or team
403APP_V1_TEAMS_COUNTRIES_02Insufficient permissions
400APP_V1_TEAMS_COUNTRIES_03Unsupported country code
400body parser codeInvalid body
500propagatedUpdate failed

DELETE /v1/teams/countries#

Admin only. Removes a country from the team. Body shape matches POST.

Returns 200 with the updated team row. Route-owned errors reuse APP_V1_TEAMS_COUNTRIES_01 / _02; unsupported-country validation is not applied on remove.

GET /v1/teams/banks#

Lists bank accounts for all profiles on the active team. The route does not enforce an admin role check.

{
	"success": true,
	"data": [
		{
			"profile": {
				"id": "profile-uuid",
				"nickname": "johndoe"
			},
			"banks": [
				{
					"id": "bank-uuid",
					"accountAlias": "Main Checking",
					"accountType": "CHECKING"
				}
			]
		}
	]
}

Each item includes the full profile and bank rows returned by the database query; the example shows commonly used fields. Missing context returns 400 with APP_V1_TEAMS_BANKS_ROUTE_01. Lookup failures return 500.

PATCH /v1/teams/default-receiver-wallet#

Session required. Admin only.

FieldTypeRequiredConstraints
defaultReceiverWalletstring or nullNoOmitted or null clears the default.

Returns 200 with the updated team row. Missing context or insufficient permissions share code APP_V1_TEAMS_DEFAULT_RECEIVER_WALLET_ROUTE_01 (Missing profile or team context / Insufficient permissions). Malformed bodies return 400; update failures return 500.

PATCH /v1/teams/metadata#

Admin only. Updates team name, description, and/or custom metadata fields.

FieldTypeRequiredConstraints
namestringNoNon-empty if provided.
descriptionstringNo
any other keyany JSON valueNoMerged into metadata. Keys devAccess and devAccessRequest are rejected.

Returns 200 with the updated team object plus optional imageUrl derived from team image metadata.

StatusCodeMessage
400APP_V1_TEAMS_METADATA_ROUTE_01Missing profile or team
403APP_V1_TEAMS_METADATA_ROUTE_01Insufficient permissions
404APP_V1_TEAMS_METADATA_ROUTE_02Team not found
400body parser codeInvalid body / restricted field
500propagatedName, description, metadata, or reload failed

PATCH /v1/teams/notification-email#

Admin only.

FieldTypeRequiredConstraints
notificationEmailstring or nullNoValid email when provided; omit or null to clear.

Returns 200 with the updated team row. Missing context is APP_V1_TEAMS_NOTIFICATION_EMAIL_ROUTE_01; insufficient permissions is _03.

POST /v1/teams/tax-info#

Admin only. Upserts a tax-info entry in team metadata.

FieldTypeRequiredConstraints
countrystringYesSupported two-letter ISO code; uppercased.
taxIdstringYesTrimmed; 1–64 chars; alphanumeric and -.
namestring or nullNo1–120 characters when provided.
addressstring or nullNo1–240 characters when provided.
setAsDefaultForCountrybooleanNo
setAsGlobalDefaultbooleanNo

Returns 200 with the updated team object (including nested metadata.taxInfo) plus optional imageUrl.

StatusCodeMessage
400APP_V1_TEAMS_TAX_INFO_01Missing profile or team
403APP_V1_TEAMS_TAX_INFO_02Admin required
400body parser codeInvalid body
500propagatedUpdate failed

PATCH /v1/teams/tax-info/default#

Admin only. Sets the global default tax-info entry.

FieldTypeRequiredConstraints
countrystringYesSupported two-letter ISO code; uppercased.
taxIdstringYesSame constraints as tax-info create.

Returns 200 with the updated team object plus optional imageUrl. Errors use APP_V1_TEAMS_TAX_INFO_DEFAULT_01 / _02.

GET /v1/teams/{teamId}/activity#

Session required. The session profile must belong to {teamId}.

Query

FieldTypeRequiredConstraints
profileIdstringNoFilters activity to one profile.
limitnumberNoInteger 1–100; defaults to 50.
exclusiveStartKeystringNoPagination cursor from a previous response.
{
	"success": true,
	"data": {
		"items": [
			{
				"PK": "TEAM#team-uuid",
				"SK": "TIMESTAMP#2026-07-28T17:00:00.000Z",
				"ActionType": "transaction",
				"ActionName": "tx_completed",
				"Status": "completed",
				"TeamId": "team-uuid",
				"ProfileId": "profile-uuid",
				"CreatedAt": "2026-07-28T17:00:00.000Z"
			}
		],
		"lastEvaluatedKey": "opaque-pagination-cursor"
	}
}

Item fields vary by action type. lastEvaluatedKey is omitted when there is no next page.

StatusCodeMessage
404APP_V1_TEAMS_ACTIVITY_01Team not found / Team not found or no access
400APP_V1_TEAMS_ACTIVITY_03Invalid limit parameter. Must be between 1 and 100
500APP_V1_TEAMS_ACTIVITY_02Failed to get activity

Team image#

Both routes require x-api-key, Authorization: Bearer <session>, and an admin profile whose team matches {teamId}. Allowed filename extensions are jpg, jpeg, png, webp, and gif; allowed MIME types are image/jpeg, image/jpg, image/png, image/webp, and image/gif.

POST /v1/teams/{teamId}/image/upload-url#

FieldTypeRequired
fileNamestringYes
contentTypeallowed MIME enumYes
{
	"success": true,
	"data": {
		"uploadUrl": "https://storage.example/signed-upload",
		"filePath": "team-images/team-uuid/1760000000000-hash.jpg",
		"expiresAt": "2026-07-28T18:00:00.000Z"
	}
}
StatusCodeMessage
400V1_TEAM_IMAGE_UPLOAD_URL_01Missing team ID / Team ID mismatch
401V1_TEAM_IMAGE_UPLOAD_URL_01Invalid session or team context
403V1_TEAM_IMAGE_UPLOAD_URL_01Insufficient permissions - admin required
400body parser codeInvalid body
500V1_TEAM_IMAGE_UPLOAD_URL_02Failed to generate upload URL

POST /v1/teams/{teamId}/image#

Call after uploading the file to the signed URL.

FieldTypeRequired
filePathnon-empty stringYes
{
	"success": true,
	"data": { "imageUrl": "/team/team-uuid/image-hash" }
}

Route-owned errors mirror the upload-url codes under V1_TEAM_IMAGE_CONFIRM_01 / _02.