> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.nunu.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Start Test Plan Run

> Start a run for a specific test plan.

<Info>
  **Permission Required**: `project:operate-runs`
</Info>

Start a run for a specific test plan. This is the test-plan-scoped equivalent
of [Start a Run](/api-reference/runs/start-run): instead of supplying
`test_plan_id` in the body, the plan is identified by the URL path. It is also
the only endpoint that accepts `slot_deployment_configs` for cross-platform
plans.

The response contains one `job_id` per test in the plan; poll each one with
[Get Job Details](/api-reference/jobs/get-job) to wait for completion.

## Path Parameters

<ParamField path="testPlanId" type="string" required>
  The test plan ID (UUID)
</ParamField>

## Request Body

<ParamField body="deployment_config_id" type="string">
  UUID of the deployment configuration for the plan's **default slot** (slot 1),
  used by every player not assigned to an extra slot; for plans without extra
  slots this is simply the config used by every test. Required unless the
  plan's slot assignments cover every player of every test and
  `slot_deployment_configs` is supplied.
</ParamField>

<ParamField body="agent_config_id" type="string">
  UUID of an agent configuration applied to every test in the plan.
</ParamField>

<ParamField body="build_id" type="string">
  UUID of a build from [Build Storage](/api-reference/builds/list-builds) for
  the default slot. Only works with deployment configs that use Build Storage
  as their build source, and requires `deployment_config_id`; otherwise the
  request returns a `400` error.
</ParamField>

<ParamField body="slot_deployment_configs" type="object">
  Per-slot deployment configs for cross-platform test plans, keyed by slot
  index (`"2"`, `"3"`, and so on). Fetch the plan with
  [Get Test Plan](/api-reference/test-plans/get-test-plan) to see its `slots`.
  Every slot used by the plan's player assignments needs an entry; keys
  referencing slots the plan does not define return a `400` error. The key
  `"1"` addresses the default slot as an alternative to the top-level
  `deployment_config_id`/`build_id` (supplying both returns a `400` error).
  Private device configs are allowed, but every player on a private device
  needs its own device; if the configs of a test cannot give each of its
  players a distinct device, the request returns a `400` error.

  <Expandable title="entry properties">
    <ParamField body="slot_deployment_configs.{index}.deployment_config_id" type="string" required>
      Deployment config to use for this slot.
    </ParamField>

    <ParamField body="slot_deployment_configs.{index}.build_id" type="string">
      Optional Build Storage build for this slot (build-storage configs only).
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="additional_tags" type="string[]">
  Tags appended to every resulting run.
</ParamField>

<ParamField body="render_templates" type="boolean" default="false">
  Enable Handlebars-style templating in test step bodies.
</ParamField>

<ParamField body="template_data" type="object">
  Key-value pairs for template variables. Used only when `render_templates` is `true`.
</ParamField>

<ParamField body="options" type="object">
  Per-run override knobs, with the same shape as `options` on
  [Start a Run](/api-reference/runs/start-run) (`performance_monitoring`,
  `mobile_auto_grant_permissions`, `mobile_geolocation`, `mobile_gpslocation`).
  Time-budget overrides are intentionally **not** accepted here; the budget
  lives on the test version itself (`min_seconds` / `max_seconds`).
</ParamField>

## Response

Returns `201 Created` on success. The body contains the started-run data
directly (no `{ ok, status, message, data }` envelope), typically including
`job_ids[]` (one per test in the plan) and `test_plan_execution_id`, so the
caller can correlate results via
[List Test Plan Executions](/api-reference/test-plans/list-test-plan-executions).

<ResponseField name="job_ids" type="string[]" required>
  Job IDs created for each test in the plan
</ResponseField>

<ResponseField name="test_plan_execution_id" type="string">
  ID of the test plan execution
</ResponseField>

<ResponseField name="slot_build_ids" type="object">
  For cross-platform plans: the build pinned for each extra slot, keyed by slot
  index. Only present when slot configs resolved to builds.
</ResponseField>

On failure, returns `{ "error": "<message>" }` with an appropriate HTTP status.

<RequestExample>
  ```bash cURL theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  curl -X POST "https://nunu.ai/api/v1/project/your-project-id/test-plans/b3f1a2c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c/runs" \
    -H "X-Api-Key: YOUR_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "deployment_config_id": "456e7890-e89b-12d3-a456-426614174000",
      "additional_tags": ["nightly"]
    }'
  ```

  ```bash cURL (cross-platform slots) theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  curl -X POST "https://nunu.ai/api/v1/project/your-project-id/test-plans/b3f1a2c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c/runs" \
    -H "X-Api-Key: YOUR_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "deployment_config_id": "456e7890-e89b-12d3-a456-426614174000",
      "slot_deployment_configs": {
        "2": { "deployment_config_id": "9a1b2c3d-4e5f-6789-a0b1-c2d3e4f5a6b7" }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  {
    "job_ids": [
      "cc6e3994-a714-4b6b-9879-0b3e53c0a3af",
      "dd1e4a55-b825-4c8b-baaf-1d4e63d1b4be"
    ],
    "test_plan_execution_id": "abc12345-6789-def0-1234-56789abcdef0"
  }
  ```

  ```json 404 Not Found theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  {
    "error": "test plan not found"
  }
  ```
</ResponseExample>


## OpenAPI

````yaml POST /project/{projectId}/test-plans/{testPlanId}/runs
openapi: 3.1.0
info:
  title: nunu.ai API
  description: >-
    The nunu.ai Public API allows you to programmatically interact with your
    projects, manage test runs, and upload builds.
  version: 1.0.0
servers:
  - url: https://nunu.ai/api/v1
security:
  - apiKeyAuth: []
paths:
  /project/{projectId}/test-plans/{testPlanId}/runs:
    parameters:
      - name: projectId
        in: path
        required: true
        schema:
          type: string
        description: >-
          The project ID. You can copy it from the project settings page or the
          project URL in the dashboard.
    post:
      tags:
        - Test Plans
      summary: Start Test Plan Run
      description: Start a run for a specific test plan.
      operationId: startTestPlanRun
      parameters:
        - name: testPlanId
          in: path
          required: true
          description: Test plan ID (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StartTestPlanRunRequest'
      responses:
        '201':
          description: Run queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StartRunResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden - missing project:operate-runs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Test plan not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    StartTestPlanRunRequest:
      type: object
      anyOf:
        - required:
            - deployment_config_id
        - required:
            - slot_deployment_configs
      dependentRequired:
        build_id:
          - deployment_config_id
      properties:
        deployment_config_id:
          type: string
          format: uuid
          description: >-
            UUID of the deployment config for the plan's default slot (slot 1),
            used by every player not assigned to an extra slot. Required unless
            the plan's slot assignments cover every player and
            `slot_deployment_configs` is supplied.
        agent_config_id:
          type: string
          format: uuid
        build_id:
          type: string
          format: uuid
          description: >-
            UUID of a build from Build Storage for the default slot. Only works
            with build-storage deployment configs, and requires
            `deployment_config_id`.
        slot_deployment_configs:
          type: object
          description: >-
            Per-slot deployment configs for cross-platform test plans, keyed by
            slot index ("2", "3", and so on; see the plan's `slots`). The key
            "1" addresses the default slot and cannot be combined with
            `deployment_config_id`/`build_id`. Every slot used by the plan's
            player assignments needs an entry. Private device configs are
            allowed, but every player on a private device needs its own device;
            if the configs of a test cannot give each of its players a distinct
            device, the request returns a 400 error.
          additionalProperties:
            type: object
            required:
              - deployment_config_id
            properties:
              deployment_config_id:
                type: string
                format: uuid
                description: Deployment config to use for this slot
              build_id:
                type: string
                format: uuid
                description: >-
                  Optional Build Storage build for this slot (build-storage
                  configs only)
        additional_tags:
          type: array
          items:
            type: string
        render_templates:
          type: boolean
          default: false
        template_data:
          type: object
        options:
          $ref: '#/components/schemas/RunOptions'
    StartRunResponse:
      type: object
      description: >-
        Response body for POST /runs and POST /test-plans/{testPlanId}/runs.
        Returned with HTTP 201 on success. The body contains the started-run
        data directly (no { ok, status, message, data } envelope).
      properties:
        job_ids:
          type: array
          items:
            type: string
            format: uuid
          description: >-
            Array of job IDs created. Single test returns 1 job; test plan
            returns one job per test.
        build_id:
          type: string
          format: uuid
          description: >-
            Build ID used for the run. Returned for multiplayer test and test
            plan runs.
        test_id:
          type: string
          format: uuid
          description: >-
            UUID of the created ephemeral test. Only returned for ephemeral
            (instructions) runs.
        test_plan_execution_id:
          type: string
          format: uuid
          description: Test plan execution ID. Returned for test plan runs.
        slot_build_ids:
          type: object
          additionalProperties:
            type: string
            format: uuid
          description: >-
            For cross-platform test plan runs: the build pinned for each extra
            slot, keyed by slot index. Only present when slot configs resolved
            to builds.
        message:
          type: string
          description: >-
            Optional status message returned for multiplayer test and test plan
            runs.
    Error:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
        details:
          type: object
    RunOptions:
      type: object
      description: >-
        Per-run override knobs. The time budget is intentionally not overridable
        here; it lives on the test version itself (see `min_seconds` /
        `max_seconds` on the test body), so callers should edit the test (or
        create a new version) when they want to change the budget.
      properties:
        performance_monitoring:
          type: boolean
          description: Enable performance data collection
        mobile_auto_grant_permissions:
          type: boolean
          description: Auto-grant app permissions on mobile
        mobile_geolocation:
          type: string
          description: IP geolocation proxy location
        mobile_gpslocation:
          type: string|object
          description: >-
            GPS location proxy location, either 'auto' or an object with 'lat'
            and 'long' string properties. Note: 'auto' requires
            `mobile_geolocation` to be set
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: API key for authentication

````