# Get bulk update job details

Read the row-by-row result of a finished bulk update job, including which rows failed and why.

- Endpoint: `GET https://api.returning.ai/v1/users/bulk-update/{id}/details`
- Section: Users and data / Bulk operations
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `getBulkUpdate` (Shown in the dashboard as "Get Bulk Update History")
- Retries: Read-only; exact retries are safe
- Guide: hand-written
- Verified: live, 2026-09-27
- Last updated: 26 Sep 2026
- Web page: https://docs.returning.ai/api-reference/bulk-operations/get-bulk-update-job-details

## When to use this

- After a CSV upload, find the traders whose rows failed and the reason for each.
- Build a correction file that holds only the failed rows, fixed, for a new upload.
- Reconcile a nightly feed by matching each row's result back to your own records.

**Instead:** Use [Get bulk update job status](https://docs.returning.ai/api-reference/bulk-operations/get-bulk-update-job-status.md) instead to check whether the job has finished.

**Wait for Completed:** Until a job is `Completed`, and for any `Failed` job, this endpoint lists the rows of the file you uploaded and marks every one `success`. Check the job status first.

## Authentication

- Header: `Authorization: Bearer <API_KEY>`
- Permission: `getBulkUpdate`

This endpoint needs `getBulkUpdate`, shown in the dashboard as Get Bulk Update History. Use a Community API key and keep it on your server. The key decides the community, so you can only read jobs from that community.

## Behaviour

Row results exist only once a job is `Completed`. Until then, and for a `Failed` job, this endpoint reads back the file you uploaded: the rows keep your original column names, every row is `success` with no errors, and `successUsers` counts every row. Check [Get bulk update job status](https://docs.returning.ai/api-reference/bulk-operations/get-bulk-update-job-status.md) first, and read details only when it returns `Completed`.

Rows are not guaranteed to be in file order, so match them to your records by `email` or identifier. `successUsers` and `failedUsers` cover the whole job; `data` holds one page. Keep reading while `hasNextPage` is `true`. A page past the last one returns an empty `data` list.

## Request

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | The job's `_id` from List bulk update jobs. (24 hex characters) |

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | `integer` | No | Page of rows, starting at 1. (Whole number, 1 or more; default 1) |
| `limit` | `integer` | No | Rows per page. (Default 10; max 100) |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key with `getBulkUpdate`. (`Bearer <API_KEY>`) |

### Example request

```bash
curl --request GET \
  --url 'https://api.returning.ai/v1/users/bulk-update/66f000000000000000000701/details?page=1&limit=100' \
  --header 'Authorization: Bearer <API_KEY>'
```

## Response

Each row is one line of your file, with `status` and `errors` added. The cells come back as strings, keyed by the lower-case column name. The standard columns use short keys: `Email` is `email`, `First Name` is `firstname`, `Last Name` is `lastname`, `Phone Number` is `phone`, `Role` is `roles`, `Tag` is `tags`, `Total XP` is `xp` and `Total Coins` is `coin`. Operator and custom field columns are lower-cased, such as `total coins_op` and `kyc status`.

A failed row lists its reasons in `errors`. Branch on `code`:

- `USER_NOT_AVAILABLE`: no trader in your community matches the row's email or identifier.
- `USER_NOT_COMMUNITY_MEMBER`: the account exists but is not a member of your community.
- `REQUIRED_VALUE`: the `Email` cell, or the identifier cell, is empty.
- `DUPLICATE_VALUE`: an earlier row in the file has the same email or identifier. Only the first row is applied.
- `IDENTIFIER_CONFLICT`: the trader already has a different broker customer ID, or another trader has this one.
- `IDENTIFIER_LOOKUP_UNAVAILABLE`: lookup by identifier wasn't available. Upload the row again later.
- `INVALID_OPERATOR`: an `_op` cell is not `add`, `subtract` or `overwrite`.
- `INVALID_VALUE`: a cell failed its check, for example an unknown role, tag or badge, text in a number column, a badly formatted date, or an unknown country or language.
- `ROW_OUTCOME_UNCERTAIN`: the coin change couldn't be checked against your community's rule that traders must visit before earning coins, so the row was not applied. Upload it again later.
- `BULK_ROW_FAILED`: any other failure, for example a trader who hasn't visited your community yet when that rule is on.

Before you upload a failed row again, read the trader back: an error while saving can leave part of a row applied. An ID that matches no job in your community returns `200` with zero counts and no `data` list, not `404`.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `status` | `string` | always | Result of the request. (`success`) |
| `message` | `string` | always | Human-readable summary. Do not branch on it. |
| `data` | `object` | always | Row counts for the whole job, paging, and this page of rows. |
| `data.successUsers` | `integer` | always | Rows in the whole job with no errors. |
| `data.failedUsers` | `integer` | always | Rows in the whole job with at least one error. |
| `data.pagination` | `object` | always | Paging totals for the job's rows. |
| `data.pagination.total` | `integer` | - | Rows in the job. `0` when the ID matches no job in your community. |
| `data.pagination.currentPage` | `integer` | - | The page you asked for. |
| `data.pagination.totalPages` | `integer` | - | Pages at this `limit`. |
| `data.pagination.hasNextPage` | `boolean` | - | `true` when a later page has rows. |
| `data.pagination.hasPrevPage` | `boolean` | - | `true` when `page` is above 1. |
| `data.data` | `object[]` | - | This page of rows. Each row repeats its uploaded cells as strings, keyed by lower-case column name. Left out when the ID matches no job. |
| `data.data.email` | `string` | - | The row's `Email` cell. Other cells follow under their own keys, such as `coin` for `Total Coins`. |
| `data.data.status` | `string` | always | `failed` when the row has errors; otherwise `success`, which means no error was recorded for the row. (`success` or `failed`) |
| `data.data.errors` | `object[]` | always | Why the row failed, up to 10 entries. Empty for a `success` row. |
| `data.data.errors.field` | `string` | - | The standard column the error is about, such as `email`, `total_coins` or `role`. `null` for custom field and identifier columns, and when no one column is at fault. |
| `data.data.errors.code` | `string` | - | The reason, as a stable code. See the list under Response. (`USER_NOT_AVAILABLE`, `USER_NOT_COMMUNITY_MEMBER`, `REQUIRED_VALUE`, `DUPLICATE_VALUE`, `IDENTIFIER_CONFLICT`, `IDENTIFIER_LOOKUP_UNAVAILABLE`, `INVALID_OPERATOR`, `INVALID_VALUE`, `ROW_OUTCOME_UNCERTAIN`, `BULK_ROW_FAILED`) |
| `data.data.errors.message` | `string` | - | A fixed description of the code. Do not branch on it. |

### Example response (200)

```json
{
  "status": "success",
  "message": "Bulk update details fetched successfully",
  "data": {
    "successUsers": 1,
    "failedUsers": 1,
    "pagination": {
      "total": 2,
      "currentPage": 1,
      "totalPages": 1,
      "hasNextPage": false,
      "hasPrevPage": false
    },
    "data": [
      {
        "email": "trader@example.com",
        "coin": "500",
        "total coins_op": "add",
        "kyc status": "verified",
        "status": "success",
        "errors": []
      },
      {
        "email": "no.account@example.com",
        "coin": "500",
        "total coins_op": "add",
        "kyc status": "verified",
        "status": "failed",
        "errors": [
          {
            "field": "email",
            "code": "USER_NOT_AVAILABLE",
            "message": "User was not found in the target community"
          }
        ]
      }
    ]
  }
}
```

## Errors

The 401, 403 and 404 errors, and `500 AUTHENTICATION_FAILED`, come from the API key check and carry the code in `meta.code`. Other errors from this endpoint have no `code`, so branch on the HTTP status.

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | - | There is no `code`, so read the body: - `Bulk update ID is required and must be a valid ObjectId`: send the job's 24-character `_id` from List bulk update jobs. - `Get bulk update details endpoint validation error.`: `limit` is over 100, or `page` or `limit` is not a number. `detail` names the field. |
| 401 | `AUTHENTICATION_REQUIRED` | The key is missing, invalid or expired. Send `Authorization: Bearer <API_KEY>` with a current Community API key. |
| 403 | `API_KEY_PERMISSION_DENIED` | The key lacks `getBulkUpdate`, or it is a personal key. Use a Community API key and add the permission in Settings > Integration > API Keys. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `COMMUNITY_NOT_FOUND` | The community this key belongs to no longer exists. Contact Returning.AI support. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 500 | - | The job could not be read. Retry the same request with exponential backoff. |
| 500 | `AUTHENTICATION_FAILED` | The key could not be checked. Retry with backoff; nothing was read. |

**Retries:** This endpoint is read-only, so retrying the exact same request is safe after a network error or a 5xx. Use bounded exponential backoff. Reading details never runs the job again. Over the [rate limit](https://docs.returning.ai/how-it-works.md#rate-limits), requests return `429`; wait for the window to reset, then retry.

## Next step

- [Bulk update users from CSV](https://docs.returning.ai/api-reference/bulk-operations/bulk-update-users-from-csv.md): `POST /v1/users/bulk-update`. Fix the failed rows and upload them as a new file with a new `log_name`. Leave out rows that succeeded, or their `add` and `subtract` changes apply twice.
- [Check a failed row's trader with Get User Data](https://docs.returning.ai/api-reference/users/get-user-data.md): `POST /v1/users/info`.
