---
title: "API Reference"
description: "Every route of the TestOrchestrator public API (/api/v1): authentication, scopes, parameters, request fields, responses and error codes, generated from its OpenAPI document."
source: "https://testorchestrator.com/docs/api-keys/reference"
---

# API Reference

The routes of the public API at `/api/v1`, with the scope each one needs, its parameters, request fields, responses and error codes. Generated from the API’s own OpenAPI document.

> **Result upload opens in a following release; keys can be created now.** Until it opens, `POST /api/v1/ingest/results` answers `503 INGEST_NOT_AVAILABLE` with `Retry-After`, after the key and its scope have been checked. When it is open, an accepted upload is recorded (`202` with an `ingest_id`); turning its results into a test run arrives later.

## Overview

The public, versioned API of TestOrchestrator. Authenticate with a workspace service key (Authorization: Bearer \<key>); the key decides the workspace, so never send X-Tenant-Workspace. Each operation states the scope it needs (x-required-scope).

Projects are named by their public id (prj_ + 12 characters), never by a numeric id.

Errors share one shape: {errorCode, message, status, details, path, timestamp}.

Lists are cursor-paged: {data, next_cursor}; pass next_cursor back as ?cursor= until it is null. ?limit= sets the page size (1-100, default 50).

- **Base URL:** `https://api.testorchestrator.com`
- **Document:** OpenAPI 3.1.0, API version 1.0.0, served without a key at `GET /api/v1/openapi.json`. This page is generated from a copy of it.

## Authentication

`serviceKey` — HTTP `bearer`. A workspace service key (to_svc_...), sent as Authorization: Bearer \<key>. Created by a Tenant Admin in Personal > API keys > Workspace service keys. Personal keys (MCP) are refused on this API. Keys and scopes are explained in [API keys](https://testorchestrator.com/docs/api-keys).

## Routes

| Route | Scope | Summary |
| --- | --- | --- |
| [`POST /api/v1/ingest/results`](https://testorchestrator.com/docs/api-keys/reference#post-api-v1-ingest-results) | `ingest:results` | Upload automated test results |
| [`GET /api/v1/openapi.json`](https://testorchestrator.com/docs/api-keys/reference#get-api-v1-openapi-json) | none | This document |
| [`GET /api/v1/projects`](https://testorchestrator.com/docs/api-keys/reference#get-api-v1-projects) | `read` | List projects |

## `POST /api/v1/ingest/results`

**Upload automated test results.** Send one results file (JUnit XML, Playwright JSON or TestOrchestrator JSON) as multipart/form-data. A project key needs no project parameter; an all-projects key passes project=prj_... Same Idempotency-Key within 24 hours returns the first response and writes nothing twice. Rate limit per key: a token bucket of 60 that refills at 1 per second (a burst of up to 60, so up to about 120 uploads in the first minute and 60 a minute after that); over it, 429 with Retry-After.

- **Scope:** `ingest:results` (`x-required-scope`)
- **Authentication:** `Authorization: Bearer <service key>`
- **Operation id:** `ingestResults`

### Parameters

| Name | In | Description |
| --- | --- | --- |
| `Idempotency-Key` | header · string | 1-255 visible ASCII characters, e.g. \<ci run id>-\<attempt>-\<shard>. |

### Request body

`multipart/form-data`, required — [`IngestResultsForm`](https://testorchestrator.com/docs/api-keys/reference#schema-ingestresultsform). multipart/form-data fields of an upload.

| Field | Type | Description |
| --- | --- | --- |
| `file` | string (binary) · **required** | The results file. Up to 25 MB; may be empty for a final complete=true call. |
| `format` | string · **required** | File format. — one of `junit-xml`, `playwright-json`, `to-results-json` |
| `project` | string | Only with an all-projects key: the project's public id. A project key may omit it; if sent it must be that key's own project. — pattern `^prj_[0-9a-hjkmnp-tv-z]{12}$`; e.g. `prj_8kd2hq0vxc1a` |
| `run_key` | string | Your stable id for the logical run (e.g. the CI run id). Uploads with the same key append to the same run. — up to 200 characters; e.g. `gha-9876543210` |
| `run_name` | string | Run name. Defaults to CI · \<branch> · #\<job>. — up to 255 characters |
| `environment` | string | An Environment name or id for the whole upload. — up to 120 characters |
| `complete` | string | true closes the run after this upload. — one of `true`, `false` |
| `tags` | string | Comma-separated names of Automation tags. — up to 1000 characters; e.g. `e2e,team-core` |
| `ci_branch` | string | CI fact: branch. — up to 255 characters; e.g. `main` |
| `ci_commit` | string | CI fact: commit SHA. — up to 64 characters; e.g. `4f2c9a1` |
| `ci_actor` | string | CI fact: who triggered the job. — up to 255 characters |
| `ci_job_url` | string (uri) | CI fact: link to the job (http or https). — up to 2048 characters |
| `ci_provider` | string | CI fact: provider (github, gitlab, circleci...). — up to 64 characters; e.g. `github` |
| `ci_build_number` | string | CI fact: build or pipeline number. — up to 64 characters; e.g. `512` |

### Responses

| Status | Meaning | Body |
| --- | --- | --- |
| `202` | Accepted and recorded. A replay of an earlier Idempotency-Key carries the header Idempotent-Replay: true. | [`IngestAccepted`](https://testorchestrator.com/docs/api-keys/reference#schema-ingestaccepted) |
| `400` | `TENANT_HEADER_NOT_ALLOWED`: Do not send X-Tenant-Workspace or X-Tenant-Subdomain: the key decides the workspace.`PROJECT_REQUIRED`: This key is for all projects: pass project=prj_... to say which one.`INVALID_PROJECT_ID`: project must be a project id like prj_8kd2hq0vxc1a, from the project's settings page.`INVALID_FORMAT`: format must be one of junit-xml, playwright-json, to-results-json.`FILE_REQUIRED`: Send the results as multipart/form-data with a file part.`INVALID_PARAMETER`: A request parameter is invalid.`IDEMPOTENCY_KEY_INVALID`: The Idempotency-Key header is malformed. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |
| `401` | `API_KEY_UNAUTHORIZED`: A valid workspace service key is required. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |
| `403` | `SCOPE_REQUIRED`: This key does not have the scope this endpoint requires.`KEY_PROJECT_MISMATCH`: This key cannot report to that project, or the project was not found or access is denied. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |
| `413` | `FILE_TOO_LARGE`: The results file is over 25 MB. Split it by shard or drop stdout from the report. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |
| `422` | `IDEMPOTENCY_KEY_REUSED`: This Idempotency-Key was already used for a different upload. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |
| `429` | `RATE_LIMIT_EXCEEDED`: Too many uploads with this key. Wait for Retry-After, then upload once per CI job. Header: `Retry-After`. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |
| `503` | `INGEST_NOT_AVAILABLE`: Automated result upload is not available yet. Try again later. Header: `Retry-After`. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |

## `GET /api/v1/openapi.json`

**This document.** The OpenAPI 3.1 description of the public API. No key required.

- **Scope:** none (`x-required-scope`)
- **Authentication:** none — no key required
- **Operation id:** `getOpenApiDocument`

### Responses

| Status | Meaning | Body |
| --- | --- | --- |
| `200` | OK | object |

## `GET /api/v1/projects`

**List projects.** The projects this key can act on, by public id. A project key lists its one project; an all-projects key lists every project of its workspace. Never returns numeric ids.

- **Scope:** `read` (`x-required-scope`)
- **Authentication:** `Authorization: Bearer <service key>`
- **Operation id:** `listProjects`

### Parameters

| Name | In | Description |
| --- | --- | --- |
| `cursor` | query · string | Opaque cursor from a previous page's next_cursor. |
| `limit` | query · integer (int32) | Page size, 1-100 (default 50). |

### Responses

| Status | Meaning | Body |
| --- | --- | --- |
| `200` | OK | [`ProjectPage`](https://testorchestrator.com/docs/api-keys/reference#schema-projectpage) |
| `400` | `TENANT_HEADER_NOT_ALLOWED`: Do not send X-Tenant-Workspace or X-Tenant-Subdomain: the key decides the workspace.`INVALID_PARAMETER`: A request parameter is invalid.`INVALID_CURSOR`: The cursor is invalid. Start again without one. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |
| `401` | `API_KEY_UNAUTHORIZED`: A valid workspace service key is required. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |
| `403` | `SCOPE_REQUIRED`: This key does not have the scope this endpoint requires. | [`Error`](https://testorchestrator.com/docs/api-keys/reference#schema-error) |

## Schemas

### `Error`

The error body of every refusal on the public API.

| Field | Type | Description |
| --- | --- | --- |
| `errorCode` | string · **required** | Machine-readable error code. — one of `API_KEY_UNAUTHORIZED`, `SCOPE_REQUIRED`, `TENANT_HEADER_NOT_ALLOWED`, `PROJECT_REQUIRED`, `INVALID_PROJECT_ID`, `KEY_PROJECT_MISMATCH`, `INVALID_FORMAT`, `FILE_REQUIRED`, `FILE_TOO_LARGE`, `INVALID_PARAMETER`, `IDEMPOTENCY_KEY_INVALID`, `IDEMPOTENCY_KEY_REUSED`, `INVALID_CURSOR`, `RATE_LIMIT_EXCEEDED`, `INGEST_NOT_AVAILABLE` |
| `message` | string · **required** | What went wrong, for a person. |
| `status` | integer · **required** | The HTTP status. |
| `details` | string \| null | Extra detail, e.g. the offending field or the retry hint. |
| `path` | string | The request path. |
| `timestamp` | string | When the error occurred (UTC). |

### `IngestAccepted`

The upload was accepted and recorded as an ingest job.

| Field | Type | Description |
| --- | --- | --- |
| `ingest_id` | string · **required** | Opaque id of the recorded upload. — e.g. `ing_7f3k9q2m4x8z1c5v` |
| `status` | string · **required** | Always accepted. — one of `accepted` |

### `IngestResultsForm`

multipart/form-data fields of an upload.

| Field | Type | Description |
| --- | --- | --- |
| `file` | string (binary) · **required** | The results file. Up to 25 MB; may be empty for a final complete=true call. |
| `format` | string · **required** | File format. — one of `junit-xml`, `playwright-json`, `to-results-json` |
| `project` | string | Only with an all-projects key: the project's public id. A project key may omit it; if sent it must be that key's own project. — pattern `^prj_[0-9a-hjkmnp-tv-z]{12}$`; e.g. `prj_8kd2hq0vxc1a` |
| `run_key` | string | Your stable id for the logical run (e.g. the CI run id). Uploads with the same key append to the same run. — up to 200 characters; e.g. `gha-9876543210` |
| `run_name` | string | Run name. Defaults to CI · \<branch> · #\<job>. — up to 255 characters |
| `environment` | string | An Environment name or id for the whole upload. — up to 120 characters |
| `complete` | string | true closes the run after this upload. — one of `true`, `false` |
| `tags` | string | Comma-separated names of Automation tags. — up to 1000 characters; e.g. `e2e,team-core` |
| `ci_branch` | string | CI fact: branch. — up to 255 characters; e.g. `main` |
| `ci_commit` | string | CI fact: commit SHA. — up to 64 characters; e.g. `4f2c9a1` |
| `ci_actor` | string | CI fact: who triggered the job. — up to 255 characters |
| `ci_job_url` | string (uri) | CI fact: link to the job (http or https). — up to 2048 characters |
| `ci_provider` | string | CI fact: provider (github, gitlab, circleci...). — up to 64 characters; e.g. `github` |
| `ci_build_number` | string | CI fact: build or pipeline number. — up to 64 characters; e.g. `512` |

### `Project`

A project the key can act on.

| Field | Type | Description |
| --- | --- | --- |
| `public_id` | string · **required** | Opaque public project id; pass it as project= with an all-projects key. — pattern `^prj_[0-9a-hjkmnp-tv-z]{12}$`; e.g. `prj_8kd2hq0vxc1a` |
| `name` | string · **required** | Project name. — e.g. `Checkout Web` |

### `ProjectPage`

A page of results with an opaque cursor to the next page.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of Project · **required** | The page's items. |
| `next_cursor` | string \| null · **required** | Opaque cursor for the next page; null on the last page. — e.g. `djE6cHJqXzhrZDJocTB2eGMxYQ` |
