# CRM · Tickets

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## POST /crm/tickets/chat-request

**Leave a request from the chat widget**

`operationId: TicketsController_createChatRequest`

For a chat widget that is away: turns the visitor's message into a ticket. Only a **guest** session token from the widget is accepted (`Authorization: Bearer <guest token>`, a guest customer of this org). Name, email and phone come from the guest session; the caller can only send `data.description` and `data.configId`, and the widget must have its away form (`offline.showForm`) switched on. The ticket is created `new`, priority `medium`, source `chat-widget`.

#### Signature

```http
POST /crm/tickets/chat-request (body) -> The created ticket
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | GUEST_REQUIRED | Guest authentication is required | No Bearer token ("Guest authentication is invalid or expired" when it does not verify). | — |
| `403` | WRONG_ORG | This guest does not belong to this organization | The token is not a guest customer of this org. | — |
| `400` | FIELDS_REQUIRED | A name, valid email, message and widget are required | The guest has no name or valid email, or `description` / `configId` is missing or too long. | — |

Plus the standard platform errors: `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `authorization` | header | string | yes | Bearer guest token from the chat widget. |

### Request body

```json
{
  "data": {
    "description": "Do you deliver on Sundays?",
    "configId": "support-widget"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created ticket |
| `400` | A name, valid email, message and widget are required — The guest has no name or valid email, or `description` / `configId` is missing or too long. |
| `401` | Guest authentication is required — No Bearer token ("Guest authentication is invalid or expired" when it does not verify). |
| `403` | This guest does not belong to this organization — The token is not a guest customer of this org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/tickets/collections

**Get ticket collections**

`operationId: TicketsController_getTicketCollections`

The ticket collection definitions for the org — the categories and schemas tickets are filed under. Read this to build a ticket form generically.

#### Signature

```http
GET /crm/tickets/collections () -> Ticket collection definitions
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /crm/tickets/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Ticket collection definitions |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/tickets/list

**List tickets (staff)**

`operationId: TicketsController_listForStaff`

Staff only. Tickets newest first, paged and filtered by the server, each with `workflowInfo` (its workflow, stage and task) when it is on one. `search` matches name, title, reporter, reporter email and description. `inWorkflow=true` keeps tickets that have a workflow task.

#### Signature

```http
GET /crm/tickets/list (status?: string, priority?: string, assignTo?: string, search?: string, inWorkflow?: string, page?: integer, pageSize?: integer) -> { data, total, page, pageSize }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | ORG_AUTH_REQUIRED | Authentication for this organization is required | The caller is not a user or customer of this org. | — |
| `403` | STAFF_ONLY | A signed-in customer is required | A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user). | — |

Plus the standard platform errors: `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `status` | query | string | — | One status or a comma-separated list. |
| `priority` | query | string | — |  |
| `assignTo` | query | string | — |  |
| `search` | query | string | — |  |
| `inWorkflow` | query | "true" \| "1" | — |  |
| `page` | query | integer | — |  |
| `pageSize` | query | integer | — | Max 200. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { data, total, page, pageSize } |
| `401` | Authentication for this organization is required — The caller is not a user or customer of this org. |
| `403` | A signed-in customer is required — A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user). |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/tickets/workflows

**Ticket workflows**

`operationId: TicketsController_workflowsOverview`

Staff only. The workflows tickets run through, each with what is in flight in it, plus `resolves` — how the org decides which workflow a new ticket takes.

#### Signature

```http
GET /crm/tickets/workflows () -> { data, total, resolves }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | ORG_AUTH_REQUIRED | Authentication for this organization is required | The caller is not a user or customer of this org. | — |
| `403` | STAFF_ONLY | A signed-in customer is required | A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user). | — |

Plus the standard platform errors: `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { data, total, resolves } |
| `401` | Authentication for this organization is required — The caller is not a user or customer of this org. |
| `403` | A signed-in customer is required — A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user). |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/tickets/workflow/{id}

**Where a ticket stands in its workflow**

`operationId: TicketsController_workflowFor`

Staff only. The ticket's workflow position (workflow, stage, task, stage history), or `null` when it is on none.

#### Signature

```http
GET /crm/tickets/workflow/{id} (id: string) -> Workflow info, or null
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | ORG_AUTH_REQUIRED | Authentication for this organization is required | The caller is not a user or customer of this org. | — |
| `403` | STAFF_ONLY | A signed-in customer is required | A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user). | — |
| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket with that id. | — |

Plus the standard platform errors: `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Ticket sk. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Workflow info, or null |
| `401` | Authentication for this organization is required — The caller is not a user or customer of this org. |
| `403` | A signed-in customer is required — A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user). |
| `404` | Ticket not found — No ticket with that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/tickets/get-by-email/{email}/{number}

**Get tickets by email**

`operationId: TicketsController_getTicketByEmail`

Fetches a customer's tickets by their email address rather than through their account — how a support portal shows someone their tickets when they have no login.

The email is the only key, so anyone who knows an address can list that person's tickets. Rate-limit any public surface built on it.

#### Signature

```http
GET /crm/tickets/get-by-email/{email}/{number} (email: string, number: string) -> The customer's tickets
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Keyed on email alone — treat the address as the credential it effectively is.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/tickets/email/create/{email}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `email` | path | string | yes | Customer email address. |
| `number` | path | string | yes | Ticket number. Omit to list all for the email. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The customer's tickets |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/tickets/email/create/{email}

**Create a ticket for an email address**

`operationId: TicketsController_createTicketByEmail`

Raises a ticket attributed to an email address rather than to a signed-in account — the intake path for a contact form or an inbound support email.

#### Signature

```http
POST /crm/tickets/email/create/{email} (email: string, body) -> The created ticket
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `DELETE /crm/tickets/delete-by-email/{email}/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `email` | path | string | yes | Customer email address. |

### Request body

The ticket to raise.

```json
{
  "data": {
    "title": "Cannot log in after password reset",
    "description": "Reset link works but sign-in still fails"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created ticket |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /crm/tickets/delete-by-email/{email}/{id}

**Delete a ticket by email**

`operationId: TicketsController_deleteTicketByEmail`

Deletes a ticket belonging to an email address. Both must match, so an id alone is not enough to delete someone else's ticket through this route.

#### Signature

```http
DELETE /crm/tickets/delete-by-email/{email}/{id} (email: string, id: string) -> Deletion result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/tickets/get-by-email/{email}/{number}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `email` | path | string | yes | Customer email address. |
| `id` | path | string | yes | Ticket id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Deletion result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/tickets/get/{number}

**Get tickets**

`operationId: TicketsController_getTickets`

Fetches one ticket by number, or lists them all when the segment is omitted. Scoped to what the caller may see.

#### Signature

```http
GET /crm/tickets/get/{number} (number: string, status?: string, enrich?: boolean) -> The ticket, or all tickets
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `GET /crm/tickets/get-by-email/{email}/{number}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `number` | path | string | yes | Ticket number. Omit to list. |
| `status` | query | string | — | Filter by status. |
| `enrich` | query | boolean | — | true to resolve linked records. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The ticket, or all tickets |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /crm/tickets/delete/{id}

**Delete a ticket**

`operationId: TicketsController_deleteTickets`

Deletes a ticket and its history. Closing it instead preserves the record of what was asked and how it was resolved.

#### Signature

```http
DELETE /crm/tickets/delete/{id} (id: string) -> Deletion result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `PATCH /crm/tickets/status/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Ticket id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Deletion result |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/tickets/create

**Create a ticket**

`operationId: TicketsController_createTicket`

Raises a support ticket on behalf of the signed-in caller. `title` is the one required field — everything else can be filled in later.

Two further create endpoints exist: `POST /crm/tickets/create-ticket` and `POST /crm/tickets/email/create/{email}`. They differ in who the ticket is attributed to.

#### Signature

```http
POST /crm/tickets/create (body) -> The created ticket
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |
| `400` | TITLE_REQUIRED | title is required | `title` is missing from the ticket data. | Every ticket needs a title; the rest is optional. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `POST /crm/tickets/create-ticket`
- `POST /crm/tickets/email/create/{email}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The ticket to raise.

```json
{
  "data": {
    "title": "Cannot log in after password reset",
    "description": "Reset link works but sign-in still fails",
    "priority": "high"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created ticket |
| `400` | title is required — `title` is missing from the ticket data. |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/tickets/report-ide-issue

**Report an IDE issue**

`operationId: TicketsController_reportIDEIssue`

Raises a ticket from the IDE integration, tagged so these reports can be triaged separately from ordinary support traffic.

#### Signature

```http
POST /crm/tickets/report-ide-issue (body) -> The created ticket
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `POST /crm/tickets/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The issue report.

```json
{
  "data": {
    "title": "Autocomplete stops responding on large files",
    "description": "Reproduces on files over 5k lines"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created ticket |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/tickets/create-ticket

**Create a ticket (alternate)**

`operationId: TicketsController_createTicketPost`

A second create endpoint, taking the ticket body without the request context the primary `create` uses. Prefer `POST /crm/tickets/create` unless you specifically need this one.

#### Signature

```http
POST /crm/tickets/create-ticket (body) -> The created ticket
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `POST /crm/tickets/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The ticket to raise.

```json
{
  "data": {
    "title": "Cannot log in",
    "priority": "high"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created ticket |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/tickets/update

**Update a ticket**

`operationId: TicketsController_updateTickets`

Updates a ticket's fields. Status, assignment and priority each have a dedicated endpoint that records the change properly — use those rather than writing the field here.

#### Signature

```http
POST /crm/tickets/update (body) -> The updated ticket
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Setting `status` here bypasses the resolution capture that `PATCH status/{ticketId}` performs.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |
| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `PATCH /crm/tickets/status/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The ticket to update, including its `sk`.

```json
{
  "sk": "66f1a2b3c4d5e6f708192a3b",
  "data": {
    "description": "Updated with reproduction steps"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated ticket |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Ticket not found — No ticket has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/tickets/reply/{ticketId}

**Reply to a ticket**

`operationId: TicketsController_replyToTicket`

Sends a reply on a ticket, by email. `to` is required — a reply with no recipient is refused rather than silently recorded.

Set `isStaffReply` to mark it as coming from support rather than the customer. Use `templateId` to send a templated reply instead of raw `html` or `text`.

#### Signature

```http
POST /crm/tickets/reply/{ticketId} (ticketId: string, body) -> The sent reply
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- `to` is not defaulted from the ticket's reporter. Send it explicitly.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |
| `400` | RECIPIENT_REQUIRED | Recipient (to) is required | `to` is missing. | A reply must name a recipient — it is not inferred from the ticket. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/tickets/messages/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `ticketId` | path | string | yes | Ticket id. |

### Request body

The reply to send.

```json
{
  "to": "ada@example.com",
  "subject": "Re: Cannot log in",
  "html": "<p>Could you try clearing your cookies?</p>",
  "isStaffReply": true
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The sent reply |
| `400` | Recipient (to) is required — `to` is missing. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Ticket not found — No ticket has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/tickets/messages/{ticketId}

**Get ticket messages**

`operationId: TicketsController_getTicketMessages`

The customer-facing conversation on a ticket — everything sent to and received from the reporter. Internal comments are separate.

#### Signature

```http
GET /crm/tickets/messages/{ticketId} (ticketId: string) -> The ticket's messages
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/tickets/comments/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `ticketId` | path | string | yes | Ticket id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The ticket's messages |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Ticket not found — No ticket has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/tickets/comments/{ticketId}

**Get internal comments**

`operationId: TicketsController_getTicketComments`

The staff-only comments on a ticket. Keep these out of any customer-facing view — they are separate from `messages` precisely so they are not shown.

#### Signature

```http
GET /crm/tickets/comments/{ticketId} (ticketId: string) -> Internal comments
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/tickets/comments/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `ticketId` | path | string | yes | Ticket id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Internal comments |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Ticket not found — No ticket has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/tickets/comments/{ticketId}

**Add an internal comment**

`operationId: TicketsController_addInternalComment`

Adds a comment visible only to staff. This is the place for triage notes and internal discussion — unlike a reply, nothing is sent to the customer.

#### Signature

```http
POST /crm/tickets/comments/{ticketId} (ticketId: string, body) -> The added comment
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/tickets/reply/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `ticketId` | path | string | yes | Ticket id. |

### Request body

The comment.

```json
{
  "message": "Third report this week — likely the session cookie change"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The added comment |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Ticket not found — No ticket has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PATCH /crm/tickets/status/{ticketId}

**Change a ticket status**

`operationId: TicketsController_changeStatus`

Moves a ticket to a new status. When closing one, supply `resolution` — the summary and root cause are what makes a closed ticket useful later, and this is the only endpoint that captures them.

#### Signature

```http
PATCH /crm/tickets/status/{ticketId} (ticketId: string, body) -> The updated ticket
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- `POST /crm/tickets/update` can also set a status, but does not capture a resolution — use this endpoint when closing.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PATCH /crm/tickets/assign/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `ticketId` | path | string | yes | Ticket id. |

### Request body

The new status, and the resolution when closing.

```json
{
  "status": "resolved",
  "resolution": {
    "summary": "Cleared stale session cookie",
    "rootCause": "Session cookie not invalidated on password reset"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated ticket |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Ticket not found — No ticket has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PATCH /crm/tickets/assign/{ticketId}

**Reassign a ticket**

`operationId: TicketsController_reassignTicket`

Assigns a ticket to a different agent, with an optional handover note.

#### Signature

```http
PATCH /crm/tickets/assign/{ticketId} (ticketId: string, body) -> The updated ticket
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PATCH /crm/tickets/priority/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `ticketId` | path | string | yes | Ticket id. |

### Request body

Who to assign it to.

```json
{
  "assignTo": "support@acme.com",
  "note": "Needs someone from the auth team"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated ticket |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Ticket not found — No ticket has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PATCH /crm/tickets/priority/{ticketId}

**Change a ticket priority**

`operationId: TicketsController_changePriority`

Sets a ticket's priority, which drives queue ordering and any SLA attached to it.

#### Signature

```http
PATCH /crm/tickets/priority/{ticketId} (ticketId: string, body) -> The updated ticket
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/tickets/bulk-update`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `ticketId` | path | string | yes | Ticket id. |

### Request body

The new priority.

```json
{
  "priority": "urgent"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated ticket |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Ticket not found — No ticket has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/tickets/bulk-update

**Update several tickets**

`operationId: TicketsController_bulkUpdate`

Applies the same status, priority or assignment to many tickets at once — for clearing a queue or handing over a shift.

Because it goes through the bulk path, no resolution is captured: closing tickets this way records the status but not why.

#### Signature

```http
POST /crm/tickets/bulk-update (body) -> The bulk update result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- No resolution is recorded. Close tickets individually where the root cause matters.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PATCH /crm/tickets/status/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

Which tickets, and what to change.

```json
{
  "ticketIds": [
    "TKT-4821",
    "TKT-4822"
  ],
  "assignTo": "support@acme.com"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The bulk update result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/tickets/canned-responses

**Get canned responses**

`operationId: TicketsController_getCannedResponses`

The saved reply templates available to agents, for answering common questions consistently.

#### Signature

```http
GET /crm/tickets/canned-responses () -> Canned responses
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/tickets/reply/{ticketId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Canned responses |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

