Simple JWT-authenticated API for lead management
API simple con autenticación JWT para gestión de leads
The CRM API allows providers to create and view leads, while clients can update lead status. All endpoints use JWT authentication.
La API CRM permite a los proveedores crear y ver leads, mientras que los clientes pueden actualizar el estado. Todos los endpoints usan autenticación JWT.
proveedor_lead_id prevents duplicatesproveedor_lead_id único previene duplicadosGet started with the CRM Relay API in 5 simple steps:
Comienza con la API CRM Relay en 5 pasos simples:
POST /api/auth/loginresponse.data.token (nested inside data)Authorization: Bearer <token> to all requestsPOST /api/crm/leads with required fieldsPOST /api/auth/loginresponse.data.token (anidado dentro de data)Authorization: Bearer <token> a todas las peticionesPOST /api/crm/leads con campos requeridoscurl -X POST "http://localhost/paqueteriacz/api/crm/leads" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{"lead":{"proveedor_lead_id":"PR-001","nombre":"Juan Pérez","telefono":"+50512345678","fecha_hora":"2025-01-15 10:00:00"}}'
{
"success": true,
"message": "Lead(s) aceptado(s) para procesamiento",
"accepted": 1,
"inbox_id": 123
}
All CRM endpoints require JWT authentication. Use the token from POST /api/auth/login.
Todos los endpoints CRM requieren autenticación JWT. Usa el token de POST /api/auth/login.
Authenticate with your credentials to receive a JWT token:
Autentícate con tus credenciales para recibir un token JWT:
{
"email": "proveedor@example.com",
"password": "your_secure_password"
}
{
"success": true,
"message": "Login exitoso",
"data": {
"id": "123",
"nombre": "Usuario Proveedor",
"email": "proveedor@example.com",
"rol": "Proveedor",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEyMyIsIm5vbWJyZSI6IlVzdWFyaW8gUHJvdmVlZG9yIiwiZW1haWwiOiJwcm92ZWVkb3JAZXhhbXBsZS5jb20iLCJyb2wiOiJQcm92ZWVkb3IiLCJleHAiOjE3MDQ4MTI0MDB9.signature_here"
}
}
data.token, not at the root level. Extract it as: response.data.token
data.token, no en el nivel raíz. Extráelo como: response.data.token
{
"success": false,
"message": "Credenciales inválidas"
}
# Login and extract token
curl -X POST "http://localhost/paqueteriacz/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"proveedor@example.com","password":"your_password"}'
# Response contains token at data.token
# Use it in subsequent requests:
curl -X GET "http://localhost/paqueteriacz/api/crm/leads" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLC..."
| Role | Permissions |
|---|---|
Proveedor | Create leads, view own leads |
Cliente | Update lead status (ownership required), view assigned leads |
Administrador | Full access + system metrics |
| Rol | Permisos |
|---|---|
Proveedor | Crear leads, ver propios leads |
Cliente | Actualizar estado de lead (requiere propiedad), ver leads asignados |
Administrador | Acceso completo + métricas del sistema |
Submit individual leads or batches. Returns 202 Accepted immediately.
Envía leads individuales o en lote. Retorna 202 Accepted inmediatamente.
Proveedor, Administrador
{
"lead": {
"proveedor_lead_id": "PR-12345",
"nombre": "Juan Pérez",
"telefono": "+50512345678",
"producto": "Laptop Dell",
"precio": 500.00,
"fecha_hora": "2025-01-15 10:30:00",
"cliente_id": 5
}
}
{
"leads": [
{"proveedor_lead_id":"PR-001","nombre":"Lead 1","telefono":"+50511111111","fecha_hora":"2025-01-15 10:00:00"},
{"proveedor_lead_id":"PR-002","nombre":"Lead 2","telefono":"+50522222222","fecha_hora":"2025-01-15 10:05:00"}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
proveedor_lead_id | string(120) | ✅ Yes | Unique lead ID (per provider) |
fecha_hora | datetime | ✅ Yes | Lead timestamp (YYYY-MM-DD HH:MM:SS) |
nombre | string(255) | No | Prospect name |
telefono | string(30) | No | Phone number |
producto | string(255) | No | Product of interest |
precio | decimal(10,2) | No | Product price |
cliente_id | integer | No | Client ID (auto-forward if has webhook) |
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
proveedor_lead_id | string(120) | ✅ Sí | ID único de lead (por proveedor) |
fecha_hora | datetime | ✅ Sí | Fecha y hora del lead (YYYY-MM-DD HH:MM:SS) |
nombre | string(255) | No | Nombre del prospecto |
telefono | string(30) | No | Número de teléfono |
producto | string(255) | No | Producto de interés |
precio | decimal(10,2) | No | Precio del producto |
cliente_id | integer | No | ID del cliente (auto-reenvío si tiene webhook) |
{
"success": true,
"message": "Lead(s) aceptado(s) para procesamiento",
"accepted": 1,
"inbox_id": 123
}
{
"success": true,
"message": "Lead(s) ya procesado(s) previamente",
"accepted": 1,
"duplicated": true
}
Update lead state with automatic normalization. Clients have full flexibility to change to any valid state.
Actualiza el estado del lead con normalización automática. Clientes tienen total flexibilidad para cambiar a cualquier estado válido.
Cliente (owner only), Administrador
Cliente (solo propietario), Administrador
{
"estado": "Aprovado",
"observaciones": "Cliente confirmó recepción"
}
| Canonical State | Accepted Aliases |
|---|---|
EN_ESPERA | ESPERA, PENDING, WAITING |
APROBADO | APROVADO, APPROVED |
CONFIRMADO | CONFIRMAR, CONFIRMED |
EN_TRANSITO | EN TRANSITO, TRANSITO, TRANSIT |
EN_BODEGA | EN BODEGA, BODEGA, WAREHOUSE |
CANCELADO | CANCELAR, CANCELLED, CANCELED |
| Estado Canónico | Alias Aceptados |
|---|---|
EN_ESPERA | ESPERA, PENDING, WAITING |
APROBADO | APROVADO, APPROVED |
CONFIRMADO | CONFIRMAR, CONFIRMED |
EN_TRANSITO | EN TRANSITO, TRANSITO, TRANSIT |
EN_BODEGA | EN BODEGA, BODEGA, WAREHOUSE |
CANCELADO | CANCELAR, CANCELLED, CANCELED |
| State | Description | When to Use |
|---|---|---|
EN_ESPERA | Waiting for approval | Initial state when order is created |
APROBADO | Approved and validated | After reviewing and approving the order |
CONFIRMADO | Confirmed with customer | Customer confirmed they want to proceed |
EN_TRANSITO | Package on the way | Package shipped and being transported |
EN_BODEGA | Package arrived at warehouse | Package received and stored |
CANCELADO | Order cancelled | Order will not proceed for any reason |
| Estado | Descripción | Cuándo Usar |
|---|---|---|
EN_ESPERA | Esperando aprobación | Estado inicial cuando se crea el pedido |
APROBADO | Aprobado y validado | Después de revisar y aprobar el pedido |
CONFIRMADO | Confirmado con cliente | Cliente confirmó que procede con el pedido |
EN_TRANSITO | Paquete en camino | Paquete salió y está siendo transportado |
EN_BODEGA | Paquete llegó a bodega | Paquete recibido y almacenado |
CANCELADO | Pedido cancelado | Pedido no procede por cualquier razón |
{
"success": true,
"message": "Estado actualizado a APROBADO",
"estado_anterior": "EN_ESPERA",
"estado_nuevo": "APROBADO"
}
{
"success": false,
"message": "Estado inválido: INVALID_STATE",
"estados_validos": ["EN_ESPERA", "APROBADO", "CONFIRMADO", "EN_TRANSITO", "EN_BODEGA", "CANCELADO"]
}
Assign one or multiple leads to a specific client. Providers can only assign their own leads.
Asigna uno o múltiples leads a un cliente específico. Los proveedores solo pueden asignar sus propios leads.
Proveedor (own leads only), Administrador
Proveedor (solo sus propios leads), Administrador
{
"cliente_id": 5,
"lead_ids": [101, 102, 103],
"observaciones": "Asignación manual"
}
| Field | Type | Required | Description |
|---|---|---|---|
cliente_id | integer | ✅ Yes | Target Client ID (User ID with role 'Cliente') |
lead_ids | array|int | ✅ Yes | List of Lead IDs or single ID |
observaciones | string | No | Optional notes |
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
cliente_id | integer | ✅ Sí | ID Cliente Destino (ID Usuario con rol 'Cliente') |
lead_ids | array|int | ✅ Sí | Lista de IDs de Leads o ID único |
observaciones | string | No | Notas opcionales |
{
"success": true,
"message": "Operación completada. 3 asignados a 'Cliente Juan'.",
"updated": 3,
"failed": 0,
"total_processed": 3
}
{
"success": true,
"message": "Operación completada. 2 asignados a 'Cliente Juan'. (1 fallos)",
"updated": 2,
"failed": 1,
"total_processed": 3,
"failed_details": [
{"lead_id": 103, "error": "No tienes permiso (No eres el proveedor creador)"}
]
}
Update the status of multiple leads simultaneously. Clients can only update leads they own (cliente_id), while admins can update any leads.
Actualiza el estado de múltiples leads simultáneamente. Los clientes solo pueden actualizar leads que les pertenecen (cliente_id), mientras que los admins pueden actualizar cualquier lead.
Cliente (own leads only), Administrador
Cliente (solo sus propios leads), Administrador
{
"lead_ids": [123, 124, 125],
"estado": "contactado",
"observaciones": "Contactados vía campaña SMS"
}
| Field | Type | Required | Description |
|---|---|---|---|
lead_ids | array of integers | ✅ Yes | Array of lead IDs to update (max 100) |
estado | string | ✅ Yes | New status (auto-normalized) |
observaciones | string | No | Optional notes |
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
lead_ids | array de enteros | ✅ Sí | Array de IDs de leads a actualizar (máx 100) |
estado | string | ✅ Sí | Nuevo estado (normalizado automáticamente) |
observaciones | string | No | Notas opcionales |
cliente_id matches their user ID. Unauthorized leads will fail individually.
cliente_id coincida con su ID de usuario. Los leads no autorizados fallarán individualmente.
curl -X POST "http://localhost/paqueteriacz/api/crm/leads/bulk-status" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{
"lead_ids": [123, 124, 125],
"estado": "aprobado",
"observaciones": "Procesados el 2026-01-02"
}'
{
"success": true,
"message": "3 de 3 leads actualizados exitosamente",
"updated": 3,
"failed": 0,
"total": 3,
"estado_nuevo": "APROBADO",
"results": [
{
"lead_id": 123,
"success": true,
"estado_anterior": "EN_ESPERA",
"estado_nuevo": "APROBADO"
},
{
"lead_id": 124,
"success": true,
"estado_anterior": "EN_PROCESO",
"estado_nuevo": "APROBADO"
},
{
"lead_id": 125,
"success": true,
"estado_anterior": "EN_ESPERA",
"estado_nuevo": "APROBADO"
}
]
}
{
"success": true,
"message": "2 de 3 leads actualizados exitosamente",
"updated": 2,
"failed": 1,
"total": 3,
"estado_nuevo": "APROBADO",
"results": [
{
"lead_id": 123,
"success": true,
"estado_anterior": "EN_ESPERA",
"estado_nuevo": "APROBADO"
},
{
"lead_id": 124,
"success": false,
"message": "No tienes permiso para este lead"
},
{
"lead_id": 125,
"success": true,
"estado_anterior": "EN_ESPERA",
"estado_nuevo": "APROBADO"
}
]
}
{
"success": false,
"message": "Límite máximo de 100 leads por request",
"received": 150
}
{
"success": false,
"message": "0 de 3 leads actualizados exitosamente",
"updated": 0,
"failed": 3,
"total": 3,
"estado_nuevo": "APROBADO",
"results": [
{
"lead_id": 999,
"success": false,
"message": "Lead no encontrado"
},
{
"lead_id": 888,
"success": false,
"message": "No tienes permiso para este lead"
},
{
"lead_id": 777,
"success": false,
"message": "Lead no encontrado"
}
]
}
Update the status of thousands of leads asynchronously. No limit on the number of leads. Responds immediately (202 Accepted) and processes in background.
Actualiza el estado de miles de leads de forma asíncrona. Sin límite en la cantidad de leads. Responde inmediatamente (202 Accepted) y procesa en segundo plano.
Cliente (own leads only), Administrador
Cliente (solo sus propios leads), Administrador
{
"lead_ids": [1, 2, 3, 4, 5, ..., 5000],
"estado": "contactado",
"observaciones": "Procesamiento masivo de campaña SMS"
}
| Limit | Value | Description |
|---|---|---|
| Pending Jobs | 10 | Max concurrent jobs in queue per user |
| Jobs Per Day | 100 | Max jobs created per day per user |
| Job Size | 10,000 | Max leads per individual job |
| Daily Leads | 50,000 | Max total leads processed per day |
| Cooldown | 30s | Min time between job submissions |
| Límite | Valor | Descripción |
|---|---|---|
| Jobs Pendientes | 10 | Máx jobs concurrentes en cola por usuario |
| Jobs Por Día | 100 | Máx jobs creados por día por usuario |
| Tamaño de Job | 10,000 | Máx leads por job individual |
| Leads Diarios | 50,000 | Máx leads totales procesados por día |
| Cooldown | 30s | Tiempo mín entre envío de jobs |
curl -X POST "http://localhost/paqueteriacz/api/crm/leads/bulk-status-async" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{
"lead_ids": [1, 2, 3, 4, 5, ..., 5000],
"estado": "aprobado",
"observaciones": "Procesamiento masivo"
}'
{
"success": true,
"job_id": "bulk_6958aaf4d1477_1767418612",
"status": "queued",
"total_leads": 5000,
"message": "Job encolado para procesamiento",
"check_status_url": "/api/crm/jobs/bulk_6958aaf4d1477_1767418612"
}
When some leads have validation errors but the job is still queued (pre-validation feedback):
Cuando algunos leads tienen errores de validación pero el job igualmente se encola (feedback de pre-validación):
{
"success": true,
"job_id": "bulk_6958b182d5773_1767420290",
"status": "queued",
"total_leads": 191,
"message": "Job encolado. 188 leads pueden fallar.",
"check_status_url": "/api/crm/jobs/bulk_6958b182d5773_1767420290",
"valid_leads_count": 3,
"error_rate": "98.43%",
"validation_warnings": [
{"lead_id": 10, "error": "No tienes permiso para este lead"},
{"lead_id": 11, "error": "Lead no encontrado"},
// ... más warnings
]
}
validation_warnings immediately, allowing you to decide whether to proceed or fix the issues.
validation_warnings inmediatamente, permitiéndote decidir si proceder o corregir los problemas.
{
"success": false,
"error": "rate_limit_exceeded",
"message": "Límite alcanzado: tienes 10 jobs pendientes (máximo 10)",
"retry_after": 60
}
| Synchronous | Síncrono | Asynchronous | Asíncrono | |
|---|---|---|---|---|
| Max LeadsMáx Leads | 100 | 100 | 10,000+ | 10,000+ |
| Response TimeTiempo Respuesta | 200-500ms | 200-500ms | ~50ms | ~50ms |
| Timeout RiskRiesgo Timeout | Possible | Posible | Never | Nunca |
| Concurrent ClientsClientes Concurrentes | Limited | Limitado | Unlimited | Ilimitados |
Check the progress and status of an asynchronous bulk update job.
Consulta el progreso y estado de un job de actualización masiva asíncrona.
Cliente (own jobs only), Administrador
Cliente (solo sus propios jobs), Administrador
| Parameter | Parámetro | Description | Descripción |
|---|---|---|---|
job_id |
job_id |
Job identifier returned when creating the async job | Identificador del job devuelto al crear el job asíncrono |
curl -X GET "http://localhost/paqueteriacz/api/crm/jobs/bulk_6958aaf4d1477_1767418612" \
-H "Authorization: Bearer <TOKEN>"
{
"success": true,
"job_id": "bulk_6958aaf4d1477_1767418612",
"status": "queued",
"total_leads": 5000,
"processed_leads": 0,
"successful_leads": 0,
"failed_leads": 0,
"estado": "APROBADO",
"created_at": "2026-01-02 23:30:00",
"started_at": null,
"completed_at": null,
"progress_percent": 0.0
}
{
"success": true,
"job_id": "bulk_6958aaf4d1477_1767418612",
"status": "processing",
"total_leads": 5000,
"processed_leads": 2500,
"successful_leads": 2498,
"failed_leads": 2,
"estado": "APROBADO",
"created_at": "2026-01-02 23:30:00",
"started_at": "2026-01-02 23:30:05",
"completed_at": null,
"progress_percent": 50.0
}
{
"success": true,
"job_id": "bulk_6958aaf4d1477_1767418612",
"status": "completed",
"total_leads": 5000,
"processed_leads": 5000,
"successful_leads": 4998,
"failed_leads": 2,
"estado": "APROBADO",
"created_at": "2026-01-02 23:30:00",
"started_at": "2026-01-02 23:30:05",
"completed_at": "2026-01-02 23:30:35",
"progress_percent": 100.0,
"failed_details": [
{"lead_id": 5, "error": "Sin permiso"},
{"lead_id": 999, "error": "Lead no encontrado"}
]
}
When there are more failures than successes, successful_details is shown instead:
Cuando hay más fallos que éxitos, se muestra successful_details en su lugar:
{
"success": true,
"job_id": "bulk_6958b3ea2e5ad_1767420906",
"status": "completed",
"total_leads": 191,
"processed_leads": 191,
"successful_leads": 3,
"failed_leads": 188,
"estado": "CANCELADO",
"created_at": "2026-01-03 00:15:06",
"started_at": "2026-01-03 00:15:06",
"completed_at": "2026-01-03 00:15:08",
"progress_percent": 100,
"successful_details": [
{"lead_id": 1, "estado_anterior": "EN_ESPERA", "estado_nuevo": "CANCELADO"},
{"lead_id": 5, "estado_anterior": "EN_PROCESO", "estado_nuevo": "CANCELADO"},
{"lead_id": 12, "estado_anterior": "APROBADO", "estado_nuevo": "CANCELADO"}
]
}
successful_details or failed_details) to optimize response size and provide the most actionable information.
successful_details o failed_details) para optimizar el tamaño de respuesta y dar la información más útil.
| Status | Estado | Description | Descripción |
|---|---|---|---|
queued |
queued |
Waiting to be processed by worker | Esperando ser procesado por el worker |
processing |
processing |
Currently being processed | Actualmente siendo procesado |
completed |
completed |
Processing finished successfully | Procesamiento terminado exitosamente |
failed |
failed |
Job failed due to error | Job falló debido a error |
Retrieve leads with filtering and pagination. Automatic role-based scoping applies.
Recupera leads con filtrado y paginación. Se aplica alcance automático basado en roles.
Cliente (own leads only), Administrador
Cliente (solo sus propios leads), Administrador
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 50 | Items per page (max 100) |
estado | string | - | Filter by state |
fecha_desde | date | - | From date (YYYY-MM-DD) |
fecha_hasta | date | - | To date (YYYY-MM-DD) |
| Parámetro | Tipo | Por Defecto | Descripción |
|---|---|---|---|
page | integer | 1 | Número de página |
limit | integer | 50 | Items por página (máx 100) |
estado | string | - | Filtrar por estado |
fecha_desde | date | - | Desde fecha (YYYY-MM-DD) |
fecha_hasta | date | - | Hasta fecha (YYYY-MM-DD) |
curl "http://localhost/paqueteriacz/api/crm/leads?page=1&limit=10&estado=APROBADO" \
-H "Authorization: Bearer <TOKEN>"
Access full lead details or status change history.
Accede a los detalles completos del lead o historial de cambios de estado.
Cliente (own lead only), Administrador
Cliente (solo su propio lead), Administrador
# View detail
curl "http://localhost/paqueteriacz/api/crm/leads/1" \
-H "Authorization: Bearer <TOKEN>"
# View timeline
curl "http://localhost/paqueteriacz/api/crm/leads/1/timeline" \
-H "Authorization: Bearer <TOKEN>"
# Ver detalle
curl "http://localhost/paqueteriacz/api/crm/leads/1" \
-H "Authorization: Bearer <TOKEN>"
# Ver cronología
curl "http://localhost/paqueteriacz/api/crm/leads/1/timeline" \
-H "Authorization: Bearer <TOKEN>"
Monitor CRM health and performance (admin-only).
Monitorea la salud y rendimiento del CRM (solo administrador).
Administrador only
Solo Administrador
curl "http://localhost/paqueteriacz/api/crm/metrics" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
Receive real-time notifications via cryptographically signed webhooks.
Recibe notificaciones en tiempo real vía webhooks firmados criptográficamente.
$payload = file_get_contents("php://input");
$signature = $_SERVER['HTTP_X_SIGNATURE'];
$secret = 'your_shared_secret';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (hash_equals($expected, $signature)) {
$data = json_decode($payload, true);
// Process webhook / Procesar webhook
} else {
http_response_code(401);
exit;
}
INSERT INTO crm_integrations (user_id, kind, webhook_url, secret, is_active)
VALUES (5, 'cliente', 'https://app.com/webhook', 'secret_123', 1);
Background worker for async processing with 3-second polling interval.
Worker en segundo plano para procesamiento asíncrono con intervalo de sondeo de 3 segundos.
# One-time execution (cron)
php cli/crm_worker.php --once
# Daemon mode (systemd)
php cli/crm_worker.php --loop
# Ejecución única (cron)
php cli/crm_worker.php --once
# Modo daemon (systemd)
php cli/crm_worker.php --loop
Processes asynchronous bulk update jobs from POST /bulk-status-async.
Procesa jobs de actualización masiva asíncrona de POST /bulk-status-async.
# Start worker (runs continuously)
php cli/crm_bulk_worker.php
Removes old jobs and monitors stuck processes. Run daily via Cron/Task Scheduler.
Elimina jobs antiguos y monitorea procesos atascados. Ejecutar diariamente vía Cron/Task Scheduler.
# Run cleanup
php cli/crm_jobs_cleanup.php
[Unit]
Description=CRM Relay Worker
After=mariadb.service
[Service]
Type=simple
User=www-data
WorkingDirectory=/xampp/htdocs/paqueteriacz
ExecStart=/usr/bin/php cli/crm_worker.php --loop
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl enable crm-worker
sudo systemctl start crm-worker
sudo journalctl -u crm-worker -f
"duplicated":trueps aux | grep crm_workercrm_outbox.last_error columnSELECT COUNT(*) FROM crm_inbox WHERE status='pending'"duplicated":trueps aux | grep crm_workercrm_outbox.last_errorSELECT COUNT(*) FROM crm_inbox WHERE status='pending'-- Pending inbox count
SELECT COUNT(*) FROM crm_inbox WHERE status='pending';
-- Failed webhooks
SELECT id, event_type, attempts, last_error
FROM crm_outbox
WHERE status='failed'
LIMIT 10;
-- Recent leads
SELECT id, proveedor_lead_id, estado_actual, created_at
FROM crm_leads
ORDER BY created_at DESC
LIMIT 20;
-- Conteo de inbox pendientes
SELECT COUNT(*) FROM crm_inbox WHERE status='pending';
-- Webhooks fallidos
SELECT id, event_type, attempts, last_error
FROM crm_outbox
WHERE status='failed'
LIMIT 10;
-- Leads recientes
SELECT id, proveedor_lead_id, estado_actual, created_at
FROM crm_leads
ORDER BY created_at DESC
LIMIT 20;
| Code | Meaning | When |
|---|---|---|
| 200 | OK | Successful query, update, or idempotent retry |
| 202 | Accepted | Lead queued for async processing |
| 400 | Bad Request | Validation error, invalid transition |
| 401 | Unauthorized | Missing/invalid JWT token |
| 403 | Forbidden | Insufficient permissions or ownership |
| 404 | Not Found | Lead ID doesn't exist |
| Código | Significado | Cuándo |
|---|---|---|
| 200 | OK | Consulta exitosa, actualización o reintento idempotente |
| 202 | Aceptado | Lead encolado para procesamiento asíncrono |
| 400 | Solicitud Incorrecta | Error de validación, transición inválida |
| 401 | No Autorizado | Token JWT faltante/inválido |
| 403 | Prohibido | Permisos insuficientes o falta de propiedad |
| 404 | No Encontrado | ID de lead no existe |