Migrate with an agent
To have a coding agent do the migration, run it from the repository that calls v1 and give it this prompt:.md to its URL, and each endpoint’s Markdown includes its OpenAPI definition.
What changes
Step 1: Create an API key
An organization admin creates API keys in the Trunk app: navigate to Settings → Developer → API Keys and click the Create Key button. Store the key right away, since it’s shown only once. See Create an API key for details. Grant the permissions your v1 calls need:
If CI only uploads impacted targets from GitHub Actions, it doesn’t need a key. Log in with the run’s own credential instead, which also works on fork pull requests.
Step 2: Switch the credential and base URL
Step 3: Resolve IDs once
v2 addresses queues and pull requests by ID. IDs don’t change, so look each one up once and store it. Queue and repository IDs. List your organization’s queues. Match each queue’srepositoryUrl and targetBranch against what your v1 code sends, and keep its id and repositoryId. Pull request lookups need the repositoryId:
hasMore is true, follow nextCursor, or narrow the list to one repository. Get its ID from GET /v2/repositories?providerUrl=https://github.com/acme/widgets (.data[0].id), then call GET /v2/merge-queues?repositoryId=<id>&targetBranch=main.
Pull request ID. Resolve one number, or up to 50 comma-separated numbers (set limit=50):
number directly, and its response includes the id for polling.
Endpoint map
Examples
Automate queue actions
Submit a pull request with a priority, from CI:priority took either a number or a name. In v2, send a number as priorityValue or a name as priorityName.
Pause a queue during an incident, and resume it afterward:
Build dashboards
Prometheus. Change your scrape job’s URL and credential, and replace anyrepo/repoHost parameters with repositoryId. Metric names, labels, and label values are unchanged, including snake_case state values such as tests_passed, so dashboards and alerts keep working. Don’t apply this guide’s rename tables to PromQL queries or alert rules. Prefer one organization-wide target over one per queue (see Scrape configuration):
testing-results with nextCursor. A request covers at most 45 days, which is also how long results are kept. Each row carries pullRequestNumber, testingDurationMs, outcome, and the verificationRunId for its details:
concludedAfter and concludedBefore replace v1’s since and until:
Renamed values
v2 enum values are camelCase. Some drop or change words, so map them with these tables, not by changing case.Queue entry states
v1 accepted either case. v2 accepts only the camelCase value and rejects any other spelling, including
TESTS_PASSED and tests_passed, with 400.
Queues
Stateswitching_modes becomes switchingModes; running, paused, and draining are unchanged. Mode single becomes queue, and parallel becomes graph. Sending a v1 mode spelling to create or switch-modes returns 400, but v2 can’t catch a swap. queue and graph are both valid, so a wrong mapping switches the queue to the other mode.
Pull request records
v1’s pull request records (getSubmittedPullRequest, and the entries of listPullRequests and getQueue) were flat. v2 puts the pull request’s fields at the top level, where id is the pull request’s ID, and its place in the queue under queueEntry. isCurrentlySubmittedToQueue is unchanged. state, stateChangedAt, priorityValue, priorityName, usedDefaultPriorityName, and readiness move under queueEntry, with the renames below. priorityValue and priorityName are optional in v2. v2’s top-level state is the pull request’s state on GitHub, not its queue state.
The new
queueEntry.readiness.mergeabilityDetails.reasons[] explains the mergeability. Its code values include all of blockReasons’ codes below, plus others such as draft and closed.
Timelines
v1’stimeline is now the queue-entry history. Its shape changes as well as its values, so update the parser field by field:
Testing results
Verification runs
v1’s testing details are a v2 verification run:checkSuites[].checkRuns[]andstatusChecks[]become one flatcheckslist. Each check’skindsays which it was, and GitHub’s raw values are inproviderStatusandproviderConclusion. A status check’scontextis itsname.dependentPrs, a list of numbers, becomesdependentPullRequests, a list of objects withid,number,sha,title, andurl.- Each of
testedPullRequestscarriesid,number,sha, andurlin place of v1’sprNumberandprUrl. impactedTargetsandimpactedTargetsForTestedPrsbecomeimpactedTargets.allandimpactedTargets.testedPullRequestsOnly. Each is an object withimpactsAllTargets,impactedTargets, andtotalImpactedTargets, not a list.statusandrequiredStatusesSourcevalues are camelCase.
Renamed settings
PATCH /v2/merge-queues/{id}/config takes the same settings as v1’s updateQueue, with these changes:
The same names appear under
config when you read a queue. v2 also bounds values v1 accepted without limit, such as concurrency (at most 1000) and list settings (at most 100 entries), so a queue configured past a bound through v1 can’t be written back unchanged.
Behavior changes
-
Resubmitting doesn’t update a queued pull request. v1’s
submitPullRequestrewrote the priority and batching of a pull request already in the queue. v2 returns409 PR_ALREADY_ENQUEUEDand changes nothing. UsePOST /v2/pull-requests/{id}/reprioritizeorPATCH /v2/pull-requests/{id}/queue-entryinstead. -
requiredStatuses: []clears the list. In v1 an empty list was ignored, and you cleared it withdeleteRequiredStatuses: true. -
The queue’s pull requests come in three lists. v1’s
getQueuereturned oneenqueuedPullRequestsarray./orderreturns:batches, each with a verification run, astate, and itspullRequests.pending, admitted entries in the order the queue will consider them, each holding its pull request inpullRequest.awaitingAdmission, pull requests, truncated.awaitingAdmissionTotalis the full count.
id,number,title,sha, andurl. For state, priority, or author, match byidagainstGET /v2/merge-queues/{id}/pull-requests?state=pending,testing,testsPassed,pendingFailure. -
List order is not queue order.
GET /v2/merge-queues/{id}/pull-requestsis ordered by when each pull request was first submitted, newest first. Use/orderfor queue position. -
Lookups don’t tell you “not in this queue”. v1’s
getSubmittedPullRequestreturned404, andgetSubmittedPullRequestsanotFoundlist, for pull requests never submitted to that queue.GET /v2/pull-requestsfinds any pull request Trunk knows in the repository. Treat a result with noqueueEntry, or whosequeueEntry.mergeQueueIdis another queue, as not found. For a batch, also compare the numbers you sent with the ones returned. -
Timelines default to oldest first. Send
direction=newestfor v1’s order, where the current state comes first. -
Errors have one shape, but not every response has it. Switch on
error.codewhen the body has anerrorobject.401,403for some credential problems,429, and503come from Trunk’s edge, and the API answers413and504as plain text. These may lack theerrorobject, so branch on their status code.403 INSUFFICIENT_PERMISSIONSmeans the key lacks a permission. See Errors. -
Read back before retrying a write. v2 doesn’t honor
Idempotency-Key, so a blind retry can, for example, restart a pull request’s tests twice. Reads are safe to retry. See Retrying. -
Pull request reads have their own, lower limit.
GETrequests on paths starting with/v2/pull-requestsare limited to 5 per second per API key or user. Most other requests share 100 per second per organization. A v1 loop that polls many pull requests may need to slow down. See Rate limits.
Finding a verification run
AtestRunId stored from v1 is the same ID in v2. If it ends in v1’s -bisection suffix, remove the suffix, which v2 doesn’t accept. What remains is that bisection run’s own ID. Otherwise, take the ID from a response you already have:
verificationRunIdon a testing result, a queue-entry history transition, or a batch in/orderqueueEntry.verifiedByVerificationRunIdon a merged pull requestidon each run from a pull request’s or a queue’s verification runs
Checklist
- An API key exists with the permissions your calls need, stored as a secret.
- No Merge Queue request uses
api.trunk.io/v1or thex-api-tokenheader. Other v1 calls, such as Flaky Tests and test-result uploads, are unchanged. - Queue and pull request IDs are resolved once and stored, not looked up per request.
- Every enum value comes from the tables above, including values stored in your own database or config.
- Every list request sets
limitand followsnextCursoruntilhasMoreisfalse, with the other parameters unchanged, including batch lookups. - Response parsing reads queue fields from
queueEntry, and no code stores the pull request’sidwhere it stored v1’s queue entryid. - Timeline and testing-result parsing uses the v2 field names above.
-
notReadyTimeoutHoursvalues are converted to minutes. - Code that resubmitted a queued pull request to change its priority now calls
reprioritizeorPATCH …/queue-entry. - Code that sent
requiredStatuses: []meant to clear the list. - Error handling switches on
error.codewhen the body has anerrorobject, otherwise on the status code, and treats404as possibly “not yours”.