# Test Case Export API

> Download a project's active test cases as JSON, XML, CSV, XLSX, or Gherkin .feature files over the integrator API.

The export endpoint downloads a project's **active** (non-archived) test cases as a single artifact. It supports five formats and three scope options, so an integrator can pull an entire project, one suite subtree, or the exact filtered slice a user is looking at in the repository view.

The endpoint is **read-only** — it emits no audit events and makes no state changes. Attachments (defect evidence, case files, result files, and the image files inside texts) are **never included** in any exported format; only text, tags, custom field values, and suite structure travel with the artifact. A text's images travel as `attachment:` references.

All requests require an authenticated request (Bearer API token or session cookie) carrying the import/export permission (held by `owner`/`admin`/`member`). Viewers receive `403 forbidden`.

## Export test cases

`GET /api/v1/projects/{projectId}/exports`

`{projectId}` is the project **code** (for example `ACME`).

### Query parameters

| Parameter      | Default   | Notes                                                                                                                                                                                                                                                 |
| -------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `format`       | —         | Required. One of `json`, `xml`, `csv`, `xlsx`, `feature`. An unlisted value returns `422 validation_failed`. `feature` returns a zip of `.feature` files, one per suite, with Gherkin cases only; a scope with no Gherkin case is refused with `422`. |
| `scope`        | `project` | `project` (the whole project), `suite` (the suite given by `suite`, plus all of its descendant suites), or `filtered` (the server re-evaluates `q`/`cf`/`reviewStatus` — see below).                                                                  |
| `suite`        | —         | Suite ULID. Required when `scope=suite`; ignored otherwise. An unresolvable suite ULID returns `404 not_found`.                                                                                                                                       |
| `q`            | —         | Substring filter, applied only when `scope=filtered`. Same substring-match contract as the [test case search endpoint](https://docs.probara.net/en/developers/api/test-case-search/).                                                                                         |
| `cf`           | —         | Repeatable custom-field filter, applied only when `scope=filtered`. Each entry is `<fieldUlid>:<value[,value…]>` — identical contract to the search/list endpoints.                                                                                   |
| `reviewStatus` | —         | Comma-separated review statuses (`not_reviewed`, `in_review`, `approved`), applied only when `scope=filtered`. Same contract as the [test case search endpoint](https://docs.probara.net/en/developers/api/test-case-search/).                                                |

### Response

`200` with the artifact body in the requested format. The response always carries a matching `Content-Type` and a `Content-Disposition: attachment` header naming the file `<projectId>-export.<extension>`, or `<projectId>-features.zip` for `feature`:

| `format`  | `Content-Type`                                                      | Extension |
| --------- | ------------------------------------------------------------------- | --------- |
| `json`    | `application/json`                                                  | `.json`   |
| `xml`     | `application/xml`                                                   | `.xml`    |
| `csv`     | `text/csv`                                                          | `.csv`    |
| `xlsx`    | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | `.xlsx`   |
| `feature` | `application/zip`                                                   | `.zip`    |

The JSON, XML, CSV, and XLSX formats carry the same underlying data: for each active test case, its title, description, tags, suite path, steps, full custom field values (fixed fields — priority/type/status — plus every custom field configured on the project), and its automation key (`automationKey` in JSON, an `<automationKey>` element in XML — empty when unset — and an `automation_key` column right after `step_expected` in CSV/XLSX). Each case also states its step format (`stepFormat`, or a `step_format` column in CSV/XLSX), and each step its Gherkin keyword (`keyword`, or a `step_keyword` column). The keyword is `null` or empty on a classic step. CSV and XLSX cells that begin with `=`, `+`, `-`, `@`, a tab, or a carriage return are prefixed with a leading `'` to neutralize spreadsheet formula injection — this is visible if you open the file in a spreadsheet application.

A case's parameters and excluded combinations are exported as well, when the case has any. JSON carries them as the `parameters` and `excludedCombinations` keys, XML as `<parameters>` and `<excludedCombinations>` elements, and CSV and XLSX as the `parameters` and `excluded_combinations` columns, which hold JSON. JSON keeps a shared parameter as a reference, while CSV and XLSX write it as a local copy of its values. The `feature` format writes the combinations the case runs as one Examples table: excluded combinations and the single/group split are not kept. See [Parameterized test cases](https://docs.probara.net/en/guides/parameterized-test-cases/).

### Size ceiling

An export is bounded to `EXPORT_MAX_CASES` (5000) active cases per request. When the resolved scope would exceed this ceiling, the request fails **before any row is read or serialized**:

```json
{ "error": { "code": "too_many_cases", "message": "..." } }
```

Narrow the scope (a smaller suite, or a `filtered` scope with `q`/`cf`) and retry.

### Rate limit

Export requests are rate-limited per organization. Exceeding the limit returns `429` with `{ "error": { "code": "too_many_requests", ... } }`. Back off and retry after a short delay.

### Errors

| Status | Code                | Condition                                                                                                                           |
| ------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `403`  | `forbidden`         | The caller's role is `viewer`                                                                                                       |
| `404`  | `not_found`         | `projectId` does not exist in the active organization, or `scope=suite` references a suite ULID that does not exist in this project |
| `422`  | `validation_failed` | `format` is missing or not one of the five listed values, or `scope=suite` is set without `suite`                                   |
| `422`  | `too_many_cases`    | The resolved scope exceeds `EXPORT_MAX_CASES`                                                                                       |
| `429`  | `too_many_requests` | The organization's export rate limit was exceeded                                                                                   |

### Re-importing an export

Every file this endpoint produces — JSON, XML, CSV, or XLSX — round-trips back in through the two-step [import API](https://docs.probara.net/en/developers/api/import/): `POST /api/v1/projects/{projectId}/imports:stage` to stage the file, then `POST /api/v1/projects/{projectId}/imports/commit` with the matching `probara_json`/`probara_xml`/`probara_csv`/`probara_xlsx` source format — a full round trip for backing up and restoring, or migrating cases between projects. Case files are never exported. The inline images of a case's texts travel as `attachment:<ulid>` references: a re-import into the same organization keeps those images while they still exist there, and drops a reference to an image it does not know. Everything else (title, description, tags, suite path, steps, and custom field values) round-trips. A `feature` export is not a re-import source: import one of its `.feature` files with the `gherkin_feature` source instead.

### Example

Export a whole project as CSV:

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -o acme-export.csv \
  "https://app.probara.net/api/v1/projects/ACME/exports?format=csv"
```

Export one suite subtree (including its descendants) as XLSX:

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -o acme-suite-export.xlsx \
  "https://app.probara.net/api/v1/projects/ACME/exports?format=xlsx&scope=suite&suite=01JAAAA..."
```

Export the filtered slice matching a search query as JSON:

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -o acme-filtered-export.json \
  "https://app.probara.net/api/v1/projects/ACME/exports?format=json&scope=filtered&q=login"
```

Export the Gherkin cases as `.feature` files, one per suite, in a zip:

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -o ACME-features.zip \
  "https://app.probara.net/api/v1/projects/ACME/exports?format=feature"
```

## Related pages

- [Test case search API](https://docs.probara.net/en/developers/api/test-case-search/) — the same `q`/`cf` filter contract used by `scope=filtered`.
- [Test case import API](https://docs.probara.net/en/developers/api/import/) — re-imports the files this endpoint produces, with full fidelity for Probara re-imports.
