API Reference

TR-AI REST API

Programmatic access to ticket resolution, analytics, and your knowledge base. RESTful, JSON, and built for developers.

Base URLhttps://api.ticketresolve.ai/v1

All requests require an API key in the Authorization header: "Authorization: Bearer <key>". Generate keys from your dashboard under Settings → API Keys. A key belongs to one trial account, carries one or more scopes, and is shown only once when you create it — revoke it there at any time.

Quick start

Create a key with the resolve scope, then submit a ticket:

curl https://api.ticketresolve.ai/v1/tickets/resolve \
  -H "Authorization: Bearer $TR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"subject": "Cannot log in", "body": "I reset my password but still get an error.", "requester_email": "jo@example.com", "channel": "email"}'

Scopes

readRead tickets, analytics, known issues and knowledge base articles; search the knowledge base.
resolveSubmit tickets for AI resolution.
writeSubmit CSAT feedback on tickets; create and update known issues.
kbAdd, update and delete knowledge base articles.

Errors

Every error response has the same JSON shape. "param" is present when a specific parameter is at fault; quote "request_id" (also sent as the X-Request-Id header on every response) when contacting support.

{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_parameter",
    "message": "subject is required.",
    "param": "subject",
    "request_id": "req_8f3c2a91d04b7e6a5c1f2d33"
  }
}
400invalid_request_errormissing_parameter, invalid_parameter, invalid_json, invalid_body
401authentication_errormissing_api_key, invalid_api_key (unknown or revoked)
403permission_errorinsufficient_scope, automation_blocked (AI paused, trial expired or inactive)
404not_found_errorticket_not_found, article_not_found, route_not_found
409invalid_request_errorticket_not_completed (feedback on a ticket still processing)
413invalid_request_errorpayload_too_large
429rate_limit_errorrate_limit_exceeded
500api_errorinternal_error, resolution_failed — safe to retry

Rate limits

Limits depend on your plan: each API key may make 60 requests per minute on Starter, 120 on Bronze and 600 on Growth Pro (and during the free trial). POST /tickets/resolve allows a quarter of that — 15, 30 and 150 requests per minute. GET /v1/me returns your key's current limits. Limits are token buckets, so short bursts up to the limit are fine. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix time, in seconds, when the bucket is full again). Over the limit you get 429 rate_limit_exceeded with a Retry-After header in seconds.

Pagination

List endpoints take page (default 1) and per_page (default 25, max 100) and return { "object": "list", "data": [...], "pagination": { "page", "per_page", "total", "total_pages", "has_more" } }. Results are newest first.

Dates and bodies

Dates are ISO 8601 — a date (2026-11-01) or a date-time with a time zone (2026-11-01T09:30:00Z) — and timestamps in responses are UTC. A date-only "to" includes that whole day. Request bodies are JSON (Content-Type: application/json), up to 1 MB.

Tickets

POST/tickets/resolveSubmit a ticket for AI resolutionscope: resolve

Runs the ticket through the resolution engine and returns the drafted reply, a confidence score, the sources used and a recommended action — nothing is posted to your help desk; you decide what to send. Tickets up to 2,000 tokens (about 8,000 characters of subject + body) are answered synchronously with 200. Longer tickets return 202 with status "processing": poll GET /tickets/{id} or subscribe to the ticket.resolved, ticket.triaged and ticket.review_pending webhooks. Your account settings apply: if AI automation is paused or the trial has expired the request fails with 403 automation_blocked, confidence thresholds decide resolved / pending / escalated, and in assisted mode resolved tickets come back with recommended_action "hold_for_review". recommended_action is one of send_reply, hold_for_review, escalate_with_reply (hand to a human and send the reply as an acknowledgement) or escalate (hand to a human, send nothing).

Parameters
subjectstringbodyrequiredTicket subject line (max 1,000 characters)
bodystringbodyrequiredFull ticket body text (max 100,000 characters)
requester_emailstringbodyoptionalCustomer email for history lookup
requester_namestringbodyoptionalCustomer name, used to personalise the reply
channelstringbodyoptionalSource channel: email, chat, web, api (default: api)
Example response
{
  "id": "tkt_5b1e0c9a7d2f4e8a91c3b6d0",
  "object": "ticket",
  "source": "api",
  "status": "completed",
  "resolution_type": "resolved",
  "recommended_action": "send_reply",
  "subject": "Cannot log in",
  "channel": "email",
  "requester_email": "jo@example.com",
  "confidence_score": 0.912,
  "reply": {
    "subject": "Re: Cannot log in",
    "body": "Hi Jo, after a password reset you need to sign out of all devices…"
  },
  "escalation_reason": null,
  "internal_note": null,
  "sources": [
    {
      "id": 48213,
      "type": "kb",
      "similarity": 0.83,
      "url": "https://help.example.com/articles/reset-password"
    }
  ],
  "resolution_log_id": 90211,
  "csat": null,
  "error": null,
  "created_at": "2026-11-05T09:30:12.000Z",
  "completed_at": "2026-11-05T09:30:16.000Z"
}
GET/tickets/{id}Get ticket resolution detailsscope: read

Retrieve the full resolution record for a ticket, including confidence score, drafted reply, sources used, escalation reason and internal note (escalations only), and CSAT if recorded. status is processing, completed or failed; resolution_type is resolved, escalated or pending (held for agent review), or null until completed.

Parameters
idstringpathrequiredTicket ID returned from POST /tickets/resolve (tkt_…) or listed by GET /tickets (tkt_… or log_… for help-desk tickets)
GET/ticketsList resolved ticketsscope: read

Paginated list of all tickets processed by TR-AI — from your help desk and from the API — newest first, with filtering by date range, resolution type, and confidence band. Help-desk tickets have ids of the form log_<n>, source set to the help desk, and recommended_action null. API tickets still processing (or failed) are not listed; fetch them by id.

Parameters
fromISO 8601 datequeryoptionalStart of date range
toISO 8601 datequeryoptionalEnd of date range (a date-only value includes that day)
resolution_typestringqueryoptionalresolved | escalated | pending
min_confidencenumberqueryoptionalLowest confidence score to include (0–1)
max_confidencenumberqueryoptionalHighest confidence score to include (0–1)
pageintegerqueryoptionalPage number (default: 1)
per_pageintegerqueryoptionalResults per page (default: 25, max: 100)
POST/tickets/{id}/feedbackRecord customer satisfaction (CSAT)scope: write

Record the customer's 1–5 satisfaction rating for a processed ticket. One rating per ticket: submitting again replaces it. Ratings feed the CSAT figures in /analytics. Returns 409 ticket_not_completed while the ticket is still processing.

Parameters
idstringpathrequiredTicket ID (tkt_… or log_…)
ratingintegerbodyrequired1 (very dissatisfied) to 5 (very satisfied)
commentstringbodyoptionalFree-text comment (max 2,000 characters)
Example response
{
  "object": "feedback",
  "ticket_id": "tkt_5b1e0c9a7d2f4e8a91c3b6d0",
  "rating": 5,
  "comment": "Fixed it, thanks!",
  "submitted_at": "2026-11-05T10:02:44.000Z"
}

Analytics

GET/analytics/summaryGet automation summary metricsscope: read

Returns ticket counts, automation rate (resolved ÷ total), average confidence score, average resolution time, CSAT breakdown (from ratings recorded with POST /tickets/{id}/feedback) and estimated time savings (8 agent-minutes per AI-resolved ticket) for a given period. Rolling periods end now.

Parameters
periodstringqueryoptional7d | 30d | 90d | custom (default: 30d)
fromISO 8601 datequeryoptionalRequired when period=custom
toISO 8601 datequeryoptionalRequired when period=custom (range up to 366 days)
Example response
{
  "object": "analytics_summary",
  "period": "30d",
  "from": "2026-10-06T09:30:00.000Z",
  "to": "2026-11-05T09:30:00.000Z",
  "tickets": {
    "total": 1240,
    "resolved": 868,
    "escalated": 310,
    "pending": 62
  },
  "automation_rate": 0.7,
  "avg_confidence_score": 0.842,
  "avg_resolution_time_ms": 4210,
  "csat": {
    "responses": 212,
    "average": 4.41,
    "satisfaction_rate": 0.877,
    "breakdown": {
      "1": 6,
      "2": 8,
      "3": 12,
      "4": 58,
      "5": 128
    }
  },
  "time_saved": {
    "hours": 115.7,
    "minutes_per_resolved_ticket": 8
  }
}
GET/analytics/trendsGet daily metric trendsscope: read

Daily time-series data (UTC days) for automation rate, ticket volume, CSAT score or average resolution time (ms). Days without data have value null (0 for ticket_volume). Useful for building custom dashboards.

Parameters
metricstringqueryrequiredautomation_rate | ticket_volume | csat | resolution_time
fromISO 8601 datequeryrequiredStart date
toISO 8601 datequeryrequiredEnd date (inclusive; range up to 366 days)
Example response
{
  "object": "analytics_trend",
  "metric": "automation_rate",
  "unit": "ratio",
  "from": "2026-11-01T00:00:00.000Z",
  "to": "2026-11-04T00:00:00.000Z",
  "data": [
    {
      "date": "2026-11-01",
      "value": 0.68
    },
    {
      "date": "2026-11-02",
      "value": 0.72
    },
    {
      "date": "2026-11-03",
      "value": null
    }
  ]
}

Knowledge Base

POST/knowledge/articlesAdd a knowledge base articlescope: kb

Push a new article directly to TR-AI's knowledge index. It is chunked, embedded and indexed before the response returns (status "indexed"), so the next ticket can use it. If indexing fails the article is saved with status "pending" and indexing is retried automatically on your next ticket or knowledge base request (at most once a minute). Articles pushed through the API survive knowledge base re-crawls: they are re-indexed the same way after a re-crawl.

Parameters
titlestringbodyrequiredArticle title (max 500 characters)
bodystringbodyrequiredArticle body in plain text or Markdown ("## " headings become sections; max 200,000 characters)
tagsstring[]bodyoptionalCategory tags (max 20), added to the indexed text so tickets using those terms match the article
urlstringbodyoptionalPublic URL of the article, cited as its source
Example response
{
  "id": "art_0f6c2b8e41d9a7c35e2b1f90",
  "object": "article",
  "title": "Refund policy",
  "tags": [
    "billing"
  ],
  "url": "https://help.example.com/refunds",
  "status": "indexed",
  "chunk_count": 2,
  "created_at": "2026-11-05T09:41:00.000Z",
  "updated_at": "2026-11-05T09:41:00.000Z"
}
GET/knowledge/articlesList knowledge base articlesscope: read

Paginated list of the articles you pushed through the API (crawled help-center pages are not listed; use /knowledge/search to query everything).

Parameters
pageintegerqueryoptionalPage number (default: 1)
per_pageintegerqueryoptionalResults per page (default: 25, max: 100)
GET/knowledge/articles/{id}Get a knowledge base articlescope: read

Returns the article, including its body.

Parameters
idstringpathrequiredArticle ID
PUT/knowledge/articles/{id}Update a knowledge base articlescope: kb

Replace an article's title, body, tags and url (send them all; omitted tags and url are cleared). The article is re-indexed before the response returns.

Parameters
idstringpathrequiredArticle ID
titlestringbodyrequiredArticle title
bodystringbodyrequiredArticle body in plain text or Markdown
tagsstring[]bodyoptionalCategory tags
urlstringbodyoptionalPublic URL of the article
DELETE/knowledge/articles/{id}Remove a knowledge base articlescope: kb

Remove an article from the knowledge index. Takes effect immediately.

Parameters
idstringpathrequiredArticle ID
Example response
{
  "id": "art_0f6c2b8e41d9a7c35e2b1f90",
  "object": "article",
  "deleted": true
}
GET/knowledge/searchSearch the knowledge basescope: read

Semantic search over everything the AI answers from — crawled help-center pages and API articles — returning the best-matching passages.

Parameters
qstringqueryrequiredSearch text (max 2,000 characters)
limitintegerqueryoptionalNumber of results (default: 5, max: 20)
Example response
{
  "object": "list",
  "query": "how do refunds work",
  "data": [
    {
      "object": "search_result",
      "chunk_id": 48213,
      "article_id": "art_0f6c2b8e41d9a7c35e2b1f90",
      "source_url": "https://help.example.com/refunds",
      "similarity": 0.81,
      "content": "[Refund policy] Refunds are available within 30 days…"
    }
  ]
}

Account

GET/meInspect the current API keyscope: any

Returns the key's name, prefix, scopes, trial account and rate limits. Handy for checking a key works.

Example response
{
  "object": "api_key",
  "name": "Production backend",
  "prefix": "tr_k3j9x2ab7q",
  "scopes": [
    "read",
    "resolve"
  ],
  "trial_id": 1042,
  "rate_limits": {
    "default": 120,
    "resolve": 30
  }
}

Known issues

GET/known-issuesList known issuesscope: read

Known issues your team is working on (status=active, the default), all of them (status=all) or one stage (investigating, identified, fix_in_progress, monitoring, resolved, wont_fix). Internal notes, message templates and affected customers are never returned.

Parameters
statusstringqueryoptionalactive (default), all, or a status.
Example response
{
  "object": "list",
  "data": [
    {
      "object": "known_issue",
      "id": 12,
      "title": "CSV export fails with ERR_EXPORT_504",
      "description": "Large CSV exports time out.",
      "status": "fix_in_progress",
      "severity": "high",
      "category": "troubleshooting",
      "product_area": "Reports",
      "workaround": "Export one month at a time.",
      "eta": "Friday",
      "keywords": [
        "ERR_EXPORT_504",
        "csv export"
      ],
      "examples": [
        "My export never finishes"
      ],
      "matching_enabled": true,
      "public": false,
      "affected_count": 37,
      "created_at": "2026-11-02T09:12:00.000Z",
      "updated_at": "2026-11-03T15:40:00.000Z",
      "resolved_at": null
    }
  ]
}
GET/known-issues/{id}Get a known issuescope: read

One known issue by id.

Parameters
idintegerpathrequiredKnown issue id.
Example response
{
  "object": "known_issue",
  "id": 12,
  "title": "CSV export fails with ERR_EXPORT_504",
  "description": "Large CSV exports time out.",
  "status": "fix_in_progress",
  "severity": "high",
  "category": "troubleshooting",
  "product_area": "Reports",
  "workaround": "Export one month at a time.",
  "eta": "Friday",
  "keywords": [
    "ERR_EXPORT_504",
    "csv export"
  ],
  "examples": [
    "My export never finishes"
  ],
  "matching_enabled": true,
  "public": false,
  "affected_count": 37,
  "created_at": "2026-11-02T09:12:00.000Z",
  "updated_at": "2026-11-03T15:40:00.000Z",
  "resolved_at": null
}
POST/known-issuesCreate a known issuescope: write

Creates a known issue (e.g. from your incident tooling). Matching tickets get the stage message instead of an AI answer, are linked as affected and are updated automatically when the issue is resolved. Give a description, examples or keywords so tickets can match. Message templates are drafted by AI in your brand voice; edit them in the dashboard.

Parameters
titlestringbodyrequiredShort title.
descriptionstringbodyoptionalWhat customers are told the problem is.
examplesstring[]bodyoptionalHow customers describe it.
keywordsstring[]bodyoptionalKeywords and error codes (an error code match is decisive).
severitystringbodyoptionallow, medium (default), high, critical.
workaroundstringbodyoptionalShared with affected customers.
etastringbodyoptionalExpected fix, e.g. "Friday".
Example response
{
  "object": "known_issue",
  "id": 12,
  "title": "CSV export fails with ERR_EXPORT_504",
  "description": "Large CSV exports time out.",
  "status": "fix_in_progress",
  "severity": "high",
  "category": "troubleshooting",
  "product_area": "Reports",
  "workaround": "Export one month at a time.",
  "eta": "Friday",
  "keywords": [
    "ERR_EXPORT_504",
    "csv export"
  ],
  "examples": [
    "My export never finishes"
  ],
  "matching_enabled": true,
  "public": false,
  "affected_count": 37,
  "created_at": "2026-11-02T09:12:00.000Z",
  "updated_at": "2026-11-03T15:40:00.000Z",
  "resolved_at": null
}
PATCH/known-issues/{id}Update a known issuescope: write

Edit fields or move the stage with status. Setting status to resolved or wont_fix sends the final message to every affected customer (once each, rate limited, retried on failure). For other stages, pass notify: true to update affected customers too.

Parameters
statusstringbodyoptionalNew stage.
notifybooleanbodyoptionalSend the stage message to affected customers (non-final stages).
Example response
{
  "object": "known_issue",
  "id": 12,
  "title": "CSV export fails with ERR_EXPORT_504",
  "description": "Large CSV exports time out.",
  "status": "fix_in_progress",
  "severity": "high",
  "category": "troubleshooting",
  "product_area": "Reports",
  "workaround": "Export one month at a time.",
  "eta": "Friday",
  "keywords": [
    "ERR_EXPORT_504",
    "csv export"
  ],
  "examples": [
    "My export never finishes"
  ],
  "matching_enabled": true,
  "public": false,
  "affected_count": 37,
  "created_at": "2026-11-02T09:12:00.000Z",
  "updated_at": "2026-11-03T15:40:00.000Z",
  "resolved_at": null
}

Webhooks (custom connectors)

On the Growth Pro plan you can register HTTPS endpoints under Settings → Webhooks and choose which events they receive. Each event is POSTed as JSON: { "id": "evt_…", "type": "ticket.resolved", "created_at": "…", "trial_id": 1042, "data": { … } }. Ticket events carry the same ticket object the API returns. Headers: X-TR-Event, X-TR-Event-Id, X-TR-Delivery-Id and X-TR-Signature. Respond with any 2xx within 10 seconds; redirects are not followed. Failed deliveries are retried 5 more times — after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours — and every attempt is listed under Recent deliveries, where you can also send a test ("ping") event.

ticket.resolvedA ticket was resolved by the AI (reply ready or sent).
ticket.triagedA ticket was escalated to a human.
ticket.review_pendingA drafted reply is waiting for agent review.
review.approvedAn agent approved a drafted reply.
review.rejectedAn agent rejected a drafted reply.
delivery.failedPosting a reply to the help desk failed.
kb.article.indexedA knowledge base article pushed through the API was indexed.
kb.article.deletedA knowledge base article pushed through the API was removed.
known_issue.createdA known issue was created.
known_issue.updatedA known issue changed (status, details or matching).
known_issue.resolvedA known issue was resolved; affected customers are being updated.

Verifying signatures

X-TR-Signature is "t=<unix seconds>,v1=<hex>", where v1 is the HMAC-SHA256 of "<t>.<raw request body>" keyed with the endpoint's signing secret (whsec_…). Recompute it over the raw body, compare in constant time, and reject timestamps older than 5 minutes.

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  return parts.v1?.length === expected.length && timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}

Need an API key?

API access is included on every plan (60, 120 or 600 requests per minute). Webhooks (custom connectors) are part of Growth Pro.

View pricing