Users and profiles
All routes require x-api-key; none requires an API-key permission flag. Profile routes also require Authorization: Bearer <session> and operate on the session’s current profile/team. The user routes accept either a valid session or a team/profile-scoped API key; header-derived context is intentionally disabled for those routes.
API-key failures return 403, rate limits return 429, and required-session failures return 401. Error bodies use the standard error envelope.
Endpoints#
| Method | Path | Context | Success |
|---|---|---|---|
GET | /v1/profile | Session | 200 |
GET | /v1/profile/banks | Session | 200 |
POST | /v1/profile/banks | Session | 201 |
GET | /v1/profile/banks/{id} | Session | 200 |
PATCH | /v1/profile/banks/{id} | Session | 200 |
DELETE | /v1/profile/banks/{id} | Session | 200 |
POST | /v1/profile/image/upload-url | Session | 200 |
POST | /v1/profile/image | Session | 200 |
PATCH | /v1/profile/metadata | Session | 200 |
PATCH | /v1/profile/username | Session | 200 |
GET | /v1/user/profiles | Session or scoped key | 200 |
PUT | /v1/user/default-profile | Session or scoped key | 200 |
PATCH | /v1/user/default-profile | Session or scoped key | 200 |
GET /v1/profile#
Returns the current profile with its team membership, team, banks, user summary, verification summary, wallets, and optional image URL.
Headers: x-api-key and Authorization: Bearer <session> (required).
{
"success": true,
"data": {
"profile": {
"id": "profile-uuid",
"nickname": "johndoe",
"metadata": {},
"teamMembers": { "role": "admin" },
"team": { "id": "team-uuid", "name": "John's Team" }
},
"banks": [
{
"bank": { "id": "bank-uuid", "accountAlias": "Main" },
"currency": { "symbol": "USD" }
}
],
"user": {
"country": "US",
"firstName": "John",
"lastName": "Doe",
"language": "en",
"username": "johndoe",
"referralCode": "ABCD1234",
"referralsCount": 0
},
"kyc": { "isVerified": false, "status": "none" },
"wallets": [],
"imageUrl": "/profile/profile-uuid/image-hash"
}
}imageUrl is omitted when no image is configured. A missing profile context returns 400 with APP_V1_PROFILE_ROUTE_01 (Missing profile context). Profile lookup failures use the propagated profile-read error and 404.
Profile banks#
Bank routes use the current session profile. {id} is the bank account ID. A bank can only be fetched, changed, or deleted through the current profile; these routes do not provide a separate team-admin override.
GET /v1/profile/banks#
Headers: x-api-key and Authorization: Bearer <session> (required).
{
"success": true,
"data": [
{
"bank": {
"id": "bank-uuid",
"accountName": "John Doe",
"accountAlias": "Main",
"accountType": "CHECKING",
"accountNumber": "1234567890",
"country": "US",
"bankSlug": "chase",
"additionalData": {}
},
"currency": { "symbol": "USD" }
}
]
}A missing profile context returns 400 with APP_V1_PROFILE_BANKS_ROUTE_02. Read failures return 500 with the propagated error.
POST /v1/profile/banks#
Headers: x-api-key, Authorization: Bearer <session>, and Content-Type: application/json.
| Field | Type | Required | Constraints |
|---|---|---|---|
accountName | string | Yes | Non-empty. |
accountAlias | string | Yes | Non-empty. |
accountType | enum | Yes | CHECKING, SAVINGS, BUSINESS, CURRENT, MONEY_MARKET. |
accountNumber | string | Yes | Non-empty. |
country | string | Yes | Exactly two characters. |
currencySymbol | string | Yes | Non-empty and must resolve to a supported currency. |
bankSlug | string | Yes | Non-empty. |
additionalData | object | No | Defaults to {} and is validated against the selected country’s bank-field schema. |
Returns 201 with the created bank account in data.
{
"success": true,
"data": {
"id": "bank-uuid",
"accountName": "John Doe",
"accountAlias": "Main",
"accountType": "CHECKING",
"accountNumber": "1234567890",
"country": "US",
"bankSlug": "chase",
"additionalData": {}
}
}Malformed bodies, missing context, and unsupported currency symbols return 400; missing context and invalid currency symbol currently share route code APP_V1_PROFILE_BANKS_ROUTE_01. Creation failures return 500 with the propagated error.
GET /v1/profile/banks/{id}#
Returns 200 with the matching bank account in data.
| Status | Code | Message |
|---|---|---|
400 | APP_V1_PROFILE_BANKS_ID_ROUTE_01 | Missing profile or team / Missing bank ID |
404 | APP_V1_PROFILE_BANKS_ID_ROUTE_01 | Bank account not found |
500 | propagated | Bank lookup failed |
PATCH /v1/profile/banks/{id}#
| Field | Type | Required | Constraints |
|---|---|---|---|
accountName | string | No | Non-empty if provided. |
accountAlias | string | No | Non-empty if provided. |
Returns 200 with the updated bank account. Missing context, path ID, or an invalid body returns 400. An update failure is returned as 404 with its propagated error.
DELETE /v1/profile/banks/{id}#
There is no request body.
{
"success": true,
"data": { "deleted": true }
}Missing context or path ID returns 400 with APP_V1_PROFILE_BANKS_ID_ROUTE_01. Deletion failures are returned as 403 with the propagated error.
Profile image#
Both routes require x-api-key, Authorization: Bearer <session>, and JSON bodies. 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/profile/image/upload-url#
| Field | Type | Required |
|---|---|---|
fileName | string | Yes |
contentType | allowed MIME enum | Yes |
{
"success": true,
"data": {
"uploadUrl": "https://storage.example/signed-upload",
"filePath": "profile-images/profile-uuid/1760000000000-hash.jpg",
"expiresAt": "2026-07-28T18:00:00.000Z"
}
}Missing context or an invalid body returns 400; route code for missing context is V1_PROFILE_IMAGE_UPLOAD_URL_01. Upload URL generation failures return 500 with V1_PROFILE_IMAGE_UPLOAD_URL_02.
POST /v1/profile/image#
Call after uploading the file to the signed URL.
| Field | Type | Required |
|---|---|---|
filePath | non-empty string | Yes |
{
"success": true,
"data": { "imageUrl": "/profile/profile-uuid/image-hash" }
}Missing context or an invalid body returns 400; route code for missing context is V1_PROFILE_IMAGE_CONFIRM_01. Update failures return 500 with V1_PROFILE_IMAGE_CONFIRM_02.
PATCH /v1/profile/metadata#
Merges fields into current profile metadata.
Headers: x-api-key, Authorization: Bearer <session>, and Content-Type: application/json.
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | No | |
image | string | No | Valid URL. |
| any other key | any JSON value | No | Custom metadata is accepted. |
Returns 200 with the updated profile object plus optional imageUrl.
{
"success": true,
"data": {
"id": "profile-uuid",
"nickname": "johndoe",
"metadata": { "name": "John Doe", "customField": "value" },
"imageUrl": "/profile/profile-uuid/image-hash"
}
}imageUrl is omitted unless stored profile-image metadata is present. Missing context returns 400 with APP_V1_PROFILE_METADATA_ROUTE_01; malformed bodies return 400; update failures return 500.
PATCH /v1/profile/username#
Headers: x-api-key, Authorization: Bearer <session>, and Content-Type: application/json.
| Field | Type | Required | Constraints |
|---|---|---|---|
nickname | string | Yes | 4–50 characters; letters, digits, _, and -; cannot start with api_. |
Returns 200 with the updated profile. Missing context returns 400 with APP_V1_PROFILE_USERNAME_ROUTE_01; malformed bodies return 400; any nickname update failure, including a conflict, is returned as 409 with the propagated error.
GET /v1/user/profiles#
Returns all profiles belonging to the context profile’s user.
Headers
| Header | Required |
|---|---|
x-api-key | Yes |
Authorization | Required unless the API key is already team/profile-scoped |
x-profile-id and x-team-id do not establish context on this route.
{
"success": true,
"data": {
"profiles": [
{
"id": "profile-uuid",
"teamId": "team-uuid",
"teamName": "John's Team",
"appId": "app-uuid",
"appName": "Example App",
"nickname": "johndoe",
"isDefault": true
}
]
}
}Missing context returns 400 with APP_V1_USER_PROFILES_ROUTE_01 (Profile and team context required). Invalid ownership/context lookup returns 403; list failures return 500.
PUT or PATCH /v1/user/default-profile#
PATCH is an alias of PUT; both have the same contract. The target profile must belong to the same user as the active context profile.
Headers
| Header | Required |
|---|---|
x-api-key | Yes |
Authorization | Required unless the API key is already team/profile-scoped |
x-profile-id and x-team-id do not establish context on this route.
| Field | Type | Required | Constraints |
|---|---|---|---|
profileId | string | Yes | UUID. |
{
"success": true,
"data": { "defaultProfileId": "profile-uuid" }
}| Status | Code | Message |
|---|---|---|
400 | APP_V1_USER_DEFAULT_PROFILE_ROUTE_01 | Profile and team context required |
400 | body parser code | Invalid body |
404 | APP_V1_USER_DEFAULT_PROFILE_ROUTE_02 | Profile not found |
403 | APP_V1_USER_DEFAULT_PROFILE_ROUTE_03 | Profile does not belong to you |
403 | propagated | Active context is invalid |
500 | propagated | Target lookup or update failed |