# TuraDesk API Reference

**Complete API documentation for TuraDesk help desk and support ticketing.**

---

## Base URL

```
https://www.turadesk.com/api/v1
```

---

## Authentication

All endpoints require authentication via Bearer token. Use your TuraLogin API key.

**Header:**
```http
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```

**Getting an API Key:**
1. Sign up at https://www.turalogin.com
2. Create an app in the dashboard
3. Copy your API key from https://www.turalogin.com/dashboard/keys

**API Key Format:**
- Test: `tl_test_xxxxxxxxxxxxxxxx`
- Production: `tl_live_xxxxxxxxxxxxxxxx`

---

## Endpoints

### POST /desk/tickets

Create a new support ticket.

**Request:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `subject` | string | Yes | Ticket subject (max 256 chars) |
| `body` | string | Yes | Ticket body/description |
| `email` | string | Yes | Requester email address |
| `priority` | string | No | `low`, `medium`, `high`, `urgent` (default: `medium`) |
| `tags` | string[] | No | Array of tag strings |

**Request Schema:**
```typescript
{
  subject: string;
  body: string;
  email: string;
  priority?: "low" | "medium" | "high" | "urgent";
  tags?: string[];
}
```

**Success Response (201):**
```json
{
  "id": "tkt_abc123xyz",
  "subject": "Login issue",
  "body": "User cannot sign in after password reset",
  "priority": "high",
  "status": "open",
  "email": "customer@example.com",
  "tags": ["login", "urgent"],
  "assignee": null,
  "createdAt": "2026-02-17T10:00:00Z",
  "updatedAt": "2026-02-17T10:00:00Z"
}
```

**Response Schema:**
```typescript
{
  id: string;
  subject: string;
  body: string;
  priority: string;
  status: string;
  email: string;
  tags: string[];
  assignee: string | null;
  createdAt: string;  // ISO 8601
  updatedAt: string;  // ISO 8601
}
```

---

### GET /desk/tickets

List tickets with optional filters and pagination.

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `status` | string | No | Filter: `open`, `pending`, `resolved`, `closed` |
| `assignee` | string | No | Filter by assignee ID or email |
| `priority` | string | No | Filter: `low`, `medium`, `high`, `urgent` |
| `tag` | string | No | Filter by tag (exact match) |
| `limit` | number | No | Max results (default: 20, max: 100) |
| `cursor` | string | No | Pagination cursor from previous response |

**Success Response (200):**
```json
{
  "tickets": [
    {
      "id": "tkt_abc123",
      "subject": "Login issue",
      "priority": "high",
      "status": "open",
      "email": "user@example.com",
      "tags": ["login"],
      "assignee": null,
      "createdAt": "2026-02-17T10:00:00Z"
    }
  ],
  "nextCursor": "eyJpZCI6InRrdF9hYmMxMjMifQ==",
  "hasMore": true
}
```

**Response Schema:**
```typescript
{
  tickets: Array<{
    id: string;
    subject: string;
    priority: string;
    status: string;
    email: string;
    tags: string[];
    assignee: string | null;
    createdAt: string;
  }>;
  nextCursor?: string;
  hasMore: boolean;
}
```

---

### GET /desk/tickets/:id

Get a single ticket with its comments.

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Ticket ID (e.g. `tkt_abc123xyz`) |

**Success Response (200):**
```json
{
  "id": "tkt_abc123xyz",
  "subject": "Login issue",
  "body": "User cannot sign in",
  "priority": "high",
  "status": "open",
  "email": "user@example.com",
  "tags": ["login", "urgent"],
  "assignee": null,
  "createdAt": "2026-02-17T10:00:00Z",
  "updatedAt": "2026-02-17T10:00:00Z",
  "comments": [
    {
      "id": "cmt_xyz789",
      "body": "Investigating now.",
      "internal": false,
      "author": "agent@support.com",
      "createdAt": "2026-02-17T10:15:00Z"
    }
  ]
}
```

**Response Schema:**
```typescript
{
  id: string;
  subject: string;
  body: string;
  priority: string;
  status: string;
  email: string;
  tags: string[];
  assignee: string | null;
  createdAt: string;
  updatedAt: string;
  comments: Array<{
    id: string;
    body: string;
    internal: boolean;
    author: string;
    createdAt: string;
  }>;
}
```

---

### PATCH /desk/tickets/:id

Update a ticket (status, assignee, priority, tags).

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Ticket ID |

**Request Body (all fields optional):**

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `open`, `pending`, `resolved`, `closed` |
| `assignee` | string | Assignee ID or email |
| `priority` | string | `low`, `medium`, `high`, `urgent` |
| `tags` | string[] | Replace tags with new array |

**Request Schema:**
```typescript
{
  status?: "open" | "pending" | "resolved" | "closed";
  assignee?: string;
  priority?: "low" | "medium" | "high" | "urgent";
  tags?: string[];
}
```

**Success Response (200):** Returns updated ticket object (same schema as GET /desk/tickets/:id).

---

### DELETE /desk/tickets/:id

Close/delete a ticket.

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Ticket ID |

**Success Response (200):**
```json
{
  "success": true,
  "message": "Ticket closed"
}
```

---

### POST /desk/tickets/:id/comments

Add a comment to a ticket.

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Ticket ID |

**Request Body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `body` | string | Yes | Comment text |
| `internal` | boolean | No | If true, only visible to agents (default: false) |

**Request Schema:**
```typescript
{
  body: string;
  internal?: boolean;
}
```

**Success Response (201):**
```json
{
  "id": "cmt_xyz789",
  "body": "Issue resolved. User can now log in.",
  "internal": false,
  "author": "agent@support.com",
  "createdAt": "2026-02-17T10:30:00Z"
}
```

**Response Schema:**
```typescript
{
  id: string;
  body: string;
  internal: boolean;
  author: string;
  createdAt: string;
}
```

---

### GET /desk/stats

Get ticket statistics.

**Success Response (200):**
```json
{
  "byStatus": {
    "open": 12,
    "pending": 5,
    "resolved": 48,
    "closed": 120
  },
  "avgResolutionTimeHours": 4.2,
  "totalTickets": 185
}
```

**Response Schema:**
```typescript
{
  byStatus: {
    open: number;
    pending: number;
    resolved: number;
    closed: number;
  };
  avgResolutionTimeHours: number;
  totalTickets: number;
}
```

---

## Error Codes

### HTTP Status Codes

| Status | Meaning | When It Occurs |
|--------|---------|----------------|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request parameters |
| 401 | Unauthorized | Missing or invalid API key |
| 404 | Not Found | Ticket or resource doesn't exist |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server-side error |

### Application Error Messages

| Error Message | Status | Solution |
|--------------|--------|----------|
| `subject is required` | 400 | Include `subject` in request body |
| `body is required` | 400 | Include `body` in request body |
| `email is required` | 400 | Include `email` in request body |
| `Invalid email address` | 400 | Use valid email format |
| `Invalid priority` | 400 | Use `low`, `medium`, `high`, or `urgent` |
| `body is required` (comment) | 400 | Include `body` in comment request |
| `Unauthorized` | 401 | Check API key is correct and in header |
| `Ticket not found` | 404 | Verify ticket ID exists |
| `Too many requests` | 429 | Wait and retry (see Retry-After header) |
| `Internal server error` | 500 | Contact support |

### Error Response Schema

All errors follow this schema:

```typescript
{
  error: string;      // Error message
  hint?: string;      // Suggestion to fix (optional)
}
```

**Example:**
```json
{
  "error": "email is required",
  "hint": "Include the requester's email address in the request body"
}
```

---

## Rate Limits

| Scope | Limit | Window |
|-------|-------|--------|
| API requests | 100 | Per minute |
| Ticket creation | 50 | Per minute |

### Rate Limit Headers

Responses include rate limit headers:

```http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1738260000
```

### Exceeding Limits

When you exceed rate limits:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 45
Content-Type: application/json

{
  "error": "Too many requests",
  "retryAfter": 45,
  "hint": "Rate limit exceeded. Try again in 45 seconds."
}
```

---

## Pagination

List endpoint (`GET /desk/tickets`) uses cursor-based pagination.

**Request:**
```http
GET /desk/tickets?limit=20&cursor=eyJpZCI6InRrdF9hYmMxMjMifQ==
```

**Response:**
```json
{
  "tickets": [...],
  "nextCursor": "eyJpZCI6InRrdF9kZWZ0NDU2In0=",
  "hasMore": true
}
```

**Pagination format:**
- `limit` — Max items per page (default: 20, max: 100)
- `cursor` — Opaque string from previous response's `nextCursor`
- `hasMore` — `true` if more results exist
- When `hasMore` is `false`, `nextCursor` may be omitted

**Example usage:**
```javascript
let cursor = null;
let allTickets = [];

do {
  const params = new URLSearchParams({ limit: 50 });
  if (cursor) params.set('cursor', cursor);
  
  const res = await fetch(`/desk/tickets?${params}`, {
    headers: { 'Authorization': `Bearer ${API_KEY}` },
  });
  const data = await res.json();
  
  allTickets = allTickets.concat(data.tickets);
  cursor = data.hasMore ? data.nextCursor : null;
} while (cursor);
```

---

## Support

**Documentation:**
- 📖 [SKILL.md](https://www.turadesk.com/SKILL.md)
- 📖 [AGENTS.md](https://www.turadesk.com/AGENTS.md)
- 📖 [QUICKSTART.md](https://www.turadesk.com/QUICKSTART.md)

**Dashboard:**
- 🌐 https://www.turadesk.com
- 🔑 API Keys: https://www.turalogin.com/dashboard/keys

---

**Last Updated:** February 2026  
**API Version:** 1.0.0
