Autenticación
Todas las solicitudes a la API usan una clave de API como token portador (bearer token). Crea una en la app en Developer → API keys. Las claves pueden ser solo lectura o lectura/escritura.
curl https://savrsoft.com/api/v1/events \ -H "Authorization: Bearer savr_live_your_key_here"
URL base: https://savrsoft.com/api/v1 · Todas las respuestas son JSON. Los errores devuelven { "error": "…" } con un estado 4xx/5xx.
Códigos de estado
| Código | Cuándo lo recibes |
|---|---|
200 | Lectura exitosa. |
201 | Creado con éxito. El cuerpo es { "id": 42 } — consulta el registro si necesitas el resto de la información. |
400 | Falta un campo obligatorio, por ejemplo { "error": "name is required" }, o no se pudo leer un cursor de paginación. |
401 | Sin clave de API, una con formato incorrecto, o una clave que ha sido revocada. |
403 | Se utilizó una clave de solo lectura para una escritura. Cree una clave de lectura/escritura en Developer → API keys. |
404 | El registro no existe, o pertenece a otra organización — ambos casos son deliberadamente indistinguibles. También se devuelve para una ruta o método no reconocido. |
Alcance de esta API
La API es solo de lectura y creación. No hay DELETE, PUT o PATCH
endpoints — una solicitud que use uno de estos devuelve 404 Unknown endpoint, igual que cualquier ruta no reconocida. Eliminar y
editar se hace en la aplicación, así que una integración no puede destruir los registros de un catering. Si necesita acceso de escritura más allá de la creación,
cuéntenos qué está construyendo.
Paginación
Todos los endpoints de listado están paginados. Pase limit (por defecto 100, máximo 500) y
siga next_cursor hasta que has_more sea false:
{
"data": [ ... ],
"has_more": true,
"next_cursor": "eyJuYW1lIjoiQWNtZSIsImlkIjo0Mn0"
}
Pase el cursor tal cual para obtener la siguiente página. Trátelo como opaco — la codificación puede cambiar:
curl "https://savrsoft.com/api/v1/clients?limit=100&cursor=eyJuYW1lIjoiQWNtZSIsImlkIjo0Mn0" \ -H "Authorization: Bearer savr_live_your_key_here"
La paginación se ancla a la última fila recibida, no a un desplazamiento, de modo que las filas creadas o eliminadas mientras se pagina no provocarán que se salte o repita un registro. Un cursor sigue siendo válido indefinidamente.
| Parámetro | Comportamiento |
|---|---|
limit | 1–500. Si está ausente, es cero, negativo o no numérico, se usa 100 por defecto; cualquier valor superior a 500 se limita a 500. |
cursor | next_cursor de la página anterior. Omítelo para la primera página. Un cursor que no se pueda leer —corrupto o de un endpoint distinto— devuelve 400 Invalid cursor en lugar de reiniciarse silenciosamente desde el principio. |
Orden: /events y /invoices del más reciente al más antiguo; /recipes y
/clients por nombre, con empates resueltos por id para que la paginación se mantenga estable cuando los nombres se repiten.
Endpoints
/v1/meTu organización (id, nombre, moneda).
/v1/eventsLista de eventos, del más reciente al más antiguo. Paginado — consulta Paginación arriba.
/v1/events/{id}Un evento con sus platos.
/v1/events · requiere lectura/escrituraCrea un evento. Cuerpo:
{ "name": "Acme holiday party", "event_date": "2026-12-18",
"guests": 120, "price_per_head": 55, "event_type": "corporate" }
/v1/recipes · GET/v1/recipes/{id}Lista de recetas (con alérgenos & indicadores de dieta), o una receta con sus ingredientes. La lista está paginada y ordenada por nombre.
/v1/clients · POST/v1/clientsLista de clientes (paginada, por nombre) o creación de uno nuevo. Cuerpo de creación: { "name": "...", "email": "...", "phone": "...", "company": "..." }
/v1/invoices · GET/v1/invoices/{id}Lista de facturas con totales calculados, de más reciente a más antigua, o una factura con sus líneas de detalle. La lista está paginada.
Webhooks
Registra un endpoint en Developer → Webhooks. Enviamos una solicitud POST con un payload JSON cuando ocurren eventos:
{
"id": "evt_abc123",
"type": "event.inquiry",
"created_at": "2026-07-17T18:20:00.000Z",
"data": { "id": 42, "name": "Corporate lunch — Dana Ruiz", ... }
}
Tipos de evento
| Tipo | Se activa cuando |
|---|---|
event.created | Se crea un evento |
event.inquiry | Llega una nueva solicitud de catering en línea |
invoice.sent | Una factura se marca como enviada |
invoice.paid | Una factura se marca como pagada |
proposal.approved | Un cliente aprueba una propuesta |
client.created | Se crea un cliente |
Verificación de firmas
Cada entrega incluye un encabezado x-savrsoft-signature : el HMAC-SHA256 del cuerpo de la solicitud sin procesar, firmado con tu clave secreta de webhook. Calcula lo mismo y compara:
// Node.js
const crypto = require('crypto');
const sig = crypto.createHmac('sha256', SIGNING_SECRET)
.update(rawBody).digest('hex');
if (sig !== req.headers['x-savrsoft-signature']) reject();
