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

# Estimate when a queued pull request will merge

> Returns a live estimate for this pull request's queue entry: when it is expected to merge, when it starts and finishes testing, and how many pull requests it is waiting behind. An entry in `pendingFailure` instead reports when that failure is expected to resolve (`kind: pendingFailureResolution`) and which pull requests it is waiting on.

The estimate is computed on each request by simulating the queue forward, so it changes as the queue moves. Read it when the answer is needed rather than in a tight loop: each call runs a fresh simulation, and like the other pull-request reads it is rate-limited per caller. When no estimate can be given — a queue with too little test history, a pull request that is not ready yet, a paused queue — the response says why (`status: unavailable`, `reason`).

Not every queue serves estimates yet: one that does not answers 404 `ESTIMATE_NOT_ENABLED`.



## OpenAPI

````yaml /openapi-v2.json get /v2/pull-requests/{id}/queue-entry-estimate
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/pull-requests/{id}/queue-entry-estimate:
    get:
      tags:
        - pullRequests
      summary: Estimate when a queued pull request will merge
      description: >-
        Returns a live estimate for this pull request's queue entry: when it is
        expected to merge, when it starts and finishes testing, and how many
        pull requests it is waiting behind. An entry in `pendingFailure` instead
        reports when that failure is expected to resolve (`kind:
        pendingFailureResolution`) and which pull requests it is waiting on.


        The estimate is computed on each request by simulating the queue
        forward, so it changes as the queue moves. Read it when the answer is
        needed rather than in a tight loop: each call runs a fresh simulation,
        and like the other pull-request reads it is rate-limited per caller.
        When no estimate can be given — a queue with too little test history, a
        pull request that is not ready yet, a paused queue — the response says
        why (`status: unavailable`, `reason`).


        Not every queue serves estimates yet: one that does not answers 404
        `ESTIMATE_NOT_ENABLED`.
      operationId: pullRequests.queueEntryEstimate
      parameters:
        - schema:
            type: string
            format: uuid
            description: The pull request's id.
            example: 0f8fad5b-d9cb-469f-a165-70867728950e
          required: true
          description: The pull request's id.
          name: id
          in: path
      responses:
        '200':
          description: The entry's estimate, or why there is none
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QueueEntryEstimate'
        '400':
          description: '`VALIDATION_FAILED`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                VALIDATION_FAILED:
                  summary: VALIDATION_FAILED
                  value:
                    error:
                      code: VALIDATION_FAILED
                      message: 'Invalid input: expected string, received undefined'
        '401':
          description: '`AUTHENTICATION_REQUIRED` | `INVALID_TOKEN`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                AUTHENTICATION_REQUIRED:
                  summary: AUTHENTICATION_REQUIRED
                  value:
                    error:
                      code: AUTHENTICATION_REQUIRED
                      message: No credential was supplied
                INVALID_TOKEN:
                  summary: INVALID_TOKEN
                  value:
                    error:
                      code: INVALID_TOKEN
                      message: The supplied token failed verification
        '403':
          description: '`INSUFFICIENT_PERMISSIONS`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                INSUFFICIENT_PERMISSIONS:
                  summary: INSUFFICIENT_PERMISSIONS
                  value:
                    error:
                      code: INSUFFICIENT_PERMISSIONS
                      message: >-
                        The supplied credential is missing the required
                        permission
        '404':
          description: '`PR_NOT_FOUND` | `ESTIMATE_NOT_ENABLED`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                PR_NOT_FOUND:
                  summary: PR_NOT_FOUND
                  value:
                    error:
                      code: PR_NOT_FOUND
                      message: Pull request not found
                ESTIMATE_NOT_ENABLED:
                  summary: ESTIMATE_NOT_ENABLED
                  value:
                    error:
                      code: ESTIMATE_NOT_ENABLED
                      message: >-
                        Estimates are not enabled for this pull request's merge
                        queue
        '409':
          description: '`PR_NOT_IN_ACTIVE_STATE`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                PR_NOT_IN_ACTIVE_STATE:
                  summary: PR_NOT_IN_ACTIVE_STATE
                  value:
                    error:
                      code: PR_NOT_IN_ACTIVE_STATE
                      message: This pull request is not in an active queue state
        '429':
          description: '`RATE_LIMIT_EXCEEDED`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                RATE_LIMIT_EXCEEDED:
                  summary: RATE_LIMIT_EXCEEDED
                  value:
                    error:
                      code: RATE_LIMIT_EXCEEDED
                      message: Rate limit exceeded; retry after the Retry-After delay
        '500':
          description: '`INTERNAL_ERROR`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                INTERNAL_ERROR:
                  summary: INTERNAL_ERROR
                  value:
                    error:
                      code: INTERNAL_ERROR
                      message: An unexpected error occurred
        '502':
          description: '`UPSTREAM_ERROR`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                UPSTREAM_ERROR:
                  summary: UPSTREAM_ERROR
                  value:
                    error:
                      code: UPSTREAM_ERROR
                      message: An upstream service did not respond
components:
  schemas:
    QueueEntryEstimate:
      oneOf:
        - $ref: '#/components/schemas/AvailableQueueEntryEstimate'
        - $ref: '#/components/schemas/UnavailableQueueEntryEstimate'
      discriminator:
        propertyName: status
        mapping:
          available: '#/components/schemas/AvailableQueueEntryEstimate'
          unavailable: '#/components/schemas/UnavailableQueueEntryEstimate'
      description: >-
        A live estimate for a queued pull request, computed by simulating the
        queue forward from its current state over the queue's recent test
        history, or why there is none. A point-in-time prediction: a new
        submission, a priority change or a failure ahead changes it, so read it
        again when it is needed rather than counting down from one answer.
    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: An unexpected error occurred
          required:
            - code
            - message
      required:
        - error
      description: >-
        The uniform error envelope. Every 4xx and 5xx response in this API has
        this shape.
    AvailableQueueEntryEstimate:
      type: object
      properties:
        status:
          type: string
          enum:
            - available
          example: available
        computedAt:
          type: string
          format: date-time
          description: >-
            When the public API received the estimate from the merge service;
            every other instant is relative to it, so each carries that call's
            latency.
          example: '2026-10-07T15:04:05.000Z'
        kind:
          $ref: '#/components/schemas/QueueEntryEstimateKind'
        estimatedAt:
          type: string
          format: date-time
          description: >-
            For `kind: merge`, the median estimate of when the pull request
            merges: half of comparable entries finish sooner, half later. For
            `kind: pendingFailureResolution`, when the failure is expected to
            resolve — an upper bound, since it waits for every run the failure
            depends on, while a restart or a passing run behind it can resolve
            it sooner.
          example: '2026-10-07T15:24:05.000Z'
        estimatedAtState:
          $ref: '#/components/schemas/QueueEntryEstimateTimeState'
        confidence:
          $ref: '#/components/schemas/QueueEntryEstimateConfidence'
        pullRequestsAhead:
          type: integer
          description: >-
            How many pull requests this one depends on and so must merge behind:
            a count of blockers, not a queue position. In a `graph` queue, pull
            requests in independent lanes are not counted. Past the graph's soft
            cap, before the pull request is admitted, it counts every pull
            request in the queue — an upper bound on its blockers.
          example: 3
        startsTestingAt:
          type: string
          format: date-time
          description: Present while the entry waits for a test slot.
          example: '2026-10-07T15:09:05.000Z'
        startsTestingAtState:
          allOf:
            - $ref: '#/components/schemas/QueueEntryEstimateTimeState'
            - description: 'Present exactly when `startsTestingAt` is: how far to trust it.'
        testsFinishAt:
          type: string
          format: date-time
          description: >-
            Present while the entry waits or tests: when its verification run is
            expected to finish passing, including expected restarts.
          example: '2026-10-07T15:23:05.000Z'
        testsFinishAtState:
          allOf:
            - $ref: '#/components/schemas/QueueEntryEstimateTimeState'
            - description: 'Present exactly when `testsFinishAt` is: how far to trust it.'
        typicalTestDurationSeconds:
          type: integer
          description: >-
            The queue's median duration over its recent passing verification
            runs, in whole seconds rounded down. Absent when the queue has none
            to report.
          example: 900
        waitingForPullRequestNumbers:
          type: array
          items:
            type: integer
          maxItems: 10000
          description: >-
            For `pendingFailureResolution`, the pull requests whose runs the
            failure is waiting on right now; empty otherwise.
          example:
            - 4901
        sampleSize:
          type: integer
          description: >-
            Passing tested pull requests in the queue's last 14 days the
            estimate draws on — one per pull request per verification run, so a
            passing batch of four counts four.
          example: 120
      required:
        - status
        - computedAt
        - kind
        - estimatedAt
        - estimatedAtState
        - confidence
        - pullRequestsAhead
        - waitingForPullRequestNumbers
        - sampleSize
      description: An estimate the merge service could compute.
    UnavailableQueueEntryEstimate:
      type: object
      properties:
        status:
          type: string
          enum:
            - unavailable
          example: unavailable
        computedAt:
          type: string
          format: date-time
          description: >-
            When the public API received the estimate from the merge service;
            every other instant is relative to it, so each carries that call's
            latency.
          example: '2026-10-07T15:04:05.000Z'
        reason:
          $ref: '#/components/schemas/QueueEntryEstimateUnavailableReason'
        sampleSize:
          type: integer
          description: >-
            Present for `insufficientHistory` and `unsupportedQueueState`.
            Passing tested pull requests in the queue's last 14 days the
            estimate draws on — one per pull request per verification run, so a
            passing batch of four counts four.
          example: 3
        samplesRequired:
          type: integer
          description: >-
            Present for `insufficientHistory`: the `sampleSize` an estimate
            needs.
          example: 8
        startsTestingAt:
          type: string
          format: date-time
          description: >-
            Present for `inBisection` while the entry waits for a test slot in
            its bisection.
          example: '2026-10-07T15:09:05.000Z'
        startsTestingAtState:
          allOf:
            - $ref: '#/components/schemas/QueueEntryEstimateTimeState'
            - description: 'Present exactly when `startsTestingAt` is: how far to trust it.'
        testsFinishAt:
          type: string
          format: date-time
          description: >-
            Present for `inBisection` while the entry waits or tests: when its
            bisection run is expected to finish passing. Passing returns the
            entry to the queue rather than merging it.
          example: '2026-10-07T15:23:05.000Z'
        testsFinishAtState:
          allOf:
            - $ref: '#/components/schemas/QueueEntryEstimateTimeState'
            - description: 'Present exactly when `testsFinishAt` is: how far to trust it.'
        typicalTestDurationSeconds:
          type: integer
          description: >-
            The queue's median duration over its recent passing verification
            runs, in whole seconds rounded down. Absent when the queue has none
            to report.
          example: 900
      required:
        - status
        - computedAt
        - reason
      description: Why no estimate could be computed.
    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: INTERNAL_ERROR
    QueueEntryEstimateKind:
      type: string
      enum:
        - merge
        - pendingFailureResolution
      description: >-
        `merge`: `estimatedAt` is when the pull request is expected to merge.
        `pendingFailureResolution`: the entry is in `pendingFailure`, and
        `estimatedAt` is when the failure is expected to resolve — by a restart,
        a bisection, a pass, or a failure — not a merge time.
      example: merge
    QueueEntryEstimateTimeState:
      type: string
      enum:
        - onTrack
        - holding
        - pastTypical
        - pastP95
      description: >-
        How far to trust the instant it accompanies. `onTrack`: a forecast that
        moves closer as time passes. `holding`: not counting down, because the
        queue is waiting on a step it treats as instant (such as a verification
        run that can start now) or the instant is under a 30-second floor; it
        does not mean the instant is near. `pastTypical`: held by a run, this
        entry's or one ahead of it, that is past its typical time, so the
        instant is likely an underestimate; on `testsFinishAtState` the run is
        this entry's own, so compare against `typicalTestDurationSeconds`.
        `pastP95`: only on `testsFinishAtState`, this entry's verification run
        is taking longer than 95% of the queue's recent passing runs.
      example: onTrack
    QueueEntryEstimateConfidence:
      type: string
      enum:
        - low
        - medium
        - high
      description: >-
        From how much history the queue has, lowered while the entry waits, sits
        deep in the queue, is held behind a failure, is bisecting, or has a
        pending failure ahead of it.
      example: medium
    QueueEntryEstimateUnavailableReason:
      type: string
      enum:
        - insufficientHistory
        - notInQueue
        - queueNotRunning
        - unsupportedQueueState
        - estimateFailed
        - inBisection
      description: >-
        `insufficientHistory`: too few passing tested pull requests in the last
        14 days — see `sampleSize` and `samplesRequired`. `notInQueue`: the
        entry is not ready yet, so it holds no position. `queueNotRunning`: the
        queue is paused, draining or switching modes. `unsupportedQueueState`:
        the queue's current shape cannot be estimated, for example a dependency
        cycle or an entry far past the graph's soft cap. `estimateFailed`: the
        estimate could not be computed; retry later. `inBisection`: the entry is
        in a bisection, where passing returns it to the queue rather than
        merging it, and when depends on the rest of the bisection.
      example: insufficientHistory
  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.

````