Complete REST API reference for the osTicket API Endpoints Plugin. This page provides detailed specifications for all 11 endpoints including request/response schemas, parameter definitions, and practical examples.
Ticket Management Endpoints:
Subticket Management Endpoints:
8. GET Parent Ticket
9. GET Child Tickets
10. POST Create Subticket Link
11. DELETE Unlink Subticket
All API endpoints require authentication via API Key sent in the X-API-Key header.
X-API-Key: YOUR_API_KEY_HERE
API Key Permissions:
Each endpoint requires specific permissions enabled on your API key:
| Endpoint | Required Permission |
|---|---|
| POST /tickets | can_create_tickets |
| GET /tickets/:number | can_read_tickets |
| PATCH /tickets/:number | can_update_tickets |
| GET /tickets/search | can_search_tickets |
| DELETE /tickets/:number | can_delete_tickets |
| GET /tickets-stats | can_read_stats |
| GET /tickets-statuses | can_read_stats |
| Subticket Endpoints | can_manage_subtickets |
Configure Permissions:
Admin Panel → Manage → API Keys → [Select Key] → Configure
https://your-domain.com/api
Replace your-domain.com with your osTicket installation domain.
Most GET endpoints support both JSON and XML formats via the URL extension:
.json - JSON response (recommended).xml - XML responseExample:
GET /api/tickets-get.php/123456.json → JSON response
GET /api/tickets-get.php/123456.xml → XML response
| Code | Meaning | Description |
|---|---|---|
200 |
OK | Request successful |
201 |
Created | Resource created successfully |
400 |
Bad Request | Invalid request parameters |
401 |
Unauthorized | Missing or invalid API key |
403 |
Forbidden | API key lacks required permission |
404 |
Not Found | Resource not found |
409 |
Conflict | Resource conflict (e.g., duplicate) |
500 |
Internal Server Error | Server error occurred |
501 |
Not Implemented | Feature/Plugin not available |
Create a new ticket with extended parameters including Markdown formatting, department routing, and subticket linking.
POST /api/tickets.json
Required Permission: can_create_tickets
Content-Type: application/json
X-API-Key: YOUR_API_KEY
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
name |
string | Yes | User's full name | "John Doe" |
email |
string | Yes | User's email address | "john@example.com" |
subject |
string | Yes | Ticket subject | "Login Issue" |
message |
string | Yes | Ticket message body | "I cannot log in..." |
phone |
string | No | User's phone number | "+1 555-1234" |
attachments[] |
file | No | File attachments (multipart/form-data) | @/path/to/file.pdf |
ip |
string | No | Client IP address | "192.168.1.100" |
source |
string | No | Ticket source | "API", "Web", "Email" |
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
format |
string | No | Message format | "text", "html", "markdown" |
departmentId |
string/int | No | Department name or ID | "Support" or 5 |
parentTicketId |
string/int | No | Parent ticket number/ID for subticket | "181752" or 123 |
topicId |
int | No | Help Topic ID | 10 |
priority |
string/int | No | Priority name or ID | "High" or 2 |
duedate |
string | No | Due date (ISO 8601) | "2025-12-31" |
autorespond |
bool | No | Send auto-response email | true, false (default: true) |
"text" - Plain text (default)"html" - HTML formatted text"markdown" - Markdown formatted text (requires Markdown Support Plugin)Success Response (201 Created):
{
"ticket_id": 123456,
"number": "ABC123",
"subject": "Login Issue",
"message": "Ticket created successfully"
}
Error Response (400 Bad Request):
{
"error": "Invalid email address"
}
Error Response (403 Forbidden):
{
"error": "API key not authorized for this operation"
}
Error Response (404 Not Found):
{
"error": "Department 'Support' not found"
}
Error Response (501 Not Implemented):
{
"error": "Markdown plugin not available"
}
curl -X POST "https://osticket.local/api/tickets.json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"email": "john@example.com",
"subject": "Login Issue",
"message": "I cannot access my account",
"phone": "+1 555-1234"
}'
curl -X POST "https://osticket.local/api/tickets.json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Smith",
"email": "jane@example.com",
"subject": "Bug Report: Payment Failed",
"message": "## Problem\n\nPayment fails with error:\n\n```\nERROR: Card declined\n```\n\n**Steps to reproduce:**\n1. Add item to cart\n2. Go to checkout\n3. Enter card details\n4. Click Pay\n\n**Expected:** Payment succeeds\n**Actual:** Error message",
"format": "markdown",
"departmentId": "Billing",
"priority": "High"
}'
curl -X POST "https://osticket.local/api/tickets.json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Developer",
"email": "dev@example.com",
"subject": "Subtask: Database Migration",
"message": "Migrate production database to new server",
"parentTicketId": "181752",
"departmentId": "Development",
"duedate": "2025-11-30"
}'
💡 Recommended Approach for Subtickets:
While the
parentTicketIdparameter above works for creating subtickets during ticket creation, we strongly recommend using the dedicated Subticket Management Endpoints instead:Benefits of dedicated endpoints:
- ✅ More flexible - Link existing tickets retroactively
- ✅ Better control - Separate permissions (
can_manage_subtickets)- ✅ Full management - View parent, list children, unlink relationships
- ✅ Validation - Comprehensive circular dependency checks
- ✅ Easier workflows - Create tickets independently, then organize
Workflow comparison:
# ❌ Old approach: Must know parent ID at creation time curl -X POST "/api/tickets.json" -d '{"parentTicketId": "181752", ...}' # ✅ Recommended: Create tickets, then link them # 1. Create tickets independently parent_id=$(curl -X POST "/api/tickets.json" -d '{...}' | jq -r '.ticket_id') child_id=$(curl -X POST "/api/tickets.json" -d '{...}' | jq -r '.ticket_id') # 2. Link them using dedicated endpoint curl -X POST "/api/tickets-subtickets-create.php" \ -d "{\"parent_id\": $parent_id, \"child_id\": $child_id}"See: POST Create Subticket Link for full documentation.
curl -X POST "https://osticket.local/api/tickets.json" \
-H "X-API-Key: YOUR_API_KEY" \
-F "name=John Doe" \
-F "email=john@example.com" \
-F "subject=Invoice Request" \
-F "message=Please see attached invoice" \
-F "attachments[]=@/path/to/invoice.pdf" \
-F "departmentId=Accounting"
parentTicketId specified, plugin verifies:
format=markdown and Markdown Support Plugin is not installed/active, returns 501 Not Implemented.autorespond: false to suppress auto-response email to user.Retrieve details of a single ticket including all thread entries.
GET /api/tickets-get.php/{number}.{format}
Required Permission: can_read_tickets
X-API-Key: YOUR_API_KEY
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
number |
string/int | Yes | Ticket number or ticket ID | "ABC123" or 123456 |
format |
string | Yes | Response format | "json" or "xml" |
Success Response (200 OK):
{
"ticket_id": 123456,
"number": "ABC123",
"subject": "Login Issue",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1 555-1234",
"status": "Open",
"priority": "Normal",
"department": "IT Support",
"topic": "Technical Support",
"sla": "Standard SLA",
"created": "2025-11-08 10:30:00",
"updated": "2025-11-08 14:20:00",
"duedate": "2025-11-15 10:30:00",
"isoverdue": false,
"source": "API",
"ip": "192.168.1.100",
"thread": [
{
"id": 1,
"type": "message",
"poster": "John Doe",
"body": "I cannot access my account",
"format": "text",
"created": "2025-11-08 10:30:00"
},
{
"id": 2,
"type": "response",
"poster": "Support Agent",
"body": "Please try resetting your password",
"format": "text",
"created": "2025-11-08 11:00:00"
},
{
"id": 3,
"type": "note",
"poster": "Admin",
"title": "Internal Note",
"body": "User has multiple failed login attempts",
"format": "text",
"created": "2025-11-08 11:15:00"
}
],
"staff": {
"assigned": "Jane Smith",
"team": "Level 1 Support"
}
}
Thread Entry Types:
"message" - User message"response" - Staff response"note" - Internal note (staff only)Success Response (200 OK):
<?xml version="1.0" encoding="UTF-8"?>
<ticket>
<ticket_id>123456</ticket_id>
<number>ABC123</number>
<subject>Login Issue</subject>
<name>John Doe</name>
<email>john@example.com</email>
<phone>+1 555-1234</phone>
<status>Open</status>
<priority>Normal</priority>
<department>IT Support</department>
<created>2025-11-08 10:30:00</created>
<updated>2025-11-08 14:20:00</updated>
<thread>
<entry>
<id>1</id>
<type>message</type>
<poster>John Doe</poster>
<body>I cannot access my account</body>
<format>text</format>
<created>2025-11-08 10:30:00</created>
</entry>
<entry>
<id>2</id>
<type>response</type>
<poster>Support Agent</poster>
<body>Please try resetting your password</body>
<format>text</format>
<created>2025-11-08 11:00:00</created>
</entry>
</thread>
</ticket>
400 Bad Request:
{
"error": "Invalid ticket number"
}
403 Forbidden:
{
"error": "API key not authorized"
}
404 Not Found:
{
"error": "Ticket not found"
}
curl "https://osticket.local/api/tickets-get.php/ABC123.json" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-get.php/123456.json" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-get.php/ABC123.xml" \
-H "X-API-Key: YOUR_API_KEY"
Ticket::lookupByNumber(), then falls back to Ticket::lookup() (by ID). This ensures ticket numbers like "123456" are not misinterpreted as IDs.format field indicating the original format (text, html, markdown).Update ticket properties such as status, department, priority, SLA plan, staff assignment, and due date.
PATCH /api/tickets-update.php/{number}.{format}
Required Permission: can_update_tickets
Content-Type: application/json
X-API-Key: YOUR_API_KEY
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
number |
string/int | Yes | Ticket number or ticket ID | "ABC123" or 123456 |
format |
string | Yes | Response format | "json" or "xml" |
All fields are optional - only include fields you want to update.
| Field | Type | Description | Example |
|---|---|---|---|
statusId |
string/int | Status name or ID | "Resolved", "Closed" or 3 |
departmentId |
string/int | Department name or ID | "Sales" or 7 |
topicId |
int | Help Topic ID | 15 |
slaId |
string/int | SLA Plan name or ID | "Premium Support" or 2 |
staffId |
string/int | Staff username or ID | "john.doe" or 5 |
teamId |
int | Team ID for assignment | 3 |
priority |
string/int | Priority name or ID | "Urgent", "High" or 4 |
duedate |
string | Due date (ISO 8601) | "2025-12-31" or "2025-12-31 23:59:59" |
note |
string | Add internal note (staff only) | "Customer called for status update" |
Success Response (200 OK):
{
"success": true,
"message": "Ticket updated successfully",
"ticket_id": 123456,
"number": "ABC123",
"updated_fields": ["status", "priority"]
}
400 Bad Request:
{
"error": "Invalid status name: 'InvalidStatus'"
}
403 Forbidden:
{
"error": "API key not authorized for this operation"
}
404 Not Found:
{
"error": "Ticket not found"
}
curl -X PATCH "https://osticket.local/api/tickets-update.php/ABC123.json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"statusId": "Resolved"
}'
curl -X PATCH "https://osticket.local/api/tickets-update.php/123456.json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"staffId": "john.doe",
"note": "Assigned to John for follow-up"
}'
curl -X PATCH "https://osticket.local/api/tickets-update.php/ABC123.json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"departmentId": "Billing",
"priority": "High"
}'
curl -X PATCH "https://osticket.local/api/tickets-update.php/ABC123.json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"statusId": "Open",
"priority": "Urgent",
"duedate": "2025-11-15",
"staffId": "admin",
"slaId": "Premium Support",
"note": "Escalated to premium support"
}'
statusId, departmentId, slaId, staffId. If name not found, falls back to ID lookup."Open", "Resolved", "Closed", "Archived". Use GET /tickets-statuses.php to list available statuses."john.doe") or staff ID (e.g., 5). Unassigns if staff not found.note field adds an internal note visible only to staff (not visible to end user).400 Bad Request if invalid.Search tickets by query string, status, department, and other filters. Supports pagination.
GET /api/tickets-search.php
Required Permission: can_search_tickets
X-API-Key: YOUR_API_KEY
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
query |
string | No | Search term (subject, message, number) | "login", "payment failed" |
status |
string | No | Filter by status name | "Open", "Closed", "Resolved" |
departmentId |
int | No | Filter by department ID | 5 |
topicId |
int | No | Filter by help topic ID | 10 |
priorityId |
int | No | Filter by priority ID | 2 |
staffId |
int | No | Filter by assigned staff ID | 3 |
limit |
int | No | Maximum results | 50 (default: 20, max: 100) |
offset |
int | No | Pagination offset | 0, 20, 40 |
sortBy |
string | No | Sort field | "created", "updated", "priority" |
sortOrder |
string | No | Sort order | "asc", "desc" (default: "desc") |
Success Response (200 OK):
{
"count": 25,
"total": 125,
"limit": 20,
"offset": 0,
"tickets": [
{
"ticket_id": 123,
"number": "ABC123",
"subject": "Login Issue",
"status": "Open",
"priority": "Normal",
"department": "IT Support",
"created": "2025-11-08 10:00:00",
"updated": "2025-11-08 14:30:00",
"duedate": "2025-11-15 10:00:00",
"isoverdue": false,
"user": {
"name": "John Doe",
"email": "john@example.com"
},
"staff": {
"assigned": "Jane Smith"
}
},
{
"ticket_id": 124,
"number": "ABC124",
"subject": "Password Reset",
"status": "Open",
"priority": "Low",
"department": "IT Support",
"created": "2025-11-08 11:30:00",
"updated": "2025-11-08 11:30:00",
"duedate": null,
"isoverdue": false,
"user": {
"name": "Jane Smith",
"email": "jane@example.com"
},
"staff": {
"assigned": null
}
}
]
}
400 Bad Request:
{
"error": "Invalid limit parameter (max: 100)"
}
403 Forbidden:
{
"error": "API key not authorized"
}
curl "https://osticket.local/api/tickets-search.php?query=login" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-search.php?status=Open" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-search.php?query=payment&status=Open&departmentId=5&limit=50" \
-H "X-API-Key: YOUR_API_KEY"
# Page 1 (results 1-20)
curl "https://osticket.local/api/tickets-search.php?status=Open&limit=20&offset=0" \
-H "X-API-Key: YOUR_API_KEY"
# Page 2 (results 21-40)
curl "https://osticket.local/api/tickets-search.php?status=Open&limit=20&offset=20" \
-H "X-API-Key: YOUR_API_KEY"
# Page 3 (results 41-60)
curl "https://osticket.local/api/tickets-search.php?status=Open&limit=20&offset=40" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-search.php?status=Open&sortBy=priority&sortOrder=desc" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-search.php?status=Open&limit=100" \
-H "X-API-Key: YOUR_API_KEY"
query parameter searches in ticket number, subject, and message body.offset and limit for pagination. Max limit is 100.total field with total number of matching tickets (useful for pagination UI).query parameter returns all tickets matching other filters.Permanently delete a ticket from the database.
⚠️ WARNING: This action is irreversible! Ticket and all thread entries will be permanently deleted.
DELETE /api/tickets-delete.php/{number}.{format}
Required Permission: can_delete_tickets
X-API-Key: YOUR_API_KEY
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
number |
string/int | Yes | Ticket number or ticket ID | "ABC123" or 123456 |
format |
string | Yes | Response format | "json" or "xml" |
Success Response (200 OK):
{
"success": true,
"message": "Ticket deleted successfully",
"ticket_id": 123456,
"number": "ABC123"
}
400 Bad Request:
{
"error": "Invalid ticket number"
}
403 Forbidden:
{
"error": "API key not authorized for this operation"
}
404 Not Found:
{
"error": "Ticket not found"
}
curl -X DELETE "https://osticket.local/api/tickets-delete.php/ABC123.json" \
-H "X-API-Key: YOUR_API_KEY"
curl -X DELETE "https://osticket.local/api/tickets-delete.php/123456.json" \
-H "X-API-Key: YOUR_API_KEY"
can_delete_tickets permission is disabled by default on all API keys for security.PATCH /tickets-update.php to set status to "Closed" or "Archived" instead of permanent deletion.ticket_pid is set to NULL (unlinked).Retrieve comprehensive ticket statistics including global counts, department breakdown, staff performance, and status distribution.
GET /api/tickets-stats.php
Required Permission: can_read_stats
X-API-Key: YOUR_API_KEY
All parameters are optional.
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
departmentId |
int | No | Filter by department ID | 5 |
staffId |
int | No | Filter by staff member ID | 3 |
dateRange |
string | No | Time period filter | "today", "week", "month", "year", "all" |
startDate |
string | No | Custom start date (ISO 8601) | "2025-01-01" |
endDate |
string | No | Custom end date (ISO 8601) | "2025-12-31" |
"today" - Today's tickets"week" - Last 7 days"month" - Last 30 days"year" - Last 365 days"all" - All tickets (default)Success Response (200 OK):
{
"global": {
"total": 1523,
"open": 342,
"closed": 1181,
"resolved": 890,
"archived": 45,
"overdue": 15,
"answered": 305,
"unanswered": 37,
"assigned": 280,
"unassigned": 62
},
"by_department": [
{
"department_id": 1,
"department_name": "IT Support",
"total": 856,
"open": 198,
"closed": 658,
"overdue": 8,
"answered": 180,
"assigned": 175
},
{
"department_id": 2,
"department_name": "Billing",
"total": 445,
"open": 89,
"closed": 356,
"overdue": 5,
"answered": 85,
"assigned": 70
},
{
"department_id": 3,
"department_name": "Sales",
"total": 222,
"open": 55,
"closed": 167,
"overdue": 2,
"answered": 40,
"assigned": 35
}
],
"by_staff": [
{
"staff_id": 1,
"staff_name": "John Doe",
"username": "john.doe",
"assigned": 45,
"closed": 120,
"answered": 40,
"avg_response_time": "2.5 hours",
"avg_resolution_time": "24 hours"
},
{
"staff_id": 2,
"staff_name": "Jane Smith",
"username": "jane.smith",
"assigned": 38,
"closed": 95,
"answered": 35,
"avg_response_time": "3 hours",
"avg_resolution_time": "28 hours"
}
],
"by_status": [
{
"status_id": 1,
"status": "Open",
"count": 342
},
{
"status_id": 2,
"status": "Resolved",
"count": 890
},
{
"status_id": 3,
"status": "Closed",
"count": 291
},
{
"status_id": 4,
"status": "Archived",
"count": 45
}
],
"by_priority": [
{
"priority_id": 1,
"priority": "Low",
"count": 450
},
{
"priority_id": 2,
"priority": "Normal",
"count": 850
},
{
"priority_id": 3,
"priority": "High",
"count": 180
},
{
"priority_id": 4,
"priority": "Urgent",
"count": 43
}
],
"date_range": {
"start": "2020-01-01",
"end": "2025-11-08",
"label": "All time"
}
}
403 Forbidden:
{
"error": "API key not authorized"
}
curl "https://osticket.local/api/tickets-stats.php" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-stats.php?departmentId=5" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-stats.php?dateRange=month" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-stats.php?startDate=2025-01-01&endDate=2025-03-31" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-stats.php?staffId=3&dateRange=month" \
-H "X-API-Key: YOUR_API_KEY"
dateRange filters.Retrieve all available ticket statuses with IDs and states. Useful for resolving status names to IDs.
GET /api/tickets-statuses.php
Required Permission: can_read_stats
X-API-Key: YOUR_API_KEY
None.
Success Response (200 OK):
{
"statuses": [
{
"id": 1,
"name": "Open",
"state": "open",
"properties": {
"description": "Ticket is open and awaiting response"
}
},
{
"id": 2,
"name": "Resolved",
"state": "closed",
"properties": {
"description": "Ticket has been resolved"
}
},
{
"id": 3,
"name": "Closed",
"state": "closed",
"properties": {
"description": "Ticket is closed"
}
},
{
"id": 4,
"name": "Archived",
"state": "archived",
"properties": {
"description": "Ticket has been archived"
}
},
{
"id": 5,
"name": "Deleted",
"state": "deleted",
"properties": {
"description": "Ticket has been deleted"
}
}
]
}
| State | Description |
|---|---|
open |
Ticket is open and active |
closed |
Ticket is closed or resolved |
archived |
Ticket is archived (no longer active) |
deleted |
Ticket is marked as deleted |
403 Forbidden:
{
"error": "API key not authorized"
}
curl "https://osticket.local/api/tickets-statuses.php" \
-H "X-API-Key: YOUR_API_KEY"
curl "https://osticket.local/api/tickets-statuses.php" \
-H "X-API-Key: YOUR_API_KEY" | \
jq '.statuses[] | select(.state == "open")'
PATCH /tickets-update endpointThe following endpoints require the Subticket Manager Plugin to be installed and active. If plugin is not available, endpoints return 501 Not Implemented.
Retrieve the parent ticket of a child ticket (subticket).
GET /api/tickets-subtickets-parent.php/{id}.{format}
Required Permission: can_manage_subtickets
X-API-Key: YOUR_API_KEY
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
id |
int | Yes | Child ticket ID (not number) | 200 |
format |
string | Yes | Response format | "json" or "xml" |
Success Response (200 OK) - Parent Exists:
{
"parent": {
"ticket_id": 100,
"number": "ABC100",
"subject": "Server Migration Project",
"status": "Open",
"department": "IT Operations",
"created": "2025-11-01 10:00:00",
"updated": "2025-11-08 14:30:00"
}
}
Success Response (200 OK) - No Parent:
{
"parent": null
}
400 Bad Request:
{
"error": "Invalid ticket ID"
}
403 Forbidden:
{
"error": "API key not authorized for subticket operations"
}
404 Not Found:
{
"error": "Child ticket not found"
}
501 Not Implemented:
{
"error": "Subticket plugin not available. Please install and enable the Subticket Manager Plugin."
}
curl "https://osticket.local/api/tickets-subtickets-parent.php/200.json" \
-H "X-API-Key: YOUR_API_KEY"
response=$(curl -s "https://osticket.local/api/tickets-subtickets-parent.php/200.json" \
-H "X-API-Key: YOUR_API_KEY")
if [ "$(echo $response | jq -r '.parent')" = "null" ]; then
echo "Ticket has no parent"
else
echo "Parent ticket: $(echo $response | jq -r '.parent.number')"
fi
GET /tickets-get.php/{number}.json first.Retrieve all child tickets (subtickets) of a parent ticket.
GET /api/tickets-subtickets-list.php/{id}.{format}
Required Permission: can_manage_subtickets
X-API-Key: YOUR_API_KEY
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
id |
int | Yes | Parent ticket ID (not number) | 100 |
format |
string | Yes | Response format | "json" or "xml" |
Success Response (200 OK) - With Children:
{
"children": [
{
"ticket_id": 123,
"number": "ABC123",
"subject": "Subtask: Database Migration",
"status": "Open",
"department": "Development",
"created": "2025-11-02 10:00:00",
"updated": "2025-11-08 09:15:00",
"assigned": "John Doe"
},
{
"ticket_id": 124,
"number": "ABC124",
"subject": "Subtask: DNS Configuration",
"status": "Resolved",
"department": "IT Operations",
"created": "2025-11-02 11:00:00",
"updated": "2025-11-07 16:30:00",
"assigned": "Jane Smith"
},
{
"ticket_id": 125,
"number": "ABC125",
"subject": "Subtask: Testing",
"status": "Closed",
"department": "QA",
"created": "2025-11-02 12:00:00",
"updated": "2025-11-08 14:00:00",
"assigned": "Bob Wilson"
}
]
}
Success Response (200 OK) - No Children:
{
"children": []
}
400 Bad Request:
{
"error": "Invalid ticket ID"
}
403 Forbidden:
{
"error": "API key not authorized for subticket operations"
}
404 Not Found:
{
"error": "Parent ticket not found"
}
501 Not Implemented:
{
"error": "Subticket plugin not available"
}
curl "https://osticket.local/api/tickets-subtickets-list.php/100.json" \
-H "X-API-Key: YOUR_API_KEY"
child_count=$(curl -s "https://osticket.local/api/tickets-subtickets-list.php/100.json" \
-H "X-API-Key: YOUR_API_KEY" | \
jq '.children | length')
echo "Parent ticket has $child_count child tickets"
curl -s "https://osticket.local/api/tickets-subtickets-list.php/100.json" \
-H "X-API-Key: YOUR_API_KEY" | \
jq '.children[] | select(.status == "Open")'
Create a parent-child relationship between two existing tickets.
POST /api/tickets-subtickets-create.php
Required Permission: can_manage_subtickets
Content-Type: application/json
X-API-Key: YOUR_API_KEY
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
parent_id |
int | Yes | Parent ticket ID | 100 |
child_id |
int | Yes | Child ticket ID | 200 |
Success Response (201 Created):
{
"success": true,
"message": "Subticket relationship created successfully",
"parent": {
"ticket_id": 100,
"number": "ABC100",
"subject": "Server Migration Project",
"status": "Open"
},
"child": {
"ticket_id": 200,
"number": "ABC200",
"subject": "Database Backup",
"status": "Open"
}
}
400 Bad Request - Self-Link:
{
"error": "Parent and child ticket cannot be the same"
}
400 Bad Request - Invalid IDs:
{
"error": "Invalid parent_id or child_id"
}
403 Forbidden:
{
"error": "API key not authorized for subticket operations"
}
404 Not Found:
{
"error": "Parent ticket not found"
}
409 Conflict - Child Already Has Parent:
{
"error": "Child ticket already has a parent (Ticket #ABC150)"
}
409 Conflict - Circular Dependency:
{
"error": "Circular dependency detected: Cannot make parent a child of its descendant"
}
501 Not Implemented:
{
"error": "Subticket plugin not available"
}
The endpoint performs the following validations:
parent_id and child_id are valid integerscurl -X POST "https://osticket.local/api/tickets-subtickets-create.php" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"parent_id": 100,
"child_id": 200
}'
#!/bin/bash
API_URL="https://osticket.local/api"
API_KEY="YOUR_API_KEY"
PARENT_ID=100
# Array of child ticket IDs
CHILDREN=(200 201 202 203)
for child_id in "${CHILDREN[@]}"; do
echo "Linking child ticket $child_id to parent $PARENT_ID..."
curl -X POST "$API_URL/tickets-subtickets-create.php" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"parent_id\": $PARENT_ID, \"child_id\": $child_id}"
echo ""
done
DELETE /tickets-subtickets-unlink), then create new link.Remove the parent-child relationship of a child ticket, making it a standalone ticket again.
DELETE /api/tickets-subtickets-unlink.php
Required Permission: can_manage_subtickets
Content-Type: application/json
X-API-Key: YOUR_API_KEY
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
child_id |
int | Yes | Child ticket ID to unlink | 200 |
Success Response (200 OK):
{
"success": true,
"message": "Subticket relationship removed successfully",
"child": {
"ticket_id": 200,
"number": "ABC200",
"subject": "Database Backup",
"status": "Open"
},
"former_parent": {
"ticket_id": 100,
"number": "ABC100"
}
}
400 Bad Request:
{
"error": "Invalid child_id"
}
403 Forbidden:
{
"error": "API key not authorized for subticket operations"
}
404 Not Found - No Parent:
{
"error": "Ticket has no parent relationship"
}
404 Not Found - Ticket Not Found:
{
"error": "Child ticket not found"
}
501 Not Implemented:
{
"error": "Subticket plugin not available"
}
curl -X DELETE "https://osticket.local/api/tickets-subtickets-unlink.php" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"child_id": 200
}'
#!/bin/bash
API_URL="https://osticket.local/api"
API_KEY="YOUR_API_KEY"
# Array of child ticket IDs to unlink
CHILDREN=(200 201 202 203)
for child_id in "${CHILDREN[@]}"; do
echo "Unlinking child ticket $child_id..."
curl -X DELETE "$API_URL/tickets-subtickets-unlink.php" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"child_id\": $child_id}"
echo ""
done
#!/bin/bash
API_URL="https://osticket.local/api"
API_KEY="YOUR_API_KEY"
CHILD_ID=200
# Check if ticket has parent
parent_response=$(curl -s "$API_URL/tickets-subtickets-parent.php/$CHILD_ID.json" \
-H "X-API-Key: $API_KEY")
if [ "$(echo $parent_response | jq -r '.parent')" != "null" ]; then
parent_number=$(echo $parent_response | jq -r '.parent.number')
echo "Unlinking child ticket $CHILD_ID from parent $parent_number..."
curl -X DELETE "$API_URL/tickets-subtickets-unlink.php" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"child_id\": $CHILD_ID}"
else
echo "Ticket $CHILD_ID has no parent"
fi
POST /tickets-subtickets-create.php.This example demonstrates a complete workflow using all API endpoints to manage a complex project with subtickets.
#!/bin/bash
API_URL="https://osticket.local/api"
API_KEY="YOUR_API_KEY"
echo "=== Server Migration Project Workflow ==="
echo ""
# 1. Create parent ticket
echo "1. Creating parent ticket..."
parent_response=$(curl -s -X POST "$API_URL/tickets.json" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Project Manager",
"email": "pm@example.com",
"subject": "Server Migration Project",
"message": "# Migration Plan\n\n## Tasks\n- Database backup\n- DNS configuration\n- Testing\n\n## Timeline\n**Start:** 2025-11-10\n**End:** 2025-11-20",
"format": "markdown",
"departmentId": "IT Operations",
"priority": "High",
"duedate": "2025-11-20"
}')
parent_id=$(echo $parent_response | jq -r '.ticket_id')
parent_number=$(echo $parent_response | jq -r '.number')
echo " ✓ Parent ticket created: #$parent_number (ID: $parent_id)"
echo ""
# 2. Create child tickets
echo "2. Creating child tickets..."
# Subtask 1: Database Backup
child1_response=$(curl -s -X POST "$API_URL/tickets.json" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "DBA",
"email": "dba@example.com",
"subject": "Subtask: Database Backup",
"message": "Create full backup of production database before migration",
"departmentId": "Development",
"priority": "Urgent"
}')
child1_id=$(echo $child1_response | jq -r '.ticket_id')
child1_number=$(echo $child1_response | jq -r '.number')
echo " ✓ Child 1: #$child1_number (ID: $child1_id)"
# Subtask 2: DNS Configuration
child2_response=$(curl -s -X POST "$API_URL/tickets.json" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Network Admin",
"email": "netadmin@example.com",
"subject": "Subtask: DNS Configuration",
"message": "Update DNS records to point to new server",
"departmentId": "IT Operations",
"priority": "High"
}')
child2_id=$(echo $child2_response | jq -r '.ticket_id')
child2_number=$(echo $child2_response | jq -r '.number')
echo " ✓ Child 2: #$child2_number (ID: $child2_id)"
# Subtask 3: Testing
child3_response=$(curl -s -X POST "$API_URL/tickets.json" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "QA Engineer",
"email": "qa@example.com",
"subject": "Subtask: Migration Testing",
"message": "Test all services after migration",
"departmentId": "QA",
"priority": "Normal"
}')
child3_id=$(echo $child3_response | jq -r '.ticket_id')
child3_number=$(echo $child3_response | jq -r '.number')
echo " ✓ Child 3: #$child3_number (ID: $child3_id)"
echo ""
# 3. Link subtickets to parent
echo "3. Linking subtickets to parent..."
curl -s -X POST "$API_URL/tickets-subtickets-create.php" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"parent_id\": $parent_id, \"child_id\": $child1_id}" > /dev/null
echo " ✓ Linked #$child1_number"
curl -s -X POST "$API_URL/tickets-subtickets-create.php" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"parent_id\": $parent_id, \"child_id\": $child2_id}" > /dev/null
echo " ✓ Linked #$child2_number"
curl -s -X POST "$API_URL/tickets-subtickets-create.php" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"parent_id\": $parent_id, \"child_id\": $child3_id}" > /dev/null
echo " ✓ Linked #$child3_number"
echo ""
# 4. List all child tickets
echo "4. Listing all child tickets..."
children=$(curl -s "$API_URL/tickets-subtickets-list.php/$parent_id.json" \
-H "X-API-Key: $API_KEY")
echo "$children" | jq -r '.children[] | " • #\(.number): \(.subject) [\(.status)]"'
echo ""
# 5. Update child ticket (simulate progress)
echo "5. Updating child ticket #$child1_number (Database Backup completed)..."
curl -s -X PATCH "$API_URL/tickets-update.php/$child1_number.json" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"statusId": "Resolved",
"note": "Database backup completed successfully. Backup file: prod_db_2025-11-08.sql"
}' > /dev/null
echo " ✓ Status updated to Resolved"
echo ""
# 6. Search for open subtickets
echo "6. Searching for remaining open subtickets..."
open_tickets=$(curl -s "$API_URL/tickets-search.php?status=Open" \
-H "X-API-Key: $API_KEY")
open_count=$(echo "$open_tickets" | jq '.count')
echo " → Found $open_count open tickets"
echo ""
# 7. Get parent ticket details
echo "7. Retrieving parent ticket details..."
parent_details=$(curl -s "$API_URL/tickets-get.php/$parent_number.json" \
-H "X-API-Key: $API_KEY")
echo " Subject: $(echo $parent_details | jq -r '.subject')"
echo " Status: $(echo $parent_details | jq -r '.status')"
echo " Priority: $(echo $parent_details | jq -r '.priority')"
echo " Created: $(echo $parent_details | jq -r '.created')"
echo " Thread entries: $(echo $parent_details | jq '.thread | length')"
echo ""
# 8. Get ticket statistics
echo "8. Getting ticket statistics..."
stats=$(curl -s "$API_URL/tickets-stats.php" \
-H "X-API-Key: $API_KEY")
echo " Global Stats:"
echo " • Total: $(echo $stats | jq '.global.total')"
echo " • Open: $(echo $stats | jq '.global.open')"
echo " • Closed: $(echo $stats | jq '.global.closed')"
echo ""
echo "=== Workflow Complete ==="
The API does not implement built-in rate limiting. Consider implementing rate limiting at the web server level:
# In http block
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# In server block
location /api/ {
limit_req zone=api burst=20 nodelay;
# ... other configuration
}
# Requires mod_ratelimit
<Location /api>
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 400
SetEnv rate-initial-burst 100
</Location>
When integrating with the API, implement proper error handling:
#!/bin/bash
response=$(curl -s -w "\n%{http_code}" -X POST "$API_URL/tickets.json" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{...}')
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
case $http_code in
200|201)
echo "Success: $body"
;;
400)
echo "Bad Request: $body"
exit 1
;;
401)
echo "Unauthorized: Check API key"
exit 1
;;
403)
echo "Forbidden: Check API key permissions"
exit 1
;;
404)
echo "Not Found: $body"
exit 1
;;
500)
echo "Server Error: $body"
exit 1
;;
501)
echo "Not Implemented: Feature/Plugin unavailable"
exit 1
;;
*)
echo "Unexpected error ($http_code): $body"
exit 1
;;
esac
Last Updated: 2025-11-08