REST-API
Die Frihet REST-API ermöglicht den programmatischen Zugriff auf und die Manipulation der Ressourcen Ihres Kontos. Die gesamte Kommunikation erfolgt über HTTPS und die Antworten sind JSON.
Basis-URL
https://api.frihet.io/v1
Alle auf dieser Seite beschriebenen Endpunkte beziehen sich auf diese Basis-URL.
Erkennung: Eine GET-Anfrage an die Wurzel (https://api.frihet.io/) gibt die Hauptlinks ohne Authentifizierung zurück:
{
"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"
}
Die OpenAPI 3.1-Spezifikation ist unter https://api.frihet.io/openapi.yaml verfügbar.
Wenn Sie TypeScript oder JavaScript verwenden, vereinfacht das offizielle SDK die Integration:
npm install @frihet/sdk
import Frihet from '@frihet/sdk';
const frihet = new Frihet({ apiKey: 'fri_...' });
const invoices = await frihet.invoices.list({ status: 'overdue' });
Repository: github.com/Frihet-io/frihet-sdk
Authentifizierung
Jede Anfrage muss einen API-Schlüssel im Header X-API-Key enthalten. Die Schlüssel werden unter Einstellungen > Entwickler > API-Schlüssel im Frihet-Panel erstellt.
Die Schlüssel haben das Präfix fri_ gefolgt von 32 zufälligen, in base64url kodierten Bytes. Beispiel: fri_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345678.
curl https://api.frihet.io/v1/clients \
-H "X-API-Key: fri_tu-clave-aqui"
Alternativ können Sie den Schlüssel als Bearer-Token im Authorization-Header senden:
curl https://api.frihet.io/v1/clients \
-H "Authorization: Bearer fri_tu-clave-aqui"
Sicherheit der Schlüssel
- Der Klartext-Schlüssel wird nur einmal bei der Erstellung angezeigt. Er kann danach nicht mehr wiederhergestellt werden.
- Der Server speichert einen SHA-256-Hash des Schlüssels. Selbst im Falle einer Datenpanne ist der ursprüngliche Schlüssel nicht wiederherstellbar.
- Sie können Schlüssel mit konfigurierbarem Ablaufdatum erstellen.
- Wenn Sie vermuten, dass ein Schlüssel kompromittiert wurde, widerrufen Sie ihn sofort über das Panel.
Lebenszyklus der Schlüssel
Schlüssel erstellen:
- Gehen Sie zu Einstellungen > Entwickler > API-Schlüssel
- Klicken Sie auf Schlüssel erstellen
- Weisen Sie einen beschreibenden Namen zu (z.B.
buchhaltungs-integration) - Optional legen Sie ein Ablaufdatum in Tagen fest. Wenn Sie es leer lassen, läuft der Schlüssel nicht ab.
- Kopieren Sie den Schlüssel sofort -- Sie werden ihn danach nicht mehr sehen können.
Ablauf:
Schlüssel mit einem Ablaufdatum funktionieren automatisch nicht mehr, sobald die Frist abgelaufen ist. Anfragen mit einem abgelaufenen Schlüssel erhalten einen 401 Unauthorized.
Widerruf:
Sie können einen Schlüssel jederzeit unter Einstellungen > Entwickler > API-Schlüssel widerrufen. Der Widerruf ist sofort und irreversibel: laufende Anfragen mit diesem Schlüssel werden ab diesem Zeitpunkt fehlschlagen.
Schlüsselrotation:
Um einen Schlüssel ohne Dienstunterbrechung zu rotieren:
- Erstellen Sie einen neuen Schlüssel mit demselben Geltungsbereich.
- Aktualisieren Sie Ihre Integration, um den neuen Schlüssel zu verwenden.
- Überprüfen Sie, ob die Anfragen korrekt funktionieren.
- Widerrufen Sie den alten Schlüssel.
Überwachung:
Jeder Schlüssel zeigt das Datum des letzten Gebrauchs im Einstellungs-Panel an. Überprüfen Sie regelmäßig inaktive Schlüssel und widerrufen Sie diejenigen, die nicht mehr verwendet werden.
Ratenlimit
Jeder API-Schlüssel hat ein Limit von 100 Anfragen pro Minute. Bei Überschreitung antwortet die API mit dem Code 429:
{
"error": "Ratenlimit überschritten",
"message": "Maximal 100 Anfragen pro Minute",
"retryAfter": 60
}
Ratenlimit-Header
Alle API-Antworten enthalten Header, damit Sie das Limit proaktiv verwalten können:
| Header | Beschreibung | Beispiel |
|---|---|---|
X-RateLimit-Limit | Erlaubte Anfragen pro Minute | 100 |
X-RateLimit-Remaining | Verbleibende Anfragen im aktuellen Fenster | 87 |
X-RateLimit-Reset | Unix-Timestamp (Sekunden), zu dem das Fenster zurückgesetzt wird | 1709312400 |
Beispiel einer Antwort mit Ratenlimit-Headern:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1709312400
Content-Type: application/json
Verwaltung des Codes 429:
Wenn Sie einen 429 erhalten, verwenden Sie die Header, um zu berechnen, wie lange Sie vor einem erneuten Versuch warten müssen:
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;
}
Empfehlungen:
- Wenn Sie einen
429erhalten, warten Sie bis zum inX-RateLimit-Resetangegebenen Timestamp, bevor Sie es erneut versuchen. - Überwachen Sie
X-RateLimit-Remaining, um Anfragen zu drosseln, bevor das Limit erreicht ist. - Verteilen Sie Anfragen über die Zeit, anstatt Bursts zu senden.
Anfragengröße
Der Body von POST-, PUT- und PATCH-Anfragen darf 1 MB nicht überschreiten. Größere Anfragen erhalten den Code 413.
Ressourcen
Die API stellt 7 Hauptressourcen bereit: invoices, expenses, clients, products, quotes, vendors und webhooks. Alle unterstützen vollständige CRUD-Operationen (GET, POST, PUT/PATCH, DELETE). Darüber hinaus verfügen Kunden über CRM-Unterkollektionen: contacts, activities und notes.
Sowohl PUT als auch PATCH akzeptieren partielle Updates. Sie müssen die vollständige Ressource nicht senden – nur die Felder, die Sie ändern möchten.
Rechnungen (/invoices)
Rechnungen auflisten
GET /v1/invoices
Abfrageparameter:
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
limit | integer | 50 | Ergebnisse pro Seite (maximal 100) |
offset | integer | 0 | Anzahl der zu überspringenden Ergebnisse (maximal 10.000) |
status | string | -- | Nach Status filtern: draft, sent, paid, overdue, cancelled |
from | string | -- | Startdatum (ISO 8601: JJJJ-MM-TT). Filtert nach issueDate |
to | string | -- | Enddatum (ISO 8601: JJJJ-MM-TT). Filtert nach issueDate |
Beispiel:
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"
Antwort (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
}
Rechnung abrufen
GET /v1/invoices/:id
curl https://api.frihet.io/v1/invoices/abc123 \
-H "X-API-Key: fri_tu-clave-aqui"
Antwort (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"
}
Rechnung erstellen
POST /v1/invoices
Erforderliche Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
clientName | string | Name des Kunden (max. 10.000 Zeichen) |
items | array | Liste der Rechnungspositionen. Jede Position: { description, quantity, unitPrice } |
Optionale Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
status | string | draft (Standard), sent, paid, overdue, cancelled |
issueDate | string | Ausstellungsdatum (ISO 8601). Standard: heute |
dueDate | string | Fälligkeitsdatum (ISO 8601) |
notes | string | Interne Notizen (max. 10.000 Zeichen) |
taxRate | number | Steuerprozentsatz (0-100). Z.B.: 21 für 21% Mehrwertsteuer |
Struktur jeder Position (items[]):
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
description | string | Ja | Beschreibung des Konzepts (max. 10.000 Zeichen) |
quantity | number | Ja | Menge |
unitPrice | number | Ja | Stückpreis |
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"
}'
Antwort (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"
}
Rechnung aktualisieren (PUT oder PATCH)
PUT /v1/invoices/:id
PATCH /v1/invoices/:id
Sie müssen nur die Felder senden, die Sie ändern möchten. Nicht enthaltene Felder bleiben unverändert.
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"
}'
Antwort (200): Aktualisiertes Rechnungsobjekt.
Wenn Sie items senden, müssen Sie das gesamte Array senden – partielle Updates einzelner Positionen werden nicht unterstützt.
Rechnung löschen
DELETE /v1/invoices/:id
curl -X DELETE https://api.frihet.io/v1/invoices/def456 \
-H "X-API-Key: fri_tu-clave-aqui"
Antwort: 204 No Content
Rechnung als PDF herunterladen
GET /v1/invoices/:id/pdf
Gibt die Rechnung als PDF (application/pdf) mit dem Header Content-Disposition: attachment zurück.
curl -o factura.pdf https://api.frihet.io/v1/invoices/abc123/pdf \
-H "X-API-Key: fri_tu-clave-aqui"
Rechnung per E-Mail senden
POST /v1/invoices/:id/send
Sendet die Rechnung über Resend an den angegebenen Empfänger. Befindet sich die Rechnung im Status draft, wird sie automatisch auf sent aktualisiert.
Felder:
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
recipientEmail | string | Ja | E-Mail des Empfängers (max. 255 Zeichen) |
recipientName | string | Nein | Name des Empfängers (max. 200 Zeichen) |
customMessage | string | Nein | Personalisierte Nachricht im E-Mail-Text (max. 5.000 Zeichen) |
locale | string | Nein | Sprache der E-Mail: es (Standard) oder 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"
}'
Antwort (200):
{ "success": true, "messageId": "re_abc123..." }
Rechnung als bezahlt markieren
POST /v1/invoices/:id/paid
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
paidDate | string | Nein | Zahlungsdatum (ISO 8601). Standard: heute |
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" }'
Antwort (200):
{ "success": true, "status": "paid", "paidAt": "2026-03-15" }
Ausgaben (/expenses)
Ausgaben auflisten
GET /v1/expenses
Abfrageparameter:
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
limit | integer | 50 | Ergebnisse pro Seite (maximal 100) |
offset | integer | 0 | Anzahl der zu überspringenden Ergebnisse |
from | string | -- | Startdatum (ISO 8601). Filtert nach date |
to | string | -- | Enddatum (ISO 8601). Filtert nach date |
curl "https://api.frihet.io/v1/expenses?limit=20&from=2026-01-01" \
-H "X-API-Key: fri_tu-clave-aqui"
Antwort (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
}
Ausgabe abrufen
GET /v1/expenses/:id
Ausgabe erstellen
POST /v1/expenses
Erforderliche Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
description | string | Beschreibung der Ausgabe (max. 10.000 Zeichen) |
amount | number | Betrag der Ausgabe |
Optionale Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
category | string | Kategorie der Ausgabe (max. 10.000 Zeichen) |
date | string | Datum der Ausgabe (ISO 8601). Standard: heute |
vendor | string | Lieferant (max. 10.000 Zeichen) |
taxDeductible | boolean | Ob die Ausgabe abzugsfähig ist |
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
}'
Antwort (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"
}
Ausgabe aktualisieren (PUT oder 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 }'
Antwort (200): Aktualisiertes Ausgabeobjekt.
Ausgabe löschen
DELETE /v1/expenses/:id
Antwort: 204 No Content
Kunden (/clients)
Kunden auflisten
GET /v1/clients
Akzeptiert limit, offset, from und to (filtert nach createdAt).
curl "https://api.frihet.io/v1/clients?limit=50" \
-H "X-API-Key: fri_tu-clave-aqui"
Kunde abrufen
GET /v1/clients/:id
Kunde erstellen
POST /v1/clients
Erforderliche Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
name | string | Name des Kunden (max. 10.000 Zeichen) |
Optionale Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
email | string | Kontakt-E-Mail |
phone | string | Telefon |
taxId | string | Steuer-ID |
address | object | Adresse (siehe Struktur unten) |
Struktur von address:
| Feld | Typ | Beschreibung |
|---|---|---|
street | string | Straße und Hausnummer |
city | string | Stadt |
state | string | Provinz oder Bundesland |
postalCode | string | Postleitzahl |
country | string | Land |
Alle Felder von address sind optional.
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"
}
}'
Antwort (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"
}
Kunde aktualisieren (PUT oder 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" }'
Antwort (200): Aktualisiertes Kundenobjekt.
Kunde löschen
DELETE /v1/clients/:id
Antwort: 204 No Content
CRM: Ansprechpartner, Aktivitäten und Notizen
Kunden verfügen über drei Unterkollektionen zur Verwaltung von CRM-Beziehungen: Ansprechpartner, Aktivitäten und Notizen. Alle Endpunkte erfordern eine gültige clientId in der URL.
Ansprechpartner (/v1/clients/:id/contacts)
Kontakte auflisten
GET /v1/clients/:id/contacts
curl "https://api.frihet.io/v1/clients/cli001/contacts" \
-H "X-API-Key: fri_tu-clave-aqui"
Kontakt abrufen
GET /v1/clients/:id/contacts/:contactId
curl "https://api.frihet.io/v1/clients/cli001/contacts/con001" \
-H "X-API-Key: fri_tu-clave-aqui"
Kontakt erstellen
POST /v1/clients/:id/contacts
Erforderliche Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
name | string | Name des Ansprechpartners |
Optionale Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
email | string | E-Mail des Kontakts |
phone | string | Telefon |
role | string | Position oder Rolle (z.B. "Finanzdirektor") |
isPrimary | boolean | Ob es der primäre Kontakt des Kunden ist |
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
}'
Antwort (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"
}
Kontakt aktualisieren
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" }'
Antwort (200): Aktualisiertes Kontaktobjekt.
Kontakt löschen
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"
Antwort: 204 No Content
Aktivitäten (/v1/clients/:id/activities)
Die Aktivitäten-Timeline zeichnet Interaktionen mit einem Kunden auf. Systemaktivitäten (wie invoice_created, quote_sent usw.) werden automatisch generiert. Sie können auch manuelle Aktivitäten erstellen.
Aktivitäten sind unveränderlich. Sie können nach der Erstellung weder aktualisiert noch gelöscht werden.
Aktivitäten auflisten
GET /v1/clients/:id/activities
curl "https://api.frihet.io/v1/clients/cli001/activities" \
-H "X-API-Key: fri_tu-clave-aqui"
Aktivität abrufen
GET /v1/clients/:id/activities/:activityId
curl "https://api.frihet.io/v1/clients/cli001/activities/act001" \
-H "X-API-Key: fri_tu-clave-aqui"
Aktivität erstellen
POST /v1/clients/:id/activities
Erforderliche Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
type | string | Art der Aktivität: call, email, meeting oder task |
title | string | Beschreibender Titel der Aktivität |
Optionale Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
description | string | Detaillierte Beschreibung |
metadata | object | Zusätzliche Daten im freien Format |
Die Typen call, email, meeting und task sind für manuell erstellte Aktivitäten. Systemtypen wie invoice_created, quote_sent oder expense_linked werden automatisch generiert und können nicht über die API erstellt werden.
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"
}
}'
Antwort (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"
}
Notizen (/v1/clients/:id/notes)
Notizen auflisten
GET /v1/clients/:id/notes
curl "https://api.frihet.io/v1/clients/cli001/notes" \
-H "X-API-Key: fri_tu-clave-aqui"
Notiz abrufen
GET /v1/clients/:id/notes/:noteId
curl "https://api.frihet.io/v1/clients/cli001/notes/note001" \
-H "X-API-Key: fri_tu-clave-aqui"
Notiz erstellen
POST /v1/clients/:id/notes
Erforderliche Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
content | string | Inhalt der Notiz |
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."
}'
Antwort (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"
}
Notiz aktualisieren
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."
}'
Antwort (200): Aktualisiertes Notizobjekt.
Notiz löschen
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"
Antwort: 204 No Content
Produkte (/products)
Produkte auflisten
GET /v1/products
Akzeptiert limit, offset, from und to (filtert nach createdAt).
Produkt abrufen
GET /v1/products/:id
Produkt erstellen
POST /v1/products
Erforderliche Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
name | string | Name des Produkts oder Dienstleistung (max. 10.000 Zeichen) |
unitPrice | number | Stückpreis |
Optionale Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
description | string | Beschreibung (max. 10.000 Zeichen) |
taxRate | number | Steuerprozentsatz (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
}'
Antwort (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"
}
Produkt aktualisieren (PUT oder 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 }'
Antwort (200): Aktualisiertes Produktobjekt.
Produkt löschen
DELETE /v1/products/:id
Antwort: 204 No Content
Angebote (/quotes)
Angebote auflisten
GET /v1/quotes
Abfrageparameter:
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
limit | integer | 50 | Ergebnisse pro Seite (maximal 100) |
offset | integer | 0 | Anzahl der zu überspringenden Ergebnisse |
status | string | -- | Nach Status filtern: draft, sent, accepted, rejected, expired |
from | string | -- | Startdatum (ISO 8601). Filtert nach issueDate |
to | string | -- | Enddatum (ISO 8601). Filtert nach issueDate |
curl "https://api.frihet.io/v1/quotes?status=sent" \
-H "X-API-Key: fri_tu-clave-aqui"
Angebot abrufen
GET /v1/quotes/:id
Angebot erstellen
POST /v1/quotes
Erforderliche Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
clientName | string | Name des Kunden (max. 10.000 Zeichen) |
items | array | Positionen des Angebots. Jede Position: { description, quantity, unitPrice } |
Optionale Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
validUntil | string | Gültigkeitsdatum (ISO 8601) |
notes | string | Notizen oder Bedingungen (max. 10.000 Zeichen) |
status | string | draft (Standard), 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"
}'
Antwort (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"
}
Angebot aktualisieren (PUT oder PATCH)
PUT /v1/quotes/:id
PATCH /v1/quotes/:id
Angebot löschen
DELETE /v1/quotes/:id
Antwort: 204 No Content
Angebot als PDF herunterladen
GET /v1/quotes/:id/pdf
Funktioniert wie /invoices/:id/pdf. Gibt application/pdf zurück.
curl -o presupuesto.pdf https://api.frihet.io/v1/quotes/quo001/pdf \
-H "X-API-Key: fri_tu-clave-aqui"
Angebot per E-Mail senden
POST /v1/quotes/:id/send
Dieselbe Felder wie bei /invoices/:id/send (recipientEmail, recipientName, customMessage, locale). Befindet sich das Angebot im Status draft, wird es automatisch auf sent aktualisiert.
Batch-Operationen (/batch)
Alle Hauptressourcen unterstützen die Batch-Erstellung. Senden Sie ein Array von bis zu 50 Elementen in einer einzigen Anfrage.
POST /v1/{resource}/batch
Unterstützte Ressourcen: invoices, expenses, clients, products, quotes
Anfragetext:
{
"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 }] }
]
}'
Antwort (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 }
}
Wenn eines der Elemente die Validierung fehlschlägt, werden die restlichen trotzdem erstellt. Einzelne Fehler werden im results-Array zurückgegeben:
{
"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 }
}
Limits:
| Konzept | Limit |
|---|---|
| Elemente pro Batch | 50 maximal |
| Anfragengröße | 1 MB maximal |
Idempotenz
POST-Anfragen akzeptieren einen Idempotency-Key-Header, um die doppelte Erstellung von Ressourcen bei Netzwerk-Wiederholungsversuchen zu vermeiden. Wenn derselbe Schlüssel innerhalb der nächsten 24 Stunden gesendet wird, gibt die API die ursprüngliche Antwort zurück, ohne die Operation erneut auszuführen.
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 }]
}'
Verhalten:
| Szenario | Ergebnis |
|---|---|
| Erste Anfrage mit dem Schlüssel | Die Ressource wird normal erstellt |
| Wiederholte Anfrage mit demselben Schlüssel (innerhalb von 24h) | Die ursprüngliche Antwort wird zurückgegeben, ohne Duplizierung |
| Derselbe Schlüssel nach 24h | Wird als neue Anfrage behandelt |
Antwort-Header:
Wenn die API einen wiederholten Schlüssel erkennt, enthält sie den Header X-Idempotent-Replayed: true, damit der Konsument weiß, dass die Antwort eine Replik ist.
HTTP/1.1 201 Created
X-Idempotent-Replayed: true
Content-Type: application/json
Anforderungen:
- Der Schlüssel darf maximal 64 Zeichen lang sein.
- Wir empfehlen die Verwendung von UUID v4.
- Gilt nur für
POST-Anfragen (Ressourcen erstellen).
Suche
Die Auflistungs-Endpunkte unterstützen die Volltextsuche über den Parameter q:
curl "https://api.frihet.io/v1/invoices?q=acme" \
-H "X-API-Key: fri_tu-clave-aqui"
Die Suche wird auf die Haupttextfelder der Ressource angewendet (Kundenname, Beschreibung, Notizen usw.). Sie kann mit bestehenden Filtern (status, from, to) kombiniert werden.
Intelligenz-Endpunkte
Diese Endpunkte liefern aggregierte Daten und Geschäftskontext. Sie sind besonders nützlich für KI-Agenten und externe Dashboards.
Geschäftskontext (/context)
GET /v1/context
Gibt eine vollständige Geschäftsübersicht zurück, die darauf ausgelegt ist, KI-Agenten mit dem notwendigen Kontext für fundierte Entscheidungen zu versorgen. Enthält eine Finanzübersicht, aktuelle Aktivitäten, Warnungen und die Steuerkonfiguration.
curl https://api.frihet.io/v1/context \
-H "X-API-Key: fri_tu-clave-aqui"
Antwort (200):
{
"business": {
"name": "BRTHLS Studio",
"taxId": "12345678A",
"fiscalZone": "canarias",
"currency": "EUR"
},
"summary": {
"revenue": { "fakturiert": 15000, "bezahlt": 12000, "ausstehend": 2000, "überfällig": 1000 },
"expenses": { "gesamt": 4500 },
"profit": 7500,
"counts": { "Rechnungen": 25, "Kunden": 12, "Produkte": 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" }
]
}
Monatliche GuV (/monthly)
GET /v1/monthly?month=YYYY-MM
Gibt die Gewinn- und Verlustrechnung (GuV) eines bestimmten Monats zurück: fakturierte Einnahmen, Ausgaben nach Kategorie, Nettogewinn und Vergleich zum Vormonat.
curl "https://api.frihet.io/v1/monthly?month=2026-02" \
-H "X-API-Key: fri_tu-clave-aqui"
Antwort (200):
{
"month": "2026-02",
"revenue": {
"fakturiert": 8500.00,
"eingezogen": 6200.00,
"ausstehend": 2300.00
},
"expenses": {
"gesamt": 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
}
}
Quartalsweise Steuerzahlen (/quarterly)
GET /v1/quarterly?quarter=YYYY-Q1
Gibt die Steuerzahlen eines Quartals zurück, vorbereitet für die Einreichung des Modells 303 (Umsatzsteuer) und Modells 130 (Einkommensteuer). Enthält Bemessungsgrundlagen, entstandene Quoten, abzugsfähige Quoten und das Ergebnis der Abrechnung.
curl "https://api.frihet.io/v1/quarterly?quarter=2026-Q1" \
-H "X-API-Key: fri_tu-clave-aqui"
Antwort (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
}
}
Die Endpunkte /context, /monthly und /quarterly sind als idealer Einstiegspunkt für KI-Agenten konzipiert. Sie stellen die notwendigen Informationen in einem einzigen Aufruf bereit, ohne dass mehrere individuelle Endpunkte abgefragt werden müssen.
Finanz-Dashboard (/summary)
GET /v1/summary
Gibt eine Finanzübersicht des Geschäfts zurück: Einnahmen, Ausgaben, Gewinn und Zähler.
Abfrageparameter:
| Parameter | Typ | Beschreibung |
|---|---|---|
from | string | Startdatum (ISO 8601) |
to | string | Enddatum (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"
Antwort (200):
{
"period": { "from": "2026-01-01", "to": "2026-03-31" },
"revenue": {
"fakturiert": 15000.00,
"bezahlt": 12000.00,
"ausstehend": 2000.00,
"überfällig": 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 }
}
Fehlercodes
Die API verwendet standardmäßige HTTP-Codes. Fehlerantworten enthalten ein JSON-Objekt mit den Feldern error und optional message und details.
| Code | Bedeutung | Beschreibung |
|---|---|---|
400 | Bad Request | Erforderliches Feld fehlt, falsches Format oder nicht zulässiges Feld |
401 | Unauthorized | Der API-Schlüssel wurde nicht bereitgestellt, ist ungültig, hat ein falsches Format oder ist abgelaufen |
403 | Forbidden | Der API-Schlüssel hat keine Berechtigungen für den Zugriff auf diese Ressource |
404 | Not Found | Die angeforderte Ressource existiert nicht |
405 | Method Not Allowed | Die HTTP-Methode wird für diesen Endpunkt nicht unterstützt |
413 | Payload Too Large | Der Anfragetext überschreitet 1 MB |
422 | Unprocessable Entity | Gültige Daten, aber der Server kann sie nicht verarbeiten (z.B. Steuerprofil nicht konfiguriert) |
429 | Too Many Requests | Das Limit von 100 Anfragen pro Minute wurde überschritten |
500 | Internal Server Error | Interner Serverfehler |
Struktur der Fehlerantworten
Validierungsfehler (400):
{
"error": "Validierungsfehler",
"details": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": ["clientName"],
"message": "Erforderlich"
}
]
}
Validierungsfehler verwenden das Zod-Format. Das Feld path gibt an, welches Feld den Fehler enthält, und message beschreibt das Problem.
Ungültiger oder abgelaufener API-Schlüssel (401):
{
"error": "Ungültiger oder abgelaufener API-Schlüssel"
}
Ungültiges Schlüsselformat (401):
{
"error": "Ungültiges API-Schlüsselformat"
}
Ressource nicht gefunden (404):
{
"error": "Ressource nicht gefunden"
}
Ungültiger Status im Filter (400):
{
"error": "Ungültiger Statusfilter",
"message": "Gültige Werte: draft, sent, paid, overdue, cancelled"
}
Ratenlimit überschritten (429):
{
"error": "Ratenlimit überschritten",
"message": "Maximal 100 Anfragen pro Minute",
"retryAfter": 60
}
Interner Fehler (500):
{
"error": "Interner Serverfehler"
}
Paginierung
Die Auflistungs-Endpunkte geben paginierte Ergebnisse mit der folgenden Struktur zurück:
{
"data": [],
"total": 142,
"limit": 50,
"offset": 0
}
total: Gesamtzahl der Datensätze, die den angewendeten Filtern entsprechenlimit: Anzahl der auf dieser Seite zurückgegebenen Datensätze (maximal 100)offset: Anzahl der übersprungenen Datensätze (maximal 10.000)
Um die nächste Seite abzurufen:
curl "https://api.frihet.io/v1/invoices?limit=50&offset=50" \
-H "X-API-Key: fri_tu-clave-aqui"
Die Ergebnisse werden nach dem natürlichen Datum der Ressource in absteigender Reihenfolge (neueste zuerst) sortiert:
- Rechnungen und Angebote:
issueDate - Ausgaben:
date - Kunden und Produkte:
createdAt
Strenge Validierung
Die API verwendet strenge Validierung (Zod strict mode). Anfragen mit unbekannten Feldern werden mit einem 400-Fehler abgelehnt:
{
"error": "Validierungsfehler",
"details": [
{
"code": "unrecognized_keys",
"keys": ["campoInventado"],
"path": [],
"message": "Unrecognized key(s) in object: 'campoInventado'"
}
]
}
Dies verhindert stille Fehler durch Tippfehler in den Feldnamen.
CORS
Die API unterstützt CORS für Anfragen vom Browser. Die zulässigen Ursprünge sind:
https://app.frihet.iohttps://frihet.iohttps://www.frihet.io
Für Server-zu-Server-Integrationen ist CORS nicht relevant. Wenn Sie vom Browser aus von einer anderen Domäne auf die API zugreifen müssen, verwenden Sie einen Proxy in Ihrem Backend.
Sicherheits-Header
Alle Antworten enthalten Sicherheits-Header:
| Header | Wert |
|---|---|
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
X-XSS-Protection | 1; mode=block |
X-Request-Id | Eindeutige Anfrage-ID (nützlich für die Fehlerbehebung) |
OAuth für MCP
Der Endpunkt POST /api/oauth/api-key ermöglicht die automatische Bereitstellung eines API-Schlüssels über den MCP OAuth-Fluss. Der MCP-Server verwendet diesen Endpunkt intern – Sie müssen ihn nicht direkt aufrufen.
Der Ablauf:
- Der Benutzer authentifiziert sich über Firebase Auth.
- Der MCP-Client sendet das Firebase-Token an
/api/oauth/api-key. - Der Server gibt einen neuen Schlüssel
fri_xxx...zurück, der als "MCP OAuth" gekennzeichnet ist. - Der Schlüssel läuft nach 365 Tagen ab.
Limit: 5 aktive Schlüssel pro Benutzer. Wenn das Limit überschritten wird, antwortet der Endpunkt mit einem 429.
Best Practices
- Speichern Sie den API-Schlüssel sicher. Fügen Sie ihn niemals in Frontend-Code, öffentlichen Repositorys oder Logs ein.
- Verwalten Sie das Ratenlimit. Implementieren Sie ein exponentielles Backoff, wenn Sie einen
429erhalten. - Verwenden Sie Paginierung. Fordern Sie nicht alle Datensätze auf einmal an; iterieren Sie mit
limitundoffset. - Überprüfen Sie die Antwortcodes. Gehen Sie nicht davon aus, dass alle Anfragen erfolgreich sein werden.
- Rotieren Sie Schlüssel regelmäßig. Erstellen Sie einen neuen Schlüssel, aktualisieren Sie Ihre Integration und widerrufen Sie den alten.
- Verwenden Sie immer HTTPS. Alle Anfragen an die API müssen über HTTPS erfolgen.
- Verwenden Sie Filter. Die Parameter
status,fromundtoreduzieren die Menge der übertragenen Daten. - Nutzen Sie PATCH. Senden Sie für partielle Updates nur die geänderten Felder.