# User Management

Users are at the center of everything you build with Tendar. Before you can run a credit check, disburse a loan, or collect a repayment, the person on the other end needs to exist as a user inside the Onboarding Service. This guide walks you through the complete lifecycle of a user - from creation to verification to bulk operations - So you can integrate with Tendar seamlessly.

### Before you begin

***

Make sure you have:

* Your **secret key** from the [Tendar dashboard](https://app.tendar.co/services?service-level=api-keys-and-webhooks).
* A server-side environment to make API calls (never expose your secret key in client-side code).
* All requests must include the `Authorization` header:

```http
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx
```

Every user you create is automatically scoped to your company. You will never see another company's data, and they will never see yours.

### The user object

***

When you create a user, Tendar stores a profile with the fields below. Understanding this object helps you decide what to collect from your customers and what Tendar computes automatically.

| Field                 | Type      | Description                                                                                       |
| --------------------- | --------- | ------------------------------------------------------------------------------------------------- |
| `id`                  | string    | Internal Tendar ID. Useful for internal references.                                               |
| `user_id`             | string    | A unique identifier for the user. You can supply your own or let Tendar generate one (`usr-xxx`). |
| `first_name`          | string    | User's first name.                                                                                |
| `last_name`           | string    | User's last name.                                                                                 |
| `middle_name`         | string    | User's middle name (optional).                                                                    |
| `email`               | string    | Email address. Must be a valid email format. Unique per role within your company.                 |
| `phone`               | string    | Phone number in **E.164** international format, e.g. `+2348012345678`.                            |
| `date_of_birth`       | string    | Date of birth in `DD/MM/YYYY` format. Tendar auto-calculates the `age` field from this.           |
| `gender`              | string    | One of `male`, `female`, or `others`.                                                             |
| `address`             | string    | Full address.                                                                                     |
| `country`             | string    | Country name in lowercase, e.g. `nigeria`. Tendar auto-derives the `country_code` (e.g. `NG`).    |
| `state`               | string    | State of residence.                                                                               |
| `city`                | string    | City of residence.                                                                                |
| `country_code`        | string    | *Auto-generated.* Two-letter country code derived from `country`.                                 |
| `image`               | string    | URL to the user's profile image (optional).                                                       |
| `employment_status`   | string    | One of `employed`, `self-employed`, `contract-worker`, `business-owner`, `student`, `unemployed`. |
| `years_of_employment` | integer   | Number of years the user has been employed (optional).                                            |
| `email_verified`      | boolean   | Whether the email has been verified through Tendar's verification flow.                           |
| `phone_verified`      | boolean   | Whether the phone number has been verified.                                                       |
| `active`              | boolean   | Whether the user is active. Defaults to `true`.                                                   |
| `role`                | string    | One of `customer`, `merchant`, or `admin`. Defaults to `customer`.                                |
| `age`                 | integer   | *Auto-calculated* from `date_of_birth`.                                                           |
| `metadata`            | object    | A flexible key-value store for any custom data you want to attach to the user.                    |
| `created_at`          | timestamp | When the user was created.                                                                        |
| `updated_at`          | timestamp | When the user was last updated.                                                                   |

{% hint style="info" %}
The `metadata` field is a great place to store your platform's own internal IDs, tags, or any custom attributes that don't map to a standard Tendar field. It accepts any valid JSON object.
{% endhint %}

### Create a user

***

Creating a user is the first step of every onboarding flow. Send the user's profile information to the API, and Tendar returns a full user object with a `user_id` that you'll reference across every other service.

**Endpoint**

```http
POST /api/v1/user/create
```

**Request body**

```json
{
  "user_id": "",
  "first_name": "John",
  "last_name": "Doe",
  "middle_name": "",
  "email": "johndoe@example.com",
  "phone": "+2348012345678",
  "date_of_birth": "01/01/1990",
  "gender": "male",
  "country": "nigeria",
  "state": "lagos",
  "city": "ikeja",
  "address": "No 1, John Doe Street, Ikeja, Lagos",
  "employment_status": "employed",
  "active": true,
  "metadata": {},
  "role": "customer"
}
```

**A few things to know:**

* **`user_id`** - If you leave this empty, Tendar generates a unique ID like `usr-lyfriztafj`. If you already have your own user identifier (from your database, for example), pass it here and Tendar will use it instead. This makes it easy to correlate Tendar users with your existing records.
* **Duplicate handling -** If a user with the same `email` (and the same `role`) already exists under your company, Tendar returns the existing user instead of creating a duplicate. If you pass a custom `user_id` and a user with that email already exists, Tendar updates the existing user's `user_id` to the one you provided and returns the user. This makes the endpoint idempotent and safe to retry.
* **Auto-computed fields** - You don't need to calculate `age` or `country_code`. Tendar derives `age` from the `date_of_birth` and `country_code` from the `country` name automatically.
* **Billing** - Creating a user with the `customer` role is a billable action. Tendar charges your wallet based on your subscription's pricing plan. Make sure your wallet has sufficient balance.

**Response** `200 OK`

```json
{
  "data": {
    "id": "67ad212f3d5b0234cbb53527",
    "user_id": "usr-lyfriztafj",
    "first_name": "John",
    "last_name": "Doe",
    "middle_name": "",
    "email": "johndoe@example.com",
    "phone": "+2348012345678",
    "date_of_birth": "01/01/1990",
    "gender": "male",
    "country": "nigeria",
    "state": "lagos",
    "city": "ikeja",
    "country_code": "NG",
    "address": "No 1, John Doe Street, Ikeja, Lagos",
    "image": "",
    "employment_status": "employed",
    "years_of_employment": 0,
    "email_verified": false,
    "phone_verified": false,
    "active": true,
    "role": "customer",
    "age": 35,
    "metadata": {},
    "created_at": "2025-02-12T22:31:11.619Z",
    "updated_at": "2025-02-12T22:31:11.619Z"
  },
  "error": false,
  "message": "User created successfully"
}
```

Notice that `email_verified` and `phone_verified` are both `false`. The user exists, but their contact details haven't been confirmed yet. The next section shows you how to change that.

### Verify a user's email

***

Email verification is a two-step process: you ask Tendar to send a verification code to the user's email, and then you submit the code the user provides back to Tendar.

{% stepper %}
{% step %}

#### Send the verification code

```http
GET /api/v1/user/email-verification/send/:user_id
```

Replace `:user_id` with the user's `user_id` (e.g. `usr-lyfriztafj`). Tendar sends a 6-digit code to the email address on file. The code expires after **10 minutes**.

**Response**

```json
{
  "error": false,
  "message": "Email verification token sent successfully"
}
```

> If the user's email is already verified, the API returns a `400` error with the message `"email already verified"`.
> {% endstep %}

{% step %}

#### Submit the code

```http
POST /api/v1/user/email-verification/verify/:user_id
```

**Request body**

```json
{
  "token": "442974"
}
```

**Response**

```json
{
  "error": false,
  "message": "Email verified successfully"
}
```

Once verified, the user's `email_verified` field flips to `true`. This is a one-time action — subsequent calls to the send endpoint for the same user will return an error since the email is already verified.
{% endstep %}
{% endstepper %}

### Verify a user's phone number

***

Phone verification works exactly like email verification, but the code is delivered via SMS instead.

{% stepper %}
{% step %}

#### Send the verification code

```http
GET /api/v1/user/phone-verification/send/:user_id
```

Tendar sends a 6-digit code to the user's phone number. The code expires after **10 minutes**.

**Response**

```json
{
  "error": false,
  "message": "Phone verification token sent successfully"
}
```

> The user must have a phone number on file. If the `phone` field is empty, the API returns a `400` error with the message `"phone number not found for this user"`.
> {% endstep %}

{% step %}

#### Submit the code

```http
POST /api/v1/user/phone-verification/verify/:user_id
```

**Request body**

```json
{
  "token": "198694"
}
```

**Response**

```json
{
  "error": false,
  "message": "Phone verified successfully"
}
```

> **Billing note:** Both email and phone verification OTPs are billable based on your subscription's pricing plan (email OTP and SMS OTP are priced separately).
> {% endstep %}
> {% endstepper %}

### Fetch a user

***

You can retrieve a single user by their `user_id` or `email`. This is useful for displaying user details in your dashboard, or for confirming a user exists before performing other operations.

```http
GET /api/v1/user/fetch/:email_or_id
```

The `:email_or_id` parameter is flexible — pass either the user's email address or their `user_id`.

**Example — fetch by user ID:**

```http
GET /api/v1/user/fetch/usr-lyfriztafj
```

**Example — fetch by email:**

```http
GET /api/v1/user/fetch/johndoe@example.com
```

**Response** `200 OK`

```json
{
  "data": {
    "id": "67ad212f3d5b0234cbb53527",
    "user_id": "usr-lyfriztafj",
    "first_name": "John",
    "last_name": "Doe",
    "email": "johndoe@example.com",
    "phone": "+2348012345678",
    "date_of_birth": "01/01/1990",
    "gender": "male",
    "country": "nigeria",
    "state": "lagos",
    "city": "ikeja",
    "country_code": "NG",
    "email_verified": true,
    "phone_verified": false,
    "active": true,
    "role": "customer",
    "age": 35,
    "metadata": {},
    "created_at": "2025-02-12T22:31:11.619Z",
    "updated_at": "2025-02-12T22:31:11.619Z"
  },
  "error": false,
  "message": "User fetched successfully"
}
```

If no user is found, the API responds with a `404` and the message `"user not found"`.

### List users

***

When you need to display a table of users in your dashboard or search through your user base, the list endpoint gives you paginated results with powerful filtering and sorting.

```http
GET /api/v1/user/list
```

### Query parameters

| Parameter         | Type    | Description                                                                                      |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------ |
| `page`            | integer | Page number. Defaults to `1`.                                                                    |
| `limit`           | integer | Number of results per page. Defaults to `50`.                                                    |
| `search`          | string  | Free-text search across user fields.                                                             |
| `active`          | boolean | Filter by active status (`true` or `false`).                                                     |
| `role`            | string  | Filter by role (`customer`, `merchant`, `admin`).                                                |
| `country`         | string  | Filter by country.                                                                               |
| `email_verified`  | boolean | Filter by email verification status.                                                             |
| `phone_verified`  | boolean | Filter by phone verification status.                                                             |
| `created_at`      | string  | Date range filter in `DD-MM-YYYY/DD-MM-YYYY` format, e.g. `01-03-2024/29-03-2024`.               |
| `sort-created_at` | string  | Sort by creation date. Use `asc` for oldest first, `desc` (or any other value) for newest first. |

**Example — list active customers, page 1, 20 per page, sorted newest first:**

```http
GET /api/v1/user/list?page=1&limit=20&active=true&sort-created_at=desc
```

**Response** `200 OK`

```json
{
  "data": {
    "total": 142,
    "page": 1,
    "per_page": 20,
    "prev": 0,
    "next": 2,
    "total_page": 8,
    "docs": [
      {
        "user_id": "usr-lyfriztafj",
        "first_name": "John",
        "last_name": "Doe",
        "email": "johndoe@example.com",
        "active": true,
        "..."
      }
    ]
  },
  "error": false,
  "message": "Users fetched successfully"
}
```

The `docs` array contains the user objects for the current page. Use the `next` field to determine if there are more pages — a value of `0` means you're on the last page.

### Edit a user

***

User profiles change over time — people move, change phone numbers, or update their employment status. The edit endpoint lets you update any combination of fields in a single request. Only include the fields you want to change; everything else stays as-is.

```http
PUT /api/v1/user/edit/:user_id
```

**Request body** — only the fields you want to update:

```json
{
  "first_name": "John",
  "last_name": "Doe",
  "middle_name": "Michael",
  "phone": "+2348012345678",
  "employment_status": "self-employed",
  "metadata": {
    "internal_tier": "gold"
  }
}
```

**A few things to know:**

* **Email uniqueness** — If you update the `email` field, Tendar checks that no other user with the same role under your company already uses that email. If they do, you'll get a `400` error.
* **Auto-recomputed fields** — If you update `date_of_birth`, the `age` field is recalculated. If you update `country`, the `country_code` is re-derived.
* **User not found** — If the `user_id` doesn't match any user under your company, the API returns a `404`.

**Response** `200 OK`

```json
{
  "data": {
    "user_id": "usr-lyfriztafj",
    "first_name": "John",
    "last_name": "Doe",
    "middle_name": "Michael",
    "employment_status": "self-employed",
    "metadata": {
      "internal_tier": "gold"
    },
    "..."
  },
  "error": false,
  "message": "User updated successfully"
}
```

### Bulk create users

***

If you're migrating an existing user base into Tendar, or you need to onboard a batch of customers at once, the bulk create endpoint lets you create up to **1,000 users** in a single request.

```http
POST /api/v1/user/bulk/create
```

#### Option 1 — JSON body

Send an array of user objects in the request body:

```json
{
  "users": [
    {
      "first_name": "John",
      "last_name": "Doe",
      "email": "johndoe+1@example.com",
      "phone": "+2348012345678",
      "date_of_birth": "01/01/1990",
      "gender": "male",
      "country": "nigeria",
      "state": "lagos",
      "city": "ikeja",
      "address": "No 1, John Doe Street",
      "employment_status": "employed",
      "active": true,
      "metadata": {}
    },
    {
      "first_name": "Jane",
      "last_name": "Smith",
      "email": "janesmith@example.com",
      "phone": "+2349087654321",
      "date_of_birth": "15/06/1995",
      "gender": "female",
      "country": "nigeria",
      "state": "abuja",
      "city": "garki",
      "address": "Plot 5, Garki Area",
      "employment_status": "self-employed",
      "active": true,
      "metadata": {}
    }
  ]
}
```

#### Option 2 — CSV upload

You can also upload a CSV file using `multipart/form-data`. The file should be attached under the field name `csv`. To see the expected column format, download the [sample CSV](#bulk-create-users) first.

#### How bulk creation works

1. Tendar validates every user in the batch.
2. A background import job is created with a status of `processing`.
3. Users whose email or `user_id` already exist in your account are **silently skipped** — they won't cause the whole batch to fail.
4. New users are inserted in bulk.
5. Once complete, the import status moves to `completed`.

> **Billing:** Each new user created is charged individually based on your plan's `customer.create` pricing. If your batch has 500 users but 100 already exist, you're only billed for the 400 new ones.

**Response** `200 OK`

```json
{
  "error": false,
  "message": "Users created successfully"
}
```

> Only one bulk import can run at a time. If a previous import is still `processing`, the API returns a `400` error with the message `"upload is processing"`.

### Export users

***

Need to pull your user data into a spreadsheet or feed it into another system? The export endpoint generates a CSV file, uploads it to cloud storage, and returns a temporary download link.

```http
GET /api/v1/user/export
```

### Query parameters

You can narrow down the export using the same filters available on the [list endpoint](#list-users):

| Parameter         | Type    | Description                                   |
| ----------------- | ------- | --------------------------------------------- |
| `limit`           | integer | Maximum number of users to export.            |
| `active`          | boolean | Filter by active status.                      |
| `created_at`      | string  | Date range in `DD-MM-YYYY/DD-MM-YYYY` format. |
| `sort-created_at` | string  | Sort by creation date (`asc` or `desc`).      |

**Example — export all active users created in March 2024:**

```http
GET /api/v1/user/export?active=true&created_at=01-03-2024/31-03-2024
```

**Response** `200 OK`

```json
{
  "data": {
    "path": "https://tendar.s3.us-east-2.amazonaws.com/yourcompany_users_1719362941",
    "expires_at": "2025-02-12T22:50:01.756Z"
  },
  "error": false,
  "message": "Users exported successfully"
}
```

The `path` is a pre-signed URL that you can use to download the CSV file. It's temporary — the `expires_at` timestamp tells you when the link becomes invalid (typically within a few minutes).

The exported CSV includes these columns:

`user_id`, `first_name`, `last_name`, `middle_name`, `email`, `phone`, `date_of_birth`, `gender`, `address`, `country`, `state`, `city`, `country_code`, `image`, `employment_status`, `years_of_employment`, `email_verified`, `phone_verified`, `active`, `age`, `metadata`, `created_at`, `updated_at`

### Download a sample CSV

***

If you plan to use the CSV upload option for bulk creation, start by downloading the sample template. It shows you exactly which columns are expected and includes an example row.

```http
GET /api/v1/user/sample-csv
```

**Response** `200 OK`

```json
{
  "data": {
    "path": "https://tendar.s3.us-east-2.amazonaws.com/sample_users_1719362952",
    "expires_at": "2025-02-12T22:50:12.818Z"
  },
  "error": false,
  "message": "Sample csv fetched successfully"
}
```

The sample CSV contains these columns:

`user_id`, `first_name`, `last_name`, `middle_name`, `email`, `phone`, `date_of_birth`, `gender`, `image`, `country`, `state`, `city`, `address`, `employment_status`, `active`

Fill in your data following the same format and upload it via the [bulk create](#bulk-create-users) endpoint.

### Putting it all together

***

A typical onboarding integration follows this flow:

```
1. Create user  ──▶  2. Verify email  ──▶  3. Verify phone  ──▶  4. Run KYC  ──▶  5. Ready
        │                                                             │
        │                                                             ▼
        │                                                    (BVN, NIN, etc.)
        ▼
   User exists in
   Tendar system
```

Here's how that looks end-to-end in code:

```bash
# 1. Create the user
curl -X POST {{base_url}}/api/v1/user/create \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Amina",
    "last_name": "Bello",
    "email": "amina.bello@example.com",
    "phone": "+2348099887766",
    "date_of_birth": "22/03/1995",
    "gender": "female",
    "country": "nigeria",
    "state": "abuja",
    "city": "wuse",
    "address": "12 Wuse Zone 3, Abuja",
    "employment_status": "employed",
    "active": true,
    "metadata": { "source": "mobile_app" }
  }'
# Save the returned user_id — e.g. "usr-abcxyz1234"

# 2. Send email verification
curl -X GET {{base_url}}/api/v1/user/email-verification/send/usr-abcxyz1234 \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx"

# 3. User receives the code and enters it in your app
curl -X POST {{base_url}}/api/v1/user/email-verification/verify/usr-abcxyz1234 \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "token": "583921" }'

# 4. Send phone verification
curl -X GET {{base_url}}/api/v1/user/phone-verification/send/usr-abcxyz1234 \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx"

# 5. User receives the SMS code and enters it in your app
curl -X POST {{base_url}}/api/v1/user/phone-verification/verify/usr-abcxyz1234 \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "token": "174582" }'

# The user is now fully onboarded and ready for KYC, credit scoring, loans, etc.
```

### Error handling

***

User endpoints can return several types of errors. Here are the most common:

| Scenario                                                   | HTTP Status | Message                                           |
| ---------------------------------------------------------- | ----------- | ------------------------------------------------- |
| User with the given `user_id` already exists               | 400         | `"user already exists"`                           |
| Email already in use by another user (on edit)             | 400         | `"email already exists"`                          |
| Email verification requested but email is already verified | 400         | `"email already verified"`                        |
| Phone verification requested but phone is already verified | 400         | `"phone already verified"`                        |
| Phone verification requested but user has no phone number  | 400         | `"phone number not found for this user"`          |
| A bulk import is already running                           | 400         | `"upload is processing"`                          |
| Uploaded file is not a CSV during bulk import              | 400         | `"invalid file type, only csv files are allowed"` |
| Invalid or expired OTP token during verification           | 400         | `"invalid token"`                                 |
| Insufficient wallet balance                                | 400         | Charge-related error                              |
| Missing required field                                     | 400         | Validation error specifying the missing field     |
| User not found (invalid `user_id` or email)                | 404         | `"user not found"`                                |

All errors follow the standard Tendar error format:

```json
{
  "error": true,
  "message": "A description of what went wrong"
}
```

### Next steps

***

Now that your users are created and verified, you're ready to explore the rest of the Onboarding Service:

* [OTP](https://docs.tendar.co/documentation/onboarding/one-time-password) — Send custom one-time passwords for transaction confirmations or any verification flow.
* [KYC](https://docs.tendar.co/documentation/onboarding/know-your-customer) — Identity Verification — Verify user identities against BVN, NIN, driver's license, passport, and more.
* [UID Management](https://docs.tendar.co/documentation/know-your-customer#uid-management) — Manage and verify identity documents after a KYC lookup.

Explore other Tendar services:

* [**Credit Score Service**](https://docs.tendar.co/documentation/credit-scoring) — Calculate risk scores and credit reports for your users.
* [**Disbursement Service**](https://docs.tendar.co/documentation/disbursement/disbursement) — Create and manage loans, disburse funds, and track repayments.
* [**Recollection Service**](https://docs.tendar.co/documentation/recollection/recollection) — Automate repayment collection from your borrowers.
