# Zapini API - Complete Documentation

**Version:** 1.2.0
**Base URL:** `https://zapini.app/api/v1`

---

## Introduction

The Zapini API allows you to integrate your system with the WhatsApp messaging platform. You can send messages, manage conversations, contacts, and much more.

### Key Features

- **RESTful API** with JSON responses
- **Authentication** via Bearer Token (Sanctum)
- **Rate Limiting** for abuse protection
- **Webhooks** for real-time event notifications
- **Media support** (images, videos, audio, documents)

---

## Authentication

The API supports two authentication methods:

### 1. User Tokens (Sanctum)
After logging in via `/auth/login`, you receive a user token for full platform access.

### 2. Instance API Tokens (Recommended for Integrations)
Generate API tokens (`sk_` prefix) for specific WhatsApp instances. These tokens:
- Don't require user login
- Are scoped to a single instance
- Can be revoked independently
- Work with all API endpoints

### Required Headers

```
Authorization: Bearer {your_token}
Accept: application/json
Content-Type: application/json
```

**Example with API Token:**
```bash
curl -X GET https://zapini.app/api/v1/tags \
  -H "Authorization: Bearer sk_abc123..." \
  -H "Accept: application/json"
```

---

## Base URLs

The API can be accessed via two URLs:

### Main API URL
```
https://zapini.app/api/v1
```
Access all endpoints from the main platform URL.

### Instance Subdomain URL (Unified Gateway)
```
https://instance-{id}.zapini.app
```
Each WhatsApp instance has its own subdomain that serves as a **unified API gateway**:
- Direct WhatsApp operations (faster, no proxy)
- All platform API endpoints (proxied automatically)

**Benefits of Instance Subdomain:**
- Lower latency for WhatsApp operations
- Single base URL for all operations
- Same `sk_` token works for everything

**Example:**
```bash
# Direct WhatsApp status (fast)
curl https://instance-18.zapini.app/status \
  -H "Authorization: Bearer sk_abc123..."

# Platform API (proxied)
curl https://instance-18.zapini.app/api/v1/tags \
  -H "Authorization: Bearer sk_abc123..."
```

---

## Response Format

### Success Response

```json
{
  "success": true,
  "message": "Operation completed successfully",
  "data": {
    // response data
  }
}
```

### Paginated Response

```json
{
  "success": true,
  "data": [...],
  "meta": {
    "pagination": {
      "total": 100,
      "per_page": 20,
      "current_page": 1,
      "last_page": 5,
      "from": 1,
      "to": 20
    }
  }
}
```

### Error Response

```json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Error description",
    "details": {}
  }
}
```

---

## HTTP Error Codes

| Code | Description |
|------|-------------|
| 200 | Success |
| 201 | Resource created |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Resource not found |
| 409 | Conflict (resource already exists) |
| 422 | Validation error |
| 429 | Rate limit exceeded / Quota exceeded |
| 500 | Internal server error |

---

## Usage Limits

- **Rate Limit:** 100 requests per 15 minutes per IP
- **WhatsApp Messages:** 500 messages per day per instance
- **File Upload:**
  - Images: 16 MB
  - Videos: 64 MB
  - Audio: 16 MB
  - Documents: 100 MB

---

## Endpoint Index

1. [Authentication](#authentication-endpoints)
2. [WhatsApp Instances](#whatsapp-instances)
3. [Direct WhatsApp Endpoints](#direct-whatsapp-endpoints-instance-subdomain)
4. [Messages](#messages)
5. [Conversations](#conversations)
6. [Automation Control](#automation-control)
7. [Tags](#tags)
8. [Contacts](#contacts)
9. [Scheduled Messages](#scheduled-messages)
10. [Media](#media)
11. [Chat API](#chat-api-ui-optimized)

---

## Authentication Endpoints

### POST /auth/login

Authenticate and receive an access token.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| email | string | Yes | User email |
| password | string | Yes | User password |
| device_name | string | No | Device name |

**Example Request:**

```bash
curl -X POST https://zapini.app/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "your_password",
    "device_name": "My App"
  }'
```

**Success Response:**

```json
{
  "success": true,
  "message": "Login successful",
  "data": {
    "user": {
      "id": 1,
      "name": "User",
      "email": "user@example.com",
      "role": "admin",
      "locale": "en",
      "tenant": {
        "id": 1,
        "name": "My Company",
        "status": "active"
      }
    },
    "token": "1|abc123xyz...",
    "token_type": "Bearer",
    "expires_at": null
  }
}
```

**2FA Required Response:**

```json
{
  "success": true,
  "two_factor_required": true,
  "message": "Two-factor verification required"
}
```

---

### POST /auth/two-factor

Complete login with 2FA code.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| email | string | Yes | User email |
| password | string | Yes | User password |
| code | string | Yes | 2FA code (6 digits) |
| device_name | string | No | Device name |

---

### POST /auth/register

Register a new user.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | Yes | Full name |
| email | string | Yes | Email (unique) |
| password | string | Yes | Password (min 8 characters) |
| password_confirmation | string | Yes | Password confirmation |
| device_name | string | No | Device name |
| terms_accepted | boolean | Yes | Terms acceptance |

---

### GET /auth/me

Returns authenticated user data.

**Headers:** Requires authentication

**Response:**

```json
{
  "success": true,
  "data": {
    "user": {
      "id": 1,
      "name": "User",
      "email": "user@example.com",
      "role": "admin",
      "locale": "en",
      "timezone": "America/New_York",
      "email_verified": true,
      "two_factor_enabled": false,
      "tenant": {
        "id": 1,
        "name": "My Company",
        "status": "active"
      }
    }
  }
}
```

---

### PATCH /auth/me

Update user profile.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | No | New name |
| locale | string | No | Language (en, pt-BR, es) |
| timezone | string | No | Timezone |

---

### POST /auth/logout

End current session (revoke token).

---

### POST /auth/logout-all

End all sessions (revoke all tokens).

---

### POST /auth/refresh

Refresh the access token.

**Response:**

```json
{
  "success": true,
  "message": "Token refreshed",
  "data": {
    "token": "2|new_token...",
    "token_type": "Bearer",
    "expires_at": null
  }
}
```

---

### POST /auth/password/forgot

Send password recovery email.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| email | string | Yes | User email |

---

### POST /auth/password/reset

Reset password with recovery token.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| token | string | Yes | Recovery token |
| email | string | Yes | User email |
| password | string | Yes | New password |
| password_confirmation | string | Yes | Confirmation |

---

### POST /auth/password/change

Change password (authenticated user).

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| current_password | string | Yes | Current password |
| password | string | Yes | New password |
| password_confirmation | string | Yes | Confirmation |

---

### GET /auth/sessions

List all active user sessions.

**Response:**

```json
{
  "success": true,
  "data": {
    "sessions": [
      {
        "id": 1,
        "name": "Chrome - Windows",
        "last_used_at": "2025-01-15T10:30:00Z",
        "created_at": "2025-01-10T08:00:00Z",
        "is_current": true
      }
    ]
  }
}
```

---

### DELETE /auth/sessions/{id}

Revoke a specific session.

---

## WhatsApp Instances

### GET /instances

List all WhatsApp instances.

**Query Parameters:**

| Field | Type | Description |
|-------|------|-------------|
| status | string | Filter by status (connected, disconnected, qr_ready, pending) |

**Response:**

```json
{
  "success": true,
  "data": {
    "instances": [
      {
        "uuid": "550e8400-e29b-41d4-a716-446655440000",
        "phone_number": "15551234567",
        "status": "connected",
        "connected": true,
        "messages_sent_today": 50,
        "remaining_quota": 450,
        "can_send_messages": true,
        "last_activity_at": "2025-01-15T10:30:00Z",
        "created_at": "2025-01-01T00:00:00Z"
      }
    ]
  }
}
```

---

### GET /instances/{uuid}

Returns instance details.

---

### GET /instances/{uuid}/qr

Get QR Code for WhatsApp connection.

**Response:**

```json
{
  "success": true,
  "data": {
    "status": "qr_ready",
    "qr_code": "data:image/png;base64,...",
    "qr_code_text": "2@...",
    "qr_generated_at": "2025-01-15T10:30:00Z",
    "qr_expired": false,
    "message": "QR code ready to scan"
  }
}
```

---

### GET /instances/{uuid}/status

Returns instance connection status.

**Query Parameters:**

| Field | Type | Description |
|-------|------|-------------|
| realtime | boolean | Get real-time status from server |

---

### GET /instances/{uuid}/stats

Returns instance statistics.

**Response:**

```json
{
  "success": true,
  "data": {
    "messages_sent_today": 50,
    "remaining_quota": 450,
    "daily_limit": 500,
    "messages_this_week": 200,
    "messages_this_month": 800,
    "messages_received_today": 30,
    "conversations_count": 150,
    "contacts_count": 500,
    "failed_attempts": 0,
    "last_activity_at": "2025-01-15T10:30:00Z"
  }
}
```

---

### POST /instances/{uuid}/disconnect

Disconnect the WhatsApp instance.

---

### POST /instances/{uuid}/reconnect

Start reconnection process.

---

## Direct WhatsApp Endpoints (Instance Subdomain)

These endpoints are available **only** via the instance subdomain URL (`https://instance-{id}.zapini.app`). They provide direct access to WhatsApp operations with lower latency.

### GET /status

Get real-time connection status.

**Example:**
```bash
curl https://instance-18.zapini.app/status \
  -H "Authorization: Bearer sk_abc123..."
```

**Response:**
```json
{
  "success": true,
  "status": "connected",
  "connected": true,
  "connecting": false,
  "uptime": 3600.5,
  "phoneNumber": "5511999999999",
  "lastHeartbeat": "2025-01-15T10:30:00Z"
}
```

---

### GET /qr

Get QR code for authentication.

**Response:**
```json
{
  "success": true,
  "status": "qr_ready",
  "qr_code": "data:image/png;base64,...",
  "qr_code_text": "2@abc..."
}
```

---

### POST /send-message

Send a text message directly via WhatsApp.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| number | string | Yes | Recipient number (e.g., 5511999999999) |
| message | string | Yes | Message content |

**Example:**
```bash
curl -X POST https://instance-18.zapini.app/send-message \
  -H "Authorization: Bearer sk_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "number": "5511999999999",
    "message": "Hello from the API!"
  }'
```

**Response:**
```json
{
  "success": true,
  "messageId": "3EB0...",
  "timestamp": 1705312200
}
```

---

### POST /send-media

Send media (image, video, audio, document).

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| number | string | Yes | Recipient number |
| media_url | string | Yes | Public URL of the media file |
| media_type | string | Yes | Type: image, video, audio, document |
| caption | string | No | Caption for the media |
| filename | string | No | Filename (for documents) |

**Example:**
```bash
curl -X POST https://instance-18.zapini.app/send-media \
  -H "Authorization: Bearer sk_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "number": "5511999999999",
    "media_url": "https://example.com/image.jpg",
    "media_type": "image",
    "caption": "Check this out!"
  }'
```

---

### POST /send-reaction

React to a message with an emoji.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| message_id | string | Yes | WhatsApp message ID |
| remote_jid | string | Yes | Chat JID |
| emoji | string | Yes | Reaction emoji |

---

### POST /delete-message

Delete a sent message.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| message_id | string | Yes | WhatsApp message ID |
| remote_jid | string | Yes | Chat JID |

---

### GET /groups

List all WhatsApp groups.

**Response:**
```json
{
  "success": true,
  "groups": [
    {
      "jid": "120363001234567890@g.us",
      "subject": "My Group",
      "participants_count": 15,
      "creation": 1705312200
    }
  ]
}
```

---

### POST /group/create

Create a new WhatsApp group.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| subject | string | Yes | Group name |
| participants | array | Yes | Array of phone numbers |

---

### GET /group/{jid}/metadata

Get group details and participants.

---

### POST /reconnect

Force reconnection to WhatsApp.

---

### POST /logout

Logout and clear session (requires new QR scan).

---

## Messages

### GET /messages

List messages with filters.

**Query Parameters:**

| Field | Type | Description |
|-------|------|-------------|
| instance_id | string | Instance UUID |
| conversation_id | integer | Conversation ID |
| direction | string | Direction (incoming, outgoing) |
| status | string | Status (pending, sent, delivered, read, failed) |
| from | datetime | Start date |
| to | datetime | End date |
| search | string | Search in message body |
| per_page | integer | Items per page (default: 20) |

---

### POST /messages/send

Send a text message.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| instance_id | string | Yes | Instance UUID |
| recipient | string | Yes | Recipient number (format: +15551234567) |
| message | string | Yes | Message content (max 4096 chars) |
| schedule_at | datetime | No | Schedule send for specific date/time |
| reply_to | string | No | UUID or ID of message to reply to |

**Example Request:**

```bash
curl -X POST https://zapini.app/api/v1/messages/send \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_id": "550e8400-e29b-41d4-a716-446655440000",
    "recipient": "+15551234567",
    "message": "Hello! This is a test message."
  }'
```

**Response:**

```json
{
  "success": true,
  "message": "Message added to queue",
  "data": {
    "uuid": "660e8400-e29b-41d4-a716-446655440001",
    "status": "pending",
    "scheduled_at": null,
    "delay_seconds": 15,
    "remaining_quota": 449
  }
}
```

---

### POST /messages/send-media

Send a message with media.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| instance_id | string | Yes | Instance UUID |
| recipient | string | Yes | Recipient number |
| media_url | string | Yes | Public URL of the file |
| media_type | string | Yes | Type (image, video, audio, document) |
| caption | string | No | Caption (max 4096 chars) |
| filename | string | No | Filename (for documents) |
| schedule_at | datetime | No | Schedule send |

**Example:**

```bash
curl -X POST https://zapini.app/api/v1/messages/send-media \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_id": "550e8400-e29b-41d4-a716-446655440000",
    "recipient": "+15551234567",
    "media_url": "https://example.com/image.jpg",
    "media_type": "image",
    "caption": "Check out this image!"
  }'
```

---

### GET /messages/{uuid}

Returns message details.

---

### GET /messages/{uuid}/status

Returns only the message status.

**Response:**

```json
{
  "success": true,
  "data": {
    "uuid": "660e8400-e29b-41d4-a716-446655440001",
    "status": "delivered",
    "sent_at": "2025-01-15T10:30:00Z",
    "delivered_at": "2025-01-15T10:30:05Z",
    "read_at": null,
    "error_message": null,
    "retry_count": 0
  }
}
```

---

### PATCH /messages/{uuid}/edit

Edit a sent message.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| message | string | Yes | New message content |

**Notes:**
- Only outgoing messages can be edited
- Message must have status sent, delivered or read

---

### DELETE /messages/{uuid}

Delete a message.

**Notes:**
- Only outgoing messages can be deleted
- Message will also be deleted from recipient's WhatsApp

---

### POST /messages/{uuid}/reaction

Add a reaction to a message.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| emoji | string | Yes | Reaction emoji |

---

### DELETE /messages/{uuid}/reaction

Remove reaction from a message.

---

### GET /messages/{uuid}/reactions

List all reactions for a message.

---

## Conversations

### GET /conversations

List conversations.

**Query Parameters:**

| Field | Type | Description |
|-------|------|-------------|
| instance_id | string | Instance UUID |
| unread | boolean | Only conversations with unread messages |
| search | string | Search by name or number |
| is_group | boolean | Filter groups or individuals |
| per_page | integer | Items per page |

---

### GET /conversations/archived

List archived conversations.

---

### GET /conversations/{uuid}

Returns conversation details.

---

### GET /conversations/{uuid}/messages

List messages from a conversation.

**Query Parameters:**

| Field | Type | Description |
|-------|------|-------------|
| from | datetime | Start date |
| to | datetime | End date |
| before_id | integer | Messages before this ID (for infinite scroll) |
| after_id | integer | Messages after this ID |
| limit | integer | Message limit (default: 50) |

---

### POST /conversations/{uuid}/mark-read

Mark conversation as read (reset unread counter).

---

### POST /conversations/{uuid}/archive

Archive the conversation.

---

### POST /conversations/{uuid}/unarchive

Unarchive the conversation.

---

### DELETE /conversations/{uuid}

Delete conversation and all messages.

---

## Automation Control

Manage AI automation for conversations. When automation is active, manual messages are blocked until you take over the chat.

**Base URL:** `/api/v1/chat`

### Important: Automation Behavior

- When automation is **active**, manual messages are blocked to prevent conflicts with AI responses.
- Use **pause** to take over the chat and send messages manually.
- Use **resume** to let the AI handle the conversation again.
- **IMPORTANT**: To resume automation, you must first **archive** the conversation.

> **Security: Archive Required to Resume**
>
> Automation can only be resumed after archiving the conversation. This ensures the manual service session is properly closed before returning control to AI. When resuming, the conversation will be automatically unarchived.

### POST /chat/conversations/{uuid}/pause-automation

Pauses automation for a conversation, allowing manual messages.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| reason | string | No | Optional reason for pausing automation |

**Example Request:**

```bash
curl -X POST https://zapini.app/api/v1/chat/conversations/550e8400-e29b-41d4-a716-446655440045/pause-automation \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Manual takeover for VIP client"}'
```

**Response:**

```json
{
  "success": true,
  "message": "Automation paused successfully.",
  "data": {
    "conversation_id": "550e8400-e29b-41d4-a716-446655440045",
    "automation_paused": true,
    "paused_at": "2025-12-20T14:30:00Z",
    "paused_by": "John Doe",
    "reason": "Manual takeover for VIP client"
  }
}
```

**Error Responses:**

| Code | Error | Description |
|------|-------|-------------|
| 400 | NO_ACTIVE_AUTOMATION | Conversation has no active automation |
| 400 | AUTOMATION_ALREADY_PAUSED | Automation is already paused |
| 404 | NOT_FOUND | Conversation not found |

---

### POST /chat/conversations/{uuid}/resume-automation

Resumes automation for a conversation, letting AI handle responses.

> **Note:** The conversation must be archived before resuming automation.

**Example Request:**

```bash
curl -X POST https://zapini.app/api/v1/chat/conversations/550e8400-e29b-41d4-a716-446655440045/resume-automation \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json"
```

**Response:**

```json
{
  "success": true,
  "message": "Automation resumed successfully.",
  "data": {
    "conversation_id": "550e8400-e29b-41d4-a716-446655440045",
    "automation_paused": false,
    "resumed_at": "2025-12-20T15:00:00Z"
  }
}
```

**Error Responses:**

| Code | Error | Description |
|------|-------|-------------|
| 400 | NO_ACTIVE_AUTOMATION | Conversation has no active automation |
| 400 | AUTOMATION_NOT_PAUSED | Automation is not currently paused |
| 404 | NOT_FOUND | Conversation not found |

---

## Tags

Manage conversation tags for organizing and categorizing chats. Tags are tenant-scoped and can be assigned to multiple conversations.

### GET /tags

List all tags for the current tenant.

**Response:**

```json
{
  "success": true,
  "data": {
    "tags": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "name": "VIP",
        "color": "#f59e0b",
        "conversations_count": 15,
        "created_at": "2025-01-15T10:30:00Z",
        "updated_at": "2025-01-15T10:30:00Z"
      }
    ]
  }
}
```

---

### POST /tags

Create a new tag.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | Yes | Tag name (max 50 characters, unique per tenant) |
| color | string | Yes | Hex color code (#RRGGBB) |

**Example:**

```bash
curl -X POST https://zapini.app/api/v1/tags \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Important",
    "color": "#ef4444"
  }'
```

**Response:**

```json
{
  "success": true,
  "message": "Tag created successfully",
  "data": {
    "tag": {
      "id": "550e8400-e29b-41d4-a716-446655440003",
      "name": "Important",
      "color": "#ef4444",
      "conversations_count": 0,
      "created_at": "2025-01-15T12:00:00Z",
      "updated_at": "2025-01-15T12:00:00Z"
    }
  }
}
```

---

### GET /tags/{uuid}

Returns tag details.

---

### PATCH /tags/{uuid}

Update an existing tag.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | No | New tag name (max 50 characters) |
| color | string | No | New hex color code (#RRGGBB) |

---

### DELETE /tags/{uuid}

Delete a tag. The tag will be automatically removed from all conversations.

---

### PUT /conversations/{uuid}/tags

Update tags assigned to a conversation. This replaces all existing tags with the provided list.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| tag_ids | array | Yes | Array of tag UUIDs to assign |

**Example:**

```bash
curl -X PUT https://zapini.app/api/v1/conversations/550e8400-e29b-41d4-a716-446655440045/tags \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "tag_ids": ["550e8400-e29b-41d4-a716-446655440001", "550e8400-e29b-41d4-a716-446655440002", "550e8400-e29b-41d4-a716-446655440003"]
  }'
```

**Response:**

```json
{
  "success": true,
  "message": "Conversation tags updated",
  "data": {
    "tags": [
      {"id": "550e8400-e29b-41d4-a716-446655440001", "name": "VIP", "color": "#f59e0b"},
      {"id": "550e8400-e29b-41d4-a716-446655440002", "name": "Support", "color": "#3b82f6"},
      {"id": "550e8400-e29b-41d4-a716-446655440003", "name": "Important", "color": "#ef4444"}
    ]
  }
}
```

**Note:** To remove all tags from a conversation, send an empty array: `{"tag_ids": []}`

> **Breaking Change v1.2.0:** Tag IDs are now UUIDs instead of integers.

---

## Chat API (UI-Optimized)

The Chat API provides endpoints optimized for building WhatsApp-like chat interfaces. Returns pre-formatted data with resolved @mentions, grouped reactions, and alignment helpers.

**Base URL:** `/api/v1/chat`

### Key Features

| Field | Description |
|-------|-------------|
| `formatted_body` | Message text with @mentions resolved to names (HTML) |
| `is_from_me` | Boolean for easy message alignment |
| `grouped_reactions` | Reactions grouped by emoji with counts |
| `mentioned_jids` | Array of mentioned WhatsApp JIDs |
| `tags` | Conversation tags included |

### GET /chat/conversations

List conversations optimized for chat UI.

**Query Parameters:**

| Field | Type | Description |
|-------|------|-------------|
| instance_id | string | Filter by instance UUID |
| search | string | Search by name or number |
| unread | boolean | Only unread conversations |
| per_page | integer | Items per page |

**Response:**

```json
{
  "success": true,
  "data": {
    "data": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440045",
        "contact_number": "5521999999999",
        "display_name": "John Doe",
        "profile_picture_url": "https://...",
        "last_message": "Hello!",
        "unread_count": 3,
        "tags": [{"id": "550e8400-e29b-41d4-a716-446655440001", "name": "VIP", "color": "#f59e0b"}],
        "instance": {"uuid": "abc-123", "status": "connected"}
      }
    ]
  }
}
```

---

### GET /chat/conversations/{uuid}/messages

Get messages with formatted body and grouped reactions.

**Query Parameters:**

| Field | Type | Description |
|-------|------|-------------|
| before_id | integer | Messages before this ID |
| after_id | integer | Messages after this ID |
| limit | integer | Message limit (default: 50) |

**Response:**

```json
{
  "success": true,
  "data": {
    "messages": [
      {
        "id": 456,
        "direction": "incoming",
        "is_from_me": false,
        "message_body": "Hello @5521999999999!",
        "formatted_body": "Hello <span class=\"mention\">@John</span>!",
        "mentioned_jids": ["5521999999999@s.whatsapp.net"],
        "grouped_reactions": [
          {"emoji": "👍", "count": 2, "has_my_reaction": true}
        ]
      }
    ],
    "has_more": true
  }
}
```

---

### POST /chat/send

Send a text message with chat-optimized response.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| instance_id | string | Yes | Instance UUID |
| recipient | string | Yes | Recipient number |
| message | string | Yes | Message content |
| reply_to | string | No | Message UUID to reply |

---

### POST /chat/send-media

Send media with chat-optimized response.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| instance_id | string | Yes | Instance UUID |
| recipient | string | Yes | Recipient number |
| media_url | string | Yes | Media file URL |
| media_type | string | Yes | Type (image, video, audio, document) |
| caption | string | No | Caption |

---

### PATCH /chat/messages/{uuid}

Edit a sent message.

---

### DELETE /chat/messages/{uuid}

Delete a sent message.

---

### POST /chat/messages/{uuid}/reaction

Add emoji reaction.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| emoji | string | Yes | Emoji to react |

---

### DELETE /chat/messages/{uuid}/reaction

Remove reaction.

---

### POST /chat/conversations/{uuid}/mark-read

Mark conversation as read.

---

### POST /chat/conversations/{uuid}/archive

Archive conversation.

---

### Alternative: ?format=chat

Use standard endpoints with `?format=chat` parameter:

```bash
GET /api/v1/conversations?format=chat
GET /api/v1/conversations/550e8400-e29b-41d4-a716-446655440045/messages?format=chat
```

---

## Contacts

### GET /contacts

List contacts.

**Query Parameters:**

| Field | Type | Description |
|-------|------|-------------|
| instance_id | string | Instance UUID |
| search | string | Search by name, number, email or company |
| is_group | boolean | Filter groups |
| sort | string | Sort field |
| dir | string | Direction (asc, desc) |
| per_page | integer | Items per page |

---

### POST /contacts

Create a new contact.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| instance_id | string | Yes | Instance UUID |
| phone_number | string | Yes | Phone number |
| first_name | string | No | First name |
| last_name | string | No | Last name |
| display_name | string | No | Display name |
| email | string | No | Email |
| company | string | No | Company |
| job_title | string | No | Job title |
| notes | string | No | Notes |
| custom_fields | object | No | Custom fields |

---

### POST /contacts/import

Bulk import contacts.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| instance_id | string | Yes | Instance UUID |
| contacts | array | Yes | Contact list (max 1000) |

**Example:**

```bash
curl -X POST https://zapini.app/api/v1/contacts/import \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_id": "550e8400-e29b-41d4-a716-446655440000",
    "contacts": [
      {
        "phone_number": "+15551234567",
        "first_name": "John",
        "last_name": "Doe",
        "email": "john@example.com"
      },
      {
        "phone_number": "+15559876543",
        "first_name": "Jane",
        "company": "XYZ Corp"
      }
    ]
  }'
```

---

### GET /contacts/{uuid}

Returns contact details.

---

### PATCH /contacts/{uuid}

Update a contact.

---

### DELETE /contacts/{uuid}

Delete a contact.

---

### GET /contacts/{uuid}/messages

List messages exchanged with a contact.

---

## Scheduled Messages

### GET /scheduled

List scheduled messages.

**Query Parameters:**

| Field | Type | Description |
|-------|------|-------------|
| instance_id | string | Instance UUID |
| from | datetime | Start date |
| to | datetime | End date |
| per_page | integer | Items per page |

---

### POST /scheduled

Schedule a new message.

**Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| instance_id | string | Yes | Instance UUID |
| recipient | string | Yes* | Recipient number |
| contact_id | string | Yes* | Contact UUID (alternative to recipient) |
| message | string | Yes | Message content |
| scheduled_at | datetime | Yes | Send date/time |
| media_url | string | No | Media URL |
| media_type | string | No | Media type |

*One of the two is required: recipient or contact_id

---

### GET /scheduled/{uuid}

Returns scheduled message details.

---

### PATCH /scheduled/{uuid}

Update a pending scheduled message.

---

### DELETE /scheduled/{uuid}

Cancel a scheduled message.

---

### POST /scheduled/{uuid}/send-now

Send the scheduled message immediately.

---

## Media

### POST /media/upload

Upload a file.

**Parameters (multipart/form-data):**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| file | file | Yes | File to upload |
| type | string | Yes | Type (image, video, audio, document) |

**Limits:**

| Type | Max Size | Accepted Formats |
|------|----------|------------------|
| image | 16 MB | JPEG, PNG, GIF, WebP |
| video | 64 MB | MP4, 3GPP, QuickTime |
| audio | 16 MB | MP3, OGG, Opus, WAV, AAC |
| document | 100 MB | PDF, DOC(X), XLS(X), PPT(X), TXT, CSV |

**Example:**

```bash
curl -X POST https://zapini.app/api/v1/media/upload \
  -H "Authorization: Bearer {token}" \
  -F "file=@/path/to/image.jpg" \
  -F "type=image"
```

---

### GET /media/{id}

Returns file details.

---

### DELETE /media/{id}

Delete a file.

---

## Code Examples

### PHP (Guzzle)

```php
<?php
use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://zapini.app/api/v1/',
    'headers' => [
        'Authorization' => 'Bearer ' . $token,
        'Accept' => 'application/json',
    ]
]);

// Login
$response = $client->post('auth/login', [
    'json' => [
        'email' => 'user@example.com',
        'password' => 'your_password'
    ]
]);
$data = json_decode($response->getBody(), true);
$token = $data['data']['token'];

// Send message
$response = $client->post('messages/send', [
    'json' => [
        'instance_id' => '550e8400-e29b-41d4-a716-446655440000',
        'recipient' => '+15551234567',
        'message' => 'Hello from PHP!'
    ]
]);

$result = json_decode($response->getBody(), true);
echo "Message sent! UUID: " . $result['data']['uuid'];
```

### Python (Requests)

```python
import requests

BASE_URL = 'https://zapini.app/api/v1'

# Login
response = requests.post(f'{BASE_URL}/auth/login', json={
    'email': 'user@example.com',
    'password': 'your_password'
})
data = response.json()
token = data['data']['token']

headers = {
    'Authorization': f'Bearer {token}',
    'Accept': 'application/json'
}

# Send message
response = requests.post(f'{BASE_URL}/messages/send',
    headers=headers,
    json={
        'instance_id': '550e8400-e29b-41d4-a716-446655440000',
        'recipient': '+15551234567',
        'message': 'Hello from Python!'
    }
)

result = response.json()
print(f"Message sent! UUID: {result['data']['uuid']}")
```

### JavaScript (Fetch)

```javascript
const BASE_URL = 'https://zapini.app/api/v1';

// Login
async function login(email, password) {
    const response = await fetch(`${BASE_URL}/auth/login`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({ email, password })
    });
    const data = await response.json();
    return data.data.token;
}

// Send message
async function sendMessage(token, instanceId, recipient, message) {
    const response = await fetch(`${BASE_URL}/messages/send`, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            instance_id: instanceId,
            recipient: recipient,
            message: message
        })
    });
    return await response.json();
}

// Usage
const token = await login('user@example.com', 'your_password');
const result = await sendMessage(
    token,
    '550e8400-e29b-41d4-a716-446655440000',
    '+15551234567',
    'Hello from JavaScript!'
);
console.log('Message sent! UUID:', result.data.uuid);
```

### Node.js (Axios)

```javascript
const axios = require('axios');

const api = axios.create({
    baseURL: 'https://zapini.app/api/v1',
    headers: {
        'Accept': 'application/json'
    }
});

// Login
async function login(email, password) {
    const { data } = await api.post('/auth/login', { email, password });
    api.defaults.headers.common['Authorization'] = `Bearer ${data.data.token}`;
    return data.data.token;
}

// Send message
async function sendMessage(instanceId, recipient, message) {
    const { data } = await api.post('/messages/send', {
        instance_id: instanceId,
        recipient: recipient,
        message: message
    });
    return data;
}

// Usage
(async () => {
    await login('user@example.com', 'your_password');
    const result = await sendMessage(
        '550e8400-e29b-41d4-a716-446655440000',
        '+15551234567',
        'Hello from Node.js!'
    );
    console.log('Message sent! UUID:', result.data.uuid);
})();
```

---

## Webhooks

Configure webhooks to receive real-time events.

### Available Events

| Event | Description |
|-------|-------------|
| message.received | New message received |
| message.sent | Message sent |
| message.delivered | Message delivered |
| message.read | Message read |
| message.failed | Send failed |
| instance.connected | Instance connected |
| instance.disconnected | Instance disconnected |
| instance.qr_ready | QR Code available |

### Payload Format

```json
{
  "event": "message.received",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message": {
      "unique_id": "...",
      "direction": "incoming",
      "sender_number": "15551234567",
      "message_body": "Hello!"
    },
    "instance": {
      "uuid": "...",
      "phone_number": "15559876543"
    }
  }
}
```

---

## Support

- **Email:** support@zapini.app
- **Documentation:** https://zapini.app/docs

---

*Last updated: December 2025*

---

## Kanban & Calendar API

The Zapini API includes full Kanban pipeline management and Calendar/Appointment management endpoints.

**See dedicated documentation:** [kanban-calendar.md](kanban-calendar.md)

### Kanban API (21 endpoints)
- `GET /api/v1/kanban/boards` — List boards
- `POST /api/v1/kanban/boards` — Create board
- `GET /api/v1/kanban/boards/{uuid}` — Get board + stats
- `PUT /api/v1/kanban/boards/{uuid}` — Update board
- `DELETE /api/v1/kanban/boards/{uuid}` — Delete board
- `GET /api/v1/kanban/boards/{uuid}/columns` — List columns
- `POST /api/v1/kanban/boards/{uuid}/columns` — Create column
- `POST /api/v1/kanban/boards/{uuid}/columns/reorder` — Reorder columns
- `PUT /api/v1/kanban/columns/{uuid}` — Update column
- `DELETE /api/v1/kanban/columns/{uuid}` — Delete column
- `GET /api/v1/kanban/boards/{uuid}/leads` — List leads (with filters)
- `POST /api/v1/kanban/leads` — Create lead
- `GET /api/v1/kanban/leads/{uuid}` — Get lead
- `PUT /api/v1/kanban/leads/{uuid}` — Update lead
- `DELETE /api/v1/kanban/leads/{uuid}` — Delete lead
- `POST /api/v1/kanban/leads/{uuid}/move` — Move to column
- `POST /api/v1/kanban/leads/{uuid}/won` — Mark as won
- `POST /api/v1/kanban/leads/{uuid}/lost` — Mark as lost
- `POST /api/v1/kanban/leads/{uuid}/note` — Add note
- `GET /api/v1/kanban/leads/{uuid}/activities` — Activity timeline
- `POST /api/v1/kanban/leads/{uuid}/ai-qualify` — AI qualification

### Calendar API (9 endpoints)
- `GET /api/v1/calendar/appointments` — List appointments
- `POST /api/v1/calendar/appointments` — Create appointment
- `GET /api/v1/calendar/appointments/{uuid}` — Get appointment
- `PUT /api/v1/calendar/appointments/{uuid}` — Update appointment
- `DELETE /api/v1/calendar/appointments/{uuid}` — Delete appointment
- `POST /api/v1/calendar/appointments/{uuid}/confirm` — Confirm
- `POST /api/v1/calendar/appointments/{uuid}/complete` — Complete
- `POST /api/v1/calendar/appointments/{uuid}/cancel` — Cancel
- `POST /api/v1/calendar/appointments/{uuid}/to-kanban` — Convert to Kanban lead
