Pular para o conteúdo

Despesas (expenses)

O recurso expenses expõe as despesas do seu financeiro. Cada despesa traz os valores (original, desconto e final), os vínculos com categoria, fornecedor, centro de custo, conta bancária e cartão, as datas de vencimento e pagamento, tags livres e as informações de recorrência e parcelas. É um recurso de leitura e escrita.

Para criar despesas com parcelas e rateios consistentes, use os endpoints /expenses/operations. Eles confirmam o conjunto em uma transação, trabalham com centavos inteiros BRL e retornam um recibo recuperável pelo mesmo Idempotency-Key. As rotas originais continuam disponíveis para despesas que ainda não entraram nesse fluxo operacional.

Módulo despesas
Escopos expenses:read para leitura, expenses:write para escrita
Operações leitura e escrita

Sem o módulo no contrato, a resposta é 403 module_not_enabled. Sem o escopo na chave, é 403 insufficient_scope. expenses:write concede também a leitura.

Método Endpoint Escopo Descrição
GET /v1/expenses expenses:read Lista despesas, com paginação por cursor
GET /v1/expenses/{id} expenses:read Busca uma despesa pelo id, com ?include=recurring opcional
POST /v1/expenses expenses:write Cria uma despesa e responde 201
PATCH /v1/expenses/{id} expenses:write Atualização parcial: envie só os campos que mudam
DELETE /v1/expenses/{id} expenses:write Desativa a despesa e responde 204
POST /v1/expenses/operations expenses:write Cria cabeçalho, parcelas e rateios juntos
PATCH /v1/expenses/{id}/operations expenses:write Edita o conjunto preservando o histórico financeiro
POST /v1/expenses/{id}/cancel expenses:write Cancela com motivo após reverter pagamentos
GET /v1/expenses/{id}/installments expenses:read Consulta parcelas e valor liquidado em centavos
GET /v1/expenses/{id}/allocations expenses:read Consulta rateios por centro de custo
POST /v1/expenses/recurrences expenses:write Cria regra a partir de uma despesa operacional
GET /v1/expenses/recurrences expenses:read Lista regras gerenciadas por este fluxo
GET /v1/expenses/recurrences/{id} expenses:read Consulta regra e próxima competência
PATCH /v1/expenses/recurrences/{id} expenses:write Altera frequência, intervalo ou término
POST /v1/expenses/recurrences/{id}/pause expenses:write Pausa novas ocorrências
POST /v1/expenses/recurrences/{id}/resume expenses:write Retoma regra pausada
POST /v1/expenses/recurrences/{id}/end expenses:write Encerra definitivamente a regra
POST /v1/expenses/recurrences/{id}/generate expenses:write Gera a próxima ocorrência já vencida ou de hoje
GET /v1/expenses/recurrences/{id}/occurrences expenses:read Consulta despesas geradas por competência

Todas as novas escritas da tabela acima exigem Idempotency-Key com UUID e retornam 200, com data e meta.request_id, meta.command_id, meta.action. Use o mesmo UUID e corpo ao repetir uma requisição sem resposta. Mudar o corpo com esse UUID resulta em 409 idempotency_conflict.

Exemplo de corpo de POST /v1/expenses/operations para R$ 100,00:

{
"description": "Equipamentos para o escritório",
"expense_type": "one_time",
"amount_cents": 10000,
"due_date": "2026-10-31",
"installments": 3,
"payment_method": "bank_slip",
"allocations": [
{ "cost_center_id": "5d4e3c2b-1a0f-4e9d-8c7b-6a5f4e3d2c1b", "basis_points": 10000 }
]
}

expense_type aceita fixed, variable ou one_time. amount_cents deve ser inteiro positivo, no máximo 9.999.999.999, e deve comportar pelo menos um centavo por parcela. installments aceita de 1 a 120 e assume 1. Nesse exemplo, as parcelas são 3333, 3333 e 3334 centavos; o resíduo fica na última parcela.

Os campos opcionais são purchase_date, category_id, subcategory_id, supplier_id, bank_account_id, credit_card_id, payment_method, notes e allocations. Categoria, fornecedor, conta, cartão e centros devem estar ativos na mesma equipe. A subcategoria deve pertencer à categoria informada. Não envie equipe, autor, estado de pagamento, saldo ou vínculo com Produção/Repasses.

Um rateio usa somente um dos formatos por requisição:

  • amount_cents: os valores devem somar exatamente o total da despesa.
  • basis_points: os pesos inteiros devem somar 10000, equivalentes a 100%.

Centros duplicados são rejeitados. No formato percentual, cada parcela de rateio é truncada em centavos; o último centro informado recebe o resíduo. Omitir rateios na criação deixa a despesa sem rateio. allocations: [] remove o rateio na edição; omitir o campo preserva seus valores. Ao mudar o total de uma despesa já rateada, envie também o rateio correspondente ao novo total.

O PATCH /{id}/operations recebe somente os campos alterados. Ele preserva a identidade das parcelas existentes. Parcelas com histórico de baixa não podem ser apagadas nem ter a quantidade da série alterada. Uma baixa pendente de estorno impede mudanças financeiras; parcelas que já participaram de pagamento de fatura mantêm valor e vínculo com essa fatura. Edições descritivas continuam disponíveis.

Para cancelar, envie {"reason":"Compra registrada em duplicidade"} a POST /{id}/cancel. Primeiro reverta as baixas ou pagamentos de fatura aplicáveis. O motivo fica registrado; repetir o cancelamento não gera novas reversões nem apaga parcelas. A despesa recebe status: "cancelled".

Registre baixas parciais ou totais em POST /v1/expenses/installments/{id}/settlements, indicando amount_cents, payer_kind (bank_account ou wallet), payer_id, effective_date e payment_method. O histórico fica em GET /v1/expenses/{id}/settlements; estornos usam POST /v1/expenses/settlements/{id}/reversals com valor, data e motivo. Essas operações registram o movimento interno e não executam um pagamento no banco.

Despesas credit_card ou credit_card_installments exigem credit_card_id. purchase_date determina a primeira fatura, usando due_date se omitida. A data de fechamento é inclusiva, dias 29–31 são limitados ao último dia do mês, e o vencimento é a primeira ocorrência posterior ao fechamento. Uma fatura fechada encaminha novas obrigações à próxima aberta. Cada parcela pertence à sua fatura; a baixa direta da parcela de cartão é recusada. O caixa muda no pagamento da fatura.

Parcelas, rateios, regras e ocorrências aceitam limit de 1 a 100 (padrão 50) e cursor UUID. A resposta inclui pagination.has_more e pagination.next_cursor. Essas consultas usam projeções fixas; não aceitam os filtros e fields das rotas legadas. Parcelas incluem amount_cents, settled_cents, vencimento, estado, número, quantidade da série e credit_card_invoice_id. settled_cents corresponde às baixas diretas, líquidas de estornos; para cartão, consulte o histórico da fatura.

Crie a regra com uma despesa já gerenciada pelas operações acima:

{
"expense_template_id": "2e6c1a9b-4d7f-4b3a-9c25-8f0e1d2c3b4a",
"frequency": "monthly",
"interval": 1,
"start_date": "2027-01-31",
"end_date": "2027-12-31"
}

frequency aceita daily, weekly, monthly e yearly; interval vai de 1 a 120. start_date é a primeira nova competência. A despesa usada como modelo continua existindo e não é contada automaticamente como ocorrência da regra.

Cada competência gera cabeçalho, parcelas e rateios com a autoria da regra. A data permanece ancorada: uma série mensal iniciada em 31 de janeiro passa por 28/29 de fevereiro e volta a 31 de março. O gerador existente e o endpoint generate compartilham a mesma proteção contra duplicação. Cada chamada gera no máximo uma competência; datas futuras são recusadas. Ao ultrapassar end_date, a regra é encerrada e a resposta contém a regra, em vez de uma nova despesa.

Envie {} em pause, resume, end e generate, sempre com UUID Idempotency-Key. Pausar não muda ocorrências anteriores. Retomar mantém a próxima competência pendente, inclusive se estiver atrasada. Encerrar é definitivo. O PATCH aceita frequency, interval e end_date (null remove a data final); alterações na frequência se aplicam ao avanço após a próxima competência já agendada.

A geração revalida a chave criadora, o autor e o acesso ao módulo. Revogar ou expirar essa chave interrompe novas gerações; não altera as despesas existentes. As consultas operacionais de regras listam somente regras criadas por esse fluxo. Regras anteriores continuam acessíveis pelo contrato legado de recorrência.

Conflitos de histórico, origem incompatível, regra pausada/encerrada ou geração antecipada retornam 409 resource_conflict. Falhas transitórias de concorrência retornam 503; repita com o mesmo UUID.

Os campos e exemplos a seguir descrevem as rotas originais /expenses e /expenses/{id}, que mantêm valores decimais em reais.

Campo Tipo Descrição
id uuid Identificador da despesa
expense_number string Número da despesa. Se omitido no POST, é gerado como API-<timestamp>-<sufixo>
expense_type string Tipo da despesa. Obrigatório na criação
description string Descrição da despesa. Obrigatório na criação
original_amount number Valor original, maior ou igual a zero. Obrigatório na criação
discount_amount number | null Desconto aplicado, maior ou igual a zero
final_amount number Valor final, maior ou igual a zero. Obrigatório na criação
category_id uuid | null Categoria da despesa
subcategory_id uuid | null Subcategoria da despesa
cost_center_id uuid | null Centro de custo
supplier_id uuid | null Fornecedor vinculado
payment_method string | null Forma de pagamento, por exemplo pix, bank_slip ou credit_card
bank_account_id uuid | null Conta bancária usada no pagamento
credit_card_id uuid | null Cartão de crédito usado no pagamento
check_number string | null Número do cheque, quando aplicável
installments integer | null Quantidade de parcelas, maior ou igual a 1
status string | null Status da despesa, por exemplo pending ou paid
payment_date date (YYYY-MM-DD) | null Data do pagamento
due_date date (YYYY-MM-DD) | null Data de vencimento
notes string | null Observações livres
tags string[] | null Tags livres para organização
is_recurring boolean | null Indica se a despesa veio de uma recorrência
is_active boolean | null false quando a despesa foi desativada
payment_reference string | null Referência externa do pagamento
recurring_expense_id uuid | null Recorrência que gerou esta despesa
installment_number integer | null Número desta parcela. Somente leitura
total_installments integer | null Total de parcelas da série. Somente leitura
created_at timestamp Data de criação, em ISO 8601
updated_at timestamp | null Última atualização, em ISO 8601
recurring object | null Recorrência embutida. Só aparece com ?include=recurring

No POST, os campos obrigatórios são expense_type, description, original_amount e final_amount. No PATCH, todos os campos aceitos no POST são opcionais: envie apenas o que quer alterar. installment_number e total_installments não são aceitos na escrita.

Nos GET, o parâmetro fields devolve campos parciais: ?fields=id,description,final_amount,due_date,status.

A sintaxe do filtro é filter[campo][operador]=valor, com os operadores eq, neq, gt, gte, lt, lte, in, nin, ilike e is_null. Os detalhes estão em Paginação, filtros e ordenação.

Campo Exemplo
expense_number filter[expense_number][eq]=DESP-2026-0142
expense_type filter[expense_type][eq]=fixed
description filter[description][ilike]=%aluguel%
category_id filter[category_id][eq]=3f2a7b1c-8d9e-4f0a-b1c2-d3e4f5a6b7c8
subcategory_id filter[subcategory_id][eq]=1c2b3a4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d
cost_center_id filter[cost_center_id][eq]=5d4e3c2b-1a0f-4e9d-8c7b-6a5f4e3d2c1b
supplier_id filter[supplier_id][eq]=9a0f8e7d-6c5b-4a3f-2e1d-0c9b8a7f6e5d
payment_method filter[payment_method][in]=pix,bank_slip
bank_account_id filter[bank_account_id][eq]=0d4f2c9a-1b7e-4c3d-9a52-8e6f1b2c3d4e
credit_card_id filter[credit_card_id][is_null]=true
status filter[status][eq]=pending
payment_date filter[payment_date][gte]=2026-07-01
due_date filter[due_date][lte]=2026-07-31
is_recurring filter[is_recurring][eq]=true
is_active filter[is_active][eq]=true
created_at filter[created_at][gte]=2026-01-01

A ordenação padrão é -created_at, da mais recente para a mais antiga. Os campos aceitos em sort são created_at, updated_at, payment_date e due_date.

Um exemplo de combinação, para as despesas pendentes e ativas que vencem em julho de 2026:

Janela do terminal
curl -G "$LIGGA_BASE/v1/expenses" \
-H "Authorization: Bearer $LIGGA_API_KEY" \
--data-urlencode "filter[status][eq]=pending" \
--data-urlencode "filter[is_active][eq]=true" \
--data-urlencode "filter[due_date][gte]=2026-07-01" \
--data-urlencode "filter[due_date][lte]=2026-07-31" \
--data-urlencode "sort=due_date"

GET /v1/expenses/{id} aceita ?include=recurring. Quando a despesa veio de uma recorrência, ou seja, quando recurring_expense_id está preenchido, a resposta embute o objeto recurring com os dados da série. Caso contrário, recurring vem como null.

Campo de recurring Tipo
id uuid
expense_template_id uuid
recurrence_frequency string, por exemplo monthly
recurrence_interval integer
start_date date
end_date date | null
next_generation_date date
last_generated_expense_id uuid | null
is_active boolean | null
created_at timestamp

Os exemplos abaixo supõem as duas variáveis de ambiente do Início rápido:

Janela do terminal
export LIGGA_API_KEY='ligga_live_…'
export LIGGA_BASE='https://api.ligga.app/functions/v1/api'
Janela do terminal
curl -G "$LIGGA_BASE/v1/expenses" \
-H "Authorization: Bearer $LIGGA_API_KEY" \
--data-urlencode "limit=2" \
--data-urlencode "sort=-due_date" \
--data-urlencode "filter[is_active][eq]=true"
{
"data": [
{
"id": "2e6c1a9b-4d7f-4b3a-9c25-8f0e1d2c3b4a",
"expense_number": "DESP-2026-0142",
"expense_type": "fixed",
"description": "Aluguel da loja, julho de 2026",
"original_amount": 3500,
"discount_amount": null,
"final_amount": 3500,
"category_id": "3f2a7b1c-8d9e-4f0a-b1c2-d3e4f5a6b7c8",
"subcategory_id": null,
"cost_center_id": "5d4e3c2b-1a0f-4e9d-8c7b-6a5f4e3d2c1b",
"supplier_id": "9a0f8e7d-6c5b-4a3f-2e1d-0c9b8a7f6e5d",
"payment_method": "pix",
"bank_account_id": "0d4f2c9a-1b7e-4c3d-9a52-8e6f1b2c3d4e",
"credit_card_id": null,
"check_number": null,
"installments": null,
"status": "pending",
"payment_date": null,
"due_date": "2026-07-10",
"notes": "Reajuste anual previsto para agosto.",
"tags": ["aluguel", "loja-centro"],
"is_recurring": true,
"is_active": true,
"payment_reference": null,
"recurring_expense_id": "b8c7d6e5-f4a3-4b2c-9d1e-0f9a8b7c6d5e",
"installment_number": null,
"total_installments": null,
"created_at": "2026-07-01T08:00:12.334Z",
"updated_at": null
},
{
"id": "7f1e2d3c-4b5a-4968-8776-655443322110",
"expense_number": "API-1751925600000-a1b2c3",
"expense_type": "variable",
"description": "Ração premium, reposição de estoque",
"original_amount": 1280.5,
"discount_amount": 80.5,
"final_amount": 1200,
"category_id": "3f2a7b1c-8d9e-4f0a-b1c2-d3e4f5a6b7c8",
"subcategory_id": "1c2b3a4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"cost_center_id": null,
"supplier_id": "9a0f8e7d-6c5b-4a3f-2e1d-0c9b8a7f6e5d",
"payment_method": "bank_slip",
"bank_account_id": null,
"credit_card_id": null,
"check_number": null,
"installments": 2,
"status": "paid",
"payment_date": "2026-07-03",
"due_date": "2026-07-05",
"notes": null,
"tags": ["estoque"],
"is_recurring": false,
"is_active": true,
"payment_reference": "NF-e 12345",
"recurring_expense_id": null,
"installment_number": 1,
"total_installments": 2,
"created_at": "2026-06-30T15:42:51.902Z",
"updated_at": "2026-07-03T10:11:23.556Z"
}
],
"pagination": {
"next_cursor": "eyJpZCI6IjdmMWUyZDNjLS4uLiIsInYiOiIyMDI2LTA3LTA1In0",
"has_more": true,
"limit": 2
},
"meta": {
"request_id": "req_01JZXQ8V2K5M7N9P0R1S2T3U4V"
}
}

O cursor da próxima página está em pagination.next_cursor. Repasse-o em ?cursor= enquanto has_more for true.

Janela do terminal
curl "$LIGGA_BASE/v1/expenses/2e6c1a9b-4d7f-4b3a-9c25-8f0e1d2c3b4a?include=recurring" \
-H "Authorization: Bearer $LIGGA_API_KEY"
{
"data": {
"id": "2e6c1a9b-4d7f-4b3a-9c25-8f0e1d2c3b4a",
"expense_number": "DESP-2026-0142",
"description": "Aluguel da loja, julho de 2026",
"final_amount": 3500,
"status": "pending",
"due_date": "2026-07-10",
"is_recurring": true,
"is_active": true,
"recurring_expense_id": "b8c7d6e5-f4a3-4b2c-9d1e-0f9a8b7c6d5e",
"recurring": {
"id": "b8c7d6e5-f4a3-4b2c-9d1e-0f9a8b7c6d5e",
"expense_template_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"recurrence_frequency": "monthly",
"recurrence_interval": 1,
"start_date": "2026-01-10",
"end_date": null,
"next_generation_date": "2026-08-10",
"last_generated_expense_id": "2e6c1a9b-4d7f-4b3a-9c25-8f0e1d2c3b4a",
"is_active": true,
"created_at": "2026-01-05T09:12:00.000Z"
}
},
"meta": { "request_id": "req_01JZY7B2C3D4E5F6G7H8J9K0L1" }
}

O objeto acima está resumido para caber na página. A resposta real traz todos os campos da tabela de Campos.

POST é uma operação de escrita. Envie o cabeçalho Idempotency-Key para poder fazer uma nova tentativa sem duplicar o lançamento.

Janela do terminal
curl -X POST "$LIGGA_BASE/v1/expenses" \
-H "Authorization: Bearer $LIGGA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"expense_type": "variable",
"description": "Manutenção do ar-condicionado",
"original_amount": 450,
"discount_amount": 50,
"final_amount": 400,
"supplier_id": "9a0f8e7d-6c5b-4a3f-2e1d-0c9b8a7f6e5d",
"payment_method": "pix",
"due_date": "2026-07-20",
"status": "pending",
"tags": ["manutencao"]
}'

A resposta é 201 Created. Como expense_number não foi enviado, a API gerou um:

{
"data": {
"id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"expense_number": "API-1752148800000-f3e2d1",
"expense_type": "variable",
"description": "Manutenção do ar-condicionado",
"original_amount": 450,
"discount_amount": 50,
"final_amount": 400,
"category_id": null,
"subcategory_id": null,
"cost_center_id": null,
"supplier_id": "9a0f8e7d-6c5b-4a3f-2e1d-0c9b8a7f6e5d",
"payment_method": "pix",
"bank_account_id": null,
"credit_card_id": null,
"check_number": null,
"installments": null,
"status": "pending",
"payment_date": null,
"due_date": "2026-07-20",
"notes": null,
"tags": ["manutencao"],
"is_recurring": null,
"is_active": true,
"payment_reference": null,
"recurring_expense_id": null,
"installment_number": null,
"total_installments": null,
"created_at": "2026-07-10T12:00:03.118Z",
"updated_at": null
},
"meta": { "request_id": "req_01JZY0A1B2C3D4E5F6G7H8J9K0" }
}

O PATCH legado é parcial: os campos que você não enviar continuam como estão. Para despesas operacionais, use as operações e baixas descritas acima.

Janela do terminal
curl -X PATCH "$LIGGA_BASE/v1/expenses/2e6c1a9b-4d7f-4b3a-9c25-8f0e1d2c3b4a" \
-H "Authorization: Bearer $LIGGA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "notes": "Documento conferido em 08/07." }'

A resposta é 200 OK com a despesa já atualizada, no envelope de item.

Janela do terminal
curl -X DELETE "$LIGGA_BASE/v1/expenses/2e6c1a9b-4d7f-4b3a-9c25-8f0e1d2c3b4a" \
-H "Authorization: Bearer $LIGGA_API_KEY"

A resposta é 204 No Content, sem corpo. A despesa passa a ter is_active: false.

HTTP type Quando acontece
401 invalid_token Cabeçalho Authorization ausente ou chave inexistente
401 token_revoked A chave foi revogada
401 token_expired A chave passou da data de expiração
403 module_not_enabled Módulo despesas fora do contrato
403 insufficient_scope Chave sem expenses:read na leitura ou expenses:write na escrita
404 not_found id inexistente ou pertencente a outra conta
409 idempotency_conflict Mesma Idempotency-Key reaproveitada com corpo diferente
409 resource_conflict Operação incompatível com pagamentos, origem ou estado da recorrência
422 validation Corpo inválido, como final_amount negativo ou data fora do formato YYYY-MM-DD; filtro, operador ou cursor não aceito
429 rate-limit-exceeded Limite de requisições do plano excedido
503 service_unavailable Conflito transitório de concorrência; repetir a operação com o mesmo UUID

O corpo do erro traz type, title, status e request_id. O formato completo está em Erros.