# Zapini API - Documentacion Completa

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

> **Breaking Changes v1.2.0:** Los IDs de conversaciones, tags, mensajes programados e integraciones ahora son UUIDs (strings) en lugar de enteros.

---

## Introduccion

La API de Zapini le permite integrar su sistema con la plataforma de mensajeria WhatsApp. Con ella puede enviar mensajes, gestionar conversaciones, contactos y mucho mas.

### Caracteristicas Principales

- **API RESTful** con respuestas en JSON
- **Autenticacion** via Bearer Token (Sanctum)
- **Rate Limiting** para proteccion contra abusos
- **Webhooks** para recibir eventos en tiempo real
- **Soporte de medios** (imagenes, videos, audios, documentos)

---

## Autenticacion

La API soporta dos metodos de autenticacion:

### 1. Tokens de Usuario (Sanctum)
Despues de iniciar sesion via `/auth/login`, recibe un token de usuario para acceso completo a la plataforma.

### 2. Tokens de API de Instancia (Recomendado para Integraciones)
Genere tokens de API (prefijo `sk_`) para instancias especificas de WhatsApp. Estos tokens:
- No requieren inicio de sesion de usuario
- Estan limitados a una sola instancia
- Pueden revocarse independientemente
- Funcionan con todos los endpoints de la API

### Headers Obligatorios

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

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

---

## URLs Base

La API se puede acceder por dos URLs:

### URL Principal de la API
```
https://zapini.app/api/v1
```
Acceda a todos los endpoints desde la URL principal de la plataforma.

### URL del Subdominio de Instancia (Gateway Unificado)
```
https://instance-{id}.zapini.app
```
Cada instancia de WhatsApp tiene su propio subdominio que funciona como un **gateway de API unificado**:
- Operaciones directas de WhatsApp (mas rapidas, sin proxy)
- Todos los endpoints de la plataforma (proxy automatico)

**Beneficios del Subdominio de Instancia:**
- Menor latencia para operaciones de WhatsApp
- Una sola URL base para todas las operaciones
- El mismo token `sk_` funciona para todo

**Ejemplo:**
```bash
# Estado directo de WhatsApp (rapido)
curl https://instance-18.zapini.app/status \
  -H "Authorization: Bearer sk_abc123..."

# API de la plataforma (via proxy)
curl https://instance-18.zapini.app/api/v1/tags \
  -H "Authorization: Bearer sk_abc123..."
```

---

## Formato de Respuestas

### Respuesta Exitosa

```json
{
  "success": true,
  "message": "Operacion realizada con exito",
  "data": {
    // datos de respuesta
  }
}
```

### Respuesta con Paginacion

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

### Respuesta de Error

```json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Descripcion del error",
    "details": {}
  }
}
```

---

## Codigos de Error HTTP

| Codigo | Descripcion |
|--------|-------------|
| 200 | Exito |
| 201 | Recurso creado |
| 400 | Solicitud invalida |
| 401 | No autorizado |
| 403 | Acceso prohibido |
| 404 | Recurso no encontrado |
| 409 | Conflicto (recurso ya existe) |
| 422 | Error de validacion |
| 429 | Rate limit excedido / Cuota excedida |
| 500 | Error interno del servidor |

---

## Limites de Uso

- **Rate Limit:** 100 solicitudes por 15 minutos por IP
- **Mensajes WhatsApp:** 500 mensajes por dia por instancia
- **Subida de archivos:**
  - Imagenes: 16 MB
  - Videos: 64 MB
  - Audios: 16 MB
  - Documentos: 100 MB

---

## Indice de Endpoints

1. [Autenticacion](#endpoints-de-autenticacion)
2. [Instancias WhatsApp](#instancias-whatsapp)
3. [Mensajes](#mensajes)
4. [Conversaciones](#conversaciones)
5. [Control de Automatización](#control-de-automatización)
6. [Etiquetas](#etiquetas)
7. [Contactos](#contactos)
8. [Programaciones](#mensajes-programados)
9. [Medios](#medios)
10. [Chat API](#chat-api-optimizada-para-ui)

---

## Endpoints de Autenticacion

### POST /auth/login

Realiza login y devuelve el token de acceso.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| email | string | Si | Email del usuario |
| password | string | Si | Contrasena del usuario |
| device_name | string | No | Nombre del dispositivo |

**Ejemplo de Solicitud:**

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

**Respuesta Exitosa:**

```json
{
  "success": true,
  "message": "Inicio de sesion exitoso",
  "data": {
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440001",
      "name": "Usuario",
      "email": "usuario@ejemplo.com",
      "role": "admin",
      "locale": "es",
      "tenant": {
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "name": "Mi Empresa",
        "status": "active"
      }
    },
    "token": "1|abc123xyz...",
    "token_type": "Bearer",
    "expires_at": null
  }
}
```

**Respuesta con 2FA Requerido:**

```json
{
  "success": true,
  "two_factor_required": true,
  "message": "Verificacion de dos factores requerida"
}
```

---

### POST /auth/two-factor

Completa el login con codigo 2FA.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| email | string | Si | Email del usuario |
| password | string | Si | Contrasena del usuario |
| code | string | Si | Codigo 2FA (6 digitos) |
| device_name | string | No | Nombre del dispositivo |

---

### POST /auth/register

Registra un nuevo usuario.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| name | string | Si | Nombre completo |
| email | string | Si | Email (unico) |
| password | string | Si | Contrasena (min 8 caracteres) |
| password_confirmation | string | Si | Confirmacion de contrasena |
| device_name | string | No | Nombre del dispositivo |
| terms_accepted | boolean | Si | Aceptacion de terminos |

---

### GET /auth/me

Devuelve los datos del usuario autenticado.

**Headers:** Requiere autenticacion

**Respuesta:**

```json
{
  "success": true,
  "data": {
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440001",
      "name": "Usuario",
      "email": "usuario@ejemplo.com",
      "role": "admin",
      "locale": "es",
      "timezone": "America/Mexico_City",
      "email_verified": true,
      "two_factor_enabled": false,
      "tenant": {
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "name": "Mi Empresa",
        "status": "active"
      }
    }
  }
}
```

---

### PATCH /auth/me

Actualiza el perfil del usuario.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| name | string | No | Nuevo nombre |
| locale | string | No | Idioma (en, pt-BR, es) |
| timezone | string | No | Zona horaria |

---

### POST /auth/logout

Termina la sesion actual (revoca el token).

---

### POST /auth/logout-all

Termina todas las sesiones (revoca todos los tokens).

---

### POST /auth/refresh

Renueva el token de acceso.

**Respuesta:**

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

---

### POST /auth/password/forgot

Envia email de recuperacion de contrasena.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| email | string | Si | Email del usuario |

---

### POST /auth/password/reset

Restablece la contrasena con token de recuperacion.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| token | string | Si | Token de recuperacion |
| email | string | Si | Email del usuario |
| password | string | Si | Nueva contrasena |
| password_confirmation | string | Si | Confirmacion |

---

### POST /auth/password/change

Cambia la contrasena (usuario autenticado).

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| current_password | string | Si | Contrasena actual |
| password | string | Si | Nueva contrasena |
| password_confirmation | string | Si | Confirmacion |

---

### GET /auth/sessions

Lista todas las sesiones activas del usuario.

**Respuesta:**

```json
{
  "success": true,
  "data": {
    "sessions": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "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}

Revoca una sesion especifica.

---

## Instancias WhatsApp

### GET /instances

Lista todas las instancias WhatsApp.

**Parametros Query:**

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| status | string | Filtrar por estado (connected, disconnected, qr_ready, pending) |

**Respuesta:**

```json
{
  "success": true,
  "data": {
    "instances": [
      {
        "uuid": "550e8400-e29b-41d4-a716-446655440000",
        "phone_number": "5215551234567",
        "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}

Devuelve detalles de una instancia.

---

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

Obtiene el codigo QR para conexion.

**Respuesta:**

```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": "Codigo QR listo para escanear"
  }
}
```

---

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

Devuelve el estado de conexion de la instancia.

**Parametros Query:**

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| realtime | boolean | Obtener estado en tiempo real del servidor |

---

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

Devuelve estadisticas de la instancia.

**Respuesta:**

```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

Desconecta la instancia de WhatsApp.

---

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

Inicia el proceso de reconexion.

---

## Endpoints Directos de WhatsApp (Subdominio de Instancia)

Estos endpoints estan disponibles **solo** via URL del subdominio de instancia (`https://instance-{id}.zapini.app`). Proporcionan acceso directo a las operaciones de WhatsApp con menor latencia.

### GET /status

Obtiene el estado de conexion en tiempo real.

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

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

---

### GET /qr

Obtiene el codigo QR para autenticacion.

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

---

### POST /send-message

Envia un mensaje de texto directamente via WhatsApp.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| number | string | Si | Numero del destinatario (ej: 5511999999999) |
| message | string | Si | Contenido del mensaje |

**Ejemplo:**
```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": "Hola desde la API!"
  }'
```

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

---

### POST /send-media

Envia medios (imagen, video, audio, documento).

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| number | string | Si | Numero del destinatario |
| media_url | string | Si | URL publica del archivo de medios |
| media_type | string | Si | Tipo: image, video, audio, document |
| caption | string | No | Leyenda para el medio |
| filename | string | No | Nombre del archivo (para documentos) |

---

### POST /send-reaction

Reacciona a un mensaje con un emoji.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| message_id | string | Si | ID del mensaje en WhatsApp |
| remote_jid | string | Si | JID del chat |
| emoji | string | Si | Emoji de reaccion |

---

### POST /delete-message

Elimina un mensaje enviado.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| message_id | string | Si | ID del mensaje en WhatsApp |
| remote_jid | string | Si | JID del chat |

---

### GET /groups

Lista todos los grupos de WhatsApp.

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

---

### POST /group/create

Crea un nuevo grupo de WhatsApp.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| subject | string | Si | Nombre del grupo |
| participants | array | Si | Array de numeros de telefono |

---

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

Obtiene detalles y participantes del grupo.

---

### POST /reconnect

Fuerza reconexion a WhatsApp.

---

### POST /logout

Cierra sesion y limpia la sesion (requiere nuevo codigo QR).

---

## Mensajes

### GET /messages

Lista mensajes con filtros.

**Parametros Query:**

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| instance_id | string | UUID de la instancia |
| conversation_id | integer | ID de la conversacion |
| direction | string | Direccion (incoming, outgoing) |
| status | string | Estado (pending, sent, delivered, read, failed) |
| from | datetime | Fecha inicial |
| to | datetime | Fecha final |
| search | string | Buscar en cuerpo del mensaje |
| per_page | integer | Items por pagina (defecto: 20) |

---

### POST /messages/send

Envia un mensaje de texto.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| instance_id | string | Si | UUID de la instancia |
| recipient | string | Si | Numero del destinatario (formato: +5215551234567) |
| message | string | Si | Contenido del mensaje (max 4096 caracteres) |
| schedule_at | datetime | No | Programar envio para fecha/hora especifica |
| reply_to | string | No | UUID o ID del mensaje a responder |

**Ejemplo de Solicitud:**

```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": "+5215551234567",
    "message": "Hola! Este es un mensaje de prueba."
  }'
```

**Respuesta:**

```json
{
  "success": true,
  "message": "Mensaje agregado a la cola",
  "data": {
    "uuid": "660e8400-e29b-41d4-a716-446655440001",
    "status": "pending",
    "scheduled_at": null,
    "delay_seconds": 15,
    "remaining_quota": 449
  }
}
```

---

### POST /messages/send-media

Envia un mensaje con medios.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| instance_id | string | Si | UUID de la instancia |
| recipient | string | Si | Numero del destinatario |
| media_url | string | Si | URL publica del archivo |
| media_type | string | Si | Tipo (image, video, audio, document) |
| caption | string | No | Leyenda (max 4096 caracteres) |
| filename | string | No | Nombre del archivo (para documentos) |
| schedule_at | datetime | No | Programar envio |

**Ejemplo:**

```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": "+5215551234567",
    "media_url": "https://ejemplo.com/imagen.jpg",
    "media_type": "image",
    "caption": "Mira esta imagen!"
  }'
```

---

### GET /messages/{uuid}

Devuelve detalles de un mensaje.

---

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

Devuelve solo el estado de un mensaje.

**Respuesta:**

```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

Edita un mensaje ya enviado.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| message | string | Si | Nuevo contenido del mensaje |

**Notas:**
- Solo mensajes salientes pueden ser editados
- El mensaje debe tener estado sent, delivered o read

---

### DELETE /messages/{uuid}

Elimina un mensaje.

**Notas:**
- Solo mensajes salientes pueden ser eliminados
- El mensaje sera eliminado tambien en WhatsApp del destinatario

---

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

Agrega una reaccion a un mensaje.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| emoji | string | Si | Emoji de la reaccion |

---

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

Elimina la reaccion de un mensaje.

---

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

Lista todas las reacciones de un mensaje.

---

## Conversaciones

### GET /conversations

Lista conversaciones.

**Parametros Query:**

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| instance_id | string | UUID de la instancia |
| unread | boolean | Solo conversaciones con mensajes no leidos |
| search | string | Buscar por nombre o numero |
| is_group | boolean | Filtrar grupos o individuales |
| per_page | integer | Items por pagina |

---

### GET /conversations/archived

Lista conversaciones archivadas.

---

### GET /conversations/{uuid}

Devuelve detalles de una conversacion.

---

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

Lista mensajes de una conversacion.

**Parametros Query:**

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| from | datetime | Fecha inicial |
| to | datetime | Fecha final |
| before_id | integer | Mensajes antes de este ID (para scroll infinito) |
| after_id | integer | Mensajes despues de este ID |
| limit | integer | Limite de mensajes (defecto: 50) |

---

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

Marca la conversacion como leida (resetea contador de no leidos).

---

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

Archiva la conversacion.

---

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

Desarchiva la conversacion.

---

### DELETE /conversations/{uuid}

Elimina la conversacion y todos los mensajes.

---

## Control de Automatización

Gestione la automatización de IA para conversaciones. Cuando la automatización está activa, los mensajes manuales están bloqueados hasta que asuma el chat.

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

### Importante: Comportamiento de la Automatización

- Cuando la automatización está **activa**, los mensajes manuales están bloqueados para evitar conflictos con las respuestas de IA.
- Use **pause** para asumir el chat y enviar mensajes manualmente.
- Use **resume** para dejar que la IA maneje la conversación nuevamente.
- **IMPORTANTE**: Para reanudar la automatización, primero debe **archivar** la conversación.

> **Seguridad: Archivo Requerido para Reanudar**
>
> La automatización solo puede reanudarse después de archivar la conversación. Esto asegura que la sesión de servicio manual se cierre correctamente antes de devolver el control a la IA. Al reanudar, la conversación se desarchivará automáticamente.

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

Pausa la automatización para una conversación, permitiendo mensajes manuales.

**Parámetros:**

| Campo | Tipo | Requerido | Descripción |
|-------|------|-----------|-------------|
| reason | string | No | Razón opcional para pausar la automatización |

**Ejemplo de Solicitud:**

```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": "Tomando control manual para cliente VIP"}'
```

**Respuesta:**

```json
{
  "success": true,
  "message": "Automatización pausada con éxito.",
  "data": {
    "conversation_id": "550e8400-e29b-41d4-a716-446655440045",
    "automation_paused": true,
    "paused_at": "2025-12-20T14:30:00Z",
    "paused_by": "Juan García",
    "reason": "Tomando control manual para cliente VIP"
  }
}
```

**Respuestas de Error:**

| Código | Error | Descripción |
|--------|-------|-------------|
| 400 | NO_ACTIVE_AUTOMATION | La conversación no tiene automatización activa |
| 400 | AUTOMATION_ALREADY_PAUSED | La automatización ya está pausada |
| 404 | NOT_FOUND | Conversación no encontrada |

---

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

Reanuda la automatización para una conversación, dejando que la IA maneje las respuestas.

> **Nota:** La conversación debe estar archivada antes de reanudar la automatización.

**Ejemplo de Solicitud:**

```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"
```

**Respuesta:**

```json
{
  "success": true,
  "message": "Automatización reanudada con éxito.",
  "data": {
    "conversation_id": "550e8400-e29b-41d4-a716-446655440045",
    "automation_paused": false,
    "resumed_at": "2025-12-20T15:00:00Z"
  }
}
```

**Respuestas de Error:**

| Código | Error | Descripción |
|--------|-------|-------------|
| 400 | NO_ACTIVE_AUTOMATION | La conversación no tiene automatización activa |
| 400 | AUTOMATION_NOT_PAUSED | La automatización no está pausada |
| 404 | NOT_FOUND | Conversación no encontrada |

---

## Etiquetas

Gestione etiquetas de conversaciones para organizar y categorizar chats. Las etiquetas estan limitadas por tenant y pueden asignarse a multiples conversaciones.

### GET /tags

Lista todas las etiquetas del tenant actual.

**Respuesta:**

```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

Crea una nueva etiqueta.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| name | string | Si | Nombre de la etiqueta (max. 50 caracteres, unico por tenant) |
| color | string | Si | Codigo de color hexadecimal (#RRGGBB) |

**Ejemplo:**

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

**Respuesta:**

```json
{
  "success": true,
  "message": "Etiqueta creada con exito",
  "data": {
    "tag": {
      "id": "550e8400-e29b-41d4-a716-446655440003",
      "name": "Importante",
      "color": "#ef4444",
      "conversations_count": 0,
      "created_at": "2025-01-15T12:00:00Z",
      "updated_at": "2025-01-15T12:00:00Z"
    }
  }
}
```

---

### GET /tags/{uuid}

Devuelve detalles de una etiqueta.

---

### PATCH /tags/{uuid}

Actualiza una etiqueta existente.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| name | string | No | Nuevo nombre de la etiqueta (max. 50 caracteres) |
| color | string | No | Nuevo codigo de color hexadecimal (#RRGGBB) |

---

### DELETE /tags/{uuid}

Elimina una etiqueta. La etiqueta se eliminara automaticamente de todas las conversaciones.

---

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

Actualiza las etiquetas asignadas a una conversacion. Esto reemplaza todas las etiquetas existentes con la lista proporcionada.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| tag_ids | array | Si | Array de IDs de etiquetas para asignar |

**Ejemplo:**

```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"]
  }'
```

**Respuesta:**

```json
{
  "success": true,
  "message": "Etiquetas de la conversacion actualizadas",
  "data": {
    "tags": [
      {"id": "550e8400-e29b-41d4-a716-446655440001", "name": "VIP", "color": "#f59e0b"},
      {"id": 2, "name": "Soporte", "color": "#3b82f6"},
      {"id": "550e8400-e29b-41d4-a716-446655440003", "name": "Importante", "color": "#ef4444"}
    ]
  }
}
```

**Nota:** Para eliminar todas las etiquetas de una conversacion, envie un array vacio: `{"tag_ids": []}`

---

## Chat API (Optimizada para UI)

La Chat API proporciona endpoints optimizados para crear interfaces de chat tipo WhatsApp. Devuelve datos preformateados con @menciones resueltas, reacciones agrupadas y helpers de alineacion.

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

### Caracteristicas Principales

| Campo | Descripcion |
|-------|-------------|
| `formatted_body` | Texto con @menciones resueltas a nombres (HTML) |
| `is_from_me` | Boolean para alineacion de mensajes |
| `grouped_reactions` | Reacciones agrupadas por emoji con conteo |
| `mentioned_jids` | Array de JIDs de WhatsApp mencionados |
| `tags` | Tags de la conversacion incluidos |

### GET /chat/conversations

Lista conversaciones optimizadas para UI de chat.

**Parametros Query:**

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| instance_id | string | Filtrar por UUID de instancia |
| search | string | Buscar por nombre o numero |
| unread | boolean | Solo conversaciones no leidas |
| per_page | integer | Items por pagina |

**Respuesta:**

```json
{
  "success": true,
  "data": {
    "data": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440045",
        "contact_number": "5521999999999",
        "display_name": "Juan Garcia",
        "profile_picture_url": "https://...",
        "last_message": "Hola!",
        "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

Obtiene mensajes con cuerpo formateado y reacciones agrupadas.

**Parametros Query:**

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| before_id | integer | Mensajes antes de este ID |
| after_id | integer | Mensajes despues de este ID |
| limit | integer | Limite de mensajes (default: 50) |

**Respuesta:**

```json
{
  "success": true,
  "data": {
    "messages": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440045"6,
        "direction": "incoming",
        "is_from_me": false,
        "message_body": "Hola @5521999999999!",
        "formatted_body": "Hola <span class=\"mention\">@Juan</span>!",
        "mentioned_jids": ["5521999999999@s.whatsapp.net"],
        "grouped_reactions": [
          {"emoji": "👍", "count": 2, "has_my_reaction": true}
        ]
      }
    ],
    "has_more": true
  }
}
```

---

### POST /chat/send

Envia mensaje de texto con respuesta optimizada para chat.

**Parametros:**

| Campo | Tipo | Requerido | Descripcion |
|-------|------|-----------|-------------|
| instance_id | string | Si | UUID de la instancia |
| recipient | string | Si | Numero del destinatario |
| message | string | Si | Contenido del mensaje |
| reply_to | string | No | UUID del mensaje a responder |

---

### POST /chat/send-media

Envia media con respuesta optimizada para chat.

**Parametros:**

| Campo | Tipo | Requerido | Descripcion |
|-------|------|-----------|-------------|
| instance_id | string | Si | UUID de la instancia |
| recipient | string | Si | Numero del destinatario |
| media_url | string | Si | URL del archivo de media |
| media_type | string | Si | Tipo (image, video, audio, document) |
| caption | string | No | Leyenda |

---

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

Edita un mensaje enviado.

---

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

Elimina un mensaje enviado.

---

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

Agrega reaccion emoji.

**Parametros:**

| Campo | Tipo | Requerido | Descripcion |
|-------|------|-----------|-------------|
| emoji | string | Si | Emoji para reaccionar |

---

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

Elimina reaccion.

---

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

Marca conversacion como leida.

---

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

Archiva conversacion.

---

### Alternativa: ?format=chat

Use los endpoints estandar con el parametro `?format=chat`:

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

---

## Contactos

### GET /contacts

Lista contactos.

**Parametros Query:**

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| instance_id | string | UUID de la instancia |
| search | string | Buscar por nombre, numero, email o empresa |
| is_group | boolean | Filtrar grupos |
| sort | string | Campo para ordenar |
| dir | string | Direccion (asc, desc) |
| per_page | integer | Items por pagina |

---

### POST /contacts

Crea un nuevo contacto.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| instance_id | string | Si | UUID de la instancia |
| phone_number | string | Si | Numero de telefono |
| first_name | string | No | Nombre |
| last_name | string | No | Apellido |
| display_name | string | No | Nombre para mostrar |
| email | string | No | Email |
| company | string | No | Empresa |
| job_title | string | No | Cargo |
| notes | string | No | Observaciones |
| custom_fields | object | No | Campos personalizados |

---

### POST /contacts/import

Importa contactos en masa.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| instance_id | string | Si | UUID de la instancia |
| contacts | array | Si | Lista de contactos (max 1000) |

**Ejemplo:**

```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": "+5215551234567",
        "first_name": "Juan",
        "last_name": "Perez",
        "email": "juan@ejemplo.com"
      },
      {
        "phone_number": "+5215559876543",
        "first_name": "Maria",
        "company": "Empresa XYZ"
      }
    ]
  }'
```

---

### GET /contacts/{uuid}

Devuelve detalles de un contacto.

---

### PATCH /contacts/{uuid}

Actualiza un contacto.

---

### DELETE /contacts/{uuid}

Elimina un contacto.

---

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

Lista mensajes intercambiados con un contacto.

---

## Mensajes Programados

### GET /scheduled

Lista mensajes programados.

**Parametros Query:**

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| instance_id | string | UUID de la instancia |
| from | datetime | Fecha inicial |
| to | datetime | Fecha final |
| per_page | integer | Items por pagina |

---

### POST /scheduled

Programa un nuevo mensaje.

**Parametros:**

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| instance_id | string | Si | UUID de la instancia |
| recipient | string | Si* | Numero del destinatario |
| contact_id | string | Si* | UUID del contacto (alternativa a recipient) |
| message | string | Si | Contenido del mensaje |
| scheduled_at | datetime | Si | Fecha/hora del envio |
| media_url | string | No | URL del medio |
| media_type | string | No | Tipo del medio |

*Uno de los dos es obligatorio: recipient o contact_id

---

### GET /scheduled/{uuid}

Devuelve detalles de un mensaje programado.

---

### PATCH /scheduled/{uuid}

Actualiza un mensaje programado pendiente.

---

### DELETE /scheduled/{uuid}

Cancela un mensaje programado.

---

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

Envia el mensaje programado inmediatamente.

---

## Medios

### POST /media/upload

Sube un archivo.

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

| Campo | Tipo | Obligatorio | Descripcion |
|-------|------|-------------|-------------|
| file | file | Si | Archivo a subir |
| type | string | Si | Tipo (image, video, audio, document) |

**Limites:**

| Tipo | Tamano Maximo | Formatos Aceptados |
|------|---------------|-------------------|
| 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 |

**Ejemplo:**

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

---

### GET /media/{id}

Devuelve detalles de un archivo.

---

### DELETE /media/{id}

Elimina un archivo.

---

## Ejemplos de Codigo

### 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' => 'usuario@ejemplo.com',
        'password' => 'su_contrasena'
    ]
]);
$data = json_decode($response->getBody(), true);
$token = $data['data']['token'];

// Enviar mensaje
$response = $client->post('messages/send', [
    'json' => [
        'instance_id' => '550e8400-e29b-41d4-a716-446655440000',
        'recipient' => '+5215551234567',
        'message' => 'Hola desde PHP!'
    ]
]);

$result = json_decode($response->getBody(), true);
echo "Mensaje enviado! 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': 'usuario@ejemplo.com',
    'password': 'su_contrasena'
})
data = response.json()
token = data['data']['token']

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

# Enviar mensaje
response = requests.post(f'{BASE_URL}/messages/send',
    headers=headers,
    json={
        'instance_id': '550e8400-e29b-41d4-a716-446655440000',
        'recipient': '+5215551234567',
        'message': 'Hola desde Python!'
    }
)

result = response.json()
print(f"Mensaje enviado! 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;
}

// Enviar mensaje
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();
}

// Uso
const token = await login('usuario@ejemplo.com', 'su_contrasena');
const result = await sendMessage(
    token,
    '550e8400-e29b-41d4-a716-446655440000',
    '+5215551234567',
    'Hola desde JavaScript!'
);
console.log('Mensaje enviado! 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;
}

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

// Uso
(async () => {
    await login('usuario@ejemplo.com', 'su_contrasena');
    const result = await sendMessage(
        '550e8400-e29b-41d4-a716-446655440000',
        '+5215551234567',
        'Hola desde Node.js!'
    );
    console.log('Mensaje enviado! UUID:', result.data.uuid);
})();
```

---

## Webhooks

Configure webhooks para recibir eventos en tiempo real.

### Eventos Disponibles

| Evento | Descripcion |
|--------|-------------|
| message.received | Nuevo mensaje recibido |
| message.sent | Mensaje enviado |
| message.delivered | Mensaje entregado |
| message.read | Mensaje leido |
| message.failed | Fallo en envio |
| instance.connected | Instancia conectada |
| instance.disconnected | Instancia desconectada |
| instance.qr_ready | Codigo QR disponible |

### Formato del Payload

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

---

## Soporte

- **Email:** soporte@zapini.app
- **Documentacion:** https://zapini.app/docs

---

*Ultima actualizacion: Enero 2025*

---

## API Kanban & Calendario

La API Zapini incluye gestión completa de pipeline Kanban y endpoints de Calendario/Citas.

**Ver documentación dedicada:** [kanban-calendar.md](kanban-calendar.md)

### API Kanban (21 endpoints)
- `GET /api/v1/kanban/boards` — Listar tableros
- `POST /api/v1/kanban/boards` — Crear tablero
- `GET /api/v1/kanban/boards/{uuid}` — Obtener tablero + estadísticas
- `PUT /api/v1/kanban/boards/{uuid}` — Actualizar tablero
- `DELETE /api/v1/kanban/boards/{uuid}` — Eliminar tablero
- `GET /api/v1/kanban/boards/{uuid}/columns` — Listar columnas
- `POST /api/v1/kanban/boards/{uuid}/columns` — Crear columna
- `POST /api/v1/kanban/boards/{uuid}/columns/reorder` — Reordenar columnas
- `PUT /api/v1/kanban/columns/{uuid}` — Actualizar columna
- `DELETE /api/v1/kanban/columns/{uuid}` — Eliminar columna
- `GET /api/v1/kanban/boards/{uuid}/leads` — Listar leads (con filtros)
- `POST /api/v1/kanban/leads` — Crear lead
- `GET /api/v1/kanban/leads/{uuid}` — Obtener lead
- `PUT /api/v1/kanban/leads/{uuid}` — Actualizar lead
- `DELETE /api/v1/kanban/leads/{uuid}` — Eliminar lead
- `POST /api/v1/kanban/leads/{uuid}/move` — Mover a columna
- `POST /api/v1/kanban/leads/{uuid}/won` — Marcar como ganado
- `POST /api/v1/kanban/leads/{uuid}/lost` — Marcar como perdido
- `POST /api/v1/kanban/leads/{uuid}/note` — Agregar nota
- `GET /api/v1/kanban/leads/{uuid}/activities` — Historial de actividades
- `POST /api/v1/kanban/leads/{uuid}/ai-qualify` — Calificación por IA

### API Calendario (9 endpoints)
- `GET /api/v1/calendar/appointments` — Listar citas
- `POST /api/v1/calendar/appointments` — Crear cita
- `GET /api/v1/calendar/appointments/{uuid}` — Obtener cita
- `PUT /api/v1/calendar/appointments/{uuid}` — Actualizar cita
- `DELETE /api/v1/calendar/appointments/{uuid}` — Eliminar cita
- `POST /api/v1/calendar/appointments/{uuid}/confirm` — Confirmar
- `POST /api/v1/calendar/appointments/{uuid}/complete` — Completar
- `POST /api/v1/calendar/appointments/{uuid}/cancel` — Cancelar
- `POST /api/v1/calendar/appointments/{uuid}/to-kanban` — Convertir a lead Kanban
