Zum Hauptinhalt springen

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.

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

  1. Gehen Sie zu Einstellungen > Entwickler > API-Schlüssel
  2. Klicken Sie auf Schlüssel erstellen
  3. Weisen Sie einen beschreibenden Namen zu (z.B. buchhaltungs-integration)
  4. Optional legen Sie ein Ablaufdatum in Tagen fest. Wenn Sie es leer lassen, läuft der Schlüssel nicht ab.
  5. 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:

  1. Erstellen Sie einen neuen Schlüssel mit demselben Geltungsbereich.
  2. Aktualisieren Sie Ihre Integration, um den neuen Schlüssel zu verwenden.
  3. Überprüfen Sie, ob die Anfragen korrekt funktionieren.
  4. 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:

HeaderBeschreibungBeispiel
X-RateLimit-LimitErlaubte Anfragen pro Minute100
X-RateLimit-RemainingVerbleibende Anfragen im aktuellen Fenster87
X-RateLimit-ResetUnix-Timestamp (Sekunden), zu dem das Fenster zurückgesetzt wird1709312400

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 429 erhalten, warten Sie bis zum in X-RateLimit-Reset angegebenen 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.

PATCH vs PUT

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:

ParameterTypStandardBeschreibung
limitinteger50Ergebnisse pro Seite (maximal 100)
offsetinteger0Anzahl der zu überspringenden Ergebnisse (maximal 10.000)
statusstring--Nach Status filtern: draft, sent, paid, overdue, cancelled
fromstring--Startdatum (ISO 8601: JJJJ-MM-TT). Filtert nach issueDate
tostring--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:

FeldTypBeschreibung
clientNamestringName des Kunden (max. 10.000 Zeichen)
itemsarrayListe der Rechnungspositionen. Jede Position: { description, quantity, unitPrice }

Optionale Felder:

FeldTypBeschreibung
statusstringdraft (Standard), sent, paid, overdue, cancelled
issueDatestringAusstellungsdatum (ISO 8601). Standard: heute
dueDatestringFälligkeitsdatum (ISO 8601)
notesstringInterne Notizen (max. 10.000 Zeichen)
taxRatenumberSteuerprozentsatz (0-100). Z.B.: 21 für 21% Mehrwertsteuer

Struktur jeder Position (items[]):

FeldTypErforderlichBeschreibung
descriptionstringJaBeschreibung des Konzepts (max. 10.000 Zeichen)
quantitynumberJaMenge
unitPricenumberJaStü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.

vorsicht

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:

FeldTypErforderlichBeschreibung
recipientEmailstringJaE-Mail des Empfängers (max. 255 Zeichen)
recipientNamestringNeinName des Empfängers (max. 200 Zeichen)
customMessagestringNeinPersonalisierte Nachricht im E-Mail-Text (max. 5.000 Zeichen)
localestringNeinSprache 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
FeldTypErforderlichBeschreibung
paidDatestringNeinZahlungsdatum (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:

ParameterTypStandardBeschreibung
limitinteger50Ergebnisse pro Seite (maximal 100)
offsetinteger0Anzahl der zu überspringenden Ergebnisse
fromstring--Startdatum (ISO 8601). Filtert nach date
tostring--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:

FeldTypBeschreibung
descriptionstringBeschreibung der Ausgabe (max. 10.000 Zeichen)
amountnumberBetrag der Ausgabe

Optionale Felder:

FeldTypBeschreibung
categorystringKategorie der Ausgabe (max. 10.000 Zeichen)
datestringDatum der Ausgabe (ISO 8601). Standard: heute
vendorstringLieferant (max. 10.000 Zeichen)
taxDeductiblebooleanOb 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:

FeldTypBeschreibung
namestringName des Kunden (max. 10.000 Zeichen)

Optionale Felder:

FeldTypBeschreibung
emailstringKontakt-E-Mail
phonestringTelefon
taxIdstringSteuer-ID
addressobjectAdresse (siehe Struktur unten)

Struktur von address:

FeldTypBeschreibung
streetstringStraße und Hausnummer
citystringStadt
statestringProvinz oder Bundesland
postalCodestringPostleitzahl
countrystringLand

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:

FeldTypBeschreibung
namestringName des Ansprechpartners

Optionale Felder:

FeldTypBeschreibung
emailstringE-Mail des Kontakts
phonestringTelefon
rolestringPosition oder Rolle (z.B. "Finanzdirektor")
isPrimarybooleanOb 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.

Unveränderlich

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:

FeldTypBeschreibung
typestringArt der Aktivität: call, email, meeting oder task
titlestringBeschreibender Titel der Aktivität

Optionale Felder:

FeldTypBeschreibung
descriptionstringDetaillierte Beschreibung
metadataobjectZusätzliche Daten im freien Format
Aktivitätstypen

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:

FeldTypBeschreibung
contentstringInhalt 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:

FeldTypBeschreibung
namestringName des Produkts oder Dienstleistung (max. 10.000 Zeichen)
unitPricenumberStückpreis

Optionale Felder:

FeldTypBeschreibung
descriptionstringBeschreibung (max. 10.000 Zeichen)
taxRatenumberSteuerprozentsatz (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:

ParameterTypStandardBeschreibung
limitinteger50Ergebnisse pro Seite (maximal 100)
offsetinteger0Anzahl der zu überspringenden Ergebnisse
statusstring--Nach Status filtern: draft, sent, accepted, rejected, expired
fromstring--Startdatum (ISO 8601). Filtert nach issueDate
tostring--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:

FeldTypBeschreibung
clientNamestringName des Kunden (max. 10.000 Zeichen)
itemsarrayPositionen des Angebots. Jede Position: { description, quantity, unitPrice }

Optionale Felder:

FeldTypBeschreibung
validUntilstringGültigkeitsdatum (ISO 8601)
notesstringNotizen oder Bedingungen (max. 10.000 Zeichen)
statusstringdraft (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:

KonzeptLimit
Elemente pro Batch50 maximal
Anfragengröße1 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:

SzenarioErgebnis
Erste Anfrage mit dem SchlüsselDie 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 24hWird 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
}
}
tipp

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:

ParameterTypBeschreibung
fromstringStartdatum (ISO 8601)
tostringEnddatum (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.

CodeBedeutungBeschreibung
400Bad RequestErforderliches Feld fehlt, falsches Format oder nicht zulässiges Feld
401UnauthorizedDer API-Schlüssel wurde nicht bereitgestellt, ist ungültig, hat ein falsches Format oder ist abgelaufen
403ForbiddenDer API-Schlüssel hat keine Berechtigungen für den Zugriff auf diese Ressource
404Not FoundDie angeforderte Ressource existiert nicht
405Method Not AllowedDie HTTP-Methode wird für diesen Endpunkt nicht unterstützt
413Payload Too LargeDer Anfragetext überschreitet 1 MB
422Unprocessable EntityGültige Daten, aber der Server kann sie nicht verarbeiten (z.B. Steuerprofil nicht konfiguriert)
429Too Many RequestsDas Limit von 100 Anfragen pro Minute wurde überschritten
500Internal Server ErrorInterner 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 entsprechen
  • limit: 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.io
  • https://frihet.io
  • https://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:

HeaderWert
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
X-XSS-Protection1; mode=block
X-Request-IdEindeutige 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:

  1. Der Benutzer authentifiziert sich über Firebase Auth.
  2. Der MCP-Client sendet das Firebase-Token an /api/oauth/api-key.
  3. Der Server gibt einen neuen Schlüssel fri_xxx... zurück, der als "MCP OAuth" gekennzeichnet ist.
  4. 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

  1. Speichern Sie den API-Schlüssel sicher. Fügen Sie ihn niemals in Frontend-Code, öffentlichen Repositorys oder Logs ein.
  2. Verwalten Sie das Ratenlimit. Implementieren Sie ein exponentielles Backoff, wenn Sie einen 429 erhalten.
  3. Verwenden Sie Paginierung. Fordern Sie nicht alle Datensätze auf einmal an; iterieren Sie mit limit und offset.
  4. Überprüfen Sie die Antwortcodes. Gehen Sie nicht davon aus, dass alle Anfragen erfolgreich sein werden.
  5. Rotieren Sie Schlüssel regelmäßig. Erstellen Sie einen neuen Schlüssel, aktualisieren Sie Ihre Integration und widerrufen Sie den alten.
  6. Verwenden Sie immer HTTPS. Alle Anfragen an die API müssen über HTTPS erfolgen.
  7. Verwenden Sie Filter. Die Parameter status, from und to reduzieren die Menge der übertragenen Daten.
  8. Nutzen Sie PATCH. Senden Sie für partielle Updates nur die geänderten Felder.