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

# Create a ticket for a test

> Files a new ticket in the test collection's default ticketing connection and links it to a test, returning the ticket as Trunk now holds it. A collection with no default connection is refused with `TICKETING_NOT_CONFIGURED`.

The ticket gets the default fields saved on that connection, with `fields` merged over them. The provider may still refuse the ticket: a field it does not accept, or a required one neither sets, is `422 TICKET_FIELDS_REJECTED` with the provider's own message, and a connection the provider will not let create tickets (no access to its project, a missing scope, a plan limit) is `409 TICKETING_CREATE_REFUSED`.

A `title` or `description` you leave out is generated the way the web app generates it: the test's location, failure rate, owners and most common failure reasons, with a link back to the test. Failure summaries are left out while your organization has AI features turned off. A test with no data to generate from is refused with `TICKET_CONTENT_UNAVAILABLE`; pass both to create its ticket anyway.

A test carries at most one ticket. If one is already linked, the call is refused with `TEST_ALREADY_LINKED` and files nothing, unless `replaceExisting` is true.

`Idempotency-Key` is not honored. The call files the ticket with the provider and answers `504` if that takes longer than five seconds; a `504` does not mean nothing was filed. Before retrying, read the test (`GET /v2/tests/{testId}`): if its `ticket` is set, the ticket was created and linked. A `502 UPSTREAM_ERROR` means the provider did not answer; when it was rate-limiting, `Retry-After` says how long to wait.

A test in another organization is reported as `404`, not `403`, and so is a malformed id.



## OpenAPI

````yaml /openapi-v2.json post /v2/tests/{testId}/create-ticket
openapi: 3.1.0
info:
  title: Trunk API
  version: 2.0.0
  description: >-
    The Trunk public API. Every endpoint is scoped to a single organization and
    requires a credential.


    ## Authentication


    For automation, send an API key as `Authorization: Bearer <key>`. An
    organization admin creates one in the Trunk app under **Settings → Developer
    → API Keys**, and chooses the merge-queue permissions it holds. A key with
    no permissions can read merge queues but change nothing. Those permissions
    scope merge queue only: every key can use the Flaky Tests and Dynamic CI
    endpoints, including their writes, whatever permissions it holds.


    A short-lived first-party token in the `x-trunk-token` header is what the
    `trunk` CLI sends after `trunk auth login`.


    A GitHub Actions job can upload impacted targets with no Trunk secret: the
    `trunk-io/login` action exchanges the run's own GitHub credential (its OIDC
    token, or on a pull request from a fork its `GITHUB_TOKEN`) for a
    short-lived first-party token. That token is accepted only by `PUT
    /v2/impacted-targets`, for the run's own repository — and on a fork pull
    request, only for that pull request and its head sha.


    ## Errors


    Every 4xx and 5xx response is a `ErrorResponse`: `{ "error": { "code",
    "message" } }`. Switch on `code`, which is stable; `message` is prose and
    may change at any time.


    **An authorization denial on a read is reported as `404`, not `403`** —
    whether a resource exists must not leak across organizations, so a resource
    in another organization is indistinguishable from one that does not exist.
    `403 INSUFFICIENT_PERMISSIONS` means the credential is valid but is not
    permitted the action (or carries no organization).


    Every response carries an `X-Request-Id` header. Quote it in support
    requests.


    ## Rate limiting


    Requests are limited per organization (currently 100 requests/second).
    Exceeding it returns `429` with `RATE_LIMIT_EXCEEDED`; retry with backoff.
    Note that the limit is enforced at the edge, which does not yet wrap its
    `429` in the `ErrorResponse` envelope described above — treat any `429` as
    rate limiting regardless of body.


    ## Compatibility


    Within v2, Trunk may add: new endpoints, new **optional** response fields,
    new members to any enum (including new `code` values, new queue-entry
    states, and new providers), and new optional request parameters. **Clients
    must tolerate values and fields they do not recognize** rather than failing
    closed — a client that switches exhaustively over an enum will break when a
    member is added. Removals and semantic changes require a new major version.


    ## Provider support


    Merge queues, verification runs and checks are **GitHub-only** today;
    `Check.provider` has a single member for that reason. Impacted-target
    uploads additionally require a `github.com` repository named `owner/repo`.
    The `Provider` enum lists values the repository index can store, not
    surfaces that are fully supported.


    ## Not yet available


    A pull request's numeric **position** in its queue is not exposed, and list
    responses are not ordered by queue position. `GET
    /v2/merge-queues/{id}/order` returns the order the engine will consider pull
    requests in, but an array index there is not a wait: use a pending entry's
    `directlyAhead` rather than counting, and `GET
    /v2/pull-requests/{id}/queue-entry-estimate` for when it is expected to
    merge (on queues that serve estimates). This API delivers no events of its
    own — poll it, or subscribe to [Merge Queue's
    webhooks](https://docs.trunk.io/merge-queue/webhooks). `Idempotency-Key` is
    not yet honored, so a mutation that times out must be reconciled by reading
    the resource back rather than blindly retried.
  contact:
    name: Trunk Support
    url: https://docs.trunk.io
servers:
  - url: https://api.trunk.io
    description: Production
security:
  - apiKey: []
  - trunkToken: []
tags:
  - name: mergeQueues
    x-group: Merge Queues
  - name: pullRequests
    x-group: Pull Requests
  - name: mergeQueueVerificationRuns
    x-group: Verification Runs
  - name: impactedTargets
    x-group: Impacted Targets
  - name: repositories
    x-group: Repositories
  - name: dynamicCi
    x-group: Dynamic CI
  - name: ciJobs
    x-group: CI Jobs
  - name: ciWorkflowRuns
    x-group: CI Workflow Runs
  - name: ciFailureClusters
    x-group: CI Failure Clusters
  - name: testCollections
    x-group: Test Collections
  - name: tests
    x-group: Tests
  - name: testUploads
    x-group: Test Uploads
paths:
  /v2/tests/{testId}/create-ticket:
    post:
      tags:
        - tests
      summary: Create a ticket for a test
      description: >-
        Files a new ticket in the test collection's default ticketing connection
        and links it to a test, returning the ticket as Trunk now holds it. A
        collection with no default connection is refused with
        `TICKETING_NOT_CONFIGURED`.


        The ticket gets the default fields saved on that connection, with
        `fields` merged over them. The provider may still refuse the ticket: a
        field it does not accept, or a required one neither sets, is `422
        TICKET_FIELDS_REJECTED` with the provider's own message, and a
        connection the provider will not let create tickets (no access to its
        project, a missing scope, a plan limit) is `409
        TICKETING_CREATE_REFUSED`.


        A `title` or `description` you leave out is generated the way the web
        app generates it: the test's location, failure rate, owners and most
        common failure reasons, with a link back to the test. Failure summaries
        are left out while your organization has AI features turned off. A test
        with no data to generate from is refused with
        `TICKET_CONTENT_UNAVAILABLE`; pass both to create its ticket anyway.


        A test carries at most one ticket. If one is already linked, the call is
        refused with `TEST_ALREADY_LINKED` and files nothing, unless
        `replaceExisting` is true.


        `Idempotency-Key` is not honored. The call files the ticket with the
        provider and answers `504` if that takes longer than five seconds; a
        `504` does not mean nothing was filed. Before retrying, read the test
        (`GET /v2/tests/{testId}`): if its `ticket` is set, the ticket was
        created and linked. A `502 UPSTREAM_ERROR` means the provider did not
        answer; when it was rate-limiting, `Retry-After` says how long to wait.


        A test in another organization is reported as `404`, not `403`, and so
        is a malformed id.
      operationId: tests.createTicket
      parameters:
        - schema:
            type: string
            minLength: 1
            description: The test's globally unique id.
            example: bfeebcf4-0000-8000-8000-000000000001
          required: true
          description: The test's globally unique id.
          name: testId
          in: path
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTicketRequest'
      responses:
        '201':
          description: The created and linked ticket
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Ticket'
        '400':
          description: '`VALIDATION_FAILED`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: '`AUTHENTICATION_REQUIRED` | `INVALID_TOKEN`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: '`INSUFFICIENT_PERMISSIONS`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: '`TEST_NOT_FOUND`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            `TICKETING_NOT_CONFIGURED` | `TICKETING_CREDENTIALS_INVALID` |
            `TICKETING_SITE_REFUSED` | `TEST_ALREADY_LINKED` |
            `TICKET_LINK_CONFLICT` | `TICKETING_CREATE_REFUSED`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: '`TICKET_FIELDS_REJECTED` | `TICKET_CONTENT_UNAVAILABLE`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: '`RATE_LIMIT_EXCEEDED`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: '`INTERNAL_ERROR`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: '`UPSTREAM_ERROR`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    CreateTicketRequest:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 250
          description: >-
            The ticket's title. Defaults to the one the web app generates, such
            as `[Flaky Test] suite::test_case_name`.
          example: '[Flaky Test] suite::test_case_name'
        description:
          type: string
          description: >-
            The ticket's body, in Markdown. Defaults to the one the web app
            generates: the test's location, failure rate, owners and most common
            failure reasons, with a link back to the test in Trunk.
          example: >-
            See all details on the [Trunk Test Detail
            page](https://app.trunk.io/acme/flaky-tests/collections/ab12cd34/tests/bfeebcf4-0000-8000-8000-000000000001)
        fields:
          type: object
          additionalProperties: {}
          description: >-
            Provider fields to set on the ticket, keyed by the provider's field
            id (a Jira custom field id, `assignee`, a Linear project id), in the
            shape the provider takes. Merged over the default fields saved on
            the collection's ticketing connection; a field set here wins.
          example:
            assignee: 5b10ac8d82e05b22cc7d4ef5
        replaceExisting:
          type: boolean
          description: >-
            Replace a ticket already linked to the test. When false or absent, a
            test that has a ticket is refused with `TEST_ALREADY_LINKED`.
          example: false
      additionalProperties: false
      description: The ticket to create for a test in a test collection.
    Ticket:
      type: object
      properties:
        id:
          type: string
          description: Trunk's id for the linked ticket.
          example: 5d4c3b2a-1f0e-4a1b-8c9d-0e1f2a3b4c5d
        provider:
          $ref: '#/components/schemas/TicketProvider'
        key:
          type: string
          description: >-
            The provider's human-readable key. Mutable on some providers (a Jira
            issue moved between projects gets a new one).
          example: TRUNK-1234
        url:
          type: string
          format: uri
          example: https://acme.atlassian.net/browse/TRUNK-1234
        title:
          type: string
          example: '[Flaky Test] suite::test_case_name'
        status:
          type: string
          description: The provider's status name, as last synced.
          example: In Progress
        assignee:
          type:
            - string
            - 'null'
          description: The provider's assignee display name; null when unassigned.
          example: Ada Lovelace
        syncedAt:
          type: string
          format: date-time
          description: When Trunk last read the ticket back from its provider.
          example: '2026-09-15T14:45:01.000Z'
      required:
        - id
        - provider
        - key
        - url
        - title
        - status
        - assignee
        - syncedAt
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
              description: >-
                Human-readable description of what went wrong. Not stable — do
                not parse.
              example: No merge queue found with ID xK9mP2nQ
          required:
            - code
            - message
      required:
        - error
      description: >-
        The uniform error envelope. Every 4xx and 5xx response in this API has
        this shape.
    TicketProvider:
      type: string
      enum:
        - jiraCloud
        - linear
        - asana
        - jiraDataCenter
      description: The ticketing system a ticket lives in.
      example: jiraCloud
    ErrorCode:
      type: string
      enum:
        - AUTHENTICATION_REQUIRED
        - INVALID_TOKEN
        - INSUFFICIENT_PERMISSIONS
        - INTERNAL_ERROR
        - VALIDATION_FAILED
        - NOT_FOUND
        - INVALID_CURSOR
        - MERGE_QUEUE_NOT_FOUND
        - PR_ALREADY_ENQUEUED
        - PR_NOT_ENQUEUEABLE
        - UPSTREAM_ERROR
        - VERIFICATION_RUN_NOT_FOUND
        - PR_NOT_FOUND
        - REPOSITORY_NOT_FOUND
        - IMPACTED_TARGETS_UNSUPPORTED
        - QUEUE_ALREADY_EXISTS
        - QUEUE_HAS_ACTIVE_ENTRIES
        - MODE_CHANGE_IN_PROGRESS
        - PR_NOT_IN_ACTIVE_STATE
        - MERGE_QUEUE_NOT_CREATABLE
        - PR_TESTS_NOT_RESTARTABLE
        - ALLOWED_BOT_SUBMITTERS_FROZEN
        - ESTIMATE_NOT_ENABLED
        - PROVIDER_URL_NOT_FOUND
        - CI_HOST_UNSUPPORTED
        - CI_SCOPE_NOT_FOUND
        - CI_JOB_NOT_FOUND
        - CI_FAILURE_CLUSTER_NOT_FOUND
        - TEST_COLLECTION_NOT_FOUND
        - TEST_NOT_FOUND
        - TEST_UPLOAD_NOT_FOUND
        - TICKETING_NOT_CONFIGURED
        - TICKETING_CREDENTIALS_INVALID
        - TICKETING_SITE_REFUSED
        - TICKET_NOT_FOUND
        - TICKET_LINK_CONFLICT
        - TEST_ALREADY_LINKED
        - TICKET_FIELDS_REJECTED
        - TICKET_CONTENT_UNAVAILABLE
        - TICKETING_CREATE_REFUSED
        - RATE_LIMIT_EXCEEDED
      description: >-
        Stable machine-readable error identifier. Switch on this, never on
        `message`. A shipped code's meaning never changes, but **new codes may
        be added within v2** — treat an unrecognized code as a generic failure
        of its HTTP status rather than failing closed.
      example: MERGE_QUEUE_NOT_FOUND
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: >-
        An API key, sent as `Authorization: Bearer <key>`. An organization admin
        creates one in the Trunk app under **Settings → Developer → API Keys**
        and chooses the merge-queue permissions it holds; a key with none can
        read merge queues but change nothing. Those permissions scope merge
        queue only: every key can use the Flaky Tests and Dynamic CI endpoints,
        including their writes, whatever permissions it holds.
    trunkToken:
      type: apiKey
      in: header
      name: x-trunk-token
      description: >-
        A short-lived first-party token, sent in the `x-trunk-token` header. The
        `trunk` CLI sends one after `trunk auth login`, and the `trunk-io/login`
        action obtains one for a GitHub Actions run.


        **A token from a GitHub Actions run can only upload impacted targets**
        (`PUT /v2/impacted-targets`), for the repository the run belongs to;
        every other endpoint refuses it.

````