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

# Create Test

> Create a new test case in the authenticated project.

<Info>
  **Permission Required**: `project:edit-tests`
</Info>

Create a new test case in your project. The shape of `test` depends on
`test_type`; this endpoint accepts the same `verification` / `discovery` /
`task` variants returned by [Get Test](/api-reference/tests/get-test).

<Note>
  By default, requests that include shared collection steps inline are
  rejected with `400`. Pass `shared_collection_steps_mode=drop` to silently
  drop the inline steps (they will be resolved from the shared step
  collection at run time).
</Note>

## Query Parameters

<ParamField query="include_shared_collection_steps" type="boolean" default="true">
  Whether shared collection steps are expanded inline in the response.
</ParamField>

<ParamField query="shared_collection_steps_mode" type="string" default="error">
  How to handle shared collection items that contain inline steps:
  `error` (reject) or `drop` (silently strip the inline steps).
</ParamField>

## Request Body

<ParamField body="test" type="TestCase" required>
  Test definition. `id` may be omitted to have one generated. `version` is
  ignored on create. See [Get Test](/api-reference/tests/get-test) for the
  detailed shape.
</ParamField>

<Note>
  **Expected results for verification steps.** Use `expected_results` to give a
  step one or more independent checks. It accepts:

  * a single string — one result,
  * a list of strings — one result per element,
  * a list of `{ "expected": "..." }` objects (forward-compatible form).

  One element is exactly one result; newlines inside a string are **not**
  split. The legacy `finish_condition` (single string) is still accepted as an
  alias for a single result and is ignored when `expected_results` is present.
</Note>

<ParamField body="folder_path" type="string">
  Folder path to place the test in (e.g. `"regression/smoke"`). `null` or
  `"/"` places it at the project root. Mutually exclusive with `folder_id`.
</ParamField>

<ParamField body="folder_id" type="string">
  Pre-resolved folder UUID. Mutually exclusive with `folder_path`.
</ParamField>

## Test-level time budget

The runtime budget is configured **on the test itself**, not on individual
steps or players. There is no per-run override surface — to change the
budget, edit the test (or create a new version).

| Test type      | Allowed fields               | Default                | Notes                                                            |
| -------------- | ---------------------------- | ---------------------- | ---------------------------------------------------------------- |
| `verification` | `max_seconds`                | `19800` (5.5h)         | `min_seconds` is rejected — verification has no minimum runtime. |
| `discovery`    | `min_seconds`, `max_seconds` | `3600`–`10800` (1h–3h) | Both bounds optional; defaults apply when omitted.               |
| `task`         | `min_seconds`, `max_seconds` | `0`–`19800` (0–5.5h)   | Both bounds optional; defaults apply when omitted.               |

For `discovery` and `task` the server validates `min_seconds <= max_seconds`
against the *effective* budget — i.e. caller-supplied values **after**
defaults are applied. Supplying just one bound that conflicts with the
default for the other (e.g. `min_seconds: 14400` on a discovery create with
no `max_seconds`) is rejected with `400`; either supply the other bound
explicitly, or pick a value that fits inside the default range.

The hard upper bound for any single run is `5.5h` (`19800` seconds).

## Response

Returns the newly created test with status `201`. The response shape is
identical to [Get Test](/api-reference/tests/get-test).

<RequestExample>
  ```bash cURL theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  curl -X POST "https://nunu.ai/api/v1/project/your-project-id/tests" \
    -H "X-Api-Key: YOUR_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "test": {
        "name": "Sanity smoke",
        "test_type": "verification",
        "tags": ["smoke"],
        "incident_auto_approve": false,
        "quarantined": false,
        "testrail": null,
        "max_seconds": 1800,
        "players_by_key": {
          "1": {
            "name": "P1",
            "agent_config_id": null,
            "game_funcs_config_id": null,
            "items_by_key": {
              "1": {
                "type": "step",
                "goal": "Reach the main menu",
                "expected_results": [
                  { "expected": "the main menu is visible" },
                  { "expected": "no loading spinner remains on screen" }
                ],
                "screenshots": 1,
                "pause_game": false,
                "performance_monitoring": false,
                "hint_manual": []
              }
            },
            "item_order": ["1"]
          }
        },
        "player_order": ["1"]
      },
      "folder_path": "regression/smoke"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  {
    "id": "a5060a07-d386-4269-98ce-b33fb894201e",
    "project_id": "test",
    "name": "Sanity smoke",
    "version": 0,
    "tags": ["smoke"],
    "incident_auto_approve": false,
    "test_type": "verification",
    "quarantined": false,
    "testrail": null,
    "source": "repository",
    "players_by_key": { "...": "..." },
    "player_order": ["1"]
  }
  ```

  ```json 400 Bad Request theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  {
    "error": "invalid request: test.name: Required"
  }
  ```
</ResponseExample>


## OpenAPI

````yaml POST /project/{projectId}/tests
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}/tests:
    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:
        - Tests
      summary: Create Test
      description: Create a new test case in the authenticated project.
      operationId: createTest
      parameters:
        - name: include_shared_collection_steps
          in: query
          description: Whether shared collection steps are expanded inline in the response
          schema:
            type: boolean
            default: true
        - name: shared_collection_steps_mode
          in: query
          description: How to treat shared collection items with inline steps
          schema:
            type: string
            enum:
              - error
              - drop
            default: error
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTestRequest'
      responses:
        '201':
          description: Test created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestCase'
        '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:edit-tests
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    CreateTestRequest:
      type: object
      required:
        - test
      properties:
        test:
          $ref: '#/components/schemas/TestCase'
        folder_path:
          type: string
          nullable: true
          description: >-
            Folder path placement. "/" or null = root. Mutually exclusive with
            folder_id.
        folder_id:
          type: string
          format: uuid
          description: Folder UUID placement. Mutually exclusive with folder_path.
    TestCase:
      type: object
      description: >-
        Full test case body. Shape varies by test_type (verification, discovery,
        task); see the docs page for the exact shape.
      properties:
        id:
          type: string
          format: uuid
        project_id:
          type: string
        name:
          type: string
        version:
          type: integer
        tags:
          type: array
          items:
            type: string
        incident_auto_approve:
          type: boolean
        test_type:
          type: string
          enum:
            - verification
            - discovery
            - task
        quarantined:
          type: boolean
        testrail:
          type: object
          nullable: true
        source:
          type: string
          enum:
            - repository
            - bot
        min_seconds:
          type: integer
          minimum: 0
          maximum: 19800
          description: >-
            Test-level minimum runtime in seconds. Allowed for `discovery` and
            `task` only; sending this on a `verification` test is rejected with
            400. Defaults when omitted: discovery=3600 (1h), task=0. Must be ≤
            `max_seconds` (validated against the effective budget after defaults
            are applied).
        max_seconds:
          type: integer
          minimum: 0
          maximum: 19800
          description: >-
            Test-level maximum runtime in seconds. Hard ceiling for any single
            run is 19800 (5.5h). Defaults when omitted: verification=19800
            (5.5h), discovery=10800 (3h), task=19800 (5.5h).
        players_by_key:
          type: object
          additionalProperties: true
        player_order:
          type: array
          items:
            type: string
    Error:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
        details:
          type: object
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: API key for authentication

````