Introducción
La API REST del CRM Aguda permite leer y escribir datos de clientes, tareas operativas
y empresas desde cualquier sistema externo. Todas las respuestas son JSON
(Content-Type: application/json).
El módulo activo de gestión de tareas es En Marcha — tablero Kanban operativo del equipo.
https://saymar.aguda-crm.com/api/v1
Autenticación
Cada petición debe incluir una API Key válida en el header HTTP.
Header requerido
Formato de la clave
54 caracteres en total · 160 bits de entropía · prefijo de 8 hex usado como índice de búsqueda
Proceso de validación
-
1
Se lee el header
X-Api-Key. - 2 Se valida el formato con expresión regular.
- 3 Se extrae el prefijo de 8 hex y se busca en la base de datos.
- 4 Se verifica el hash completo con bcrypt (cost 12).
- 5 Se comprueba que la clave esté activa y no expirada.
- 6 Se contabiliza el request en la ventana de rate limiting.
-
7
Se actualiza
last_used_atde la clave.
Rate Limiting
Límite de 60 peticiones por minuto por API Key, con ventana deslizante de 60 segundos.
Headers de respuesta
Al superar el límite
{ "error": "Rate limit alcanzado. Máximo 60 requests/min." }
El límite es por API Key, no por IP. Cada key tiene su propio contador independiente.
Scopes (Permisos)
Cada API Key tiene uno o varios scopes que determinan qué operaciones puede realizar.
| Scope | Acceso | Métodos permitidos |
|---|---|---|
| read:en_marcha | Leer tareas En Marcha | GET /api/v1/en-marcha/tasks, GET /api/v1/en-marcha/tasks/{id} |
| write:en_marcha:create | Crear tareas En Marcha | POST /api/v1/en-marcha/tasks |
| write:en_marcha:edit | Editar tareas En Marcha | PUT, PATCH /api/v1/en-marcha/tasks/{id} |
| write:en_marcha:delete | Eliminar tareas En Marcha | DELETE /api/v1/en-marcha/tasks/{id} |
| read:clients | Leer Clientes | GET /api/v1/clients, GET /api/v1/clients/{id} |
| write:clients:create | Crear Clientes | POST /api/v1/clients |
| write:clients:edit | Editar Clientes | PUT, PATCH /api/v1/clients/{id} |
| write:clients:delete | Archivar Clientes | DELETE /api/v1/clients/{id} |
| read:companies | Leer Empresas | GET /api/v1/companies, GET /api/v1/companies/{id} |
| write:companies:create | Crear Empresas | POST /api/v1/companies |
| write:companies:edit | Editar Empresas | PUT, PATCH /api/v1/companies/{id} |
| write:companies:delete | Archivar Empresas | DELETE /api/v1/companies/{id} |
| * | Acceso Total | Todos los anteriores |
Los scopes se configuran al crear o editar una API Key desde el panel de administración.
Cada operación de escritura requiere su propio scope — tener write:clients:create
no otorga permiso para editar ni eliminar.
Meta — Descubrimiento
/api/v1/meta
Endpoint de descubrimiento para obtener los IDs válidos del CRM antes de crear registros. Cualquier API Key autenticada puede llamarlo — no requiere scope específico. Úsalo como primer paso al integrar o configurar un agente de IA.
/api/v1/meta
Departamentos y etapas del pipeline
solo autenticación
Respuesta 200
{
"departments": [
{ "id": 1, "name": "Administración" },
{ "id": 2, "name": "Comercial" },
{ "id": 3, "name": "Técnico" }
],
"en_marcha": {
"views": [
{
"name": "Predeterminada",
"pipeline_name": "EM:Predeterminada",
"department_id": null,
"department_name": null,
"stages": [
{ "id": 6, "name": "Pendiente", "position": 1 },
{ "id": 7, "name": "En Progreso", "position": 2 },
{ "id": 8, "name": "Revisión", "position": 3 },
{ "id": 9, "name": "Completado", "position": 4 }
]
}
]
},
"_hints": {
"clients": "Usa department_id al crear un cliente (POST /api/v1/clients). Omitirlo deja el cliente sin departamento.",
"en_marcha": "stage_id es obligatorio al crear una tarea (POST /api/v1/en-marcha/tasks). Usa el stage_id de la vista (embudo) correspondiente."
}
}
Flujo recomendado — primer paso siempre
1. GET /api/v1/meta → obtener IDs de departamentos y stages de cada embudo
2. POST /api/v1/en-marcha/tasks con stage_id del embudo correcto
3. POST /api/v1/clients con department_id para asociar al departamento
Clientes
/api/v1/clients
/api/v1/clients
Listar clientes
read:clients
Parámetros de query
page
integer
Nº de página (default: 1)
limit
integer
Resultados por página (default: 25, máx: 100)
archived
integer
0 = activos (default), 1 = archivados
Respuesta 200
{
"data": [
{
"id": 1,
"name": "Juan García",
"email": "juan@empresa.com",
"phone": "600111222",
"company": "Empresa S.L.",
"status": "Activo",
"description": "Cliente desde 2024",
"deleted_at": null,
"created_at": "2026-01-10 09:00:00",
"labels": []
}
],
"pagination": {
"total": 150,
"per_page": 25,
"current_page": 1,
"last_page": 6
}
}
/api/v1/clients/{id}
Obtener cliente
read:clients
Respuesta 200
{
"data": {
"id": 1,
"name": "Juan García",
"email": "juan@empresa.com",
"phone": "600111222",
"company": "Empresa S.L.",
"status": "Activo",
"description": "Cliente desde 2024",
"deleted_at": null,
"created_at": "2026-01-10 09:00:00",
"labels": ["VIP", "Renovación"],
"department": { "id": 1, "name": "Ventas" }
}
}
/api/v1/clients
Crear cliente
write:clients:create
Body (JSON)
name
string
requerido
Nombre del cliente
email
string
opcional
Correo electrónico
phone
string
opcional
Teléfono
company
string
opcional
Empresa
status
string
opcional
"Activo" | "Inactivo" | "Potencial" (default: "Activo")
description
string
opcional
Notas o descripción
department_id
integer
opcional
ID del departamento (ver GET /api/v1/meta). Sin él, el cliente queda sin departamento asignado.
Respuesta 201
{
"data": {
"id": 42,
"name": "María López",
"email": "maria@empresa.com",
"phone": null,
"company": null,
"status": "Activo",
"description": null,
"deleted_at": null,
"created_at": "2026-06-01 10:30:00",
"labels": [],
"department": { "id": 1, "name": "Ventas" }
}
}
/api/v1/clients/{id}
Actualizar cliente (parcial)
write:clients:edit
Body (JSON)
name
string
opcional
Nombre
email
string
opcional
Correo electrónico
phone
string
opcional
Teléfono
company
string
opcional
Empresa
status
string
opcional
"Activo" | "Inactivo" | "Potencial"
description
string
opcional
Descripción
Solo se actualizan los campos presentes en el body. PUT acepta los mismos campos.
Respuesta 200
{ "data": { /* cliente actualizado */ } }
/api/v1/clients/{id}
Archivar cliente
write:clients:delete
Soft delete: establece deleted_at. El cliente queda accesible con ?archived=1.
Respuesta 200
{ "message": "Cliente archivado correctamente." }
En Marcha — Tareas
/api/v1/en-marcha/tasks
Tablero Kanban operativo. Cada tarea pertenece a un embudo (vista) y a una etapa dentro de ese embudo. Obtén los stage_id válidos desde GET /api/v1/meta antes de crear tareas.
/api/v1/en-marcha/tasks
Listar tareas
read:en_marcha
Parámetros de query
page
integer
Nº de página (default: 1)
limit
integer
Por página (default: 25, máx: 100)
stage_id
integer
Filtrar por etapa específica
view
string
Nombre del embudo (ej: Predeterminada)
department_id
integer
Filtrar tareas de un departamento
status
string
active (default) · postponed · won · lost · all
Respuesta 200
{
"data": [
{
"id": 32,
"title": "Instalar servidor de producción",
"description": "Migración al nuevo VPS",
"amount": null,
"client_id": null,
"stage_id": 7,
"stage_name": "En Progreso",
"view_name": "Predeterminada",
"priority": "Alta",
"probability": null,
"expected_close_date": "2026-06-30",
"user_id": 1,
"created_at": "2026-06-10 09:00:00",
"updated_at": "2026-06-12 14:30:00",
"task_status": "a definir",
"postponed_until": null
}
],
"pagination": { "total": 32, "per_page": 25, "current_page": 1, "last_page": 2 }
}
/api/v1/en-marcha/tasks/{id}
Obtener tarea
read:en_marcha
Devuelve 404 si el ID no existe o pertenece a un módulo distinto de En Marcha.
Respuesta 200
{ "data": { /* mismos campos que en el listado */ } }
/api/v1/en-marcha/tasks
Crear tarea
write:en_marcha:create
Body (JSON)
title
string
requerido
Título (máx. 100 caracteres)
stage_id
integer
requerido
ID de etapa del embudo — obtener de GET /api/v1/meta
description
string
opcional
Descripción detallada
priority
string
opcional
"Baja" | "Media" (default) | "Alta"
amount
number
opcional
Valor económico asociado
client_id
integer
opcional
ID del cliente vinculado
probability
integer
opcional
Porcentaje de avance (0–100)
expected_close_date
string
opcional
Fecha estimada YYYY-MM-DD
department_id
integer
opcional
Asignar al departamento indicado
El user_id se asigna automáticamente al propietario de la API Key.
Si el stage_id no pertenece a En Marcha, la API devuelve 422.
Respuesta 201
{ "data": { /* tarea creada con stage_name y view_name */ } }
/api/v1/en-marcha/tasks/{id}
Actualizar tarea (parcial)
write:en_marcha:edit
Campos editables
title
Título
stage_id
Mover a otra etapa del mismo o distinto embudo En Marcha
description
Descripción
priority
"Baja" | "Media" | "Alta"
amount
Valor económico
client_id
Cliente vinculado
probability
0–100
expected_close_date
YYYY-MM-DD
Solo se actualizan los campos presentes en el body. PUT acepta los mismos campos.
Respuesta 200
{ "data": { /* tarea actualizada */ } }
/api/v1/en-marcha/tasks/{id}
Eliminar tarea
write:en_marcha:delete
Hard delete: elimina la tarea y todos sus datos relacionados (etiquetas, equipo, notas, archivos). Irreversible.
Respuesta 200
{ "message": "Tarea eliminada correctamente." }
Empresas
/api/v1/companies
/api/v1/companies
Listar empresas
read:companies
Parámetros de query
page
integer
Nº de página (default: 1)
limit
integer
Resultados por página (default: 25, máx: 100)
archived
integer
0 = activas (default), 1 = archivadas
Respuesta 200
{
"data": [
{
"id": 3,
"name": "Empresa Ejemplo S.L.",
"email": "info@empresa.com",
"phone": "910000001",
"website": "https://empresa.com",
"address": "Calle Mayor 1",
"city": "Madrid",
"state": "Madrid",
"country": "España",
"zip": "28001",
"industry": "Tecnología",
"description": null,
"deleted_at": null,
"created_at": "2026-02-15 11:00:00"
}
],
"pagination": { "total": 20, "per_page": 25, "current_page": 1, "last_page": 1 }
}
/api/v1/companies/{id}
Obtener empresa
read:companies
Respuesta 200
{
"data": {
"id": 3,
"name": "Empresa Ejemplo S.L.",
"email": "info@empresa.com",
"phone": "910000001",
"website": "https://empresa.com",
"address": "Calle Mayor 1",
"city": "Madrid",
"state": "Madrid",
"country": "España",
"zip": "28001",
"industry": "Tecnología",
"description": null,
"deleted_at": null,
"created_at": "2026-02-15 11:00:00"
}
}
/api/v1/companies
Crear empresa
write:companies:create
Body (JSON)
name
string
requerido
Nombre de la empresa
email
string
opcional
Correo electrónico
phone
string
opcional
Teléfono
website
string
opcional
Sitio web (URL)
address
string
opcional
Dirección
city
string
opcional
Ciudad
state
string
opcional
Provincia / Estado
country
string
opcional
País
zip
string
opcional
Código postal
industry
string
opcional
Sector / Industria
description
string
opcional
Descripción o notas
Respuesta 201
{ "data": { /* empresa creada */ } }
/api/v1/companies/{id}
Actualizar empresa (parcial)
write:companies:edit
Body (JSON)
name
string
opcional
y cualquier campo de POST
Respuesta 200
{ "data": { /* empresa actualizada */ } }
/api/v1/companies/{id}
Archivar empresa
write:companies:delete
Soft delete: establece deleted_at. Accesible con ?archived=1.
Respuesta 200
{ "message": "Empresa archivada correctamente." }
Códigos de estado y errores
Todos los errores devuelven un objeto JSON con el campo error.
| Código | Estado | Causa habitual |
|---|---|---|
| 200 | OK | Petición correcta (GET, PATCH, PUT, DELETE exitoso) |
| 201 | Created | Recurso creado (POST exitoso) |
| 401 | Unauthorized | Header ausente, formato incorrecto, clave inválida o expirada |
| 403 | Forbidden | La clave no tiene el scope necesario para la operación |
| 404 | Not Found | El ID solicitado no existe o no pertenece al propietario de la clave |
| 405 | Method Not Allowed | Método HTTP no soportado en ese endpoint |
| 410 | Gone | Endpoint deshabilitado (ej: /api/v1/deals — módulo inactivo) |
| 422 | Unprocessable Entity | Faltan campos obligatorios o el formato de un valor es incorrecto |
| 429 | Too Many Requests | Se superaron las 60 peticiones por minuto |
| 500 | Internal Server Error | Error inesperado en el servidor |
Formato de error
{ "error": "Descripción del error específico." }
Ejemplos de código
Flujo completo: descubrir embudos via Meta → crear tarea → moverla a otra etapa.
API_KEY="aguda_XXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
BASE="https://saymar.aguda-crm.com/api/v1"
# 1. Descubrir embudos y etapas de En Marcha
curl -s "$BASE/meta" -H "X-Api-Key: $API_KEY" | jq '.en_marcha.views'
# 2. Crear tarea en "Pendiente" (stage_id: 6)
curl -s -X POST "$BASE/en-marcha/tasks" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Configurar entorno de staging",
"stage_id": 6,
"priority": "Alta",
"expected_close_date": "2026-07-15",
"description": "Levantar VPS de pruebas para el equipo"
}'
# 3. Mover tarea al ID 12 a "En Progreso" (stage_id: 7)
curl -s -X PATCH "$BASE/en-marcha/tasks/12" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "stage_id": 7 }'
<?php
$apiKey = 'aguda_XXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
$baseUrl = 'https://saymar.aguda-crm.com/api/v1';
function apiRequest(string $method, string $path, ?array $body, string $apiKey, string $baseUrl): array {
$ch = curl_init($baseUrl . $path);
$opts = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => ['X-Api-Key: ' . $apiKey, 'Content-Type: application/json'],
];
if ($body !== null) {
$opts[CURLOPT_POSTFIELDS] = json_encode($body);
}
curl_setopt_array($ch, $opts);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['status' => $status, 'body' => json_decode($response, true)];
}
// 1. Descubrir embudos
$meta = apiRequest('GET', '/meta', null, $apiKey, $baseUrl);
$views = $meta['body']['en_marcha']['views'];
echo "Embudos disponibles:\n";
foreach ($views as $view) {
echo " [{$view['name']}] ";
foreach ($view['stages'] as $s) {
echo "{$s['name']} (ID {$s['id']}) ";
}
echo "\n";
}
// 2. Crear tarea
$created = apiRequest('POST', '/en-marcha/tasks', [
'title' => 'Configurar entorno de staging',
'stage_id' => 6,
'priority' => 'Alta',
'expected_close_date' => '2026-07-15',
'description' => 'Levantar VPS de pruebas para el equipo',
], $apiKey, $baseUrl);
$taskId = $created['body']['data']['id'];
echo "Tarea creada: ID {$taskId}\n";
// 3. Mover a "En Progreso"
$moved = apiRequest('PATCH', "/en-marcha/tasks/{$taskId}", ['stage_id' => 7], $apiKey, $baseUrl);
echo "Etapa actualizada: " . $moved['body']['data']['stage_name'] . "\n";
const API_KEY = 'aguda_XXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const BASE_URL = 'https://saymar.aguda-crm.com/api/v1';
async function api(method, path, body = null) {
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers: {
'X-Api-Key': API_KEY,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.error}`);
return data;
}
(async () => {
// 1. Descubrir embudos y etapas
const meta = await api('GET', '/meta');
const views = meta.en_marcha.views;
console.log('Embudos:', views.map(v => `${v.name} (${v.stages.length} etapas)`));
// 2. Crear tarea en "Pendiente" (stage_id: 6)
const { data: task } = await api('POST', '/en-marcha/tasks', {
title: 'Configurar entorno de staging',
stage_id: 6,
priority: 'Alta',
expected_close_date: '2026-07-15',
description: 'Levantar VPS de pruebas para el equipo',
});
console.log(`Tarea creada: ID ${task.id} — ${task.stage_name}`);
// 3. Mover a "En Progreso" (stage_id: 7)
const { data: moved } = await api('PATCH', `/en-marcha/tasks/${task.id}`, { stage_id: 7 });
console.log(`Etapa actualizada: ${moved.stage_name}`);
})().catch(console.error);
import requests
API_KEY = 'aguda_XXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
BASE_URL = 'https://saymar.aguda-crm.com/api/v1'
HEADERS = {'X-Api-Key': API_KEY, 'Content-Type': 'application/json'}
def api(method, path, body=None):
r = requests.request(method, BASE_URL + path, json=body, headers=HEADERS)
r.raise_for_status()
return r.json()
# 1. Descubrir embudos
meta = api('GET', '/meta')
views = meta['en_marcha']['views']
for v in views:
stages = ', '.join(f"{s['name']} (ID {s['id']})" for s in v['stages'])
print(f"[{v['name']}] {stages}")
# 2. Crear tarea en "Pendiente" (stage_id: 6)
task = api('POST', '/en-marcha/tasks', {
'title': 'Configurar entorno de staging',
'stage_id': 6,
'priority': 'Alta',
'expected_close_date': '2026-07-15',
'description': 'Levantar VPS de pruebas para el equipo',
})['data']
print(f"Tarea creada: ID {task['id']} — {task['stage_name']}")
# 3. Mover a "En Progreso" (stage_id: 7)
moved = api('PATCH', f"/en-marcha/tasks/{task['id']}", {'stage_id': 7})['data']
print(f"Etapa actualizada: {moved['stage_name']}")