Users
Manage users, update user information, and order cards
Overview
The Users endpoints allow you to create new users, update existing user information, and order cards for users. This guide applies to both agentic payments and white label integrations. All endpoints require authentication using a Bearer token obtained from the authentication endpoint.
Authorization: Bearer {access_token} header and the x-appid: {clientId} header in your requests.
mobile) and a physical address (address, a single-line string) on the user record before you call order-card. Create the user with both fields set, or update the user with PATCH if they were missing. Omitting either can cause issuance or compliance checks to fail.
Create User
Creates a new user in the system. The user will be associated with the company specified in the request.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
firstname |
string | Yes | User's first name (max 50 characters) |
lastname |
string | Yes | User's last name (max 50 characters) |
email |
string | Yes | User's email address (must be unique within the company) |
companyId |
string | Yes | The ID of the company this user belongs to |
active |
boolean | No | Whether the user is active (default: false). Set to true to skip activation step. |
reason |
string | No | User status reason. Use "active" for active users. |
emailVerified |
boolean | No | Whether the email has been verified (default: false) |
mobile |
string | Yes | User's mobile phone number (E.164 or format accepted by your environment). Required for card-issuance flows. |
address |
string | Yes | Single-line postal or residential address (street, city, postal code, country as your program requires). Required for card-issuance flows. You may also send structured billingAddress where your program supports it; at minimum, populate address for ordering cards. |
employeeId |
string | No | Employee ID or external identifier |
Example Request
Using cURL:
curl -X POST https://sandbox.custodia-tech.com/md/api/User \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"firstname": "John",
"lastname": "Doe",
"email": "john.doe@example.com",
"companyId": "your-company-id",
"mobile": "+15551234567",
"address": "123 Example Street, Example City, 10001, US",
"active": true,
"reason": "active",
"emailVerified": true
}'
Using JavaScript (fetch):
const response = await fetch('https://sandbox.custodia-tech.com/md/api/User', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
firstname: 'John',
lastname: 'Doe',
email: 'john.doe@example.com',
companyId: 'your-company-id',
mobile: '+15551234567',
address: '123 Example Street, Example City, 10001, US',
active: true,
reason: 'active',
emailVerified: true
})
});
const user = await response.json();
Response
Success Response (200 OK)
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"firstname": "John",
"lastname": "Doe",
"email": "john.doe@example.com",
"companyId": "your-company-id",
"mobile": "+15551234567",
"address": "123 Example Street, Example City, 10001, US",
"active": true,
"reason": "active",
"emailVerified": true,
"created": "2024-01-15T10:30:00.000Z",
"modified": "2024-01-15T10:30:00.000Z"
}
Update User
Updates an existing user's information. Only include the fields you want to update in the request body. Use PATCH to add or correct mobile and address before ordering a card if either was missing at create time.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
userId |
string | Yes | The unique identifier of the user to update |
Request Body
Include only the fields you want to update. All fields are optional.
| Parameter | Type | Description |
|---|---|---|
firstname |
string | User's first name |
lastname |
string | User's last name |
email |
string | User's email address |
mobile |
string | User's mobile phone number. Set before order-card if absent. |
address |
string | Single-line postal or residential address. Set before order-card if absent. |
employeeId |
string | Employee ID or external identifier |
office |
string | Office phone number |
extension |
string | Phone extension |
Example Request
Using cURL:
curl -X PATCH https://sandbox.custodia-tech.com/md/api/User/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"firstname": "Jane",
"mobile": "+15559876543",
"address": "456 Sample Avenue, Sample City, 10002, US"
}'
Using JavaScript (fetch):
const userId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/User/${userId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
firstname: 'Jane',
mobile: '+15559876543',
address: '456 Sample Avenue, Sample City, 10002, US'
})
});
const updatedUser = await response.json();
Response
Success Response (200 OK)
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"firstname": "Jane",
"lastname": "Doe",
"email": "john.doe@example.com",
"mobile": "+15559876543",
"address": "456 Sample Avenue, Sample City, 10002, US",
"modified": "2024-01-15T11:00:00.000Z"
}
Search User Cards
Searches users and returns card status data for each matched user. This endpoint is useful when you need to find users by email or text and inspect their card access in the same request.
/User/cards-status (lowercase, plural). For example, /User/Card-Status will return 404.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
filter |
object | No | User-level filter object. Supports searchTerm, where, limit, offset, and fields. |
cardsFilter |
object | No | Card-level filter object. Supports where, fields, multiple, and otherCardsFilter. |
Common User Filters (filter)
| Field | Type | Required | Example | Description |
|---|---|---|---|---|
searchTerm |
string | No | "user@example.com" |
Free-text search over users. Can be used with email, name, and other searchable user fields. |
where.email |
string/object | No | {"email":"user@example.com"} |
Exact user email match. |
where.companyId |
string | No | {"companyId":"company-id"} |
Optional additional scoping: restrict results to a specific company when you want an explicit company filter. Access is still enforced by your token and session context; omit this when searching within your normal accessible scope. |
limit |
number | No | 50 |
Maximum number of users returned. |
offset |
number | No | 0 |
Pagination offset. |
fields |
object | No | {"id":true,"email":true} |
Reduce returned user fields. The top-level object remains a user object. |
Common Card Filters (cardsFilter)
| Field | Type | Example | Description |
|---|---|---|---|
where.state |
object | {"state":{"inq":["ACTIVE"]}} |
Filter cards by status. |
where.cardProductId |
string/object | {"cardProductId":"your card product Id"} |
Return only cards from a specific card product. |
multiple |
boolean | true |
Return additional cards in user.cards. Keep this at the top level of cardsFilter, not inside where. |
otherCardsFilter |
object | {} |
Optional filter for additional cards when multiple is true. |
fields |
object | {"id":true,"state":true,"cardType":true,"vendor":true} |
Limit returned card fields. |
Example Request
Using cURL:
curl -X POST https://sandbox.custodia-tech.com/md/api/User/cards-status \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"filter": {
"searchTerm": "user@example.com",
"fields": {
"id": true,
"email": true
}
},
"cardsFilter": {
"multiple": true,
"where": {
"state": { "inq": ["ACTIVE"] },
"cardProductId": "your card product Id"
},
"otherCardsFilter": {},
"fields": {
"id": true,
"ownerId": true,
"cardNo": true,
"state": true,
"cardType": true,
"vendor": true,
"cardProductId": true
}
}
}'
Using JavaScript (fetch):
const response = await fetch('https://sandbox.custodia-tech.com/md/api/User/cards-status', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
filter: {
searchTerm: 'user@example.com',
fields: {
id: true,
email: true
}
},
cardsFilter: {
multiple: true,
where: {
state: {inq: ['ACTIVE']},
cardProductId: 'your card product Id'
},
otherCardsFilter: {},
fields: {
id: true,
ownerId: true,
cardNo: true,
state: true,
cardType: true,
vendor: true,
cardProductId: true
}
}
})
});
const usersWithCards = await response.json();
Response
Success Response (200 OK)
[
{
"id": "297e880a-07f6-4817-b7f0-c9d172329748",
"email": "user@example.com",
"card": {
"cardId": "2358fb0c-1298-4300-b596-4aef822b2959",
"ownerId": "297e880a-07f6-4817-b7f0-c9d172329748",
"cardNo": "6221",
"state": "ACTIVE",
"cardType": "virtual",
"vendor": "card vendor"
},
"cards": [
{
"cardId": "35b07f7e-244f-4f69-a3a6-1a58be6f61b1",
"ownerId": "297e880a-07f6-4817-b7f0-c9d172329748",
"cardNo": "1044",
"state": "ACTIVE",
"cardType": "physical",
"vendor": "card vendor"
}
]
}
]
card and optional additional cards array. If your integration needs one unified array, normalize it in your client code (for example: combine card + cards by card id).
Order Card for User
Orders a new card for a user. The card will be associated with a card product. If no card product is specified, the system will use the default card product for the user's company.
DEFAULT and f:Card/OrderCards in the authenticate request scope array. See Card issuance (authentication and scopes).
mobile (phone number) and address (single-line address) populated. See Overview and Create User / Update User.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
userId |
string | Yes | The unique identifier of the user to order a card for |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
cardProductId |
string | No | The ID of the card product to use. If not provided, the default card product for the company will be used. |
instructions |
object | No | Additional instructions for card ordering. See below for available options. |
Instructions Object
The instructions object can contain the following optional fields:
| Parameter | Type | Description |
|---|---|---|
address2use |
string | Address to use for shipping. Options: "user" (user's address), "business" (company address) |
expedite |
boolean | Whether to expedite the card shipment (if supported by the card provider) |
Example Request
Using cURL:
curl -X POST https://sandbox.custodia-tech.com/md/api/User/550e8400-e29b-41d4-a716-446655440000/order-card \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"cardProductId": "card-product-id",
"instructions": {
"address2use": "user",
"expedite": false
}
}'
Using JavaScript (fetch):
const userId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/User/${userId}/order-card`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
cardProductId: 'card-product-id',
instructions: {
address2use: 'user',
expedite: false
}
})
});
const card = await response.json();
Ordering with default card product (no cardProductId):
Using JavaScript (fetch):
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/User/${userId}/order-card`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
instructions: {
address2use: 'user'
}
})
});
const cardDefaultProduct = await response.json();
Response
Success Response (200 OK)
{
"id": "card-id-123",
"ownerId": "550e8400-e29b-41d4-a716-446655440000",
"cardProductId": "card-product-id",
"cardNo": "****1234",
"state": "UNACTIVATED",
"cardType": "physical",
"created": "2024-01-15T12:00:00.000Z"
}
Terminate Card
Terminates (cancels) a card for a user. Once terminated, the card cannot be used for transactions. This action is typically irreversible.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
userId |
string | Yes | The unique identifier of the user who owns the card |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
pan |
string | Yes | The last 4 digits of the card number (Primary Account Number). This is used to identify the specific card to terminate. |
reasonCode |
string | Yes | A code indicating the reason for termination. Common values: "01" (requested by user/admin), "09" (card reissued), etc. |
issuer |
string | No | The card issuer/vendor. Defaults to "card issuer" if not provided. Other common values include "icc"; additional values may apply for your program. |
reason |
string | No | A human-readable description of why the card is being terminated (e.g., "Lost card", "Requested by admin", "Card reissued") |
Example Request
Using cURL:
curl -X POST https://sandbox.custodia-tech.com/md/api/User/550e8400-e29b-41d4-a716-446655440000/terminate-card \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"pan": "1234",
"reasonCode": "01",
"reason": "Card lost - requested by user",
"issuer": "card issuer"
}'
Using JavaScript (fetch):
const userId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/User/${userId}/terminate-card`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
pan: '1234',
reasonCode: '01',
reason: 'Card lost - requested by user',
issuer: 'card issuer'
})
});
const terminatedCard = await response.json();
Terminating with default issuer (card issuer):
Using JavaScript (fetch):
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/User/${userId}/terminate-card`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
pan: '1234',
reasonCode: '01',
reason: 'Requested by admin'
})
});
const terminatedCardDefaultIssuer = await response.json();
Response
Success Response (200 OK)
{
"id": "card-id-123",
"ownerId": "550e8400-e29b-41d4-a716-446655440000",
"cardNo": "****1234",
"state": "TERMINATED",
"cardType": "physical",
"vendor": "card vendor",
"terminatedOn": "2024-01-15T14:00:00.000Z"
}
pan parameter should be the last 4 digits of the card number. You can retrieve a user's cards to get the card number information before terminating. For ICC cards, use the disassociate-card endpoint instead.
Error Responses
All endpoints may return the following error responses:
| Status Code | Description | Solution |
|---|---|---|
401 Unauthorized |
Invalid or missing access token | Verify your access token is valid and included in the Authorization header |
400 Bad Request |
Invalid request parameters or missing required fields | Check that all required fields are included and have valid values |
404 Not Found |
User not found (for update/order card/terminate card endpoints) or card not found | Verify the user ID and card PAN are correct |
409 Conflict |
User with this email already exists (create user) | Use a different email address or update the existing user |
403 Forbidden |
Insufficient permissions or feature not available | Verify your access token has the required scopes and permissions |
INVALID-USER-STATE error. When terminating a card, make sure the PAN (last 4 digits) matches the card you want to terminate.