# Test Case Import API

> Import test cases from a Probara, Qase, TestRail, or Gherkin (.feature) file over the integrator API — Probara re-import preserves full fidelity.

The import endpoints stage an export file and then commit it directly into a project — there is no preview or dry-run step. A successful commit creates (or replaces) test cases and suites in one request; any row that could not be imported is reported back individually, and the rest of the file still imports.

**Probara is the default re-import platform.** Re-importing a file this API previously exported (see the [export API](https://docs.probara.net/en/developers/api/export/)) is the zero-configuration path: the JSON, XML, CSV, and XLSX exports can each be re-imported, and the import preserves **full fidelity**. Qase and TestRail exports are also accepted, with a narrower field-mapping surface (see below). A Gherkin `.feature` file is accepted as `gherkin_feature`, one file per import (see [Gherkin `.feature` files](#gherkin-feature-files)).

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`.

## Stage the file

`POST /api/v1/projects/{projectId}/imports:stage`

`{projectId}` is the project **code** (for example `ACME`). The request body is `multipart/form-data` with a single `file` part carrying the export file.

### Response

`200` with:

```json
{ "uploadUlid": "01J...", "byteSize": 4821, "filename": "acme-export.json" }
```

`uploadUlid` identifies the staged upload for the commit call below. Staged uploads are not retained indefinitely — commit soon after staging.

### Errors

| Status | Code                | Condition                                                 |
| ------ | ------------------- | --------------------------------------------------------- |
| `403`  | `forbidden`         | The caller's role is `viewer`                             |
| `413`  | `file_too_large`    | The uploaded file exceeds `IMPORT_MAX_FILE_BYTES` (10 MB) |
| `422`  | `validation_failed` | The request carries no `file` part                        |
| `429`  | `too_many_requests` | The organization's stage rate limit was exceeded          |

## Commit the import

`POST /api/v1/projects/{projectId}/imports/commit`

```json
{
  "uploadUlid": "01J...",
  "sourceFormat": "probara_json",
  "targetSuiteUlid": null,
  "replaceMatching": false
}
```

| Field             | Notes                                                                                                                                                                                                                                                                                                                            |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uploadUlid`      | The value returned by the stage call above.                                                                                                                                                                                                                                                                                      |
| `sourceFormat`    | One of `probara_json`, `probara_xml`, `probara_csv`, `probara_xlsx`, `qase_xml`, `qase_json`, `qase_csv`, `qase_xlsx`, `testrail_xml`, `testrail_csv`, `gherkin_feature`. The web wizard resolves this automatically from the uploaded file's extension/content — an integrator calling the API directly must set it explicitly. |
| `targetSuiteUlid` | `null` imports beneath the project root; otherwise the suite hierarchy in the file is recreated beneath the given suite.                                                                                                                                                                                                         |
| `replaceMatching` | When `true`, a source case whose title exactly matches an existing case in the target suite overwrites that case's content instead of creating a duplicate.                                                                                                                                                                      |

### Response

`200` with the commit report:

```json
{
  "counts": {
    "suitesCreated": 2,
    "casesCreated": 8,
    "casesReplaced": 1,
    "rowsFailed": 0,
    "reviewRequestsCreated": 0
  },
  "created": [{ "caseUlid": "01J...", "displayId": "ACME-42", "title": "Login succeeds" }],
  "replaced": [],
  "reviewRequests": [],
  "failures": [],
  "warnings": []
}
```

`failures` lists rows that did **not** import (the rest of the file still committed); `warnings` lists rows that **did** import but with a non-fatal notice — see below.

### Projects that require review

When the project's [test case review](https://docs.probara.net/en/developers/api/test-case-reviews/) is **mandatory**
(Pro or Enterprise plan), the commit writes no case. The suites are created; each case the import would create
becomes a `create` review request, and each case it would replace an `update` request, with the
caller as author and the project's default reviewers. `casesCreated` and `casesReplaced` are `0`,
`reviewRequestsCreated` counts the requests, and `reviewRequests` lists them:

```json
{
  "reviewRequests": [
    {
      "reviewUlid": "01J...",
      "displayId": "RV-7",
      "kind": "update",
      "caseUlid": "01J...",
      "title": "Login succeeds"
    },
    {
      "reviewUlid": "01J...",
      "displayId": "RV-8",
      "kind": "create",
      "caseUlid": null,
      "title": "Signup works"
    }
  ]
}
```

A case is written only when its request is merged, with the same validation as
`POST .../test-cases` (for example, required custom fields must have a value). Like the direct
import, the commit is not all-or-nothing: requests opened before a later failure stay open.

## Probara re-import: full fidelity

Re-importing a Probara export (any of the four formats) maps back **every** system classification field — priority, severity, status, type, layer, behavior, is-flaky, preconditions, and postconditions — plus every **existing** user-defined custom field value, matched automatically by name (title for JSON/XML, slugified column header for CSV/XLSX). Steps, tags, description, and suite path always round-trip regardless of source platform. So do a case's step format and each Gherkin step's keyword.

**No field or option is ever created on import.** Every match is resolved against the organization's existing custom fields and existing option values at commit time — there is no field-mapping payload in the request, and no interactive binding step. This makes re-import safe to run repeatedly (for example, restoring a backup or migrating cases between projects) without polluting the project's schema.

The case's **automation key** round-trips too. A replaced case keeps its current key when the file carries none, and an empty or whitespace-only key clears it. A key the import cannot apply — already held by another case of the project (archived included), repeated by an earlier row of the same file, or invalid — never fails the row: the case imports without it and the report carries an `automation_key_skipped` warning. Keys are checked against the project as it stood before the import, so a key the same file removes from one case cannot be given to another in that import.

The case's **parameters** and **excluded combinations** round-trip too. CSV and XLSX carry them in two JSON columns, `parameters` and `excluded_combinations`, placed after the custom-field columns; JSON and XML carry them as `parameters` and `excludedCombinations`. The columns and keys appear only when a case has parameters or excluded combinations, so an export without them is unchanged. A shared parameter comes back as a local parameter with the values it held. The names `parameters` and `excluded_combinations` are reserved: a custom field with either title does not import its values. A Probara or `.feature` file that carries no parameters clears the parameters of a case it replaces, while a Qase or TestRail file, which carries none, keeps them.

Qase and TestRail exports map a narrower set — priority, type, and status only. User-defined custom fields are not mapped for those two platforms. A Gherkin `.feature` file maps no system or custom fields; its tags and suites are imported.

### Unknown-field and unknown-option behavior

An import never fails a row just because one field's value could not be mapped — instead it commits the case and records a warning:

| Warning code              | Meaning                                                                                                                                                                                                                                                      |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `unmapped_value`          | A system or user-defined option value did not match any existing option, and the field has no configured default — the field is left unset.                                                                                                                  |
| `defaulted_value`         | Same as above, but the field has a configured default option — the field falls back to that default.                                                                                                                                                         |
| `unmapped_field`          | A user-defined field title (or CSV/XLSX column slug) did not match any field that exists on the project — the value is skipped, nothing is created.                                                                                                          |
| `automation_key_skipped`  | The automation key is already used by another case of the project, repeated by an earlier row, or invalid — the case imports without it.                                                                                                                     |
| `shared_step_skipped`     | The case referenced a shared step whose text the file does not include — that reference is skipped and the case imports its other steps.                                                                                                                     |
| `step_format_folded`      | The case's steps do not fit its stated format: a Gherkin step with no keyword or with an expected result, or a classic step with a keyword. The case imports as classic steps, each keyword kept at the start of its action.                                 |
| `parameters_skipped`      | The file's parameters break a parameter rule, such as a repeated name or row, or more than 100 values for a single parameter — the case imports without them. A replace keeps the case's existing parameters, and a `.feature` file's Examples are not kept. |
| `image_reference_dropped` | A step, description or paragraph value names an image that is not stored in this organization, as in a file exported from another organization — that image is removed and the rest of the text is imported.                                                 |

Every row failure and warning names the affected `sourceRow` and case `title`, so a partially-successful import is fully diagnosable from the response alone.

### Row failures

A row fails (and its case is **not** imported) only for these reasons — every other value mismatch is the warning family above, never a failure:

| Code                      | Condition                                                                                                                                |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `missing_title`           | The row has no title.                                                                                                                    |
| `ambiguous_match`         | `replaceMatching` is set and the title matches more than one existing case in the target suite.                                          |
| `review_already_open`     | Mandatory review only: the case the row would replace already has an open review request.                                                |
| `review_proposal_invalid` | Mandatory review only: the row's content cannot be a review request (for example, a title over 400 characters or a step with no action). |

## Gherkin `.feature` files

Stage a `.feature` file as usual, then commit it with `sourceFormat` set to `gherkin_feature`. The file holds one Feature, written with English keywords. Each Scenario and each Scenario Outline becomes one Gherkin case under a suite named for the Feature, and a Rule adds a nested suite. The Background becomes the case's preconditions. Tags become Probara tags, and the Feature's and the Rule's tags apply to every scenario under them. A Scenario Outline's Examples become one parameter named `Examples`: the headers of its tables form the columns, in order of first appearance, and every row is kept, in order, with an empty cell where a table has no column. A table with a single column becomes a single parameter named after its header. Several Examples blocks merge into that one parameter, and their names and tags are not kept. When the Examples break a parameter rule, the case imports without them and the report carries a `parameters_skipped` warning. The `.feature` export writes a case's parameters back as one Examples table, with a row for each combination the case runs. A Gherkin sync refuses such a scenario with `422`, since a sync reports no warnings. A Feature or Rule description is not imported. A file the parser cannot read fails with `422 parse_failed`, and its message names the line. The [Gherkin test cases](https://docs.probara.net/en/guides/gherkin-test-cases/) guide describes the same mapping for customers. A repository's `.feature` files can instead be kept in sync with the [Gherkin sync](https://docs.probara.net/en/developers/api-reference/#gherkin-sync), so that their cases follow the files.

## Size ceilings

An import is bounded to `IMPORT_MAX_CASES` (5000) parsed cases and `IMPORT_MAX_FILE_BYTES` (10 MB) staged bytes. A file exceeding either ceiling is rejected before any suite or case is written.

## Rate limit

Both stage and commit are rate-limited per organization (10 requests/hour each). Exceeding the limit returns `429` with `{ "error": { "code": "too_many_requests", ... } }`.

## Markdown is not a supported import format

Markdown is not, and has never been, an importable source format. A `.md` upload is rejected client-side by the wizard with a translated inline error; an API caller that stages a `.md` file and attempts to commit it with any `sourceFormat` receives `422 parse_failed` — the file will not parse as the declared format.

## Errors

| Status | Code                | Condition                                                                                                                                                             |
| ------ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `403`  | `forbidden`         | The caller's role is `viewer`                                                                                                                                         |
| `404`  | `not_found`         | `projectId`, `targetSuiteUlid`, or the staged `uploadUlid` does not resolve in the active organization                                                                |
| `413`  | `file_too_large`    | The staged bytes exceed `IMPORT_MAX_FILE_BYTES` (defense-in-depth re-check at commit time)                                                                            |
| `422`  | `parse_failed`      | The staged bytes did not parse as the declared `sourceFormat`, or the suite hierarchy nests too deeply; for `gherkin_feature`, the message names the line that failed |
| `422`  | `empty_import`      | The file parsed to zero cases                                                                                                                                         |
| `422`  | `too_many_cases`    | The parsed case count exceeds `IMPORT_MAX_CASES`                                                                                                                      |
| `429`  | `too_many_requests` | The organization's import rate limit was exceeded                                                                                                                     |

## Example

Stage a file, then commit it as a Probara JSON re-import:

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -F "file=@acme-export.json" \
  "https://app.probara.net/api/v1/projects/ACME/imports:stage"
```

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"uploadUlid":"01J...","sourceFormat":"probara_json","targetSuiteUlid":null,"replaceMatching":false}' \
  "https://app.probara.net/api/v1/projects/ACME/imports/commit"
```

## Related pages

- [Test case export API](https://docs.probara.net/en/developers/api/export/) — produces the files this endpoint re-imports.
