# API

### What can you use the Mava API for?

1. Custom Attributes: You can use the Mava API to push user attributes to Mava via the identify endpoint. This offers similar functionality to the web chat [SDK](/mava-docs/getting-started/integration-setup/web-chat-setup/automatically-capture-custom-user-data-sdk.md) but can be used with all integration options and enables you to push data from third-party systems, such as your database, into Mava. &#x20;
2. Status Updates: You can use the API to update ticket statuses, for example to close them.&#x20;
3. Ticket Updates: You can use the API to update tickets, for example adding a tag or category.&#x20;
4. Messages & Private Notes: You can use the API to send messages or add private notes to tickets.&#x20;
5. Fetch Tickets: You can use the API to collect all ticket data

### Getting Started

Head over to the [API section](https://dashboard.mava.app/dashboard/admin/api/keys) within the Mava dashboard and create an API key.&#x20;

*Please note:  If you send no customer ID, a customer will be created for you and a new customer ID sent back which should be stored and used for subsequent calls to update that same customer.*

\
**CURL**

```bash
curl -X POST https://gateway.mava.app/api/identify \
  -H "Content-Type: application/json" \
  -d '{
    "emailAddress": "",
    "mavaCustomerId": "",
    "customAttributes": [
      {
        "label": "exampleLabel",
        "value": "exampleValue"
      }
    ]
  }'
```

**NodeJS**

```javascript
fetch('https://gateway.mava.app/api/identify', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-auth-token': 'YOUR_AUTH_TOKEN_HERE'
  },
  body: JSON.stringify({
    emailAddress: '', 
    mavaCustomerId: '',
    customAttributes: [
      {
        label: 'exampleLabel',
        value: 'exampleValue'
      }
    ]
  })
})
.then(response => response.json())
.then(data => console.log(data))
.catch((error) => console.error('Error:', error));
```

**Schema**

```json
{
  "type": "object",
  "properties": {
    "emailAddress": {
      "type": "string"
    },
    "plaformUserId": {
      "type": "string"
    },
    "mavaCustomerId": {
      "type": "string"
    },
    "customAttributes": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        },
        "required": ["label", "value"]
      }
    }
  }
}
```

## API Endpoints

### 1. Get Categories

Retrieves a list of all active categories for the authenticated client.

#### Endpoint

```
GET /api/categories
```

#### Authentication

* Requires API token authentication
* Subject to rate limiting

#### Response

**Success (200 OK)**

```json
{
  "categories": [
    {
      "_id": "string",
      "name": "string"
    }
  ]
}
```

**Error (500 Internal Server Error)**

```json
{
  "error": "Internal server error"
}
```

### 2. Update Ticket

Updates a ticket's status, priority, and/or category.

#### Endpoint

```
PUT /api/ticket
```

#### Authentication

* Requires API token authentication
* Subject to rate limiting

#### Request Body

| Field      | Type   | Required | Description                         |
| ---------- | ------ | -------- | ----------------------------------- |
| ticketId   | string | Yes      | The unique identifier of the ticket |
| status     | string | No       | New ticket status                   |
| priority   | number | No       | New priority level                  |
| categoryId | string | No       | ID of the category to assign        |

#### Valid Values

* **Status**: `"Open"`, `"Resolved"`, `"Pending"`, `"Waiting"`
* **Priority**: `1`, `2`, `3`
* **CategoryId**: Must be a valid category ID from GET /categories

#### Response

**Success (200 OK)**

```json
{
  "message": "Ticket updated"
}
```

**Error Responses**

**Ticket not found (400)**

```json
{
  "error": "Ticket not found"
}
```

**Invalid category (400)**

```json
{
  "error": "Category not found"
}
```

**Invalid status/priority (400)**

```json
{
  "error": "Invalid status or priority"
}
```

**Server error (500)**

```json
{
  "error": "Internal server error"
}
```

#### Behavior

* Each change (status, priority, category) creates an audit log message
* Changes are only made if the new value differs from the current value
* Category must be active (not archived) and belong to the client

### 3. Send Message or Private Note

The send message endpoint allows you to send messages or private notes to existing tickets programmatically.

#### Endpoint

```
PUT /api/message
```

#### Authentication

* Requires API token authentication
* Subject to rate limiting

#### Behavior

You can send two types of messages:

* **External** **messages:** visible to the customer and delivered through the ticket's integration channel (e.g. email, Discord, Telegram).
* **Internal** **notes:** private messages only visible to your support team inside the Mava dashboard.

&#x20; If no messageType is specified, the message defaults to an **External** **Message**.

#### Request Body

| Field                   | Type   | Required             | Description                                                                                              |
| ----------------------- | ------ | -------------------- | -------------------------------------------------------------------------------------------------------- |
| ticketId                | string | Yes                  | The ID of the ticket to send the message to                                                              |
| content                 | string | No\*                 | The text content of the message                                                                          |
| messageType             | string | No                   | The type of message to send. Accepted values: ExternalMessage, InternalNote. Defaults to ExternalMessage |
| attachments             | array  | No\*                 | A list of file attachments to include with the message                                                   |
| attachments\[].fileName | string | Yes (if attachments) | The display name of the file                                                                             |
| attachments\[].url      | string | Yes (if attachments) | The publicly accessible URL of the file                                                                  |
| attachments\[].fileType | string | Yes (if attachments) | The MIME type of the file (e.g. image/png, application/pdf)                                              |

\*Either content or attachments must be provided.

#### Valid Values

* **messageType**: `"ExternalMessage"`, `"InternalNote"` — Defaults to "`ExternalMessage"`

#### Response

**Success (200)**

```json
{
"message": { ... },
"ticket": { ... },
"sender": { ... }
}
```

**Bad Request (400)**

Returned when required fields are missing or the ticket is not found.

```json
{
"error": "Content or attachments are required"
}
{
"error": "Ticket not found"
}
```

**Unauthorized (401)**

Returned when the API key is missing or invalid.

**Too Many Requests (429)**

Returned when the rate limit is exceeded. The API allows up to 90 requests per 30 seconds per API key.

**Internal Server Error (500)**

#### Code Examples

**Curl**

```bash
curl -X POST https://app.mava.app/api/message \
    -H "x-auth-token: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "ticketId": "TICKET_ID",
      "content": "Thanks for reaching out! We will look into this right away.",
      "messageType": "ExternalMessage"
    }'
```

**NodeJS**

```javascript
const response = await fetch("https://app.mava.app/api/message", {
    method: "POST",
    headers: {
      "x-auth-token": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      ticketId: "TICKET_ID",
      content: "Thanks for reaching out! We will look into this right away.",
      messageType: "ExternalMessage",
    }),
  });
 const data = await response.json();
```

**Schema**

```json
{
    "ticketId": "string",
    "content": "string",
    "messageType": "ExternalMessage | InternalNote",
    "attachments": [
      {
        "fileName": "string",
        "url": "string",
        "fileType": "string"
      }
    ]
  }
```

**Sending an Internal Note**

To send a message that is only visible internally to your team, set messageType to InternalNote:

```json
{
"ticketId": "TICKET_ID",
"content": "Customer has been flagged as a premium user, prioritise this ticket.",
"messageType": "InternalNote"
}
```

### 4. Fetch Tickets

Retrieves a paginated list of tickets within a date range, with optional filters for status, assignee, category, tags, CSAT rating, and origin. Optionally includes the full message thread for each ticket.

#### Endpoint

```
GET /api/tickets
```

#### Authentication

* Requires API token authentication (`x-auth-token` header)
* Subject to rate limiting (90 requests per 30 seconds)

#### Query Parameters

| Parameter       | Type                | Required | Default     | Description                                                                                                     |
| --------------- | ------------------- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------- |
| startDate       | string              | **Yes**  | —           | Start of date range (ISO 8601, e.g. `2026-01-01` or `2026-01-01T00:00:00.000Z`)                                 |
| endDate         | string              | **Yes**  | —           | End of date range (exclusive). Must be after startDate. Max range: 1 year                                       |
| limit           | integer             | No       | `50`        | Number of tickets per page (1–100)                                                                              |
| skip            | integer             | No       | `0`         | Number of tickets to skip (for pagination)                                                                      |
| status          | string or string\[] | No       | —           | Filter by status. Values: `Open`, `Resolved`, `Pending`, `Waiting`                                              |
| assignedTo      | string or string\[] | No       | —           | Filter by agent ID(s). Use `Unassigned` for unassigned tickets                                                  |
| category        | string or string\[] | No       | —           | Filter by category ID(s)                                                                                        |
| tags            | string or string\[] | No       | —           | Filter by tag ID(s)                                                                                             |
| csatValues      | string or string\[] | No       | —           | Filter by CSAT rating. Values: `1`–`5` or `no-rating`                                                           |
| origin          | string or string\[] | No       | —           | Filter by ticket origin. Values: `web`, `discord`, `telegram`, `telegram-group`, `email`                        |
| sortBy          | string              | No       | `createdAt` | Sort field. Values: `createdAt`, `status`, `priority`, `sourceType`, `assignedTo`, `category`, `resolutionTime` |
| sortOrder       | string              | No       | `desc`      | Sort direction: `asc` or `desc`                                                                                 |
| includeMessages | boolean             | No       | `false`     | When `true`, includes the full message thread for each ticket                                                   |

#### Response

**Success (200 OK)**

```json
{
  "tickets": [
    {
      "_id": "6650a1b2c3d4e5f678901234",
      "createdAt": "2026-03-15T10:30:00.000Z",
      "updatedAt": "2026-03-16T14:22:00.000Z",
      "status": "Resolved",
      "priority": 2,
      "sourceType": "discord",
      "resolutionTime": 100920000,
      "assignedTo": {
        "_id": "6650b2c3d4e5f67890123456",
        "name": "Jane Smith"
      },
      "category": {
        "_id": "6650c3d4e5f678901234567a",
        "name": "Billing"
      },
      "tags": [
        { "_id": "6650d4e5f678901234567890", "name": "VIP" }
      ],
      "csat": { "value": 5 },
      "aiStatus": "resolved",
      "discordUsers": [
        { "discordAuthorId": "123456789", "name": "user#1234" }
      ],
      "attributes": [
        { "attributeId": "6650e5f6789012345678abcd", "name": "Plan", "content": "Enterprise" }
      ],
      "customer": {
        "_id": "6650f6789012345678abcdef",
        "email": "user@example.com",
        "attributes": [
          { "attributeId": "665012345678abcdef012345", "name": "Company", "content": "Acme Inc" }
        ]
      },
      "messages": [
        {
          "_id": "665112345678abcdef012345",
          "content": "I need help with my billing",
          "senderType": "customer",
          "senderName": "user#1234",
          "messageType": "ExternalMessage",
          "createdAt": "2026-03-15T10:30:00.000Z",
          "attachments": []
        },
        {
          "_id": "665212345678abcdef012345",
          "content": "Let me look into that for you",
          "senderType": "agent",
          "senderName": null,
          "messageType": "ExternalMessage",
          "createdAt": "2026-03-15T11:00:00.000Z",
          "attachments": []
        }
      ]
    }
  ],
  "count": 285,
  "pagination": {
    "limit": 50,
    "skip": 0,
    "hasMore": true
  }
}
```

#### Response Fields

| Field                   | Description                                                                      |
| ----------------------- | -------------------------------------------------------------------------------- |
| `resolutionTime`        | Milliseconds between creation and resolution. `null` if not resolved             |
| `csat`                  | Customer satisfaction rating (1-5). `null` if no rating or telegram-group ticket |
| `aiStatus`              | AI handling status. `null` for telegram-group tickets                            |
| `discordUsers`          | Discord usernames associated with the ticket. Empty for non-Discord tickets      |
| `customer`              | Customer details with custom attributes. `null` for telegram-group tickets       |
| `messages`              | Only included when `includeMessages=true`. Sorted oldest-first                   |
| `messages[].senderType` | `customer`, `agent`, or `system`                                                 |
| `messages[].senderName` | Discord username when available, otherwise `null`                                |
| `pagination.hasMore`    | `true` if more tickets exist beyond the current page                             |

#### Error Responses

**Missing or invalid date range (400)**

```json
{ "error": "MISSING_DATE_RANGE" }
```

```json
{ "error": "INVALID_DATE_RANGE" }
```

Returned when:

* `startDate` or `endDate` is missing
* Dates are not valid ISO 8601 format
* `endDate` is before or equal to `startDate`
* Date range exceeds 1 year

\
**Invalid limit (400)**

```json
{ "error": "INVALID_LIMIT" }
```

Returned when `limit` is not an integer between 1 and 100.

\
**Invalid skip (400)**

```json
{ "error": "INVALID_SKIP" }
```

Returned when `skip` is negative.

\
**Unsupported sort field (400)**

```json
{ "error": "UNSUPPORTED_SORT_FIELD" }
```

Returned when `sortBy` is not one of the supported values.

\
**Unsupported sort order (400)**

```json
{ "error": "UNSUPPORTED_SORT_ORDER" }
```

Returned when `sortOrder` is not `asc` or `desc`.

\
**Unauthorized (401)**

Returned when the API key is missing or invalid.

#### Too Many Requests (429)

Returned when the rate limit is exceeded. The API allows up to 90 requests per 30 seconds per API key.

#### Internal Server Error (500)

```json
{ "error": "Internal server error" }
```

#### Code Examples

**Curl - Basic usage**

```bash
curl -X GET "https://gateway.mava.app/api/tickets?startDate=2026-01-01&endDate=2026-04-01&limit=20" \
  -H "x-auth-token: YOUR_API_KEY"
```

\
**Curl - With filters and messages**

```bash
curl -X GET "https://gateway.mava.app/api/tickets?startDate=2026-01-01&endDate=2026-04-01&status=Open&status=Pending&assignedTo=Unassigned&origin=discord&sortBy=priority&sortOrder=desc&includeMessages=true&limit=10" \
  -H "x-auth-token: YOUR_API_KEY"
```

\
**NodeJS - Basic usage**

```javascript
const params = new URLSearchParams({
  startDate: '2026-01-01',
  endDate: '2026-04-01',
  limit: '20',
  includeMessages: 'true',
});

const response = await fetch(`https://gateway.mava.app/api/tickets?${params}`, {
  method: 'GET',
  headers: {
    'x-auth-token': 'YOUR_API_KEY',
  },
});

const data = await response.json();
console.log(`Found ${data.count} tickets`);
```

\
**NodeJS - Paginate through all results**

```javascript
const limit = 50;
let skip = 0;
let allTickets = [];

while (true) {
  const params = new URLSearchParams({
    startDate: '2026-01-01',
    endDate: '2026-04-01',
    limit: String(limit),
    skip: String(skip),
  });

  const res = await fetch(`https://gateway.mava.app/api/tickets?${params}`, {
    headers: { 'x-auth-token': 'YOUR_API_KEY' },
  });
  const page = await res.json();
  allTickets.push(...page.tickets);

  if (!page.pagination.hasMore) break;
  skip += limit;
}

console.log(`Total tickets fetched: ${allTickets.length}`);
```

#### Filtering by multiple values

To filter by multiple values for the same parameter, repeat the parameter:

```
?status=Open&status=Pending
?origin=discord&origin=email
?csatValues=4&csatValues=5&csatValues=no-rating
```

#### Pagination

Use `skip` and `limit` to paginate through results. The response includes `pagination.hasMore` to indicate whether more pages exist.

1. Start with `skip=0`
2. After each response, if `hasMore` is `true`, increase `skip` by `limit`
3. Repeat until `hasMore` is `false`

The `count` field always reflects the total number of matching tickets across all pages.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mava.gitbook.io/mava-docs/webhooks-and-api/api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
