> For the complete documentation index, see [llms.txt](https://help.connected.illumina.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.connected.illumina.com/basespace/sequence/plan-runs/run-planning-api.md).

# Plan a Run via the API

This guide shows you how to use the Run Planning API programmatically — to plan sequencing runs, manage their content, and generate sample sheets without going through the BaseSpace Sequence Hub UI.

> **Status of this guide:** This is the first published cut. It covers the most common starting workflow — *get a token, discover catalog IDs, create a planned run, revise it, and generate the sample sheet*. We will expand it over time to cover additional endpoints (sample-sheet validation, library / library pool management, analysis configuration templates, run lifecycle, etc.). For the complete endpoint list, see the [API Reference](#api-reference) link below.

***

## In this guide

* [Prerequisites](#prerequisites)
* [About the example code](#about-the-example-code)
* [Authenticating](#authenticating) — `POST /v1/users/tokenProvider:createToken`
  * [Workgroup ID](#workgroup-id)
* [API Reference](#api-reference)
* [Discovering catalog IDs](#discovering-catalog-ids) — analysis and its versions, genomes
* Combined example: **Plan a sequencing run end to end**
  1. [Create a planned run](#1-create-a-planned-run) — `POST /v1/sequencing/runs:plan`
  2. [Replace a planned run](#2-replace-a-planned-run) — `PUT /v1/sequencing/runs/{runId}/plan`
  3. [Generate the sample sheet](#3-generate-the-sample-sheet) — `POST /v1/sequencing/runs/{runId}:generateSampleSheet`
* [What you'll see in the generated sample sheet](#what-youll-see-in-the-generated-sample-sheet)
* [Naming and value constraints](#naming-and-value-constraints)
* [Troubleshooting](#troubleshooting)

***

## Prerequisites

* An **Illumina Platform API Key**. See [Generate an API Key](https://help.ica.illumina.com/get-started/gs-getstarted#api-keys) for instructions on creating one. You can use the same API key you use for ICA / the CLI.
* Your **workgroup ID**, if you want the token to be scoped to a specific workgroup (recommended for shared resources). See [Workgroup ID](#workgroup-id) below.
* The **base hostname** for the regional Illumina Platform gateway your enterprise lives on. The platform is deployed regionally — see [BaseSpace Sequence Hub Global Regions](https://help.basespace.illumina.com/manage-your-account/regions) for the list of available regions. The hostname you need is not the same as the BaseSpace URLs shown on that page; contact your Illumina representative or support team to obtain it for your enterprise.

***

## About the example code

The examples below use Python with the `requests` library, but you can use any HTTP client (curl, Postman, etc.). Install Python from <https://www.python.org/> and install `requests` with `pip install requests`. The inline examples omit `timeout=` for readability; production scripts should set an explicit timeout on each call to avoid hanging on network issues.

***

## Authenticating

Every call must carry an Illumina Platform JWT in the `Authorization` header:

```
Authorization: Bearer <jwt>
```

To obtain this JWT, call the User Token Provider endpoint. It accepts your Illumina Platform API Key and returns a JWT that the Run Planning API will accept.

> **Note:** This token-exchange flow is specific to the Run Planning API. Other Illumina services (e.g. ICA) have their own authentication flows — see their respective documentation.

### Get a token

`POST <platform-base>/v1/users/tokenProvider:createToken`

| Header         | Value                                                                                             |
| -------------- | ------------------------------------------------------------------------------------------------- |
| `X-API-Key`    | Your raw Illumina Platform API Key. Do not Base64-encode it — the server handles that internally. |
| `Content-Type` | `application/json`                                                                                |

The body is optional. An empty `{}` works for most cases. The most useful query parameter is `cwid` — see [Workgroup ID](#workgroup-id) below.

```python
import requests

PLATFORM_BASE = "https://<platform-base>"   # e.g. https://use1.platform.illumina.com
API_KEY       = "<your-api-key>"
WORKGROUP_ID  = "<your-workgroup-id>"        # optional; see "Workgroup ID" below

token_response = requests.post(
    f"{PLATFORM_BASE}/v1/users/tokenProvider:createToken",
    params={"cwid": WORKGROUP_ID} if WORKGROUP_ID else {},
    headers={
        "X-API-Key": API_KEY,
        "Content-Type": "application/json",
    },
    json={},
)
print("Token response status:", token_response.status_code)

access_token = token_response.json()["access_token"]
auth_headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json",
}
```

A successful response is HTTP `201 Created` with:

```json
{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer"
}
```

Pass `access_token` to every API call as `Authorization: Bearer <access_token>`.

#### Scopes

The token will carry the scopes your user is permitted to receive — typically `gss.config.*` (read-only resource catalog) and `gss.sequencing.*` (planned runs, samples, libraries, etc.). The example workflow below requires:

* `gss.sequencing.create` — to create the planned run
* `gss.sequencing.update` — to replace it
* `gss.sequencing.read` — to generate the sample sheet

Scope matching is case-insensitive, so the casing of the strings in the JWT does not need to match exactly.

#### Token lifetime

Tokens are long-lived (currently 7 days; subject to change without notice). Cache the token across calls and only call `:createToken` again when the token is close to expiry or after a `401` response.

### Workgroup ID

Most resources in the Illumina platform — projects, sequencing runs, samples, libraries — belong to a **workgroup**. A user can belong to multiple workgroups; the workgroup the token is issued for determines which resources the token can see and act on.

* **Where to find it:** In the Illumina Platform UI, switch to the workgroup you want to use, then look in the URL or your user profile menu for the workgroup ID (a GUID).
* **If you omit it:** The token is issued in your **personal** context. Resources you create are owned by you (only you can read them) but workgroup-shared resources (which is most production data) will not be visible.
* **Recommendation:** Set `cwid` to the workgroup that owns the planned runs you intend to manage.

***

## API Reference

The complete, always-up-to-date list of endpoints is available as an interactive Swagger UI hosted alongside the API. The exact URL depends on your region, and follows this pattern:

```
<platform-base>/gss/swagger/index.html
```

The raw OpenAPI specification is at `<platform-base>/gss/swagger/v1/swagger.json`. Use Swagger to look up request and response schemas, error codes, and endpoints not yet covered in this guide. Your Illumina representative can provide the exact `<platform-base>` for your region.

> Swagger's **Authorize** button accepts the JWT obtained in the previous section.

***

## Discovering catalog IDs

The create / replace examples below reference an **analysis version definition (AVD)** ID and, for genome-aware analyses, a **genome ID**. You discover them with `GET` calls before your first `:plan`.

GSS distinguishes between an **analysis definition (AD)** — an analysis family such as "BCL Convert" or "DRAGEN Germline" — and an **analysis version definition (AVD)** — a specific version of that analysis. The `:plan` payload references AVD IDs, not AD IDs.

### Analysis definitions (pipelines)

Required scope for all calls in this subsection: `gss.config.read`.

**List analysis definitions**

`GET <platform-base>/v1/sequencing/analysisdefinitions`

Returns a paginated list of analysis pipelines accessible to the request token. Useful for finding the AD ID of an analysis(e.g. DRAGEN Germline) before drilling into its versions.

**Get a single analysis definition**

`GET <platform-base>/v1/sequencing/analysisdefinitions/{analysisDefinitionId}?Include=AnalysisVersions`

Returns details for a single AD and its associated versions

### Analysis version definitions

Required scope for all calls in this subsection: `gss.config.read`.

**Get a single AVD by ID**

`GET <platform-base>/v1/sequencing/analysisdefinitions/versions/{analysisVersionDefinitionId}`

Returns the full definition of a single AVD and the following table shows optional `Include` query flags:

| Flag                           | Effect                                                                                                                                                                                               |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CompatibleLibraryPrepKits`    | Inline the LPKs that are compatible (and any that are explicitly excluded)                                                                                                                           |
| `CompatibleGenomes`            | Inline the supported and excluded genomes                                                                                                                                                            |
| `JavascriptFunctions`          | Inline the associated JavaScript functions (onSubmit, onRender, etc.)                                                                                                                                |
| `CompatibleBclConvertVersions` | Inline the BCL Convert versions compatible with this AVD. **Requires** `InstrumentPlatform` or `InstrumentType` as an additional query parameter; the API returns `400 Bad Request` if it is missing |

**Settings in the response**

Each AVD response contains three top-level setting fields. The tables below provide further details of these settings and their respective child fields.

| Field                             | What it controls                                     |
| --------------------------------- | ---------------------------------------------------- |
| `analysisSettings.fields[]`       | Analysis-level settings (apply to all samples)       |
| `analysisSampleSettings.fields[]` | Per-sample settings                                  |
| `runContentSettings.fields[]`     | Per-library / lane settings consumed by the analysis |

Each entry inside `fields[]` has the following properties (the most relevant ones for an integrator):

| Property   | Description                                                                                                                                                                        |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`       | The setting key. **This is what you use as the JSON key when sending settings back to `:plan` / `PUT .../plan`.**                                                                  |
| `type`     | One of `textbox`, `checkbox`, `radio`, `select`, `genome`, `number`, `integer`, `custom`, `file`, `fileSelect`, `section`, `text`, `date`, `referenceFile`, `sample`, `fieldgroup` |
| `label`    | Human-readable display name (used by the UI; **not** a valid settings key)                                                                                                         |
| `value`    | The default value, if any                                                                                                                                                          |
| `required` | Whether the setting is mandatory                                                                                                                                                   |

For BCL Convert, sending an empty `settings: {}` is supported and produces a working sample sheet for the typical default configuration.

### Genomes

Required scope: `gss.config.read`.

For analyses that use reference genomes (e.g. DRAGEN Germline), the `ReferenceGenomeDir` setting takes a Genome ID (e.g. `gen.85b1459dde1c41cca064110602xxxxx`) that you can extract from the list of genome objects via:

`GET <platform-base>/v1/sequencing/genomes`

See Swagger for the full schema and filtering options. There is also `GET /v1/sequencing/genomes/{genomeId}` for a single-genome lookup once you have the ID.

### Library prep kits and index adapter kits

Required scope: `gss.config.read`.

Looking up a Library Prep Kit (LPK) or Index Adapter Kit (IAK) is an **advanced** step. The first runnable example below omits all kit fields, which is supported and produces a valid sample sheet. Once you have a working basic flow, see Swagger for:

* `GET /v1/sequencing/libraryPrepKits` (camelCase path segment)
* `GET /v1/sequencing/indexadapterkits` (all-lowercase path segment)

> **Note on URL casing.** The casing asymmetry between `libraryPrepKits` and `indexadapterkits` is intentional and matches the routes registered in the API — it is not a typo. Make sure your client preserves the exact casing.

***

## Combined example — plan a sequencing run end to end

The remainder of this guide walks through a single workflow: **create a planned run, revise it, and generate the sample sheet**.

### 1. Create a planned run

`POST <platform-base>/v1/sequencing/runs:plan`

Required scope: `gss.sequencing.create`

This endpoint creates a new planned run in the *Draft/Planned* state, based on the isDraft flag provided in the payload. When a run is in `Planned` state, an operator will be able to select it from the sequencer to start the sequencing.

The example below uses BCL Convert + DRAGEN Germline with two samples. It uses "Not Specified" Library Prep Kit and "Not Specified" Index Adapter Kit, so you can run it without first looking up kit IDs. The only placeholders you must fill in are the two AVD IDs and the Genome ID — see [Discovering catalog IDs](#discovering-catalog-ids).

```python
import requests

API_BASE = f"{PLATFORM_BASE}"  # base for the Run Planning API endpoints

planned_run_payload = {
    "runConfiguration": {
        "runName": "MyRun_20260611_103158",
        "description": "Example planned run created via API",
        "instrumentPlatform": "NovaSeqXSeries",
        "regulatoryMode": "RUO",
        "readType": "Paired",
        "numCyclesRead1": 151,
        "numCyclesRead2": 151,
        "numCyclesIndex1": 10,
        "numCyclesIndex2": 10
    },
    "runContents": {
        "useFlatRunContent": True,
        "laneLibraries": [
            {
                "laneNumber": 1,
                "sampleName": "Sample_001",
                "index1Sequence": "GAACTGAGCG",
                "index2Sequence": "TCGTGGAGCG",
                "overrideCycles": "Y151;I10;I10;Y151"
            },
            {
                "laneNumber": 1,
                "sampleName": "Sample_002",
                "index1Sequence": "AGGTACCAGT",
                "index2Sequence": "GACAGTAATC",
                "overrideCycles": "Y151;I10;I10;Y151"
            }
        ]
    },
    "runAnalysisConfigurations": [
        {
            "name": "implicit-bcl-convert",
            "analysisVersionDefinitionId": "<bcl-convert-avd-id>",
            "settings": {}
        },
        {
            "name": "Configuration 1: DRAGEN Germline",
            "analysisVersionDefinitionId": "<dragen-germline-avd-id>",
            "settings": { "MapAlignOutFormat": "cram" },
            "sampleSettings": [
                {
                    "sampleId": "Sample_001",
                    "settings": {
                        "ReferenceGenomeDir": "<genome-id>",
                        "VariantCallingMode": "AllVariantCallers"
                    }
                },
                {
                    "sampleId": "Sample_002",
                    "settings": {
                        "ReferenceGenomeDir": "<genome-id>",
                        "VariantCallingMode": "SmallVariantCaller"
                    }
                }
            ]
        }
    ],
    "isDraft": False  # Save the run as Planned and run full validation. Required for the sequencer to pick the run up.
}

response = requests.post(
    f"{API_BASE}/v1/sequencing/runs:plan",
    headers=auth_headers,
    json=planned_run_payload,
)
print("Create planned run status:", response.status_code)

planned_run = response.json()
run_id = planned_run["id"]
print("Planned run ID:", run_id)
```

A successful response is HTTP `201 Created` with the full sequencing-run resource. Save its `id` — you will need it for the next steps.

> **Important — `isDraft` and run visibility on the sequencer.** Only runs that are saved as `Planned` (`isDraft: false`) are surfaced on the sequencer for an operator to select and start. A `Draft` run (`isDraft: true`) is persisted on the server but **will not appear on the sequencer**. For an end-to-end create → sequencer → sequence flow, always send `isDraft: false`.

### 2. Replace a planned run

`PUT <platform-base>/v1/sequencing/runs/{runId}/plan`

Required scope: `gss.sequencing.update`

Use this when you need to update an existing planned run. The body has the **same shape as create**, and PUT semantics apply: **the run is fully replaced by what you send**. Anything you omit (or send as an empty array) is removed.

The example below mirrors the create example but changes the description and flips Sample\_002's VariantCallingMode from SmallVariantCaller to AllVariantCallers.

```python
replace_payload = {
    "runConfiguration": {
        "runName": "MyRun_20260611_103158",
        "description": "Example planned run - updated via PUT",
        "instrumentPlatform": "NovaSeqXSeries",
        "regulatoryMode": "RUO",
        "readType": "Paired",
        "numCyclesRead1": 151,
        "numCyclesRead2": 151,
        "numCyclesIndex1": 10,
        "numCyclesIndex2": 10
    },
    "runContents": {
        "useFlatRunContent": True,
        "laneLibraries": [
            {
                "laneNumber": 1,
                "sampleName": "Sample_001",
                "index1Sequence": "GAACTGAGCG",
                "index2Sequence": "TCGTGGAGCG",
                "overrideCycles": "Y151;I10;I10;Y151"
            },
            {
                "laneNumber": 1,
                "sampleName": "Sample_002",
                "index1Sequence": "AGGTACCAGT",
                "index2Sequence": "GACAGTAATC",
                "overrideCycles": "Y151;I10;I10;Y151"
            }
        ]
    },
    "runAnalysisConfigurations": [
        {
            "name": "implicit-bcl-convert",
            "analysisVersionDefinitionId": "<bcl-convert-avd-id>",
            "settings": {}
        },
        {
            "name": "Configuration 1: DRAGEN Germline",
            "analysisVersionDefinitionId": "<dragen-germline-avd-id>",
            "settings": { "MapAlignOutFormat": "cram" },
            "sampleSettings": [
                {
                    "sampleId": "Sample_001",
                    "settings": {
                        "ReferenceGenomeDir": "<genome-id>",
                        "VariantCallingMode": "AllVariantCallers"
                    }
                },
                {
                    "sampleId": "Sample_002",
                    "settings": {
                        "ReferenceGenomeDir": "<genome-id>",
                        "VariantCallingMode": "AllVariantCallers"
                    }
                }
            ]
        }
    ],
    "isDraft": False
}

response = requests.put(
    f"{API_BASE}/v1/sequencing/runs/{run_id}/plan",
    headers=auth_headers,
    json=replace_payload,
)
print("Replace planned run status:", response.status_code)
```

Notes on PUT semantics:

* **Full replace.** `runConfiguration`, `runContents`, and `runAnalysisConfigurations` are each replaced by the value you send. To remove an analysis configuration, send the list without it; to keep one unchanged, re-send it as-is.
* **`isDraft: false` saves the run as `Planned` and runs full validation.** Same behavior as POST `:plan`, and what makes the run visible to the sequencer.
* You cannot replace a run that has moved past planning (started, locked, or no longer planned).

### 3. Generate the sample sheet

`POST <platform-base>/v1/sequencing/runs/{runId}:generateSampleSheet`

Required scope: `gss.sequencing.read`

Generates a Sample Sheet CSV (and optionally a structured JSON) from the planned run identified by `{runId}` in the path. The run configuration, run contents, and analysis configurations are taken from the saved run — you do not resend them in the body. This is the same sample sheet the sequencer will execute, so it is the authoritative artifact for downstream tooling.

The body has only two optional fields:

| Field                             | Type             | Meaning                                                                                                                                   |
| --------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `getSampleSheetSnapshotIfExisted` | bool             | If `true` and a sample sheet has already been snapshotted for a launched local-analysis run, return that snapshot instead of regenerating |
| `include`                         | array of strings | Extra content to include in the response (see table below)                                                                                |

```python
generate_payload = {
    "include": ["IncludeJsonFormat"]   # optional; omit for plain CSV only
}

response = requests.post(
    f"{API_BASE}/v1/sequencing/runs/{run_id}:generateSampleSheet",
    headers=auth_headers,
    json=generate_payload,
)
print("Generate sample sheet status:", response.status_code)

sample_sheet = response.json()

with open("SampleSheet.csv", "w", encoding="utf-8", newline="") as f:
    f.write(sample_sheet["sampleSheetContent"])

# sampleSheetConfig is only present when "IncludeJsonFormat" was requested.
if sample_sheet.get("sampleSheetConfig"):
    import json
    with open("SampleSheet.json", "w", encoding="utf-8") as f:
        json.dump(sample_sheet["sampleSheetConfig"], f, indent=2)

print("Sample sheet written.")
```

The `include` flags let you control what comes back:

| Flag                           | Effect                                                                                                                                                                                                                                                                                                                       |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IncludeJsonFormat`            | Adds a `sampleSheetConfig` field with a structured JSON representation of the CSV. Omitted by default.                                                                                                                                                                                                                       |
| `IncludeFileReferences`        | Adds a `fileReferences` array with resolved paths to genomes, reference files, etc. Omitted by default.                                                                                                                                                                                                                      |
| `IncludeAnalysisReferenceInfo` | Populates the `analysisReferenceInfos[].downloadableReferenceFiles` / `nameBasedGenomeInfos` / `nameBasedReferenceFileInfos` arrays. The `analysisReferenceInfos[]` outer array is always present (one entry per analysis configuration); these inner arrays are populated only when the run uses non-cloud reference files. |
| `IncludeNotMappedFields`       | Includes analysis fields that are not formally mapped (rare; for advanced workflows)                                                                                                                                                                                                                                         |

Use this endpoint to retrieve the sample sheet for a planned run. However, please note that run execution itself does not require calling this endpoint: once a planned run is created, the sequencer fetches the run, and the operator selects it on the sequencer to start the sequencing.

***

## What you'll see in the generated sample sheet

The generated CSV contains content the API auto-populates — you do not (and cannot) supply it in the request. Knowing this avoids surprise during downstream parsing. Below is the actual CSV produced by running the create / generate example above (BCL Convert + **DRAGEN Germline**, two samples with different variant-calling modes per sample):

```csv
[Header],
FileFormatVersion,2
RunName,MyRun_20260611_103158
RunDescription,Example planned run created via API
InstrumentPlatform,NovaSeqXSeries
IndexOrientation,Forward
AnalysisLocation,Cloud

[Reads]
Read1Cycles,151
Read2Cycles,151
Index1Cycles,10
Index2Cycles,10

[BCLConvert_Settings]
SoftwareVersion,4.4.10
OverrideCycles,Y151;I10;I10;Y151
FastqCompressionFormat,gzip

[BCLConvert_Data]
Lane,Sample_ID,Index,Index2
1,Sample_001,GAACTGAGCG,TCGTGGAGCG
1,Sample_002,AGGTACCAGT,GACAGTAATC

[Cloud_DragenGermline_Settings]
SoftwareVersion,4.4.10
MapAlignOutFormat,cram

[Cloud_DragenGermline_Data]
Sample_ID,ReferenceGenomeDir,VariantCallingMode
Sample_001,urn:ilmn:ica:region:<region-id>:data:fil.<file-id>#/genomes/v11/<reference-name>.tar.gz,AllVariantCallers
Sample_002,urn:ilmn:ica:region:<region-id>:data:fil.<file-id>#/genomes/v11/<reference-name>.tar.gz,SmallVariantCaller

[Cloud_Settings]
GeneratedVersion,1.25.0.<build>
Cloud_Workflow,ica_workflow_1
BCLConvert_Pipeline,urn:ilmn:ica:pipeline:<pipeline-id>#BclConvert_v4_4_10
Cloud_DragenGermline_Pipeline,urn:ilmn:ica:pipeline:<pipeline-id>#DRAGEN_Germline_4_4_10_-_Sequencer_Integration_Only

[Cloud_Data]
Sample_ID,ProjectName,LibraryName,LibraryPrepKitName,IndexAdapterKitName
Sample_001,MyRun_20260611_103158_2026-06-11T10_31_59_<suffix>,Lib_001,,
Sample_002,MyRun_20260611_103158_2026-06-11T10_31_59_<suffix>,Lib_002,,
```

Please refer to this [GUIDE](https://help.connected.illumina.com/run-set-up/overview/sample-sheet-structure), if you wish to learn more about our Sample Sheet structure.

***

## Naming and value constraints

The fields below are validated with regular expressions. Common values like sample IDs containing periods or descriptions containing commas will be rejected with `400 Bad Request` — validate your inputs before sending.

| Field                                                        | Allowed                                                                                              |
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| `runConfiguration.runName`                                   | alphanumeric, `_`, `-`, `.`, space; must start with alphanumeric / `_` / `-`; no trailing whitespace |
| `runConfiguration.description`                               | any character except `,` `*` `"` `'`; **also avoid `[`** **`]`**                                     |
| `laneLibraries[].sampleName`                                 | alphanumeric, `_`, `-` only                                                                          |
| `laneLibraries[].index1Sequence` / `index2Sequence`          | `A`, `G`, `C`, `T` only (uppercase)                                                                  |
| `laneLibraries[].adapterSequence*` (lane- and library-level) | `A`, `G`, `C`, `T`, `+`                                                                              |

***

## Troubleshooting

Errors are grouped by the endpoint that returned them.

### `POST /v1/users/tokenProvider:createToken` (Get a token)

| Status & message                                                                                                    | Cause                                                                                                                                                                                                                                                                       | Fix                                                                                                                                                                                                                                                                                                                                         |
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401 Unauthorized — Either X-API-Key header or Authorization header must be provided`                               | None of `X-API-Key`, `Authorization`, or the `psToken` cookie were sent.                                                                                                                                                                                                    | Send `X-API-Key: <your-raw-api-key>` (do not Base64-encode it), or send `Authorization: Bearer <psToken>` / `Basic <base64(username:password)>`.                                                                                                                                                                                            |
| `401 Unauthorized — Invalid authorization header format. Expected 'Bearer <token>' or 'Basic <credentials>'`        | An `Authorization` header was sent but not in the expected `Bearer …` / `Basic …` form.                                                                                                                                                                                     | Fix the header format. For API-key auth use `X-API-Key` instead — do not put the API key in `Authorization`.                                                                                                                                                                                                                                |
| `409 Conflict — Platform Auth request failed.` (response `details` carries Platform Auth's `errorCode` / `message`) | Platform Auth rejected the credentials or could not be reached. Common causes: invalid or expired API key; `cwid` doesn't belong to a workgroup the user is a member of; the user lacks the permissions required for the scopes IMS requests; Platform Auth is unavailable. | Inspect the `details.errorCode` / `details.message` returned in the response. [Generate a new API key](https://help.ica.illumina.com/get-started/gs-getstarted#api-keys) if it's invalid/expired, confirm the `cwid` is one your user is a member of, and contact your Illumina representative if the user is missing required permissions. |

### `POST /v1/sequencing/runs:plan` (Create a planned run)

| Status                                                       | Cause                                                                                                                                                                                                                                                                                | Fix                                                                                                                                             |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request — does not match regular expression …`      | A field failed regex validation. Most common: `sampleName` / `libraryName` with periods or spaces; `index1Sequence` / `index2Sequence` outside `A`/`G`/`C`/`T`; `description` containing `,` `*` `"` `'` `[` `]`.                                                                    | Sanitize input. See [Naming and value constraints](#naming-and-value-constraints).                                                              |
| `401 Unauthorized`                                           | JWT is missing, expired, or malformed.                                                                                                                                                                                                                                               | Re-fetch a token via `:createToken`.                                                                                                            |
| `403 Forbidden — (message contains a scope name)`            | Token lacks `gss.sequencing.create`, or `cwid` does not match the workgroup that owns the referenced resources.                                                                                                                                                                      | Confirm the token's scopes; reissue the token under the correct workgroup.                                                                      |
| `409 Conflict — One or more unknown settings found: <ids>`   | A label was sent as a settings key, or an id not defined on the AVD was used.                                                                                                                                                                                                        | Use the AVD's field ids (e.g. ReferenceGenomeDir, VariantCallingMode), not the labels. See [Discovering catalog IDs](#discovering-catalog-ids). |
| `409 Conflict — Failed BCL Convert data validation check: …` | The run failed sample-sheet validation. Common reasons: insufficient index distance between samples, illegal characters in free-text fields (e.g. \[, ], tabs in runName, description, sampleName), or inconsistent OverrideCycles / kit settings across libraries on the same lane. | Read the message after the colon — it pinpoints the exact issue.                                                                                |
| `409 Conflict` (other)                                       | Strict validation failed — typically index collisions across libraries on the same lane, kit incompatibility, or cycle counts inconsistent with the read type.                                                                                                                       | Inspect error details for more information.                                                                                                     |

### `PUT /v1/sequencing/runs/{runId}/plan` (Replace a planned run)

All errors from create also apply. In addition:

| Status                               | Cause                                                                                                                                                                                                 | Fix                                                                                                          |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `403 Forbidden`                      | Token lacks `gss.sequencingruns.update` (or the parent `gss.sequencing.update`).                                                                                                                      | Reissue with the correct scope and / or workgroup.                                                           |
| `404 Not Found` / `410 Gone`         | `runId` doesn't exist (`404`) or the run was deleted (`410`).                                                                                                                                         | Verify the run id; deleted runs surface as `410`, not `404`.                                                 |
| `409 Conflict` — run is not editable | Run has progressed past planning. Specific error codes: `SequencingRunStarted` (sequencing has begun), `PlannedRunLocked` (claimed by a sequencer), `SequencingRunNotPlanned` (`IsPlanned == false`). | Replace is allowed while the run was created via run planning (draft or planned), sequencing hasn't started. |

### `POST /v1/sequencing/runs/{runId}:generateSampleSheet` (Generate the sample sheet)

| Status & message                                                                                                           | Cause                                                                                                                                                                   | Fix                                                                                                                                                                                                                     |
| -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401 Unauthorized`                                                                                                         | JWT is missing, expired, or malformed.                                                                                                                                  | Re-fetch a token via `:createToken`.                                                                                                                                                                                    |
| `403 Forbidden — Required scope ...`                                                                                       | Token lacks `gss.sequencing.read`, or `cwid` does not match the workgroup that owns the run.                                                                            | Reissue the token with the correct scope and / or workgroup.                                                                                                                                                            |
| `404 Not Found`                                                                                                            | The `{runId}` does not exist or is not visible in the current workgroup.                                                                                                | Verify the run ID and that the token's `cwid` matches the workgroup that owns the run.                                                                                                                                  |
| `409 Conflict — <resource> resource '<urn>' is either not found or inaccessible.` / `Failed to retrieve reference file(s)` | A genome, reference file, or other resource the run depends on is no longer accessible to the caller (deleted, moved, or outside the workgroup the token is scoped to). | Confirm the resource ids in the run are still valid and that the workgroup the token represents has access. If a resource was removed, point the run at a replacement via PUT :plan before generating the sample sheet. |

***

*Need an endpoint that's not covered yet? Use the* [*API Reference*](#api-reference) *and let us know which workflows you need documented next.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://help.connected.illumina.com/basespace/sequence/plan-runs/run-planning-api.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
