Navegación
Empieza con Aguda
Aguda Logo
Empieza

Documentación de la API

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.

URL Base https://saymar.aguda-crm.com/api/v1
Versión
v1
Formato
JSON
Auth
API Key
Límite
60 req/min

Autenticación

Cada petición debe incluir una API Key válida en el header HTTP.

Header requerido

X-Api-Key: aguda_a81059d4_a81059d4c2162a1e30b0bc29f19c8646e25f0cc7

Formato de la clave

aguda_[8 hex]_[40 hex]

54 caracteres en total · 160 bits de entropía · prefijo de 8 hex usado como índice de búsqueda

Importante: La clave completa solo se muestra una vez al crearla. Guárdala en un lugar seguro. Nunca la incluyas en URLs ni en código que vayas a publicar.

Proceso de validación

  1. 1 Se lee el header X-Api-Key.
  2. 2 Se valida el formato con expresión regular.
  3. 3 Se extrae el prefijo de 8 hex y se busca en la base de datos.
  4. 4 Se verifica el hash completo con bcrypt (cost 12).
  5. 5 Se comprueba que la clave esté activa y no expirada.
  6. 6 Se contabiliza el request en la ventana de rate limiting.
  7. 7 Se actualiza last_used_at de la clave.

Rate Limiting

Límite de 60 peticiones por minuto por API Key, con ventana deslizante de 60 segundos.

Headers de respuesta

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47

Al superar el límite

HTTP 429 Too Many Requests
Retry-After: 60
X-RateLimit-Remaining: 0
// Respuesta 429
{ "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.

GET /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

GET /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
  }
}
GET /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" }
  }
}
POST /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" }
  }
}
PATCH /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 */ } }
DELETE /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.

GET /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 }
}
GET /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 */ } }
POST /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 */ } }
PATCH /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 */ } }
DELETE /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

GET /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 }
}
GET /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"
  }
}
POST /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 */ } }
PATCH /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 */ } }
DELETE /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']}")