Skip to main content

Support Tickets

Read your organization's support tickets — bug reports and feature requests — programmatically.

Every request is scoped to the organization that owns your API key. There is no organization parameter, and no way to reach another organization's tickets.

note

These endpoints pass the responses from our ticketing provider (Atlassian Jira Service Management) through unchanged, which is why they live under /api/v1/atlassian. If we move to a different provider, the new endpoints will live under a new path and these will be deprecated on a published timeline rather than changed underneath you.

Listing tickets

GET /api/v1/atlassian/issues

Returns your organization's tickets, most recently changed first.

ParameterDescription
updated_sinceOnly tickets changed at or after this time. RFC 3339 with an offset, e.g. 2026-08-12T00:00:00Z
created_sinceOnly tickets raised at or after this time. RFC 3339 with an offset
statusopen, closed, or all. Defaults to all
limitTickets per page, 1–100. Defaults to 50
next_page_tokenCursor for the next page, taken from the previous response
curl -H "Authorization: Bearer $TVLABS_API_KEY" \
"https://tvlabs.ai/api/v1/atlassian/issues?status=open&limit=25"

The response is the provider's search body:

{
"issues": [
{
"key": "TVL-675",
"fields": {
"summary": "Unable to log in with valid credentials",
"status": { "name": "Pending Customer Response" },
"created": "2026-08-11T07:29:49.471-0400",
"updated": "2026-08-12T04:57:37.772-0400",
"reporter": { "displayName": "Kiran Reddy" },
"customfield_10051": { "name": "Time to first response", "completedCycles": [] }
}
}
],
"names": { "customfield_10051": "Time to first response" },
"nextPageToken": "CAEaAggD"
}

Ticket fields keep the provider's names, including opaque ids like customfield_10051. The names object maps those ids to their display names, so you can resolve them at runtime instead of hard-coding them. It sits at the top level of the response on this endpoint and on /issues/bulk, alongside issues; on the single-ticket endpoint it sits alongside that ticket's own fields.

To page through results, pass the nextPageToken from one response as next_page_token on the next request. The last page has no nextPageToken. A cursor that has expired or been altered returns 400 rather than 502, so don't retry it — start the walk again from the first page.

Descriptions and comments are not included

Listing responses carry the ticket's metadata and SLA state but not its description or conversation, so walking a large backlog stays fast. Fetch those per ticket once you know which ones you care about.

Syncing changes

updated_since is the intended way to keep a local copy current: list the tickets that changed since your last sync, then fetch the detail for just those.

curl -H "Authorization: Bearer $TVLABS_API_KEY" \
"https://tvlabs.ai/api/v1/atlassian/issues?updated_since=2026-08-12T00:00:00Z"

Two things to know:

  • Comparisons are minute-accurate. A window may return a ticket you already hold. De-duplicate on key together with fields.updated.
  • Timestamps carry the provider's offset, as in the example above. Parse them as RFC 3339 rather than assuming UTC.

Advance your cursor using the largest fields.updated you actually received, not your own clock, so a ticket changed mid-sync isn't skipped.

Fetching many tickets at once

POST /api/v1/atlassian/issues/bulk

Fetches up to 100 tickets by key in a single request, each carrying everything the single-ticket endpoint returns, descriptions included. This is the companion to updated_since: take the keys that changed and pull them in one round trip instead of one request per ticket.

curl -X POST \
-H "Authorization: Bearer $TVLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"keys": ["TVL-675", "TVL-676"]}' \
https://tvlabs.ai/api/v1/atlassian/issues/bulk
{
"issues": [
{
"key": "TVL-675",
"fields": { "summary": "Unable to log in with valid credentials" }
},
{
"key": "TVL-676",
"fields": { "summary": "Playback stalls on 4K streams" }
}
],
"names": { "customfield_10051": "Time to first response" }
}

One names map covers every ticket in the response, at the top level alongside issues, the same as on the listing endpoint.

Tickets that don't exist, and tickets belonging to another organization, are absent from issues; the two cases are indistinguishable. Diff the keys you asked for against the keys you got back to find the gaps.

Comments aren't included — fetch those per ticket.

Fetching one ticket

GET /api/v1/atlassian/issues/:key

Returns a single ticket including its description, plus everything the listing carries.

curl -H "Authorization: Bearer $TVLABS_API_KEY" \
https://tvlabs.ai/api/v1/atlassian/issues/TVL-675

Tickets that belong to another organization return 404, the same as a ticket that doesn't exist.

Fetching a ticket's comments

GET /api/v1/atlassian/issues/:key/comments

Returns the public conversation on a ticket. Internal notes between our support engineers are never included.

ParameterDescription
startIndex of the first comment. Defaults to 0
limitComments per page, 1–100. Defaults to 50
curl -H "Authorization: Bearer $TVLABS_API_KEY" \
https://tvlabs.ai/api/v1/atlassian/issues/TVL-675/comments

Use isLastPage in the response to decide whether to request another page.

Rate limits

These endpoints draw on a quota we share with our ticketing provider, so each organization gets a budget of upstream calls per minute. Listing a page and fetching one ticket each cost one call; fetching a batch, or a ticket's comments, costs two.

Batching is the cheapest way to stay inside the budget: one hundred tickets cost two calls through /issues/bulk and a hundred calls one at a time.

Every response carries your current standing:

HeaderMeaning
ratelimit-limitCalls available per window
ratelimit-remainingCalls left in the current window
ratelimit-resetSeconds until the window resets

Exceeding the budget returns 429 with a retry-after header. If our provider throttles us, you'll get 503, also with retry-after. Back off and retry in both cases; a sync that pauses for the stated interval will finish.

Errors

StatuserrorMeaning
400BadRequestA parameter was malformed — check the message
401Missing or invalid API key
404NotFoundNo such ticket, or it belongs to another organization
404SupportNotConfiguredYour organization isn't linked to a support project yet — contact [email protected]
429TooManyRequestsYour organization's budget is spent
502UpstreamErrorOur ticketing provider could not be reached
503UpstreamRateLimitedOur ticketing provider is throttling us