API REST
La API REST de Frihet permite acceder y manipular los recursos de tu cuenta de forma programatica. Toda la comunicacion se realiza sobre HTTPS y las respuestas son JSON.
URL base
https://api.frihet.io/v1
Todos los endpoints descritos en esta pagina son relativos a esta URL base.
Descubrimiento: Una peticion GET a la raiz (https://api.frihet.io/) devuelve los enlaces principales sin necesidad de autenticacion:
{
"name": "Frihet API",
"version": "1.0.0",
"docs": "https://docs.frihet.io/desarrolladores/api-rest",
"openapi": "https://api.frihet.io/openapi.yaml",
"mcp": "https://mcp.frihet.io",
"status": "https://status.frihet.io"
}
La especificacion OpenAPI 3.1 esta disponible en https://api.frihet.io/openapi.yaml.
Si usas TypeScript o JavaScript, el SDK oficial simplifica la integracion:
npm install @frihet/sdk
import Frihet from '@frihet/sdk';
const frihet = new Frihet({ apiKey: 'fri_...' });
const invoices = await frihet.invoices.list({ status: 'overdue' });
Repositorio: github.com/Frihet-io/frihet-sdk
Autenticacion
Cada peticion debe incluir una API key en la cabecera X-API-Key. Las claves se crean desde Ajustes > Desarrolladores > API Keys en el panel de Frihet.
Las claves tienen el prefijo fri_ seguido de 32 bytes aleatorios codificados en base64url. Ejemplo: fri_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345678.
curl https://api.frihet.io/v1/clients \
-H "X-API-Key: fri_tu-clave-aqui"
Alternativamente, puedes enviar la clave como Bearer token en la cabecera Authorization:
curl https://api.frihet.io/v1/clients \
-H "Authorization: Bearer fri_tu-clave-aqui"
Seguridad de las claves
- La clave en texto plano solo se muestra una vez, en el momento de la creacion. No se puede recuperar despues.
- El servidor almacena un hash SHA-256 de la clave. Incluso en caso de brecha de datos, la clave original no es recuperable.
- Puedes crear claves con fecha de expiracion configurable.
- Si sospechas que una clave ha sido comprometida, revocala inmediatamente desde el panel.
Ciclo de vida de las claves
Crear una clave:
- Ve a Ajustes > Desarrolladores > API Keys
- Pulsa Crear clave
- Asigna un nombre descriptivo (por ejemplo,
integracion-contabilidad) - Opcionalmente, establece una fecha de expiracion en dias. Si lo dejas vacio, la clave no caduca
- Copia la clave inmediatamente -- no podras verla de nuevo
Expiracion:
Las claves con fecha de expiracion dejan de funcionar automaticamente al cumplirse el plazo. Las peticiones con una clave expirada reciben un 401 Unauthorized.
Revocacion:
Puedes revocar una clave en cualquier momento desde Ajustes > Desarrolladores > API Keys. La revocacion es inmediata e irreversible: las peticiones en curso con esa clave fallaran a partir de ese instante.
Rotacion de claves:
Para rotar una clave sin interrumpir el servicio:
- Crea una nueva clave con el mismo alcance
- Actualiza tu integracion para usar la nueva clave
- Verifica que las peticiones funcionan correctamente
- Revoca la clave anterior
Monitorizacion:
Cada clave muestra la fecha de ultimo uso en el panel de Ajustes. Revisa periodicamente las claves inactivas y revoca las que ya no se utilicen.
Rate limiting
Cada clave API tiene un limite de 100 peticiones por minuto. Si se excede, la API responde con un codigo 429:
{
"error": "Rate limit exceeded",
"message": "Maximum 100 requests per minute",
"retryAfter": 60
}
Cabeceras de rate limiting
Todas las respuestas de la API incluyen cabeceras para que puedas gestionar el limite de forma proactiva:
| Cabecera | Descripcion | Ejemplo |
|---|---|---|
X-RateLimit-Limit | Peticiones permitidas por minuto | 100 |
X-RateLimit-Remaining | Peticiones restantes en la ventana actual | 87 |
X-RateLimit-Reset | Timestamp Unix (segundos) en que la ventana se reinicia | 1709312400 |
Ejemplo de respuesta con cabeceras de rate limiting:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1709312400
Content-Type: application/json
Gestion del codigo 429:
Cuando recibes un 429, usa las cabeceras para calcular cuanto esperar antes de reintentar:
async function fetchWithRateLimit(url, options) {
const response = await fetch(url, options);
if (response.status === 429) {
const resetTimestamp = response.headers.get('X-RateLimit-Reset');
const waitMs = (Number(resetTimestamp) * 1000) - Date.now();
await new Promise(resolve => setTimeout(resolve, Math.max(waitMs, 1000)));
return fetch(url, options);
}
return response;
}
Recomendaciones:
- Si recibes un
429, espera hasta el timestamp indicado enX-RateLimit-Resetantes de reintentar. - Monitoriza
X-RateLimit-Remainingpara frenar las peticiones antes de alcanzar el limite. - Distribuye las peticiones en el tiempo en lugar de enviar rafagas.
Tamano de peticion
El cuerpo de las peticiones POST, PUT y PATCH no puede exceder 1 MB. Peticiones mayores reciben un codigo 413.
Recursos
La API expone 8 recursos principales: invoices, expenses, clients, products, quotes, vendors, deposits y webhooks. Todos soportan operaciones CRUD completas (GET, POST, PUT/PATCH, DELETE). Ademas, los clientes tienen subcolecciones CRM: contacts, activities y notes.
Tanto PUT como PATCH aceptan actualizaciones parciales. No necesitas enviar el recurso completo -- solo los campos que quieras modificar.
Facturas (/invoices)
Listar facturas
GET /v1/invoices
Parametros de consulta:
| Parametro | Tipo | Por defecto | Descripcion |
|---|---|---|---|
limit | integer | 50 | Resultados por pagina (maximo 100) |
offset | integer | 0 | Numero de resultados a saltar (maximo 10.000) |
status | string | -- | Filtrar por estado: draft, sent, partial, paid, overdue, cancelled |
from | string | -- | Fecha inicio (ISO 8601: YYYY-MM-DD). Filtra por issueDate |
to | string | -- | Fecha fin (ISO 8601: YYYY-MM-DD). Filtra por issueDate |
clientId | string | -- | Filtrar por ID de cliente |
seriesId | string | -- | Filtrar por serie de numeracion |
q | string | -- | Busqueda de texto (nombre de cliente, numero, notas) |
cursor | string | -- | Cursor para paginacion basada en cursor (ignora offset) |
fields | string | -- | Campos a devolver, separados por coma (siempre incluye id) |
Ejemplo:
curl "https://api.frihet.io/v1/invoices?limit=10&status=paid&from=2026-01-01&to=2026-03-31" \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta (200):
{
"data": [
{
"id": "abc123",
"clientName": "Acme S.L.",
"items": [
{ "description": "Consultoria", "quantity": 10, "unitPrice": 75 }
],
"status": "paid",
"issueDate": "2026-01-15",
"dueDate": "2026-02-15",
"taxRate": 21,
"notes": "",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-20T14:00:00.000Z"
}
],
"total": 42,
"limit": 10,
"offset": 0
}
Obtener factura
GET /v1/invoices/:id
curl https://api.frihet.io/v1/invoices/abc123 \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta (200):
{
"id": "abc123",
"clientName": "Acme S.L.",
"items": [
{ "description": "Consultoria", "quantity": 10, "unitPrice": 75 }
],
"status": "paid",
"issueDate": "2026-01-15",
"dueDate": "2026-02-15",
"taxRate": 21,
"notes": "",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-20T14:00:00.000Z"
}
Crear factura
POST /v1/invoices
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
clientName | string | Nombre del cliente (max 10.000 caracteres) |
items | array | Lista de lineas de la factura. Cada linea: { description, quantity, unitPrice } |
Campos opcionales:
| Campo | Tipo | Descripcion |
|---|---|---|
status | string | draft (defecto), sent, partial, paid, overdue, cancelled |
issueDate | string | Fecha de emision (ISO 8601). Por defecto: hoy |
dueDate | string | Fecha de vencimiento (ISO 8601) |
notes | string | Notas internas (max 10.000 caracteres) |
taxRate | number | Porcentaje de impuesto (0-100). Ej: 21 para IVA 21% |
Estructura de cada linea (items[]):
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
description | string | Si | Descripcion del concepto (max 10.000 caracteres) |
quantity | number | Si | Cantidad |
unitPrice | number | Si | Precio unitario |
curl -X POST https://api.frihet.io/v1/invoices \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"clientName": "Acme S.L.",
"items": [
{ "description": "Desarrollo web", "quantity": 40, "unitPrice": 60 }
],
"dueDate": "2026-03-01",
"taxRate": 21,
"notes": "Proyecto Q1 2026"
}'
Respuesta (201):
{
"id": "def456",
"clientName": "Acme S.L.",
"items": [
{ "description": "Desarrollo web", "quantity": 40, "unitPrice": 60 }
],
"status": "draft",
"issueDate": "2026-02-12",
"dueDate": "2026-03-01",
"taxRate": 21,
"notes": "Proyecto Q1 2026",
"createdAt": "2026-02-12T09:00:00.000Z",
"updatedAt": "2026-02-12T09:00:00.000Z"
}
Actualizar factura (PUT o PATCH)
PUT /v1/invoices/:id
PATCH /v1/invoices/:id
Solo necesitas enviar los campos que quieras modificar. Los campos no incluidos permanecen sin cambios.
curl -X PATCH https://api.frihet.io/v1/invoices/def456 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"status": "sent",
"notes": "Enviada al cliente"
}'
Respuesta (200): Objeto de factura actualizado.
Si envias items, debes enviar el array completo -- no se admiten actualizaciones parciales de lineas individuales.
Eliminar factura
DELETE /v1/invoices/:id
curl -X DELETE https://api.frihet.io/v1/invoices/def456 \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta: 204 No Content
Descargar factura en PDF
GET /v1/invoices/:id/pdf
Devuelve el PDF de la factura como application/pdf con la cabecera Content-Disposition: attachment.
curl -o factura.pdf https://api.frihet.io/v1/invoices/abc123/pdf \
-H "X-API-Key: fri_tu-clave-aqui"
Enviar factura por email
POST /v1/invoices/:id/send
Envia la factura al destinatario especificado a traves de Resend. Si la factura esta en estado draft, se actualiza automaticamente a sent.
Campos:
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
recipientEmail | string | Si | Email del destinatario (max 255 caracteres) |
recipientName | string | No | Nombre del destinatario (max 200 caracteres) |
customMessage | string | No | Mensaje personalizado en el cuerpo del email (max 5.000 caracteres) |
locale | string | No | Idioma del email: es (defecto) o en |
curl -X POST https://api.frihet.io/v1/invoices/abc123/send \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"recipientEmail": "admin@acme.es",
"recipientName": "Departamento de Contabilidad",
"locale": "es"
}'
Respuesta (200):
{ "success": true, "messageId": "re_abc123..." }
Marcar factura como pagada
POST /v1/invoices/:id/paid
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
paidDate | string | No | Fecha de pago (ISO 8601). Por defecto: hoy |
curl -X POST https://api.frihet.io/v1/invoices/abc123/paid \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{ "paidDate": "2026-03-15" }'
Respuesta (200):
{ "success": true, "status": "paid", "paidAt": "2026-03-15" }
Crear factura rectificativa (abono)
POST /v1/invoices/:id/credit-note
Crea un abono o nota de credito a partir de una factura existente.
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
reason | string | Si | Motivo: refund, discount, error, cancellation, other |
type | string | No | Tipo R de rectificacion: R1, R2, R3, R4, R5. Si se omite, se infiere del campo reason |
reasonDescription | string | No | Descripcion libre del motivo |
fullCredit | boolean | No | true = abono total (tipo S), false = parcial (tipo I). Por defecto: true |
issueDate | string | No | Fecha de emision (YYYY-MM-DD). Por defecto: hoy |
curl -X POST https://api.frihet.io/v1/invoices/abc123/credit-note \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"reason": "error",
"type": "R1",
"reasonDescription": "Correccion de importe",
"fullCredit": true
}'
Respuesta (201):
{
"success": true,
"creditNote": {
"id": "xyz789",
"documentNumber": "CN-F2026-0042",
"originalInvoiceId": "abc123",
"reason": "error",
"type": "R1",
"fullCredit": true,
"issueDate": "2026-03-28",
"createdAt": "2026-03-28T10:00:00.000Z"
}
}
Tipos R disponibles:
| Tipo | Descripcion legal |
|---|---|
R1 | Art. 80.1, 80.2, 80.6 y art. 6 del Real Decreto 1496/2003 |
R2 | Art. 80.3 — Concurso de acreedores |
R3 | Art. 80.4 — Creditos incobrables |
R4 | Resto de causas (devolucion, descuento, anulacion) |
R5 | Facturas simplificadas (art. 7.2 del RD 1619/2012) |
Mapeo automatico de reason a tipo R cuando type se omite:
| Motivo | Tipo R inferido |
|---|---|
error | R1 |
refund | R4 |
discount | R4 |
cancellation | R4 |
other | R4 |
Descargar factura en XML (EN16931)
GET /v1/invoices/:id/xml
Devuelve la factura como fichero XML de e-factura EN16931 en formato UBL 2.1 o CII (Cross Industry Invoice), segun la configuracion de la cuenta.
Auth: API key requerida (X-API-Key).
Respuesta: application/xml con cabecera Content-Disposition: attachment.
curl -o factura.xml "https://api.frihet.io/v1/invoices/abc123/xml" \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta (200) — ejemplo UBL 2.1:
<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
<cbc:CustomizationID>urn:cen.eu:en16931:2017</cbc:CustomizationID>
<cbc:ID>F2026-0042</cbc:ID>
<cbc:IssueDate>2026-01-15</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<!-- ... -->
</Invoice>
Errores posibles:
| Codigo | Descripcion |
|---|---|
404 | Factura no encontrada |
422 | La factura no tiene datos suficientes para generar el XML (p.ej., sin NIF del cliente) |
El formato (UBL 2.1 o CII) se configura en Ajustes > Facturacion > E-factura. UBL 2.1 es el formato por defecto y el requerido para VeriFactu y Facturae en Espana.
Aplicar recargo por demora
POST /v1/invoices/:id/late-fee
Aplica un recargo por demora a una factura vencida. Genera una nota de debito con el importe calculado segun la configuracion de recargos de la cuenta (Ajustes > Facturacion > Recargos por demora).
Auth: API key requerida (X-API-Key).
Requisito: La factura debe estar en estado overdue. Si la factura no esta vencida, la API devuelve 422.
Body: {} (vacio). Los parametros del recargo se toman de la configuracion de la cuenta.
curl -X POST "https://api.frihet.io/v1/invoices/abc123/late-fee" \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{}'
Respuesta (201):
{
"success": true,
"lateFee": {
"id": "lf_abc789",
"documentNumber": "DR-F2026-0001",
"originalInvoiceId": "abc123",
"type": "debit_note",
"feeAmount": 18.15,
"currency": "EUR",
"daysOverdue": 22,
"issueDate": "2026-03-28",
"createdAt": "2026-03-28T10:00:00.000Z"
}
}
Errores posibles:
| Codigo | Descripcion |
|---|---|
404 | Factura no encontrada |
422 | La factura no esta en estado overdue o ya tiene un recargo por demora activo |
Define el porcentaje anual o importe fijo en Ajustes > Facturacion > Recargos por demora. El calculo usa la Ley 3/2004 (LMOC) para operaciones entre empresas por defecto.
Gastos (/expenses)
Listar gastos
GET /v1/expenses
Parametros de consulta:
| Parametro | Tipo | Por defecto | Descripcion |
|---|---|---|---|
limit | integer | 50 | Resultados por pagina (maximo 100) |
offset | integer | 0 | Numero de resultados a saltar |
from | string | -- | Fecha inicio (ISO 8601). Filtra por date |
to | string | -- | Fecha fin (ISO 8601). Filtra por date |
category | string | -- | Filtrar por categoria |
vendorId | string | -- | Filtrar por ID de proveedor |
q | string | -- | Busqueda de texto (proveedor, descripcion, numero) |
cursor | string | -- | Cursor para paginacion basada en cursor |
fields | string | -- | Campos a devolver, separados por coma |
curl "https://api.frihet.io/v1/expenses?limit=20&from=2026-01-01" \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta (200):
{
"data": [
{
"id": "exp789",
"description": "Licencia Adobe Creative Cloud",
"amount": 59.99,
"category": "software",
"date": "2026-02-01",
"vendor": "Adobe Inc.",
"taxDeductible": true,
"createdAt": "2026-02-01T10:00:00.000Z",
"updatedAt": "2026-02-01T10:00:00.000Z"
}
],
"total": 15,
"limit": 20,
"offset": 0
}
Obtener gasto
GET /v1/expenses/:id
Crear gasto
POST /v1/expenses
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
description | string | Descripcion del gasto (max 10.000 caracteres) |
amount | number | Importe del gasto |
Campos opcionales:
| Campo | Tipo | Descripcion |
|---|---|---|
category | string | Categoria del gasto (max 10.000 caracteres) |
date | string | Fecha del gasto (ISO 8601). Por defecto: hoy |
vendor | string | Proveedor (max 10.000 caracteres) |
taxDeductible | boolean | Si el gasto es deducible |
curl -X POST https://api.frihet.io/v1/expenses \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"description": "Licencia Adobe Creative Cloud",
"amount": 59.99,
"category": "software",
"date": "2026-02-01",
"vendor": "Adobe Inc.",
"taxDeductible": true
}'
Respuesta (201):
{
"id": "exp789",
"description": "Licencia Adobe Creative Cloud",
"amount": 59.99,
"category": "software",
"date": "2026-02-01",
"vendor": "Adobe Inc.",
"taxDeductible": true,
"createdAt": "2026-02-12T09:15:00.000Z",
"updatedAt": "2026-02-12T09:15:00.000Z"
}
Actualizar gasto (PUT o PATCH)
PUT /v1/expenses/:id
PATCH /v1/expenses/:id
curl -X PATCH https://api.frihet.io/v1/expenses/exp789 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{ "amount": 65.99, "taxDeductible": false }'
Respuesta (200): Objeto de gasto actualizado.
Eliminar gasto
DELETE /v1/expenses/:id
Respuesta: 204 No Content
Marcar gasto como facturable
POST /v1/expenses/:id/billable
Vincula un gasto a un cliente para que se pueda facturar. Opcionalmente aplica un markup.
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
clientId | string | Si | ID del cliente al que facturar |
markup | number | No | Porcentaje de markup (0-1000%) |
curl -X POST https://api.frihet.io/v1/expenses/exp789/billable \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{ "clientId": "cli001", "markup": 15 }'
Para desvincular el gasto:
DELETE /v1/expenses/:id/billable
Clientes (/clients)
Listar clientes
GET /v1/clients
Parametros de consulta:
| Parametro | Tipo | Por defecto | Descripcion |
|---|---|---|---|
limit | integer | 50 | Resultados por pagina (maximo 100) |
offset | integer | 0 | Numero de resultados a saltar |
from | string | -- | Fecha inicio (ISO 8601). Filtra por createdAt |
to | string | -- | Fecha fin (ISO 8601). Filtra por createdAt |
stage | string | -- | Filtrar por etapa del pipeline: lead, contacted, proposal, active, inactive, lost |
q | string | -- | Busqueda de texto (nombre, email, NIF) |
cursor | string | -- | Cursor para paginacion basada en cursor |
fields | string | -- | Campos a devolver, separados por coma |
curl "https://api.frihet.io/v1/clients?limit=50" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtener cliente
GET /v1/clients/:id
Crear cliente
POST /v1/clients
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
name | string | Nombre del cliente (max 10.000 caracteres) |
Campos opcionales:
| Campo | Tipo | Descripcion |
|---|---|---|
email | string | Email de contacto |
phone | string | Telefono |
taxId | string | NIF/CIF/VAT |
address | object | Direccion (ver estructura abajo) |
Estructura de address:
| Campo | Tipo | Descripcion |
|---|---|---|
street | string | Calle y numero |
city | string | Ciudad |
state | string | Provincia o estado |
postalCode | string | Codigo postal |
country | string | Pais |
Todos los campos de address son opcionales.
curl -X POST https://api.frihet.io/v1/clients \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme S.L.",
"email": "admin@acme.es",
"taxId": "B12345678",
"address": {
"street": "Calle Gran Via 42",
"city": "Madrid",
"postalCode": "28013",
"country": "ES"
}
}'
Respuesta (201):
{
"id": "cli001",
"name": "Acme S.L.",
"email": "admin@acme.es",
"taxId": "B12345678",
"address": {
"street": "Calle Gran Via 42",
"city": "Madrid",
"postalCode": "28013",
"country": "ES"
},
"createdAt": "2026-02-12T09:30:00.000Z",
"updatedAt": "2026-02-12T09:30:00.000Z"
}
Actualizar cliente (PUT o PATCH)
PUT /v1/clients/:id
PATCH /v1/clients/:id
curl -X PATCH https://api.frihet.io/v1/clients/cli001 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{ "phone": "+34 912 345 678" }'
Respuesta (200): Objeto de cliente actualizado.
Eliminar cliente
DELETE /v1/clients/:id
Respuesta: 204 No Content
CRM: Personas de contacto, Actividades y Notas
Los clientes tienen tres subcolecciones para gestionar relaciones CRM: personas de contacto, actividades y notas. Todos los endpoints requieren un clientId valido en la URL.
Personas de contacto (/v1/clients/:id/contacts)
Listar contactos
GET /v1/clients/:id/contacts
curl "https://api.frihet.io/v1/clients/cli001/contacts" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtener contacto
GET /v1/clients/:id/contacts/:contactId
curl "https://api.frihet.io/v1/clients/cli001/contacts/con001" \
-H "X-API-Key: fri_tu-clave-aqui"
Crear contacto
POST /v1/clients/:id/contacts
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
name | string | Nombre de la persona de contacto |
Campos opcionales:
| Campo | Tipo | Descripcion |
|---|---|---|
email | string | Email del contacto |
phone | string | Telefono |
role | string | Cargo o rol (ej. "Director financiero") |
isPrimary | boolean | Si es el contacto principal del cliente |
curl -X POST https://api.frihet.io/v1/clients/cli001/contacts \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"name": "Maria Garcia",
"email": "maria@acme.es",
"phone": "+34 612 345 678",
"role": "Directora financiera",
"isPrimary": true
}'
Respuesta (201):
{
"id": "con001",
"name": "Maria Garcia",
"email": "maria@acme.es",
"phone": "+34 612 345 678",
"role": "Directora financiera",
"isPrimary": true,
"createdAt": "2026-03-15T10:00:00.000Z",
"updatedAt": "2026-03-15T10:00:00.000Z"
}
Actualizar contacto
PATCH /v1/clients/:id/contacts/:contactId
curl -X PATCH https://api.frihet.io/v1/clients/cli001/contacts/con001 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{ "role": "CEO" }'
Respuesta (200): Objeto de contacto actualizado.
Eliminar contacto
DELETE /v1/clients/:id/contacts/:contactId
curl -X DELETE https://api.frihet.io/v1/clients/cli001/contacts/con001 \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta: 204 No Content
Actividades (/v1/clients/:id/activities)
El timeline de actividades registra las interacciones con un cliente. Las actividades del sistema (como invoice_created, quote_sent, etc.) se generan automaticamente. Tambien puedes crear actividades manuales.
Las actividades son inmutables. No se pueden actualizar ni eliminar una vez creadas.
Listar actividades
GET /v1/clients/:id/activities
curl "https://api.frihet.io/v1/clients/cli001/activities" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtener actividad
GET /v1/clients/:id/activities/:activityId
curl "https://api.frihet.io/v1/clients/cli001/activities/act001" \
-H "X-API-Key: fri_tu-clave-aqui"
Crear actividad
POST /v1/clients/:id/activities
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
type | string | Tipo de actividad: call, email, meeting o task |
title | string | Titulo descriptivo de la actividad |
Campos opcionales:
| Campo | Tipo | Descripcion |
|---|---|---|
description | string | Descripcion detallada |
metadata | object | Datos adicionales en formato libre |
Los tipos call, email, meeting y task son para actividades creadas manualmente. Los tipos del sistema como invoice_created, quote_sent o expense_linked se generan automaticamente y no se pueden crear via API.
curl -X POST https://api.frihet.io/v1/clients/cli001/activities \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"type": "call",
"title": "Llamada de seguimiento presupuesto Q2",
"description": "Comentamos las condiciones del presupuesto. Pendiente de confirmar.",
"metadata": {
"duration": "15min",
"outcome": "pending"
}
}'
Respuesta (201):
{
"id": "act001",
"type": "call",
"title": "Llamada de seguimiento presupuesto Q2",
"description": "Comentamos las condiciones del presupuesto. Pendiente de confirmar.",
"metadata": {
"duration": "15min",
"outcome": "pending"
},
"createdAt": "2026-03-15T14:30:00.000Z"
}
Notas (/v1/clients/:id/notes)
Listar notas
GET /v1/clients/:id/notes
curl "https://api.frihet.io/v1/clients/cli001/notes" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtener nota
GET /v1/clients/:id/notes/:noteId
curl "https://api.frihet.io/v1/clients/cli001/notes/note001" \
-H "X-API-Key: fri_tu-clave-aqui"
Crear nota
POST /v1/clients/:id/notes
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
content | string | Contenido de la nota |
curl -X POST https://api.frihet.io/v1/clients/cli001/notes \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"content": "Cliente interesado en plan Business. Contactar en abril para renovacion."
}'
Respuesta (201):
{
"id": "note001",
"content": "Cliente interesado en plan Business. Contactar en abril para renovacion.",
"createdAt": "2026-03-15T16:00:00.000Z",
"updatedAt": "2026-03-15T16:00:00.000Z"
}
Actualizar nota
PATCH /v1/clients/:id/notes/:noteId
curl -X PATCH https://api.frihet.io/v1/clients/cli001/notes/note001 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"content": "Cliente interesado en plan Business. Reunion confirmada 5 de abril."
}'
Respuesta (200): Objeto de nota actualizado.
Eliminar nota
DELETE /v1/clients/:id/notes/:noteId
curl -X DELETE https://api.frihet.io/v1/clients/cli001/notes/note001 \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta: 204 No Content
Productos (/products)
Listar productos
GET /v1/products
Acepta limit, offset, from y to (filtra por createdAt).
Obtener producto
GET /v1/products/:id
Crear producto
POST /v1/products
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
name | string | Nombre del producto o servicio (max 10.000 caracteres) |
unitPrice | number | Precio unitario |
Campos opcionales:
| Campo | Tipo | Descripcion |
|---|---|---|
description | string | Descripcion (max 10.000 caracteres) |
taxRate | number | Porcentaje de impuesto (0-100) |
curl -X POST https://api.frihet.io/v1/products \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"name": "Hora de consultoria",
"unitPrice": 75,
"description": "Consultoria estrategica",
"taxRate": 21
}'
Respuesta (201):
{
"id": "prod001",
"name": "Hora de consultoria",
"unitPrice": 75,
"description": "Consultoria estrategica",
"taxRate": 21,
"createdAt": "2026-02-12T10:00:00.000Z",
"updatedAt": "2026-02-12T10:00:00.000Z"
}
Actualizar producto (PUT o PATCH)
PUT /v1/products/:id
PATCH /v1/products/:id
curl -X PATCH https://api.frihet.io/v1/products/prod001 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{ "unitPrice": 85 }'
Respuesta (200): Objeto de producto actualizado.
Eliminar producto
DELETE /v1/products/:id
Respuesta: 204 No Content
Presupuestos (/quotes)
Listar presupuestos
GET /v1/quotes
Parametros de consulta:
| Parametro | Tipo | Por defecto | Descripcion |
|---|---|---|---|
limit | integer | 50 | Resultados por pagina (maximo 100) |
offset | integer | 0 | Numero de resultados a saltar |
status | string | -- | Filtrar por estado: draft, sent, accepted, rejected, expired |
from | string | -- | Fecha inicio (ISO 8601). Filtra por issueDate |
to | string | -- | Fecha fin (ISO 8601). Filtra por issueDate |
curl "https://api.frihet.io/v1/quotes?status=sent" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtener presupuesto
GET /v1/quotes/:id
Crear presupuesto
POST /v1/quotes
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
clientName | string | Nombre del cliente (max 10.000 caracteres) |
items | array | Lineas del presupuesto. Cada linea: { description, quantity, unitPrice } |
Campos opcionales:
| Campo | Tipo | Descripcion |
|---|---|---|
validUntil | string | Fecha de validez (ISO 8601) |
notes | string | Notas o condiciones (max 10.000 caracteres) |
status | string | draft (defecto), sent, accepted, rejected, expired |
curl -X POST https://api.frihet.io/v1/quotes \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"clientName": "Design Studio SL",
"items": [
{ "description": "Desarrollo web", "quantity": 80, "unitPrice": 60 },
{ "description": "Diseno UX", "quantity": 20, "unitPrice": 55 }
],
"validUntil": "2026-04-01",
"notes": "Incluye 2 rondas de revision"
}'
Respuesta (201):
{
"id": "quo001",
"clientName": "Design Studio SL",
"items": [
{ "description": "Desarrollo web", "quantity": 80, "unitPrice": 60 },
{ "description": "Diseno UX", "quantity": 20, "unitPrice": 55 }
],
"status": "draft",
"validUntil": "2026-04-01",
"notes": "Incluye 2 rondas de revision",
"createdAt": "2026-02-12T11:00:00.000Z",
"updatedAt": "2026-02-12T11:00:00.000Z"
}
Actualizar presupuesto (PUT o PATCH)
PUT /v1/quotes/:id
PATCH /v1/quotes/:id
Eliminar presupuesto
DELETE /v1/quotes/:id
Respuesta: 204 No Content
Descargar presupuesto en PDF
GET /v1/quotes/:id/pdf
Funciona igual que /invoices/:id/pdf. Devuelve application/pdf.
curl -o presupuesto.pdf https://api.frihet.io/v1/quotes/quo001/pdf \
-H "X-API-Key: fri_tu-clave-aqui"
Enviar presupuesto por email
POST /v1/quotes/:id/send
Mismos campos que /invoices/:id/send (recipientEmail, recipientName, customMessage, locale). Si el presupuesto esta en estado draft, se actualiza automaticamente a sent.
Proveedores (/vendors)
Listar proveedores
GET /v1/vendors
Acepta limit, offset, from, to (filtra por createdAt), q, cursor, fields.
Obtener proveedor
GET /v1/vendors/:id
Crear proveedor
POST /v1/vendors
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
name | string | Nombre del proveedor |
Campos opcionales:
| Campo | Tipo | Descripcion |
|---|---|---|
email | string | Email de contacto |
phone | string | Telefono |
taxId | string | NIF/CIF/VAT |
address | object | Direccion (street, city, province, zip, country, countryCode) |
curl -X POST https://api.frihet.io/v1/vendors \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"name": "Adobe Inc.",
"email": "billing@adobe.com",
"taxId": "US87-1166924"
}'
Actualizar proveedor (PUT o PATCH)
PUT /v1/vendors/:id
PATCH /v1/vendors/:id
Eliminar proveedor
DELETE /v1/vendors/:id
Respuesta: 204 No Content
Depositos (/deposits)
Los depositos permiten registrar pagos anticipados o senales de clientes antes de emitir la factura final.
Listar depositos
GET /v1/deposits
Acepta limit, offset, from, to (filtra por receivedDate), q, cursor, fields.
Obtener deposito
GET /v1/deposits/:id
Crear deposito
POST /v1/deposits
Campos requeridos:
| Campo | Tipo | Descripcion |
|---|---|---|
clientId | string | ID del cliente |
clientName | string | Nombre del cliente |
amount | number | Importe del deposito (positivo) |
description | string | Descripcion del deposito |
receivedDate | string | Fecha de recepcion (YYYY-MM-DD) |
Campos opcionales:
| Campo | Tipo | Descripcion |
|---|---|---|
currency | string | Codigo ISO 4217 (3 caracteres). Por defecto: EUR |
paymentMethod | string | Metodo de pago |
paymentReference | string | Referencia del pago |
curl -X POST https://api.frihet.io/v1/deposits \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"clientId": "cli001",
"clientName": "Acme S.L.",
"amount": 2000,
"description": "Señal proyecto Q2",
"receivedDate": "2026-03-15"
}'
Actualizar deposito (PUT o PATCH)
PUT /v1/deposits/:id
PATCH /v1/deposits/:id
Eliminar deposito
DELETE /v1/deposits/:id
Respuesta: 204 No Content
Aplicar deposito a una factura
POST /v1/deposits/:id/apply
Descuenta el deposito (total o parcial) del importe de una factura.
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
invoiceId | string | Si | ID de la factura |
invoiceNumber | string | Si | Numero de la factura |
amount | number | Si | Importe a aplicar |
curl -X POST https://api.frihet.io/v1/deposits/dep001/apply \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"invoiceId": "abc123",
"invoiceNumber": "F2026-0042",
"amount": 1500
}'
Reembolsar deposito
POST /v1/deposits/:id/refund
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
amount | number | No | Importe a reembolsar. Si se omite, se reembolsa el saldo restante completo |
Operaciones por lote (/batch)
Todos los recursos principales soportan creacion en lote. Envia un array de hasta 50 elementos en una sola peticion.
POST /v1/{resource}/batch
Recursos soportados: invoices, expenses, clients, vendors, products, quotes, deposits
Cuerpo de la peticion:
{
"items": [
{ "clientName": "Acme SL", "items": [{ "description": "Hora consultoria", "quantity": 1, "unitPrice": 95 }] },
{ "clientName": "TechStart SL", "items": [{ "description": "Desarrollo web", "quantity": 8, "unitPrice": 60 }] }
]
}
curl -X POST https://api.frihet.io/v1/invoices/batch \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "clientName": "Acme SL", "items": [{ "description": "Consultoria", "quantity": 1, "unitPrice": 95 }] },
{ "clientName": "TechStart SL", "items": [{ "description": "Desarrollo", "quantity": 8, "unitPrice": 60 }] }
]
}'
Respuesta (207 Multi-Status):
{
"results": [
{ "status": 201, "data": { "id": "inv_001", "clientName": "Acme SL", "status": "draft" } },
{ "status": 201, "data": { "id": "inv_002", "clientName": "TechStart SL", "status": "draft" } }
],
"summary": { "total": 2, "succeeded": 2, "failed": 0 }
}
Si alguno de los elementos falla la validacion, el resto se crea igualmente. Los errores individuales se devuelven en el array results:
{
"results": [
{ "status": 201, "data": { "id": "inv_001", "clientName": "Acme SL" } },
{ "status": 400, "error": { "message": "Missing required field: items" } }
],
"summary": { "total": 2, "succeeded": 1, "failed": 1 }
}
Limites:
| Concepto | Limite |
|---|---|
| Elementos por lote | 50 maximo |
| Tamano de peticion | 1 MB maximo |
Idempotencia
Las peticiones POST aceptan una cabecera Idempotency-Key para evitar la creacion duplicada de recursos ante reintentos de red. Si la misma clave se envia dentro de las siguientes 24 horas, la API devuelve la respuesta original sin ejecutar la operacion de nuevo.
curl -X POST https://api.frihet.io/v1/invoices \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"clientName": "Acme SL",
"items": [{ "description": "Consultoria", "quantity": 10, "unitPrice": 95 }]
}'
Comportamiento:
| Escenario | Resultado |
|---|---|
| Primera peticion con la clave | Se crea el recurso normalmente |
| Peticion repetida con la misma clave (dentro de 24h) | Se devuelve la respuesta original, sin duplicar |
| Misma clave despues de 24h | Se trata como una peticion nueva |
Cabecera de respuesta:
Cuando la API detecta una clave repetida, incluye la cabecera X-Idempotent-Replayed: true para que el consumidor sepa que la respuesta es una replica.
HTTP/1.1 201 Created
X-Idempotent-Replayed: true
Content-Type: application/json
Requisitos:
- La clave debe tener como maximo 64 caracteres
- Recomendamos usar UUID v4
- Solo aplica a peticiones
POST(crear recursos)
Busqueda
Los endpoints de listado soportan busqueda de texto completo mediante el parametro q:
curl "https://api.frihet.io/v1/invoices?q=acme" \
-H "X-API-Key: fri_tu-clave-aqui"
La busqueda se aplica sobre los campos de texto principales del recurso (nombre de cliente, descripcion, notas, etc.). Se puede combinar con los filtros existentes (status, from, to).
Endpoints de inteligencia
Estos endpoints proporcionan datos agregados y contexto de negocio. Son especialmente utiles para agentes de IA y dashboards externos.
Contexto de negocio (/context)
GET /v1/context
Devuelve un resumen completo del negocio, pensado para alimentar a agentes de IA con el contexto necesario para tomar decisiones informadas. Incluye resumen financiero, actividad reciente, alertas y configuracion fiscal.
curl https://api.frihet.io/v1/context \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta (200):
{
"business": {
"name": "BRTHLS Studio",
"taxId": "12345678A",
"fiscalZone": "canarias",
"currency": "EUR"
},
"summary": {
"revenue": { "invoiced": 15000, "paid": 12000, "pending": 2000, "overdue": 1000 },
"expenses": { "total": 4500 },
"profit": 7500,
"counts": { "invoices": 25, "clients": 12, "products": 5 }
},
"recentActivity": [
{ "type": "invoice.created", "id": "inv_001", "description": "Factura para Acme SL", "timestamp": "2026-03-18T10:00:00Z" },
{ "type": "expense.created", "id": "exp_042", "description": "Adobe Creative Cloud", "timestamp": "2026-03-17T14:30:00Z" }
],
"alerts": [
{ "type": "overdue", "count": 2, "amount": 1000 },
{ "type": "tax_deadline", "model": "303", "dueDate": "2026-04-20" }
]
}
P&L mensual (/monthly)
GET /v1/monthly?month=YYYY-MM
Devuelve la cuenta de resultados (P&L) de un mes concreto: ingresos facturados, gastos por categoria, beneficio neto y comparativa con el mes anterior.
curl "https://api.frihet.io/v1/monthly?month=2026-02" \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta (200):
{
"month": "2026-02",
"revenue": {
"invoiced": 8500.00,
"collected": 6200.00,
"outstanding": 2300.00
},
"expenses": {
"total": 3100.00,
"byCategory": {
"software": 450.00,
"marketing": 800.00,
"office": 350.00,
"professional_services": 1500.00
}
},
"profit": 5400.00,
"comparison": {
"revenueChange": 12.5,
"expenseChange": -5.2,
"profitChange": 22.1
}
}
Cifras fiscales trimestrales (/quarterly)
GET /v1/quarterly?quarter=YYYY-Q1
Devuelve las cifras fiscales de un trimestre, preparadas para la presentacion del Modelo 303 (IVA) y Modelo 130 (IRPF). Incluye bases imponibles, cuotas devengadas, cuotas deducibles y resultado de la liquidacion.
curl "https://api.frihet.io/v1/quarterly?quarter=2026-Q1" \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta (200):
{
"quarter": "2026-Q1",
"period": { "from": "2026-01-01", "to": "2026-03-31" },
"modelo303": {
"baseImponible21": 12000.00,
"cuotaDevengada21": 2520.00,
"baseImponible10": 0,
"cuotaDevengada10": 0,
"baseImponible4": 0,
"cuotaDevengada4": 0,
"totalDevengado": 2520.00,
"ivaDeducible": 980.00,
"resultado": 1540.00
},
"modelo130": {
"ingresos": 12000.00,
"gastos": 4500.00,
"rendimientoNeto": 7500.00,
"porcentaje": 20,
"cuota": 1500.00,
"retenciones": 0,
"pagosAnteriores": 0,
"resultado": 1500.00
}
}
Los endpoints /context, /monthly y /quarterly estan disenados para ser el punto de entrada ideal para agentes de IA. Proporcionan la informacion necesaria en una sola llamada, sin necesidad de consultar multiples endpoints individuales.
Dashboard financiero (/summary)
GET /v1/summary
Devuelve un resumen financiero del negocio: ingresos, gastos, beneficio y contadores.
Parametros de consulta:
| Parametro | Tipo | Descripcion |
|---|---|---|
from | string | Fecha inicio (ISO 8601) |
to | string | Fecha fin (ISO 8601) |
curl "https://api.frihet.io/v1/summary?from=2026-01-01&to=2026-03-31" \
-H "X-API-Key: fri_tu-clave-aqui"
Respuesta (200):
{
"period": { "from": "2026-01-01", "to": "2026-03-31" },
"revenue": {
"invoiced": 15000.00,
"paid": 12000.00,
"pending": 2000.00,
"overdue": 1000.00
},
"expenses": { "total": 4500.00 },
"profit": 7500.00,
"counts": {
"invoices": 25,
"quotes": 8,
"expenses": 42,
"clients": 12,
"products": 5
},
"invoicesByStatus": {
"draft": 3,
"sent": 5,
"paid": 15,
"overdue": 2
},
"overdue": { "count": 2, "amount": 1000.00 }
}
Codigos de error
La API utiliza codigos HTTP estandar. Las respuestas de error incluyen un objeto JSON con los campos error y, opcionalmente, message y details.
| Codigo | Significado | Descripcion |
|---|---|---|
400 | Bad Request | Falta un campo requerido, formato incorrecto o campo no permitido |
401 | Unauthorized | La API key no se ha proporcionado, es invalida, tiene formato incorrecto o ha expirado |
403 | Forbidden | La API key no tiene permisos para acceder a este recurso |
404 | Not Found | El recurso solicitado no existe |
405 | Method Not Allowed | El metodo HTTP no esta soportado para este endpoint |
413 | Payload Too Large | El cuerpo de la peticion excede 1 MB |
422 | Unprocessable Entity | Datos validos pero el servidor no puede procesarlos (ej: perfil fiscal no configurado) |
429 | Too Many Requests | Se ha excedido el limite de 100 peticiones por minuto |
500 | Internal Server Error | Error interno del servidor |
Estructura de las respuestas de error
Error de validacion (400):
{
"error": "Validation error",
"details": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": ["clientName"],
"message": "Required"
}
]
}
Los errores de validacion usan el formato de Zod. El campo path indica que campo contiene el error y message describe el problema.
Clave invalida o expirada (401):
{
"error": "Invalid or expired API key"
}
Formato de clave invalido (401):
{
"error": "Invalid API key format"
}
Recurso no encontrado (404):
{
"error": "Resource not found"
}
Estado invalido en filtro (400):
{
"error": "Invalid status filter",
"message": "Valid values: draft, sent, paid, overdue, cancelled"
}
Rate limit excedido (429):
{
"error": "Rate limit exceeded",
"message": "Maximum 100 requests per minute",
"retryAfter": 60
}
Error interno (500):
{
"error": "Internal server error"
}
Paginacion
Los endpoints de listado devuelven resultados paginados con la siguiente estructura:
{
"data": [],
"total": 142,
"limit": 50,
"offset": 0
}
total: numero total de registros que cumplen los filtros aplicadoslimit: numero de registros devueltos en esta pagina (maximo 100)offset: numero de registros saltados (maximo 10.000)
Para obtener la siguiente pagina:
curl "https://api.frihet.io/v1/invoices?limit=50&offset=50" \
-H "X-API-Key: fri_tu-clave-aqui"
Los resultados se ordenan por la fecha natural del recurso en orden descendente (mas recientes primero):
- Facturas y presupuestos:
issueDate - Gastos:
date - Clientes y productos:
createdAt
Validacion estricta
La API usa validacion estricta (Zod strict mode). Las peticiones con campos desconocidos se rechazan con un error 400:
{
"error": "Validation error",
"details": [
{
"code": "unrecognized_keys",
"keys": ["campoInventado"],
"path": [],
"message": "Unrecognized key(s) in object: 'campoInventado'"
}
]
}
Esto previene errores silenciosos por typos en los nombres de los campos.
CORS
La API soporta CORS para peticiones desde el navegador. Los origenes permitidos son:
https://app.frihet.iohttps://frihet.iohttps://www.frihet.io
Para integraciones server-to-server, CORS no es relevante. Si necesitas acceder a la API desde un dominio diferente en el navegador, utiliza un proxy en tu backend.
Cabeceras de seguridad
Todas las respuestas incluyen cabeceras de seguridad:
| Cabecera | Valor |
|---|---|
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
X-XSS-Protection | 1; mode=block |
X-API-Version | Version de la API (2026-03-18) |
X-Request-Id | ID unico de la peticion (util para depuracion) |
OAuth para MCP
El endpoint POST /api/oauth/api-key permite provisionar una API key automaticamente a traves del flujo OAuth de MCP. El servidor MCP usa este endpoint internamente -- no necesitas llamarlo directamente.
El flujo:
- El usuario se autentica via Firebase Auth
- El cliente MCP envia el token de Firebase a
/api/oauth/api-key - El servidor devuelve una nueva clave
fri_xxx...etiquetada como "MCP OAuth" - La clave expira a los 365 dias
Limite: 5 claves activas por usuario. Si se supera el limite, el endpoint responde con un 429.
Buenas practicas
- Almacena la API key de forma segura. Nunca la incluyas en codigo frontend, repositorios publicos o logs.
- Gestiona el rate limiting. Implementa backoff exponencial si recibes un
429. - Usa paginacion. No pidas todos los registros de golpe; itera con
limityoffset. - Verifica los codigos de respuesta. No asumas que todas las peticiones seran exitosas.
- Rota las claves periodicamente. Crea una nueva clave, actualiza tu integracion y revoca la anterior.
- Usa HTTPS siempre. Todas las peticiones a la API deben ser sobre HTTPS.
- Usa filtros. Los parametros
status,fromytoreducen la cantidad de datos transferidos. - Aprovecha PATCH. Para actualizaciones parciales, envia solo los campos modificados.