REST API
The Frihet REST API lets you access and manipulate the resources in your account programmatically. All communication is over HTTPS and responses are JSON.
Base URL
https://api.frihet.io/v1
All endpoints in this reference are relative to this base URL.
Authentication
Every request must include an API key in the X-API-Key header. Keys are created from Settings > API in the Frihet dashboard.
Keys use the prefix fri_ followed by 32 bytes of random data encoded in base64url. Example: fri_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345678.
curl https://api.frihet.io/v1/clients \
-H "X-API-Key: fri_your-key-here"
Alternatively, you can send the key as a Bearer token in the Authorization header:
curl https://api.frihet.io/v1/clients \
-H "Authorization: Bearer fri_your-key-here"
Key security
- The plaintext key is shown only once, at creation time. It cannot be recovered afterward.
- The server stores a SHA-256 hash of the key. Even in the event of a data breach, the original key is not recoverable.
- You can create keys with a configurable expiration date.
- If you suspect a key has been compromised, revoke it immediately from the dashboard.
Rate limiting
Each API key has a limit of 100 requests per minute. If exceeded, the API responds with a 429:
{
"error": "Rate limit exceeded",
"message": "Maximum 100 requests per minute",
"retryAfter": 60
}
Recommendations for handling rate limits:
- If you receive a
429, wait the number of seconds indicated inretryAfterbefore retrying. - Spread requests over time rather than sending bursts.
Request size
The body of POST and PUT requests cannot exceed 1 MB. Larger requests receive a 413.
Resources
Invoices (/invoices)
List invoices
GET /v1/invoices
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Results per page (maximum 100) |
offset | integer | 0 | Number of results to skip |
Example:
curl "https://api.frihet.io/v1/invoices?limit=10&offset=0" \
-H "X-API-Key: fri_your-key-here"
Response (200):
{
"data": [
{
"id": "abc123",
"clientName": "Acme SL",
"items": [
{ "description": "Consulting", "quantity": 10, "unitPrice": 75 }
],
"status": "paid",
"total": 750,
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-20T14:00:00.000Z"
}
],
"total": 42,
"limit": 10,
"offset": 0
}
Get invoice
GET /v1/invoices/:id
curl https://api.frihet.io/v1/invoices/abc123 \
-H "X-API-Key: fri_your-key-here"
Response (200):
{
"id": "abc123",
"clientName": "Acme SL",
"items": [
{ "description": "Consulting", "quantity": 10, "unitPrice": 75 }
],
"status": "paid",
"total": 750,
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-20T14:00:00.000Z"
}
Create invoice
POST /v1/invoices
Required fields:
| Field | Type | Description |
|---|---|---|
clientName | string | Client name |
items | array | Invoice line items |
Optional fields:
| Field | Type | Description |
|---|---|---|
status | string | Status (draft, sent, paid) |
dueDate | string | Due date (ISO 8601) |
notes | string | Internal notes |
taxRate | number | Applicable tax rate |
curl -X POST https://api.frihet.io/v1/invoices \
-H "X-API-Key: fri_your-key-here" \
-H "Content-Type: application/json" \
-d '{
"clientName": "Acme SL",
"items": [
{ "description": "Web development", "quantity": 40, "unitPrice": 60 }
],
"dueDate": "2026-03-01T00:00:00.000Z",
"notes": "Q1 2026 project"
}'
Response (201):
{
"id": "def456",
"clientName": "Acme SL",
"items": [
{ "description": "Web development", "quantity": 40, "unitPrice": 60 }
],
"dueDate": "2026-03-01T00:00:00.000Z",
"notes": "Q1 2026 project",
"createdAt": "2026-02-12T09:00:00.000Z",
"updatedAt": "2026-02-12T09:00:00.000Z"
}
Update invoice
PUT /v1/invoices/:id
curl -X PUT https://api.frihet.io/v1/invoices/def456 \
-H "X-API-Key: fri_your-key-here" \
-H "Content-Type: application/json" \
-d '{
"clientName": "Acme SL",
"items": [
{ "description": "Web development", "quantity": 40, "unitPrice": 65 }
],
"status": "sent"
}'
Response (200): Updated invoice object.
Delete invoice
DELETE /v1/invoices/:id
curl -X DELETE https://api.frihet.io/v1/invoices/def456 \
-H "X-API-Key: fri_your-key-here"
Response: 204 No Content
Expenses (/expenses)
List expenses
GET /v1/expenses
Accepts the same pagination parameters as invoices (limit, offset).
curl "https://api.frihet.io/v1/expenses?limit=20" \
-H "X-API-Key: fri_your-key-here"
Get expense
GET /v1/expenses/:id
Create expense
POST /v1/expenses
Required fields:
| Field | Type | Description |
|---|---|---|
description | string | Expense description |
amount | number | Expense amount |
Optional fields:
| Field | Type | Description |
|---|---|---|
category | string | Expense category |
date | string | Expense date (ISO 8601) |
vendor | string | Vendor name |
taxDeductible | boolean | Whether the expense is tax-deductible |
curl -X POST https://api.frihet.io/v1/expenses \
-H "X-API-Key: fri_your-key-here" \
-H "Content-Type: application/json" \
-d '{
"description": "Adobe Creative Cloud license",
"amount": 59.99,
"category": "software",
"date": "2026-02-01T00:00:00.000Z",
"taxDeductible": true
}'
Response (201):
{
"id": "exp789",
"description": "Adobe Creative Cloud license",
"amount": 59.99,
"category": "software",
"date": "2026-02-01T00:00:00.000Z",
"taxDeductible": true,
"createdAt": "2026-02-12T09:15:00.000Z",
"updatedAt": "2026-02-12T09:15:00.000Z"
}
Update expense
PUT /v1/expenses/:id
Delete expense
DELETE /v1/expenses/:id
Clients (/clients)
List clients
GET /v1/clients
Get client
GET /v1/clients/:id
Update client
PUT /v1/clients/:id
Required fields:
| Field | Type | Description |
|---|---|---|
name | string | Client name |
Optional fields:
| Field | Type | Description |
|---|---|---|
email | string | Contact email |
phone | string | Phone number |
taxId | string | Tax ID |
address | object | Address (street, city, postalCode, country) |
curl -X PUT https://api.frihet.io/v1/clients/cli001 \
-H "X-API-Key: fri_your-key-here" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme SL",
"email": "admin@acme.com",
"taxId": "B12345678"
}'
Delete client
DELETE /v1/clients/:id
Products (/products)
List products
GET /v1/products
Get product
GET /v1/products/:id
Update product
PUT /v1/products/:id
Required fields:
| Field | Type | Description |
|---|---|---|
name | string | Product or service name |
unitPrice | number | Unit price |
Optional fields:
| Field | Type | Description |
|---|---|---|
description | string | Description |
unit | string | Unit of measure (hour, unit, month) |
taxRate | number | Applicable tax rate |
sku | string | Internal reference |
curl -X PUT https://api.frihet.io/v1/products/prod001 \
-H "X-API-Key: fri_your-key-here" \
-H "Content-Type: application/json" \
-d '{
"name": "Consulting hour",
"unitPrice": 75,
"unit": "hour",
"taxRate": 21
}'
Delete product
DELETE /v1/products/:id
Quotes (/quotes)
List quotes
GET /v1/quotes
Get quote
GET /v1/quotes/:id
Update quote
PUT /v1/quotes/:id
Required fields:
| Field | Type | Description |
|---|---|---|
clientName | string | Client name |
items | array | Quote line items |
Optional fields:
| Field | Type | Description |
|---|---|---|
validUntil | string | Expiration date (ISO 8601) |
notes | string | Notes or terms |
status | string | Status (draft, sent, accepted, rejected) |
Delete quote
DELETE /v1/quotes/:id
Error codes
The API uses standard HTTP status codes. Error responses include a JSON object with error and optionally message fields.
| Code | Meaning | Description |
|---|---|---|
400 | Bad Request | A required field is missing or data format is incorrect |
401 | Unauthorized | API key not provided, invalid, or expired |
403 | Forbidden | API key does not have permission to access this resource |
404 | Not Found | The requested resource does not exist |
405 | Method Not Allowed | HTTP method is not supported for this endpoint |
413 | Payload Too Large | Request body exceeds 1 MB |
429 | Too Many Requests | Rate limit of 100 requests per minute exceeded |
500 | Internal Server Error | Server-side error |
Error response example:
{
"error": "Invalid or expired API key"
}
Error response with additional detail:
{
"error": "Bad Request",
"message": "Missing required field: clientName"
}
Pagination
List endpoints return paginated results with this structure:
{
"data": [],
"total": 142,
"limit": 50,
"offset": 0
}
total: total number of available recordslimit: number of records returned in this page (maximum 100)offset: number of records skipped
To get the next page:
curl "https://api.frihet.io/v1/invoices?limit=50&offset=50" \
-H "X-API-Key: fri_your-key-here"
CORS
The API supports CORS for browser requests. Allowed origins are:
https://app.frihet.iohttps://frihet.iohttps://www.frihet.io
For server-to-server integrations, CORS is not relevant. If you need to access the API from a different domain in the browser, use a proxy in your backend.
Best practices
- Store your API key securely. Never include it in frontend code, public repositories, or logs.
- Handle rate limiting. Implement exponential backoff when you receive a
429. - Use pagination. Don't request all records at once; iterate with
limitandoffset. - Check response codes. Don't assume every request will succeed.
- Rotate keys periodically. Create a new key, update your integration, then revoke the old one.
- Always use HTTPS. All API requests must go over HTTPS.