# Backend API

### Getting Started

1. Go to **Settings > Integrations** in your Zipchat dashboard
2. Click **Enable** on the Backend API card
3. Copy your API key (prefixed with `zc-`) — store it securely, it is only shown once

### Authentication

Include your API key as a Bearer token in the `Authorization` header:

```
Authorization: Bearer zc-your_api_key_here
```

All requests must be authenticated. Unauthenticated or invalid requests return `401 Unauthorized`.

### Base URL

```
https://app.zipchat.ai/api/integrations/backend_api/v1
```

### Endpoints

#### List Conversations

```
GET /chats/:chat_id/conversations
```

Returns a paginated list of conversations for the given chat.

**Parameters**

| Parameter               | Type    | Default | Description                                                                  |
| ----------------------- | ------- | ------- | ---------------------------------------------------------------------------- |
| `page`                  | integer | `1`     | Page number                                                                  |
| `per_page`              | integer | `50`    | Items per page (max `100`)                                                   |
| `last_message_at_since` | string  | —       | ISO 8601 timestamp. Only return conversations with activity since this time. |

**Response**

```json
{
  "conversations": [
    {
      "id": 12345,
      "chat_id": 1,
      "created_at": "2026-01-15T10:30:00Z",
      "last_message_at": "2026-01-15T10:45:00Z",
      "channel": "web",
      "manual_reply_mode": false,
      "last_escalated_at": null,
      "last_escalation_resolved_at": null,
      "assignee": null,
      "share_url": "https://app.zipchat.ai/chats/1/conversations/12345",
      "lead": {
        "id": 100,
        "full_name": "John Doe",
        "email": "john@example.com",
        "phone_number": "+1234567890",
        "email_marketing_consent": true
      }
    },
    {
      "id": 12346,
      "chat_id": 1,
      "created_at": "2026-01-15T11:00:00Z",
      "last_message_at": "2026-01-15T11:20:00Z",
      "channel": "whatsapp",
      "manual_reply_mode": true,
      "last_escalated_at": "2026-01-15T11:10:00Z",
      "last_escalation_resolved_at": "2026-01-15T11:18:00Z",
      "assignee": {
        "id": 42,
        "name": "Stan Marsh",
        "email": "stan@example.com",
        "avatar_url": "https://app.zipchat.ai/rails/active_storage/blobs/redirect/.../avatar.png"
      },
      "share_url": "https://app.zipchat.ai/chats/1/conversations/12346",
      "lead": null
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total_count": 142,
    "total_pages": 3,
    "has_next_page": true
  }
}
```

The `lead` and `assignee` fields are `null` when not set. `last_escalated_at` is set when the conversation was escalated by the AI; `last_escalation_resolved_at` is set when an agent marked the escalation resolved.

***

#### Get Conversation

```
GET /chats/:chat_id/conversations/:id
```

Returns a single conversation with its messages.

**Parameters**

| Parameter                  | Type    | Default | Description                                                       |
| -------------------------- | ------- | ------- | ----------------------------------------------------------------- |
| `message_page`             | integer | `1`     | Message page number                                               |
| `message_per_page`         | integer | `100`   | Messages per page (max `200`)                                     |
| `message_created_at_since` | string  | —       | ISO 8601 timestamp. Only return messages created since this time. |

**Response**

```json
{
  "conversation": {
    "id": 12345,
    "created_at": "2026-01-15T10:30:00Z",
    "last_message_at": "2026-01-15T10:45:00Z",
    "channel": "web",
    "manual_reply_mode": false,
    "last_escalated_at": null,
    "last_escalation_resolved_at": null,
    "assignee": null,
    "share_url": "https://app.zipchat.ai/chats/1/conversations/12345",
    "lead": {
      "id": 100,
      "first_name": "John",
      "last_name": "Doe",
      "full_name": "John Doe",
      "email": "john@example.com",
      "phone_number": "+1234567890",
      "email_marketing_consent": true,
      "phone_marketing_consent": false,
      "shopify_customer_id": "7890123456",
      "additional_attributes": {},
      "created_at": "2026-01-15T10:31:00Z"
    },
    "messages": [
      {
        "id": 50001,
        "created_at": "2026-01-15T10:30:05Z",
        "role": "user",
        "message": "Do you have this in blue?",
        "count_as_reply": false,
        "manual_reply": false,
        "status": "sent",
        "rate": null,
        "sender_id": null
      },
      {
        "id": 50002,
        "created_at": "2026-01-15T10:30:08Z",
        "role": "assistant",
        "message": "Yes! The Classic Tee is available in blue.",
        "count_as_reply": true,
        "manual_reply": false,
        "status": "sent",
        "rate": "positive",
        "sender_id": null
      }
    ],
    "messages_pagination": {
      "page": 1,
      "per_page": 100,
      "total_count": 2,
      "total_pages": 1,
      "has_next_page": false
    }
  }
}
```

The show endpoint returns expanded lead details (including `first_name`, `last_name`, `shopify_customer_id`, and `additional_attributes`) compared to the list endpoint. The `assignee`, `last_escalated_at`, and `last_escalation_resolved_at` fields follow the same shape as on the list endpoint.

***

#### List Users

```
GET /chats/:chat_id/users
```

Returns the users (the chat owner and any chat participants) who can be assigned to a conversation on this chat.

**Parameters**

| Parameter  | Type    | Default | Description                |
| ---------- | ------- | ------- | -------------------------- |
| `page`     | integer | `1`     | Page number                |
| `per_page` | integer | `50`    | Items per page (max `100`) |

**Response**

```json
{
  "users": [
    {
      "id": 42,
      "name": "Stan Marsh",
      "email": "stan@example.com",
      "avatar_url": "https://app.zipchat.ai/rails/active_storage/blobs/redirect/.../avatar.png"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total_count": 1,
    "total_pages": 1,
    "has_next_page": false
  }
}
```

`avatar_url` is `null` when the user has not uploaded an avatar.

***

#### Get User

```
GET /chats/:chat_id/users/:id
```

Returns a single user. Returns `404` if the user is not in the assignable set for this chat — the endpoint will never leak information about users from other chats.

**Response**

```json
{
  "user": {
    "id": 42,
    "name": "Stan Marsh",
    "email": "stan@example.com",
    "avatar_url": null
  }
}
```

***

#### Update Conversation Assignment

```
PATCH /chats/:chat_id/conversations/:id/assignment
```

Takes a conversation over from the AI (manual mode) or hands it back to the AI. This is a single atomic operation: setting `assignee_id` to a user puts the conversation into manual mode; setting it to `null` puts it back into AI mode and unassigns.

Use `GET /chats/:chat_id/users` to discover valid `assignee_id` values.

**Request body**

```json
{ "assignee_id": 42 }
```

Or, to hand back to the AI:

```json
{ "assignee_id": null }
```

**Response**

```json
{
  "conversation": {
    "id": 12345,
    "manual_reply_mode": true,
    "assignee": {
      "id": 42,
      "name": "Stan Marsh",
      "email": "stan@example.com",
      "avatar_url": null
    }
  }
}
```

**Errors**

| Status                     | Error message                                         | Cause                                                  |
| -------------------------- | ----------------------------------------------------- | ------------------------------------------------------ |
| `404 Not Found`            | `Conversation not found`                              | The conversation does not belong to this chat.         |
| `422 Unprocessable Entity` | `assignee_id is required (use null to assign to AI)`  | The body did not include `assignee_id`.                |
| `422 Unprocessable Entity` | `assignee_id is not an assignable user for this chat` | The given user is not the chat owner or a participant. |

***

#### Send Manual Message

```
POST /chats/:chat_id/conversations/:id/messages
```

Sends a manual reply from a human agent on a conversation that is currently in **manual mode**. The message is sent through whichever channel the conversation uses (web, email, WhatsApp, Instagram, Messenger).

You must take the conversation over first with `PATCH /conversations/:id/assignment` — sending while the conversation is still in AI mode returns `422`.

**Request body**

```json
{
  "message": "Hi! Happy to help with that.",
  "sender_id": 42
}
```

| Field       | Type    | Description                                                                                                   |
| ----------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| `message`   | string  | The reply text. Required.                                                                                     |
| `sender_id` | integer | The user id that should be recorded as the sender. Must be a member of `GET /chats/:chat_id/users`. Required. |

**Response**

```json
{
  "message": {
    "id": 50003,
    "created_at": "2026-01-15T10:45:00Z",
    "role": "assistant",
    "message": "Hi! Happy to help with that.",
    "count_as_reply": false,
    "manual_reply": true,
    "status": "sent",
    "rate": null,
    "sender_id": 42
  }
}
```

**Errors**

| Status                     | Error message                                                                     | Cause                                                                                                              |
| -------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `404 Not Found`            | `Conversation not found`                                                          | The conversation does not belong to this chat.                                                                     |
| `422 Unprocessable Entity` | `message is required`                                                             | Empty or missing `message`.                                                                                        |
| `422 Unprocessable Entity` | `sender_id is required`                                                           | Missing `sender_id`.                                                                                               |
| `422 Unprocessable Entity` | `sender_id is not an assignable user for this chat`                               | Sender is not the chat owner or a participant.                                                                     |
| `422 Unprocessable Entity` | `Conversation is in AI mode; assign it to a user before sending a manual message` | Call the assignment endpoint first.                                                                                |
| `422 Unprocessable Entity` | `Cannot send: customer last messaged more than 24 hours ago on a Meta channel`    | WhatsApp / Instagram / Messenger enforce a 24-hour reply window from Meta. Wait for the customer to message again. |

### Rate Limiting

The API allows **120 requests per 60-second window** per integration.

Every response includes rate limit headers:

| Header                  | Description                              |
| ----------------------- | ---------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests per window (`120`)      |
| `X-RateLimit-Remaining` | Requests remaining in the current window |
| `X-RateLimit-Reset`     | Unix timestamp when the window resets    |

When the limit is exceeded, the API returns `429 Too Many Requests`.

### Error Codes

| Status                     | Description                                                                       |
| -------------------------- | --------------------------------------------------------------------------------- |
| `401 Unauthorized`         | Missing or invalid API key                                                        |
| `404 Not Found`            | Conversation or user not found                                                    |
| `422 Unprocessable Entity` | Invalid parameter (e.g., malformed timestamp, missing field, ineligible assignee) |
| `429 Too Many Requests`    | Rate limit exceeded                                                               |

### Examples

#### Fetch the latest conversations

```bash
curl -H "Authorization: Bearer zc-your_api_key" \
  "https://app.zipchat.ai/api/integrations/backend_api/v1/chats/1/conversations"
```

#### Fetch conversations updated in the last hour

```bash
curl -H "Authorization: Bearer zc-your_api_key" \
  "https://app.zipchat.ai/api/integrations/backend_api/v1/chats/1/conversations?last_message_at_since=2026-01-15T09:30:00Z"
```

#### Fetch a single conversation with messages

```bash
curl -H "Authorization: Bearer zc-your_api_key" \
  "https://app.zipchat.ai/api/integrations/backend_api/v1/chats/1/conversations/12345"
```

#### Paginate through messages

```bash
curl -H "Authorization: Bearer zc-your_api_key" \
  "https://app.zipchat.ai/api/integrations/backend_api/v1/chats/1/conversations/12345?message_page=2&message_per_page=50"
```

#### Take over a conversation, reply, and hand it back to the AI

```bash
# 1. Take the conversation over and assign it to user 42
curl -X PATCH \
  -H "Authorization: Bearer zc-your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"assignee_id": 42}' \
  "https://app.zipchat.ai/api/integrations/backend_api/v1/chats/1/conversations/12345/assignment"

# 2. Send a manual reply on behalf of user 42
curl -X POST \
  -H "Authorization: Bearer zc-your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"message": "Hi! Happy to help.", "sender_id": 42}' \
  "https://app.zipchat.ai/api/integrations/backend_api/v1/chats/1/conversations/12345/messages"

# 3. Hand the conversation back to the AI
curl -X PATCH \
  -H "Authorization: Bearer zc-your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"assignee_id": null}' \
  "https://app.zipchat.ai/api/integrations/backend_api/v1/chats/1/conversations/12345/assignment"
```


---

# 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://docs.zipchat.ai/installation-and-setup/chat-settings/integrations/backend-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.
