Skip to main content

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 in retryAfter before 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:

ParameterTypeDefaultDescription
limitinteger50Results per page (maximum 100)
offsetinteger0Number 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:

FieldTypeDescription
clientNamestringClient name
itemsarrayInvoice line items

Optional fields:

FieldTypeDescription
statusstringStatus (draft, sent, paid)
dueDatestringDue date (ISO 8601)
notesstringInternal notes
taxRatenumberApplicable 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:

FieldTypeDescription
descriptionstringExpense description
amountnumberExpense amount

Optional fields:

FieldTypeDescription
categorystringExpense category
datestringExpense date (ISO 8601)
vendorstringVendor name
taxDeductiblebooleanWhether 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:

FieldTypeDescription
namestringClient name

Optional fields:

FieldTypeDescription
emailstringContact email
phonestringPhone number
taxIdstringTax ID
addressobjectAddress (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:

FieldTypeDescription
namestringProduct or service name
unitPricenumberUnit price

Optional fields:

FieldTypeDescription
descriptionstringDescription
unitstringUnit of measure (hour, unit, month)
taxRatenumberApplicable tax rate
skustringInternal 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:

FieldTypeDescription
clientNamestringClient name
itemsarrayQuote line items

Optional fields:

FieldTypeDescription
validUntilstringExpiration date (ISO 8601)
notesstringNotes or terms
statusstringStatus (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.

CodeMeaningDescription
400Bad RequestA required field is missing or data format is incorrect
401UnauthorizedAPI key not provided, invalid, or expired
403ForbiddenAPI key does not have permission to access this resource
404Not FoundThe requested resource does not exist
405Method Not AllowedHTTP method is not supported for this endpoint
413Payload Too LargeRequest body exceeds 1 MB
429Too Many RequestsRate limit of 100 requests per minute exceeded
500Internal Server ErrorServer-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 records
  • limit: 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.io
  • https://frihet.io
  • https://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

  1. Store your API key securely. Never include it in frontend code, public repositories, or logs.
  2. Handle rate limiting. Implement exponential backoff when you receive a 429.
  3. Use pagination. Don't request all records at once; iterate with limit and offset.
  4. Check response codes. Don't assume every request will succeed.
  5. Rotate keys periodically. Create a new key, update your integration, then revoke the old one.
  6. Always use HTTPS. All API requests must go over HTTPS.