# Exports - X-Radius API

> Base URL: https://x-radius.com/api/v1
> Auth: Authorization: Bearer xrt_...  (a manager API token)
> Envelope: {"data": ...}; lists add {"meta":{page,page_size,total,has_next}}
> Errors: {"error":{"code","message","request_id"}} - branch on code, never on message
> Timestamps: yyyy-MM-dd HH:mm:ss, UTC
> Money: a bare JSON number in major units, with an ISO-4217 currency code beside it
> Idempotency: redeem and activate endpoints take a client-supplied request_id (UUID)
>
> This page: https://x-radius.com/docs/api/exports
> Every group: https://x-radius.com/llms.txt

7 endpoints in 1 resource groups. 6 carry a hand-written reference entry with examples; the remaining 1 are generated from the running router and carry method, path, authentication, permission and rate-limit bucket, but no request or response example.

### List export jobs

`GET /api/v1/exports`

Jobs the caller owns, with their progress. A non-admin sees only jobs from their own manager subtree.

- Authentication: manager session (JWT) or API token
- Permission: `prm_exports_index` (View export jobs)
- Risk: read

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `filter[status]` | query | string | no | Job status. |
| `filter[format]` | query | enum | no | csv or xlsx. |
| `filter[target_table]` | query | string | no | One dataset key. The special value "reports" expands to every report dataset rather than matching exactly. |

#### Response — 200 OK

```json
{
  "data": [
    {
      "id": 3312,
      "target_table": "users",
      "format": "xlsx",
      "status": "running",
      "processed": 4200,
      "total": 8120,
      "row_count": 0,
      "manager_id": 41,
      "created_at": "2026-09-20 09:10:00",
      "started_at": "2026-09-20 09:10:04"
    }
  ],
  "meta": { "page": 1, "page_size": 50, "total": 12, "has_next": false }
}
```


### Queue an export

`POST /api/v1/exports`

Creates the job and returns immediately; a separate worker produces the file. The caller's authority scope is stamped onto the job server-side, so an export contains exactly what that manager's list view would show and nothing more.

- Authentication: manager session (JWT) or API token
- Permission: `prm_exports_create` (Create / delete export jobs)
- Risk: write
- Rate limit bucket: `t_heavy`
- Idempotent on `request_id`: retrying with the same id returns the original result

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `target_table` | body | string | yes | A dataset key from the datasets endpoint. |
| `format` | body | enum | no | csv or xlsx. Defaults to xlsx. |
| `params` | body | object | no | Dataset-specific filters, in the same shape that dataset's list endpoint accepts. |
| `request_id` | body | string | no | Optional idempotency key. Blank stays blank and means no deduplication at all — supply your own if a retry must not queue a second job. |

#### Request

```json
{
  "target_table": "users",
  "format": "xlsx",
  "params": { "enabled": true },
  "request_id": "9d41f0c2-6b17-4e58-93a2-8c0e5f7b1d34"
}
```

#### Response — 202 Accepted

```json
{
  "data": { "id": 3312 }
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | an unknown target_table, or a format that is neither csv nor xlsx |
| `ERR_FORBIDDEN` | 403 | the caller may create exports but lacks the permission to VIEW that table |

#### Note

Exporting a table needs the permission to read it as well as the permission to export. The route's own gate is not enough, so a role with export rights but no subscriber-read right gets a 403 on target_table users. That is the check that stops an export from being a way around a list permission.


### Create / delete export jobs

`POST /api/v1/exports/bulk-delete`

- Authentication: manager session (JWT) or API token
- Permission: `prm_exports_create` (Create / delete export jobs)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### List exportable datasets

`GET /api/v1/exports/datasets`

Every table this build can export, with its key. target_table on the enqueue call must be one of these, and an unknown value is a 400. Static for the life of the release, so fetch it once and cache it.

- Authentication: manager session (JWT) or API token
- Permission: `prm_exports_index` (View export jobs)
- Risk: read


### Delete an export job

`DELETE /api/v1/exports/{id}`

Removes the job and its artifact.

- Authentication: manager session (JWT) or API token
- Permission: `prm_exports_create` (Create / delete export jobs)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Job id. |


### Poll one export job

`GET /api/v1/exports/{id}`

The job's current state and progress. Poll this until status reports the job is finished, then download.

- Authentication: manager session (JWT) or API token
- Permission: `prm_exports_index` (View export jobs)
- Risk: read

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Job id. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_NOT_FOUND` | 404 | no such job, or one belonging to another manager's subtree |

#### Note

processed and total drive a progress bar; row_count is the final emitted count and stays 0 until the job finishes. A job that is not yours answers 404 rather than 403, so job ids are not enumerable across resellers.


### Download an export artifact

`GET /api/v1/exports/{id}/download`

Streams the finished file. The ownership check runs again here, because the artifact itself can contain another reseller's card codes and balances.

- Authentication: manager session (JWT) or API token
- Permission: `prm_exports_download` (Download export files)
- Risk: write
- Rate limit bucket: `t_heavy`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Job id. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_NOT_FOUND` | 404 | the job is not yours, or has not produced a file yet |

#### Note

404 until the job has actually produced an artifact, which is indistinguishable from "no such job". Poll the job first rather than treating a download 404 as a permanent failure. This route is on the per-tenant heavy budget.


