# 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](https://mava.gitbook.io/mava-docs/getting-started/integration-setup/web-chat-setup/automatically-capture-custom-user-data-sdk) 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.
