Aller au contenu principal

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.

SDK disponible

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é :

  1. Allez dans Paramètres > Développeurs > Clés API
  2. Cliquez sur Créer une clé
  3. Assignez un nom descriptif (par exemple, intégration-comptabilité)
  4. Optionnellement, définissez une date d'expiration en jours. Si vous laissez vide, la clé n'expire pas
  5. 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 :

  1. Créez une nouvelle clé avec la même portée
  2. Mettez à jour votre intégration pour utiliser la nouvelle clé
  3. Vérifiez que les requêtes fonctionnent correctement
  4. 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êteDescriptionExemple
X-RateLimit-LimitRequêtes autorisées par minute100
X-RateLimit-RemainingRequêtes restantes dans la fenêtre actuelle87
X-RateLimit-ResetTimestamp Unix (secondes) auquel la fenêtre se réinitialise1709312400

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é dans X-RateLimit-Reset avant de réessayer.
  • Surveillez X-RateLimit-Remaining pour 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.

PATCH vs PUT

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ètreTypePar défautDescription
limitinteger50Résultats par page (maximum 100)
offsetinteger0Nombre de résultats à ignorer (maximum 10 000)
statusstring--Filtrer par état : draft, sent, paid, overdue, cancelled
fromstring--Date de début (ISO 8601 : AAAA-MM-JJ). Filtre par issueDate
tostring--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 :

ChampTypeDescription
clientNamestringNom du client (max 10 000 caractères)
itemsarrayListe des lignes de facture. Chaque ligne : { description, quantity, unitPrice }

Champs optionnels :

ChampTypeDescription
statusstringdraft (par défaut), sent, paid, overdue, cancelled
issueDatestringDate d'émission (ISO 8601). Par défaut : aujourd'hui
dueDatestringDate d'échéance (ISO 8601)
notesstringNotes internes (max 10 000 caractères)
taxRatenumberPourcentage d'impôt (0-100). Ex : 21 pour TVA 21%

Structure de chaque ligne (items[]) :

ChampTypeRequisDescription
descriptionstringOuiDescription du concept (max 10 000 caractères)
quantitynumberOuiQuantité
unitPricenumberOuiPrix 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.

attention

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 :

ChampTypeRequisDescription
recipientEmailstringOuiE-mail du destinataire (max 255 caractères)
recipientNamestringNonNom du destinataire (max 200 caractères)
customMessagestringNonMessage personnalisé dans le corps de l'e-mail (max 5 000 caractères)
localestringNonLangue 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
ChampTypeRequisDescription
paidDatestringNonDate 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ètreTypePar défautDescription
limitinteger50Résultats par page (maximum 100)
offsetinteger0Nombre de résultats à ignorer
fromstring--Date de début (ISO 8601). Filtre par date
tostring--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 :

CampoTypeDescription
descriptionstringDescription de la dépense (max 10 000 caractères)
amountnumberMontant de la dépense

Champs optionnels :

CampoTypeDescription
categorystringCatégorie de la dépense (max 10 000 caractères)
datestringDate de la dépense (ISO 8601). Par défaut : aujourd'hui
vendorstringFournisseur (max 10 000 caractères)
taxDeductiblebooleanSi 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 :

CampoTypeDescription
namestringNom du client (max 10 000 caractères)

Champs optionnels :

CampoTypeDescription
emailstringE-mail de contact
phonestringTéléphone
taxIdstringNIF/CIF/TVA
addressobjectAdresse (voir structure ci-dessous)

Structure de address :

CampoTypeDescription
streetstringRue et numéro
citystringVille
statestringProvince ou état
postalCodestringCode postal
countrystringPays

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 :

CampoTypeDescription
namestringNom de la personne de contact

Champs optionnels :

CampoTypeDescription
emailstringE-mail du contact
phonestringTéléphone
rolestringPoste ou rôle (ex. « Directeur financier »)
isPrimarybooleanS'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.

Immuables

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 :

CampoTypeDescription
typestringType d'activité : call, email, meeting ou task
titlestringTitre descriptif de l'activité

Champs optionnels :

CampoTypeDescription
descriptionstringDescription détaillée
metadataobjectDonnées additionnelles au format libre
Types d'activité

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 :

CampoTypeDescription
contentstringContenu 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 :

CampoTypeDescription
namestringNom du produit ou service (max 10 000 caractères)
unitPricenumberPrix unitaire

Champs optionnels :

CampoTypeDescription
descriptionstringDescription (max 10 000 caractères)
taxRatenumberPourcentage 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ètreTypePar défautDescription
limitinteger50Résultats par page (maximum 100)
offsetinteger0Nombre de résultats à ignorer
statusstring--Filtrer par état : draft, sent, accepted, rejected, expired
fromstring--Date de début (ISO 8601). Filtre par issueDate
tostring--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 :

CampoTypeDescription
clientNamestringNom du client (max 10 000 caractères)
itemsarrayLignes du devis. Chaque ligne : { description, quantity, unitPrice }

Champs optionnels :

CampoTypeDescription
validUntilstringDate de validité (ISO 8601)
notesstringNotes ou conditions (max 10 000 caractères)
statusstringdraft (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 :

ConceptLimite
Éléments par lot50 maximum
Taille de la requête1 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énarioRé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 24hElle 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
}
}
astuce

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ètreTypeDescription
fromstringDate de début (ISO 8601)
tostringDate 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.

CodeSignificationDescription
400Bad RequestChamp requis manquant, format incorrect ou champ non autorisé
401UnauthorizedLa clé API n'a pas été fournie, est invalide, a un format incorrect ou a expiré
403ForbiddenLa clé API n'a pas les permissions d'accéder à cette ressource
404Not FoundLa ressource demandée n'existe pas
405Method Not AllowedLa méthode HTTP n'est pas supportée pour ce endpoint
413Payload Too LargeLe corps de la requête dépasse 1 Mo
422Unprocessable EntityDonnées valides mais le serveur ne peut pas les traiter (ex: profil fiscal non configuré)
429Too Many RequestsLa limite de 100 requêtes par minute a été dépassée
500Internal Server ErrorErreur 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és
  • limit : 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.io
  • https://frihet.io
  • https://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êteValeur
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
X-XSS-Protection1; mode=block
X-Request-IdID 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 :

  1. L'utilisateur s'authentifie via Firebase Auth
  2. Le client MCP envoie le jeton Firebase à /api/oauth/api-key
  3. Le serveur renvoie une nouvelle clé fri_xxx... étiquetée « MCP OAuth »
  4. 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

  1. 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.
  2. Gérez la limitation de débit. Implémentez un backoff exponentiel si vous recevez un 429.
  3. Utilisez la pagination. Ne demandez pas tous les enregistrements d'un coup ; itérez avec limit et offset.
  4. Vérifiez les codes de réponse. Ne supposez pas que toutes les requêtes seront réussies.
  5. Faites pivoter les clés périodiquement. Créez une nouvelle clé, mettez à jour votre intégration et révoquez l'ancienne.
  6. Utilisez toujours HTTPS. Toutes les requêtes vers l'API doivent être faites sur HTTPS.
  7. Utilisez des filtres. Les paramètres status, from et to réduisent la quantité de données transférées.
  8. Tirez parti de PATCH. Pour les mises à jour partielles, envoyez uniquement les champs modifiés.