API REST
A API REST da Frihet permite acessar e manipular os recursos da sua conta de forma programática. Toda a comunicação é realizada sobre HTTPS e as respostas são JSON.
URL base
https://api.frihet.io/v1
Todos os endpoints descritos nesta página são relativos a esta URL base.
Descoberta: Uma requisição GET à raiz (https://api.frihet.io/) retorna os links principais sem a necessidade de autenticação:
{
"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"
}
A especificação OpenAPI 3.1 está disponível em https://api.frihet.io/openapi.yaml.
Se você usa TypeScript ou JavaScript, o SDK oficial simplifica a integração:
npm install @frihet/sdk
import Frihet from '@frihet/sdk';
const frihet = new Frihet({ apiKey: 'fri_...' });
const invoices = await frihet.invoices.list({ status: 'overdue' });
Repositório: github.com/Frihet-io/frihet-sdk
Autenticação
Cada requisição deve incluir uma chave de API no cabeçalho X-API-Key. As chaves são criadas em Configurações > Desenvolvedores > Chaves de API no painel do Frihet.
As chaves têm o prefixo fri_ seguido de 32 bytes aleatórios codificados em base64url. Exemplo: fri_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345678.
curl https://api.frihet.io/v1/clients \
-H "X-API-Key: fri_tu-clave-aqui"
Alternativamente, você pode enviar a chave como Bearer token no cabeçalho Authorization:
curl https://api.frihet.io/v1/clients \
-H "Authorization: Bearer fri_tu-clave-aqui"
Segurança das chaves
- A chave em texto simples é exibida apenas uma vez, no momento da criação. Não pode ser recuperada depois.
- O servidor armazena um hash SHA-256 da chave. Mesmo em caso de violação de dados, a chave original não é recuperável.
- Você pode criar chaves com data de expiração configurável.
- Se você suspeita que uma chave foi comprometida, revogue-a imediatamente no painel.
Ciclo de vida das chaves
Criar uma chave:
- Vá para Configurações > Desenvolvedores > Chaves de API
- Clique em Criar chave
- Atribua um nome descritivo (por exemplo,
integracao-contabilidade) - Opcionalmente, defina uma data de expiração em dias. Se você deixar vazio, a chave não expira
- Copie a chave imediatamente -- você não poderá vê-la novamente
Expiração:
As chaves com data de expiração param de funcionar automaticamente quando o prazo expira. As requisições com uma chave expirada recebem um 401 Unauthorized.
Revogação:
Você pode revogar uma chave a qualquer momento em Configurações > Desenvolvedores > Chaves de API. A revogação é imediata e irreversível: as requisições em andamento com essa chave falharão a partir daquele instante.
Rotação de chaves:
Para rotacionar uma chave sem interromper o serviço:
- Crie uma nova chave com o mesmo escopo
- Atualize sua integração para usar a nova chave
- Verifique se as requisições funcionam corretamente
- Revogue a chave anterior
Monitoramento:
Cada chave exibe a data de último uso no painel de Configurações. Revise periodicamente as chaves inativas e revogue as que não são mais utilizadas.
Rate limiting
Cada chave de API tem um limite de 100 requisições por minuto. Se excedido, a API responde com um código 429:
{
"error": "Rate limit exceeded",
"message": "Maximum 100 requests per minute",
"retryAfter": 60
}
Cabeçalhos de rate limiting
Todas as respostas da API incluem cabeçalhos para que você possa gerenciar o limite de forma proativa:
| Cabeçalho | Descrição | Exemplo |
|---|---|---|
X-RateLimit-Limit | Requisições permitidas por minuto | 100 |
X-RateLimit-Remaining | Requisições restantes na janela atual | 87 |
X-RateLimit-Reset | Timestamp Unix (segundos) em que a janela é reiniciada | 1709312400 |
Exemplo de resposta com cabeçalhos de rate limiting:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1709312400
Content-Type: application/json
Gerenciamento do código 429:
Quando você recebe um 429, use os cabeçalhos para calcular quanto esperar antes de tentar novamente:
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;
}
Recomendações:
- Se você receber um
429, espere até o timestamp indicado emX-RateLimit-Resetantes de tentar novamente. - Monitore
X-RateLimit-Remainingpara desacelerar as requisições antes de atingir o limite. - Distribua as requisições ao longo do tempo em vez de enviar rajadas.
Tamanho da requisição
O corpo das requisições POST, PUT e PATCH não pode exceder 1 MB. Requisições maiores recebem um código 413.
Recursos
A API expõe 7 recursos principais: invoices, expenses, clients, products, quotes, vendors e webhooks. Todos suportam operações CRUD completas (GET, POST, PUT/PATCH, DELETE). Além disso, os clientes têm subcoleções CRM: contacts, activities e notes.
Tanto PUT quanto PATCH aceitam atualizações parciais. Você não precisa enviar o recurso completo -- apenas os campos que deseja modificar.
Faturas (/invoices)
Listar faturas
GET /v1/invoices
Parâmetros de consulta:
| Parâmetro | Tipo | Por padrão | Descrição |
|---|---|---|---|
limit | integer | 50 | Resultados por página (máximo 100) |
offset | integer | 0 | Número de resultados a pular (máximo 10.000) |
status | string | -- | Filtrar por status: rascunho, enviada, paga, vencida, cancelada |
from | string | -- | Data de início (ISO 8601: AAAA-MM-DD). Filtra por issueDate |
to | string | -- | Data de fim (ISO 8601: AAAA-MM-DD). Filtra por issueDate |
Exemplo:
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"
Resposta (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
}
Obter fatura
GET /v1/invoices/:id
curl https://api.frihet.io/v1/invoices/abc123 \
-H "X-API-Key: fri_tu-clave-aqui"
Resposta (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"
}
Criar fatura
POST /v1/invoices
Campos requeridos:
| Campo | Tipo | Descrição |
|---|---|---|
clientName | string | Nome do cliente (máx. 10.000 caracteres) |
items | array | Lista de linhas da fatura. Cada linha: { description, quantity, unitPrice } |
Campos opcionais:
| Campo | Tipo | Descrição |
|---|---|---|
status | string | rascunho (padrão), enviada, paga, vencida, cancelada |
issueDate | string | Data de emissão (ISO 8601). Por padrão: hoje |
dueDate | string | Data de vencimento (ISO 8601) |
notes | string | Notas internas (máx. 10.000 caracteres) |
taxRate | number | Porcentagem de imposto (0-100). Ex: 21 para IVA 21% |
Estrutura de cada linha (items[]):
| Campo | Tipo | Requerido | Descrição |
|---|---|---|---|
description | string | Sim | Descrição do conceito (máx. 10.000 caracteres) |
quantity | number | Sim | Quantidade |
unitPrice | number | Sim | Preço unitário |
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"
}'
Resposta (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"
}
Atualizar fatura (PUT ou PATCH)
PUT /v1/invoices/:id
PATCH /v1/invoices/:id
Você só precisa enviar os campos que deseja modificar. Os campos não incluídos permanecem inalterados.
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"
}'
Resposta (200): Objeto de fatura atualizado.
Se você enviar items, deve enviar o array completo -- não são permitidas atualizações parciais de linhas individuais.
Excluir fatura
DELETE /v1/invoices/:id
curl -X DELETE https://api.frihet.io/v1/invoices/def456 \
-H "X-API-Key: fri_tu-clave-aqui"
Resposta: 204 No Content
Baixar fatura em PDF
GET /v1/invoices/:id/pdf
Retorna o PDF da fatura como application/pdf com o cabeçalho Content-Disposition: attachment.
curl -o factura.pdf https://api.frihet.io/v1/invoices/abc123/pdf \
-H "X-API-Key: fri_tu-clave-aqui"
Enviar fatura por e-mail
POST /v1/invoices/:id/send
Envia a fatura ao destinatário especificado através do Resend. Se a fatura estiver no status rascunho, é atualizada automaticamente para enviada.
Campos:
| Campo | Tipo | Requerido | Descrição |
|---|---|---|---|
recipientEmail | string | Sim | E-mail do destinatário (máx. 255 caracteres) |
recipientName | string | Não | Nome do destinatário (máx. 200 caracteres) |
customMessage | string | Não | Mensagem personalizada no corpo do e-mail (máx. 5.000 caracteres) |
locale | string | Não | Idioma do e-mail: es (padrão) 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"
}'
Resposta (200):
{ "success": true, "messageId": "re_abc123..." }
Marcar fatura como paga
POST /v1/invoices/:id/paid
| Campo | Tipo | Requerido | Descrição |
|---|---|---|---|
paidDate | string | Não | Data de pagamento (ISO 8601). Por padrão: hoje |
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" }'
Resposta (200):
{ "success": true, "status": "paid", "paidAt": "2026-03-15" }
Despesas (/expenses)
Listar despesas
GET /v1/expenses
Parâmetros de consulta:
| Parâmetro | Tipo | Por padrão | Descrição |
|---|---|---|---|
limit | integer | 50 | Resultados por página (máximo 100) |
offset | integer | 0 | Número de resultados a pular |
from | string | -- | Data de início (ISO 8601). Filtra por date |
to | string | -- | Data de fim (ISO 8601). Filtra por date |
curl "https://api.frihet.io/v1/expenses?limit=20&from=2026-01-01" \
-H "X-API-Key: fri_tu-clave-aqui"
Resposta (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
}
Obter despesa
GET /v1/expenses/:id
Criar despesa
POST /v1/expenses
Campos requeridos:
| Campo | Tipo | Descrição |
|---|---|---|
description | string | Descrição da despesa (máx. 10.000 caracteres) |
amount | number | Valor da despesa |
Campos opcionais:
| Campo | Tipo | Descrição |
|---|---|---|
category | string | Categoria da despesa (máx. 10.000 caracteres) |
date | string | Data da despesa (ISO 8601). Por padrão: hoje |
vendor | string | Fornecedor (máx. 10.000 caracteres) |
taxDeductible | boolean | Se a despesa é dedutível |
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
}'
Resposta (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"
}
Atualizar despesa (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 }'
Resposta (200): Objeto de despesa atualizado.
Excluir despesa
DELETE /v1/expenses/:id
Resposta: 204 No Content
Clientes (/clients)
Listar clientes
GET /v1/clients
Aceita limit, offset, from e to (filtra por createdAt).
curl "https://api.frihet.io/v1/clients?limit=50" \
-H "X-API-Key: fri_tu-clave-aqui"
Obter cliente
GET /v1/clients/:id
Criar cliente
POST /v1/clients
Campos requeridos:
| Campo | Tipo | Descrição |
|---|---|---|
name | string | Nome do cliente (máx. 10.000 caracteres) |
Campos opcionais:
| Campo | Tipo | Descrição |
|---|---|---|
email | string | E-mail de contato |
phone | string | Telefone |
taxId | string | NIF/CIF/VAT |
address | object | Endereço (ver estrutura abaixo) |
Estrutura de address:
| Campo | Tipo | Descrição |
|---|---|---|
street | string | Rua e número |
city | string | Cidade |
state | string | Província ou estado |
postalCode | string | Código postal |
country | string | País |
Todos os campos de address são opcionais.
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"
}
}'
Resposta (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"
}
Atualizar cliente (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" }'
Resposta (200): Objeto de cliente atualizado.
Excluir cliente
DELETE /v1/clients/:id
Resposta: 204 No Content
CRM: Pessoas de contato, Atividades e Notas
Os clientes têm três subcoleções para gerenciar relacionamentos de CRM: pessoas de contato, atividades e notas. Todos os endpoints requerem um clientId válido na URL.
Pessoas de contato (/v1/clients/:id/contacts)
Listar contatos
GET /v1/clients/:id/contacts
curl "https://api.frihet.io/v1/clients/cli001/contacts" \
-H "X-API-Key: fri_tu-clave-aqui"
Obter contato
GET /v1/clients/:id/contacts/:contactId
curl "https://api.frihet.io/v1/clients/cli001/contacts/con001" \
-H "X-API-Key: fri_tu-clave-aqui"
Criar contato
POST /v1/clients/:id/contacts
Campos requeridos:
| Campo | Tipo | Descrição |
|---|---|---|
name | string | Nome da pessoa de contato |
Campos opcionais:
| Campo | Tipo | Descrição |
|---|---|---|
email | string | E-mail do contato |
phone | string | Telefone |
role | string | Cargo ou função (ex: "Diretor financeiro") |
isPrimary | boolean | Se é o contato principal do cliente |
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
}'
Resposta (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"
}
Atualizar contato
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" }'
Resposta (200): Objeto de contato atualizado.
Excluir contato
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"
Resposta: 204 No Content
Atividades (/v1/clients/:id/activities)
O timeline de atividades registra as interações com um cliente. As atividades do sistema (como invoice_created, quote_sent, etc.) são geradas automaticamente. Você também pode criar atividades manuais.
As atividades são imutáveis. Não podem ser atualizadas nem excluídas uma vez criadas.
Listar atividades
GET /v1/clients/:id/activities
curl "https://api.frihet.io/v1/clients/cli001/activities" \
-H "X-API-Key: fri_tu-clave-aqui"
Obter atividade
GET /v1/clients/:id/activities/:activityId
curl "https://api.frihet.io/v1/clients/cli001/activities/act001" \
-H "X-API-Key: fri_tu-clave-aqui"
Criar atividade
POST /v1/clients/:id/activities
Campos requeridos:
| Campo | Tipo | Descrição |
|---|---|---|
type | string | Tipo de atividade: call, email, meeting ou task |
title | string | Título descritivo da atividade |
Campos opcionais:
| Campo | Tipo | Descrição |
|---|---|---|
description | string | Descrição detalhada |
metadata | object | Dados adicionais em formato livre |
Os tipos call, email, meeting e task são para atividades criadas manualmente. Os tipos de sistema como invoice_created, quote_sent ou expense_linked são gerados automaticamente e não podem ser criados via 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"
}
}'
Resposta (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"
}
Notas (/v1/clients/:id/notes)
Listar notas
GET /v1/clients/:id/notes
curl "https://api.frihet.io/v1/clients/cli001/notes" \
-H "X-API-Key: fri_tu-clave-aqui"
Obter nota
GET /v1/clients/:id/notes/:noteId
curl "https://api.frihet.io/v1/clients/cli001/notes/note001" \
-H "X-API-Key: fri_tu-clave-aqui"
Criar nota
POST /v1/clients/:id/notes
Campos requeridos:
| Campo | Tipo | Descrição |
|---|---|---|
content | string | Conteúdo da nota |
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."
}'
Resposta (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"
}
Atualizar nota
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."
}'
Resposta (200): Objeto de nota atualizado.
Excluir nota
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"
Resposta: 204 No Content
Produtos (/products)
Listar produtos
GET /v1/products
Aceita limit, offset, from e to (filtra por createdAt).
Obter produto
GET /v1/products/:id
Criar produto
POST /v1/products
Campos requeridos:
| Campo | Tipo | Descrição |
|---|---|---|
name | string | Nome do produto ou serviço (máx. 10.000 caracteres) |
unitPrice | number | Preço unitário |
Campos opcionais:
| Campo | Tipo | Descrição |
|---|---|---|
description | string | Descrição (máx. 10.000 caracteres) |
taxRate | number | Porcentagem de imposto (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
}'
Resposta (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"
}
Atualizar produto (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 }'
Resposta (200): Objeto de produto atualizado.
Excluir produto
DELETE /v1/products/:id
Resposta: 204 No Content
Orçamentos (/quotes)
Listar orçamentos
GET /v1/quotes
Parâmetros de consulta:
| Parâmetro | Tipo | Por padrão | Descrição |
|---|---|---|---|
limit | integer | 50 | Resultados por página (máximo 100) |
offset | integer | 0 | Número de resultados a pular |
status | string | -- | Filtrar por status: draft, sent, accepted, rejected, expired |
from | string | -- | Data de início (ISO 8601). Filtra por issueDate |
to | string | -- | Data de fim (ISO 8601). Filtra por issueDate |
curl "https://api.frihet.io/v1/quotes?status=sent" \
-H "X-API-Key: fri_tu-clave-aqui"
Obter orçamento
GET /v1/quotes/:id
Criar orçamento
POST /v1/quotes
Campos requeridos:
| Campo | Tipo | Descrição |
|---|---|---|
clientName | string | Nome do cliente (máx. 10.000 caracteres) |
items | array | Linhas do orçamento. Cada linha: { description, quantity, unitPrice } |
Campos opcionais:
| Campo | Tipo | Descrição |
|---|---|---|
validUntil | string | Data de validade (ISO 8601) |
notes | string | Notas ou condições (máx. 10.000 caracteres) |
status | string | draft (padrão), 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"
}'
Resposta (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"
}
Atualizar orçamento (PUT ou PATCH)
PUT /v1/quotes/:id
PATCH /v1/quotes/:id
Excluir orçamento
DELETE /v1/quotes/:id
Resposta: 204 No Content
Baixar orçamento em PDF
GET /v1/quotes/:id/pdf
Funciona da mesma forma que /invoices/:id/pdf. Retorna application/pdf.
curl -o presupuesto.pdf https://api.frihet.io/v1/quotes/quo001/pdf \
-H "X-API-Key: fri_tu-clave-aqui"
Enviar orçamento por e-mail
POST /v1/quotes/:id/send
Mesmos campos que /invoices/:id/send (recipientEmail, recipientName, customMessage, locale). Se o orçamento estiver no status rascunho, é atualizado automaticamente para enviada.
Operações em lote (/batch)
Todos os recursos principais suportam criação em lote. Envie um array de até 50 elementos em uma única requisição.
POST /v1/{resource}/batch
Recursos suportados: invoices, expenses, clients, products, quotes
Corpo da requisição:
{
"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 }] }
]
}'
Resposta (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 }
}
Se algum dos elementos falhar a validação, o restante é criado da mesma forma. Os erros individuais são retornados no array 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:
| Conceito | Limite |
|---|---|
| Elementos por lote | 50 máximo |
| Tamanho da requisição | 1 MB máximo |
Idempotência
As requisições POST aceitam um cabeçalho Idempotency-Key para evitar a criação duplicada de recursos em caso de novas tentativas de rede. Se a mesma chave for enviada dentro das próximas 24 horas, a API retorna a resposta original sem executar a operação novamente.
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 }]
}'
Comportamento:
| Cenário | Resultado |
|---|---|
| Primeira requisição com a chave | O recurso é criado normalmente |
| Requisição repetida com a mesma chave (dentro de 24h) | A resposta original é retornada, sem duplicar |
| Mesma chave após 24h | É tratada como uma nova requisição |
Cabeçalho de resposta:
Quando a API detecta uma chave repetida, inclui o cabeçalho X-Idempotent-Replayed: true para que o consumidor saiba que a resposta é uma réplica.
HTTP/1.1 201 Created
X-Idempotent-Replayed: true
Content-Type: application/json
Requisitos:
- A chave deve ter no máximo 64 caracteres
- Recomendamos usar UUID v4
- Aplica-se apenas a requisições
POST(criar recursos)
Busca
Os endpoints de listagem suportam busca de texto completo através do parâmetro q:
curl "https://api.frihet.io/v1/invoices?q=acme" \
-H "X-API-Key: fri_tu-clave-aqui"
A busca é aplicada sobre os campos de texto principais do recurso (nome de cliente, descrição, notas, etc.). Pode ser combinada com os filtros existentes (status, from, to).
Endpoints de inteligência
Estes endpoints fornecem dados agregados e contexto de negócio. São especialmente úteis para agentes de IA e dashboards externos.
Contexto de negócio (/context)
GET /v1/context
Retorna um resumo completo do negócio, projetado para alimentar agentes de IA com o contexto necessário para tomar decisões informadas. Inclui resumo financeiro, atividade recente, alertas e configuração fiscal.
curl https://api.frihet.io/v1/context \
-H "X-API-Key: fri_tu-clave-aqui"
Resposta (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" }
]
}
P&L mensal (/monthly)
GET /v1/monthly?month=YYYY-MM
Retorna a demonstração de resultados (P&L) de um mês específico: receitas faturadas, despesas por categoria, lucro líquido e comparação com o mês anterior.
curl "https://api.frihet.io/v1/monthly?month=2026-02" \
-H "X-API-Key: fri_tu-clave-aqui"
Resposta (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
}
}
Dados fiscais trimestrais (/quarterly)
GET /v1/quarterly?quarter=YYYY-Q1
Retorna os dados fiscais de um trimestre, preparados para a apresentação do Modelo 303 (IVA) e Modelo 130 (IRPF). Inclui bases tributáveis, valores devidos, valores dedutíveis e resultado da liquidação.
curl "https://api.frihet.io/v1/quarterly?quarter=2026-Q1" \
-H "X-API-Key: fri_tu-clave-aqui"
Resposta (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
}
}
Os endpoints /context, /monthly e /quarterly são projetados para serem o ponto de entrada ideal para agentes de IA. Eles fornecem as informações necessárias em uma única chamada, sem a necessidade de consultar múltiplos endpoints individuais.
Dashboard financeiro (/summary)
GET /v1/summary
Retorna um resumo financeiro do negócio: receitas, despesas, lucro e contadores.
Parâmetros de consulta:
| Parâmetro | Tipo | Descrição |
|---|---|---|
from | string | Data de início (ISO 8601) |
to | string | Data de fim (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"
Resposta (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 }
}
Códigos de erro
A API utiliza códigos HTTP padrão. As respostas de erro incluem um objeto JSON com os campos error e, opcionalmente, message e details.
| Código | Significado | Descrição |
|---|---|---|
400 | Requisição Inválida | Falta um campo requerido, formato incorreto ou campo não permitido |
401 | Não Autorizado | A chave de API não foi fornecida, é inválida, tem formato incorreto ou expirou |
403 | Proibido | A chave de API não tem permissões para acessar este recurso |
404 | Não Encontrado | O recurso solicitado não existe |
405 | Método Não Permitido | O método HTTP não é suportado para este endpoint |
413 | Payload Muito Grande | O corpo da requisição excede 1 MB |
422 | Entidade Não Processável | Dados válidos, mas o servidor não pode processá-los (ex: perfil fiscal não configurado) |
429 | Muitas Requisições | O limite de 100 requisições por minuto foi excedido |
500 | Erro Interno do Servidor | Erro interno do servidor |
Estrutura das respostas de erro
Erro de validação (400):
{
"error": "Validation error",
"details": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": ["clientName"],
"message": "Required"
}
]
}
Os erros de validação usam o formato de Zod. O campo path indica qual campo contém o erro e message descreve o problema.
Chave inválida ou expirada (401):
{
"error": "Invalid or expired API key"
}
Formato de chave inválido (401):
{
"error": "Invalid API key format"
}
Recurso não encontrado (404):
{
"error": "Resource not found"
}
Status inválido no filtro (400):
{
"error": "Invalid status filter",
"message": "Valid values: draft, sent, paid, overdue, cancelled"
}
Rate limit excedido (429):
{
"error": "Rate limit exceeded",
"message": "Maximum 100 requests per minute",
"retryAfter": 60
}
Erro interno (500):
{
"error": "Internal server error"
}
Paginação
Os endpoints de listagem retornam resultados paginados com a seguinte estrutura:
{
"data": [],
"total": 142,
"limit": 50,
"offset": 0
}
total: número total de registros que atendem aos filtros aplicadoslimit: número de registros retornados nesta página (máximo 100)offset: número de registros pulados (máximo 10.000)
Para obter a próxima página:
curl "https://api.frihet.io/v1/invoices?limit=50&offset=50" \
-H "X-API-Key: fri_tu-clave-aqui"
Os resultados são ordenados pela data natural do recurso em ordem descendente (mais recentes primeiro):
- Faturas e orçamentos:
issueDate - Despesas:
date - Clientes e produtos:
createdAt
Validação estrita
A API usa validação estrita (Zod strict mode). As requisições com campos desconhecidos são rejeitadas com um erro 400:
{
"error": "Validation error",
"details": [
{
"code": "unrecognized_keys",
"keys": ["campoInventado"],
"path": [],
"message": "Unrecognized key(s) in object: 'campoInventado'"
}
]
}
Isso previne erros silenciosos por erros de digitação nos nomes dos campos.
CORS
A API suporta CORS para requisições do navegador. As origens permitidas são:
https://app.frihet.iohttps://frihet.iohttps://www.frihet.io
Para integrações server-to-server, CORS não é relevante. Se você precisar acessar a API de um domínio diferente no navegador, utilize um proxy no seu backend.
Cabeçalhos de segurança
Todas as respostas incluem cabeçalhos de segurança:
| Cabeçalho | Valor |
|---|---|
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
X-XSS-Protection | 1; mode=block |
X-Request-Id | ID único da requisição (útil para depuração) |
OAuth para MCP
O endpoint POST /api/oauth/api-key permite provisionar uma chave de API automaticamente através do fluxo OAuth do MCP. O servidor MCP usa este endpoint internamente -- você não precisa chamá-lo diretamente.
O fluxo:
- O usuário se autentica via Firebase Auth
- O cliente MCP envia o token do Firebase para
/api/oauth/api-key - O servidor retorna uma nova chave
fri_xxx...rotulada como "MCP OAuth" - A chave expira em 365 dias
Limite: 5 chaves ativas por usuário. Se o limite for excedido, o endpoint responde com um 429.
Boas práticas
- Armazene a chave de API de forma segura. Nunca a inclua em código frontend, repositórios públicos ou logs.
- Gerencie o rate limiting. Implemente backoff exponencial se receber um
429. - Use paginação. Não solicite todos os registros de uma vez; itere com
limiteoffset. - Verifique os códigos de resposta. Não presuma que todas as requisições serão bem-sucedidas.
- Rotacione as chaves periodicamente. Crie uma nova chave, atualize sua integração e revogue a anterior.
- Use HTTPS sempre. Todas as requisições à API devem ser sobre HTTPS.
- Use filtros. Os parâmetros
status,frometoreduzem a quantidade de dados transferidos. - Aproveite o PATCH. Para atualizações parciais, envie apenas os campos modificados.