API REST
L'API REST de Frihet permet d'accéder et de manipuler les ressources de votre compte de manière programmatique. Toute la communication s'effectue sur HTTPS et les réponses sont au format JSON.
URL de base
https://api.frihet.io/v1
Tous les endpoints décrits sur cette page sont relatifs à cette URL de base.
Découverte : Une requête GET à la racine (https://api.frihet.io/) renvoie les liens principaux sans nécessiter d'authentification :
{
"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 spécification OpenAPI 3.1 est disponible à l'adresse https://api.frihet.io/openapi.yaml.
Si vous utilisez TypeScript ou JavaScript, le SDK officiel simplifie l'intégration :
npm install @frihet/sdk
import Frihet from '@frihet/sdk';
const frihet = new Frihet({ apiKey: 'fri_...' });
const invoices = await frihet.invoices.list({ status: 'overdue' });
Dépôt : github.com/Frihet-io/frihet-sdk
Authentification
Chaque requête doit inclure une clé API dans l'en-tête X-API-Key. Les clés sont créées depuis Paramètres > Développeurs > Clés API dans le panneau Frihet.
Les clés ont le préfixe fri_ suivi de 32 octets aléatoires encodés en base64url. Exemple : fri_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345678.
curl https://api.frihet.io/v1/clients \
-H "X-API-Key: fri_tu-clave-aqui"
Alternativement, vous pouvez envoyer la clé comme jeton Bearer dans l'en-tête Authorization :
curl https://api.frihet.io/v1/clients \
-H "Authorization: Bearer fri_tu-clave-aqui"
Sécurité des clés
- La clé en texte clair n'est affichée qu'une seule fois, au moment de la création. Elle ne peut pas être récupérée par la suite.
- Le serveur stocke un hachage SHA-256 de la clé. Même en cas de fuite de données, la clé originale n'est pas récupérable.
- Vous pouvez créer des clés avec une date d'expiration configurable.
- Si vous suspectez qu'une clé a été compromise, révoquez-la immédiatement depuis le panneau.
Cycle de vie des clés
Créer une clé :
- Allez dans Paramètres > Développeurs > Clés API
- Cliquez sur Créer une clé
- Assignez un nom descriptif (par exemple,
intégration-comptabilité) - Optionnellement, définissez une date d'expiration en jours. Si vous laissez vide, la clé n'expire pas
- Copiez la clé immédiatement -- vous ne pourrez plus la voir par la suite
Expiration :
Les clés avec une date d'expiration cessent de fonctionner automatiquement à l'échéance. Les requêtes avec une clé expirée reçoivent un 401 Unauthorized.
Révocation :
Vous pouvez révoquer une clé à tout moment depuis Paramètres > Développeurs > Clés API. La révocation est immédiate et irréversible : les requêtes en cours avec cette clé échoueront à partir de cet instant.
Rotation des clés :
Pour faire pivoter une clé sans interrompre le service :
- Créez une nouvelle clé avec la même portée
- Mettez à jour votre intégration pour utiliser la nouvelle clé
- Vérifiez que les requêtes fonctionnent correctement
- Révoquez l'ancienne clé
Surveillance :
Chaque clé affiche la date de dernière utilisation dans le panneau Paramètres. Vérifiez périodiquement les clés inactives et révoquez celles qui ne sont plus utilisées.
Limitation de débit
Chaque clé API a une limite de 100 requêtes par minute. Si elle est dépassée, l'API répond avec un code 429 :
{
"error": "Rate limit exceeded",
"message": "Maximum 100 requests per minute",
"retryAfter": 60
}
En-têtes de limitation de débit
Toutes les réponses de l'API incluent des en-têtes afin que vous puissiez gérer la limite de manière proactive :
| En-tête | Description | Exemple |
|---|---|---|
X-RateLimit-Limit | Requêtes autorisées par minute | 100 |
X-RateLimit-Remaining | Requêtes restantes dans la fenêtre actuelle | 87 |
X-RateLimit-Reset | Timestamp Unix (secondes) auquel la fenêtre se réinitialise | 1709312400 |
Exemple de réponse avec les en-têtes de limitation de débit :
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1709312400
Content-Type: application/json
Gestion du code 429 :
Lorsque vous recevez un 429, utilisez les en-têtes pour calculer combien de temps attendre avant de réessayer :
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;
}
Recommandations :
- Si vous recevez un
429, attendez le timestamp indiqué dansX-RateLimit-Resetavant de réessayer. - Surveillez
X-RateLimit-Remainingpour ralentir les requêtes avant d'atteindre la limite. - Distribuez les requêtes dans le temps au lieu d'envoyer des rafales.
Taille de la requête
Le corps des requêtes POST, PUT et PATCH ne peut pas dépasser 1 Mo. Les requêtes plus grandes reçoivent un code 413.
Ressources
L'API expose 7 ressources principales : invoices, expenses, clients, products, quotes, vendors et webhooks. Toutes supportent des opérations CRUD complètes (GET, POST, PUT/PATCH, DELETE). De plus, les clients ont des sous-collections CRM : contacts, activities et notes.
Les deux, PUT et PATCH, acceptent les mises à jour partielles. Vous n'avez pas besoin d'envoyer la ressource complète -- seulement les champs que vous souhaitez modifier.
Factures (/invoices)
Lister les factures
GET /v1/invoices
Paramètres de requête :
| Paramètre | Type | Par défaut | Description |
|---|---|---|---|
limit | integer | 50 | Résultats par page (maximum 100) |
offset | integer | 0 | Nombre de résultats à ignorer (maximum 10 000) |
status | string | -- | Filtrer par état : draft, sent, paid, overdue, cancelled |
from | string | -- | Date de début (ISO 8601 : AAAA-MM-JJ). Filtre par issueDate |
to | string | -- | Date de fin (ISO 8601 : AAAA-MM-JJ). Filtre par issueDate |
Exemple :
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"
Réponse (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
}
Obtenir une facture
GET /v1/invoices/:id
curl https://api.frihet.io/v1/invoices/abc123 \
-H "X-API-Key: fri_tu-clave-aqui"
Réponse (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"
}
Créer une facture
POST /v1/invoices
Champs requis :
| Champ | Type | Description |
|---|---|---|
clientName | string | Nom du client (max 10 000 caractères) |
items | array | Liste des lignes de facture. Chaque ligne : { description, quantity, unitPrice } |
Champs optionnels :
| Champ | Type | Description |
|---|---|---|
status | string | draft (par défaut), sent, paid, overdue, cancelled |
issueDate | string | Date d'émission (ISO 8601). Par défaut : aujourd'hui |
dueDate | string | Date d'échéance (ISO 8601) |
notes | string | Notes internes (max 10 000 caractères) |
taxRate | number | Pourcentage d'impôt (0-100). Ex : 21 pour TVA 21% |
Structure de chaque ligne (items[]) :
| Champ | Type | Requis | Description |
|---|---|---|---|
description | string | Oui | Description du concept (max 10 000 caractères) |
quantity | number | Oui | Quantité |
unitPrice | number | Oui | Prix unitaire |
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"
}'
Réponse (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"
}
Mettre à jour une facture (PUT ou PATCH)
PUT /v1/invoices/:id
PATCH /v1/invoices/:id
Vous n'avez besoin d'envoyer que les champs que vous souhaitez modifier. Les champs non inclus restent inchangés.
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"
}'
Réponse (200) : Objet de facture mis à jour.
Si vous envoyez items, vous devez envoyer le tableau complet -- les mises à jour partielles de lignes individuelles ne sont pas admises.
Supprimer une facture
DELETE /v1/invoices/:id
curl -X DELETE https://api.frihet.io/v1/invoices/def456 \
-H "X-API-Key: fri_tu-clave-aqui"
Réponse : 204 No Content
Télécharger une facture en PDF
GET /v1/invoices/:id/pdf
Renvoie le PDF de la facture au format application/pdf avec l'en-tête Content-Disposition: attachment.
curl -o facture.pdf https://api.frihet.io/v1/invoices/abc123/pdf \
-H "X-API-Key: fri_tu-clave-aqui"
Envoyer une facture par e-mail
POST /v1/invoices/:id/send
Envoie la facture au destinataire spécifié via Resend. Si la facture est à l'état draft, elle est automatiquement mise à jour à sent.
Champs :
| Champ | Type | Requis | Description |
|---|---|---|---|
recipientEmail | string | Oui | E-mail du destinataire (max 255 caractères) |
recipientName | string | Non | Nom du destinataire (max 200 caractères) |
customMessage | string | Non | Message personnalisé dans le corps de l'e-mail (max 5 000 caractères) |
locale | string | Non | Langue de l'e-mail : es (par défaut) ou 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"
}'
Réponse (200) :
{ "success": true, "messageId": "re_abc123..." }
Marquer une facture comme payée
POST /v1/invoices/:id/paid
| Champ | Type | Requis | Description |
|---|---|---|---|
paidDate | string | Non | Date de paiement (ISO 8601). Par défaut : aujourd'hui |
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" }'
Réponse (200) :
{ "success": true, "status": "paid", "paidAt": "2026-03-15" }
Dépenses (/expenses)
Lister les dépenses
GET /v1/expenses
Paramètres de requête :
| Paramètre | Type | Par défaut | Description |
|---|---|---|---|
limit | integer | 50 | Résultats par page (maximum 100) |
offset | integer | 0 | Nombre de résultats à ignorer |
from | string | -- | Date de début (ISO 8601). Filtre par date |
to | string | -- | Date de fin (ISO 8601). Filtre par date |
curl "https://api.frihet.io/v1/expenses?limit=20&from=2026-01-01" \
-H "X-API-Key: fri_tu-clave-aqui"
Réponse (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
}
Obtenir une dépense
GET /v1/expenses/:id
Créer une dépense
POST /v1/expenses
Champs requis :
| Campo | Type | Description |
|---|---|---|
description | string | Description de la dépense (max 10 000 caractères) |
amount | number | Montant de la dépense |
Champs optionnels :
| Campo | Type | Description |
|---|---|---|
category | string | Catégorie de la dépense (max 10 000 caractères) |
date | string | Date de la dépense (ISO 8601). Par défaut : aujourd'hui |
vendor | string | Fournisseur (max 10 000 caractères) |
taxDeductible | boolean | Si la dépense est déductible |
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
}'
Réponse (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"
}
Mettre à jour une dépense (PUT ou 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 }'
Réponse (200) : Objet de dépense mis à jour.
Supprimer une dépense
DELETE /v1/expenses/:id
Réponse : 204 No Content
Clients (/clients)
Lister les clients
GET /v1/clients
Accepte limit, offset, from et to (filtre par createdAt).
curl "https://api.frihet.io/v1/clients?limit=50" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtenir un client
GET /v1/clients/:id
Créer un client
POST /v1/clients
Champs requis :
| Campo | Type | Description |
|---|---|---|
name | string | Nom du client (max 10 000 caractères) |
Champs optionnels :
| Campo | Type | Description |
|---|---|---|
email | string | E-mail de contact |
phone | string | Téléphone |
taxId | string | NIF/CIF/TVA |
address | object | Adresse (voir structure ci-dessous) |
Structure de address :
| Campo | Type | Description |
|---|---|---|
street | string | Rue et numéro |
city | string | Ville |
state | string | Province ou état |
postalCode | string | Code postal |
country | string | Pays |
Tous les champs de address sont optionnels.
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"
}
}'
Réponse (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"
}
Mettre à jour un client (PUT ou 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" }'
Réponse (200) : Objet de client mis à jour.
Supprimer un client
DELETE /v1/clients/:id
Réponse : 204 No Content
CRM : Personnes de contact, Activités et Notes
Les clients ont trois sous-collections pour gérer les relations CRM : personnes de contact, activités et notes. Tous les endpoints requièrent un clientId valide dans l'URL.
Personnes de contact (/v1/clients/:id/contacts)
Lister les contacts
GET /v1/clients/:id/contacts
curl "https://api.frihet.io/v1/clients/cli001/contacts" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtenir un contact
GET /v1/clients/:id/contacts/:contactId
curl "https://api.frihet.io/v1/clients/cli001/contacts/con001" \
-H "X-API-Key: fri_tu-clave-aqui"
Créer un contact
POST /v1/clients/:id/contacts
Champs requis :
| Campo | Type | Description |
|---|---|---|
name | string | Nom de la personne de contact |
Champs optionnels :
| Campo | Type | Description |
|---|---|---|
email | string | E-mail du contact |
phone | string | Téléphone |
role | string | Poste ou rôle (ex. « Directeur financier ») |
isPrimary | boolean | S'il est le contact principal du client |
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
}'
Réponse (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"
}
Mettre à jour un contact
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" }'
Réponse (200) : Objet de contact mis à jour.
Supprimer un contact
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"
Réponse : 204 No Content
Activités (/v1/clients/:id/activities)
La chronologie des activités enregistre les interactions avec un client. Les activités système (telles que invoice_created, quote_sent, etc.) sont générées automatiquement. Vous pouvez également créer des activités manuelles.
Les activités sont immuables. Elles ne peuvent pas être mises à jour ni supprimées une fois créées.
Lister les activités
GET /v1/clients/:id/activities
curl "https://api.frihet.io/v1/clients/cli001/activities" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtenir une activité
GET /v1/clients/:id/activities/:activityId
curl "https://api.frihet.io/v1/clients/cli001/activities/act001" \
-H "X-API-Key: fri_tu-clave-aqui"
Créer une activité
POST /v1/clients/:id/activities
Champs requis :
| Campo | Type | Description |
|---|---|---|
type | string | Type d'activité : call, email, meeting ou task |
title | string | Titre descriptif de l'activité |
Champs optionnels :
| Campo | Type | Description |
|---|---|---|
description | string | Description détaillée |
metadata | object | Données additionnelles au format libre |
Les types call, email, meeting et task sont pour les activités créées manuellement. Les types système comme invoice_created, quote_sent ou expense_linked sont générés automatiquement et ne peuvent pas être créés via l'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"
}
}'
Réponse (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"
}
Notes (/v1/clients/:id/notes)
Lister les notes
GET /v1/clients/:id/notes
curl "https://api.frihet.io/v1/clients/cli001/notes" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtenir une note
GET /v1/clients/:id/notes/:noteId
curl "https://api.frihet.io/v1/clients/cli001/notes/note001" \
-H "X-API-Key: fri_tu-clave-aqui"
Créer une note
POST /v1/clients/:id/notes
Champs requis :
| Campo | Type | Description |
|---|---|---|
content | string | Contenu de la note |
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."
}'
Réponse (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"
}
Mettre à jour une note
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."
}'
Réponse (200) : Objet de note mis à jour.
Supprimer une note
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"
Réponse : 204 No Content
Produits (/products)
Lister les produits
GET /v1/products
Accepte limit, offset, from et to (filtre par createdAt).
Obtenir un produit
GET /v1/products/:id
Créer un produit
POST /v1/products
Champs requis :
| Campo | Type | Description |
|---|---|---|
name | string | Nom du produit ou service (max 10 000 caractères) |
unitPrice | number | Prix unitaire |
Champs optionnels :
| Campo | Type | Description |
|---|---|---|
description | string | Description (max 10 000 caractères) |
taxRate | number | Pourcentage d'impôt (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
}'
Réponse (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"
}
Mettre à jour un produit (PUT ou 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 }'
Réponse (200) : Objet de produit mis à jour.
Supprimer un produit
DELETE /v1/products/:id
Réponse : 204 No Content
Devis (/quotes)
Lister les devis
GET /v1/quotes
Paramètres de requête :
| Paramètre | Type | Par défaut | Description |
|---|---|---|---|
limit | integer | 50 | Résultats par page (maximum 100) |
offset | integer | 0 | Nombre de résultats à ignorer |
status | string | -- | Filtrer par état : draft, sent, accepted, rejected, expired |
from | string | -- | Date de début (ISO 8601). Filtre par issueDate |
to | string | -- | Date de fin (ISO 8601). Filtre par issueDate |
curl "https://api.frihet.io/v1/quotes?status=sent" \
-H "X-API-Key: fri_tu-clave-aqui"
Obtenir un devis
GET /v1/quotes/:id
Créer un devis
POST /v1/quotes
Champs requis :
| Campo | Type | Description |
|---|---|---|
clientName | string | Nom du client (max 10 000 caractères) |
items | array | Lignes du devis. Chaque ligne : { description, quantity, unitPrice } |
Champs optionnels :
| Campo | Type | Description |
|---|---|---|
validUntil | string | Date de validité (ISO 8601) |
notes | string | Notes ou conditions (max 10 000 caractères) |
status | string | draft (par défaut), 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"
}'
Réponse (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"
}
Mettre à jour un devis (PUT ou PATCH)
PUT /v1/quotes/:id
PATCH /v1/quotes/:id
Supprimer un devis
DELETE /v1/quotes/:id
Réponse : 204 No Content
Télécharger un devis en PDF
GET /v1/quotes/:id/pdf
Fonctionne comme /invoices/:id/pdf. Renvoie application/pdf.
curl -o devis.pdf https://api.frihet.io/v1/quotes/quo001/pdf \
-H "X-API-Key: fri_tu-clave-aqui"
Envoyer un devis par e-mail
POST /v1/quotes/:id/send
Mêmes champs que /invoices/:id/send (recipientEmail, recipientName, customMessage, locale). Si le devis est à l'état draft, il est automatiquement mis à jour à sent.
Opérations par lot (/batch)
Toutes les ressources principales supportent la création par lot. Envoyez un tableau de jusqu'à 50 éléments en une seule requête.
POST /v1/{resource}/batch
Ressources supportées : invoices, expenses, clients, products, quotes
Corps de la requête :
{
"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 }] }
]
}'
Réponse (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 l'un des éléments échoue la validation, le reste est créé de la même manière. Les erreurs individuelles sont renvoyées dans le tableau 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 :
| Concept | Limite |
|---|---|
| Éléments par lot | 50 maximum |
| Taille de la requête | 1 Mo maximum |
Idempotence
Les requêtes POST acceptent un en-tête Idempotency-Key pour éviter la création de ressources en double lors des tentatives de réseau. Si la même clé est envoyée dans les 24 heures suivantes, l'API renvoie la réponse originale sans exécuter l'opération à nouveau.
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 }]
}'
Comportement :
| Scénario | Résultat |
|---|---|
| Première requête avec la clé | La ressource est créée normalement |
| Requête répétée avec la même clé (dans les 24h) | La réponse originale est renvoyée, sans duplication |
| Même clé après 24h | Elle est traitée comme une nouvelle requête |
En-tête de réponse :
Lorsque l'API détecte une clé répétée, elle inclut l'en-tête X-Idempotent-Replayed: true pour que le consommateur sache que la réponse est une réplique.
HTTP/1.1 201 Created
X-Idempotent-Replayed: true
Content-Type: application/json
Exigences :
- La clé doit avoir au maximum 64 caractères
- Nous recommandons d'utiliser UUID v4
- S'applique uniquement aux requêtes
POST(créer des ressources)
Recherche
Les endpoints de liste supportent la recherche en texte intégral via le paramètre q :
curl "https://api.frihet.io/v1/invoices?q=acme" \
-H "X-API-Key: fri_tu-clave-aqui"
La recherche s'applique aux champs de texte principaux de la ressource (nom du client, description, notes, etc.). Elle peut être combinée avec les filtres existants (status, from, to).
Endpoints d'intelligence
Ces endpoints fournissent des données agrégées et un contexte métier. Ils sont particulièrement utiles pour les agents d'IA et les tableaux de bord externes.
Contexte métier (/context)
GET /v1/context
Renvoie un résumé complet de l'entreprise, conçu pour alimenter les agents d'IA avec le contexte nécessaire pour prendre des décisions éclairées. Inclut un résumé financier, l'activité récente, les alertes et la configuration fiscale.
curl https://api.frihet.io/v1/context \
-H "X-API-Key: fri_tu-clave-aqui"
Réponse (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" }
]
}
Compte de profits et pertes mensuel (/monthly)
GET /v1/monthly?month=YYYY-MM
Renvoie le compte de profits et pertes (P&L) d'un mois spécifique : revenus facturés, dépenses par catégorie, bénéfice net et comparaison avec le mois précédent.
curl "https://api.frihet.io/v1/monthly?month=2026-02" \
-H "X-API-Key: fri_tu-clave-aqui"
Réponse (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
}
}
Chiffres fiscaux trimestriels (/quarterly)
GET /v1/quarterly?quarter=YYYY-Q1
Renvoie les chiffres fiscaux d'un trimestre, préparés pour la présentation du Modèle 303 (TVA) et du Modèle 130 (IRPP). Inclut les bases imposables, les montants dus, les montants déductibles et le résultat de la liquidation.
curl "https://api.frihet.io/v1/quarterly?quarter=2026-Q1" \
-H "X-API-Key: fri_tu-clave-aqui"
Réponse (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
}
}
Les endpoints /context, /monthly et /quarterly sont conçus pour être le point d'entrée idéal pour les agents d'IA. Ils fournissent les informations nécessaires en un seul appel, sans avoir besoin de consulter plusieurs endpoints individuels.
Tableau de bord financier (/summary)
GET /v1/summary
Renvoie un résumé financier de l'entreprise : revenus, dépenses, bénéfice et compteurs.
Paramètres de requête :
| Paramètre | Type | Description |
|---|---|---|
from | string | Date de début (ISO 8601) |
to | string | Date de 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"
Réponse (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 }
}
Codes d'erreur
L'API utilise des codes HTTP standard. Les réponses d'erreur incluent un objet JSON avec les champs error et, optionnellement, message et details.
| Code | Signification | Description |
|---|---|---|
400 | Bad Request | Champ requis manquant, format incorrect ou champ non autorisé |
401 | Unauthorized | La clé API n'a pas été fournie, est invalide, a un format incorrect ou a expiré |
403 | Forbidden | La clé API n'a pas les permissions d'accéder à cette ressource |
404 | Not Found | La ressource demandée n'existe pas |
405 | Method Not Allowed | La méthode HTTP n'est pas supportée pour ce endpoint |
413 | Payload Too Large | Le corps de la requête dépasse 1 Mo |
422 | Unprocessable Entity | Données valides mais le serveur ne peut pas les traiter (ex: profil fiscal non configuré) |
429 | Too Many Requests | La limite de 100 requêtes par minute a été dépassée |
500 | Internal Server Error | Erreur interne du serveur |
Structure des réponses d'erreur
Erreur de validation (400) :
{
"error": "Validation error",
"details": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": ["clientName"],
"message": "Required"
}
]
}
Les erreurs de validation utilisent le format de Zod. Le champ path indique quel champ contient l'erreur et message décrit le problème.
Clé invalide ou expirée (401) :
{
"error": "Invalid or expired API key"
}
Format de clé invalide (401) :
{
"error": "Invalid API key format"
}
Ressource introuvable (404) :
{
"error": "Resource not found"
}
Statut invalide dans le filtre (400) :
{
"error": "Invalid status filter",
"message": "Valid values: draft, sent, paid, overdue, cancelled"
}
Limite de débit dépassée (429) :
{
"error": "Rate limit exceeded",
"message": "Maximum 100 requests per minute",
"retryAfter": 60
}
Erreur interne (500) :
{
"error": "Internal server error"
}
Pagination
Les endpoints de liste renvoient des résultats paginés avec la structure suivante :
{
"data": [],
"total": 142,
"limit": 50,
"offset": 0
}
total: nombre total d'enregistrements qui correspondent aux filtres appliquéslimit: nombre d'enregistrements renvoyés sur cette page (maximum 100)offset: nombre d'enregistrements ignorés (maximum 10 000)
Pour obtenir la page suivante :
curl "https://api.frihet.io/v1/invoices?limit=50&offset=50" \
-H "X-API-Key: fri_tu-clave-aqui"
Les résultats sont triés par la date naturelle de la ressource dans l'ordre décroissant (les plus récents en premier) :
- Factures et devis :
issueDate - Dépenses :
date - Clients et produits :
createdAt
Validation stricte
L'API utilise une validation stricte (mode strict Zod). Les requêtes avec des champs inconnus sont rejetées avec une erreur 400 :
{
"error": "Validation error",
"details": [
{
"code": "unrecognized_keys",
"keys": ["campoInventado"],
"path": [],
"message": "Unrecognized key(s) in object: 'campoInventado'"
}
]
}
Cela prévient les erreurs silencieuses dues à des fautes de frappe dans les noms des champs.
CORS
L'API supporte CORS pour les requêtes depuis le navigateur. Les origines autorisées sont :
https://app.frihet.iohttps://frihet.iohttps://www.frihet.io
Pour les intégrations de serveur à serveur, CORS n'est pas pertinent. Si vous devez accéder à l'API depuis un domaine différent dans le navigateur, utilisez un proxy sur votre backend.
En-têtes de sécurité
Toutes les réponses incluent des en-têtes de sécurité :
| En-tête | Valeur |
|---|---|
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
X-XSS-Protection | 1; mode=block |
X-Request-Id | ID unique de la requête (utile pour le débogage) |
OAuth pour MCP
L'endpoint POST /api/oauth/api-key permet de provisionner automatiquement une clé API via le flux OAuth de MCP. Le serveur MCP utilise cet endpoint en interne -- vous n'avez pas besoin de l'appeler directement.
Le flux :
- L'utilisateur s'authentifie via Firebase Auth
- Le client MCP envoie le jeton Firebase à
/api/oauth/api-key - Le serveur renvoie une nouvelle clé
fri_xxx...étiquetée « MCP OAuth » - La clé expire après 365 jours
Limite : 5 clés actives par utilisateur. Si la limite est dépassée, l'endpoint répond avec un 429.
Bonnes pratiques
- Stockez la clé API de manière sécurisée. Ne l'incluez jamais dans le code frontend, les dépôts publics ou les journaux.
- Gérez la limitation de débit. Implémentez un backoff exponentiel si vous recevez un
429. - Utilisez la pagination. Ne demandez pas tous les enregistrements d'un coup ; itérez avec
limitetoffset. - Vérifiez les codes de réponse. Ne supposez pas que toutes les requêtes seront réussies.
- Faites pivoter les clés périodiquement. Créez une nouvelle clé, mettez à jour votre intégration et révoquez l'ancienne.
- Utilisez toujours HTTPS. Toutes les requêtes vers l'API doivent être faites sur HTTPS.
- Utilisez des filtres. Les paramètres
status,fromettoréduisent la quantité de données transférées. - Tirez parti de PATCH. Pour les mises à jour partielles, envoyez uniquement les champs modifiés.