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.

Note: All endpoints require proper authentication. Make sure to include the Authorization: Bearer {access_token} header and the x-appid: {clientId} header in your requests.
Phone and address on the user: For integrations that order cards, each user must have a phone number (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

POST /md/api/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

PATCH /md/api/User/{userId}

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

POST /md/api/User/cards-status

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.

Route name is case-sensitive: use /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"
      }
    ]
  }
]
Important response behavior: this endpoint returns a user object with a primary 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

POST /md/api/User/{userId}/order-card

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.

OAuth scopes: Your access token must include DEFAULT and f:Card/OrderCards in the authenticate request scope array. See Card issuance (authentication and scopes).
User record: The user must already have mobile (phone number) and address (single-line address) populated. See Overview and Create User / Update User.
Important: The user must be active and not blocked. The user's company must have an active card product configured.

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

POST /md/api/User/{userId}/terminate-card

Terminates (cancels) a card for a user. Once terminated, the card cannot be used for transactions. This action is typically irreversible.

Important: Terminating a card is a permanent action. The card will be immediately deactivated and cannot be reactivated. Make sure you have the correct card information before proceeding.

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"
}
Note: The 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
Note: When ordering a card, if the user is blocked (inactive, fraud, or system status), the request will fail with an INVALID-USER-STATE error. When terminating a card, make sure the PAN (last 4 digits) matches the card you want to terminate.