Create and issue invoices
Draft an invoice with tax codes, let BIRP compute the VAT, issue it, and record payments.
Version v1.1, updated
On this page
You will create invoices in BIRP from your own system and record their payments. You send what was sold and its tax code; BIRP computes every total and the VAT.
Before you start
A key with
invoices:writeandinvoices:read.The customer in BIRP. See Match and update customers.
The company details an invoice needs (name, address, tax ids) filled in BIRP. Otherwise invoices answer 422
company_profile_incomplete.
Steps
Find the tax codes
Every invoice line needs a tax_code. GET /v1/tax-codes lists the codes valid at a date under the rules of the company's country, with their rate and validity. Without date, the current day of the company applies.
curl "https://api.birp.io/v1/tax-codes" \
-H "Authorization: Bearer $BIRP_API_KEY"const response = await fetch("https://api.birp.io/v1/tax-codes", {
headers: {
Authorization: `Bearer ${process.env.BIRP_API_KEY}`,
},
});
if (!response.ok) throw new Error(`BIRP API error ${response.status}`);
const result = await response.json();<?php
$ch = curl_init("https://api.birp.io/v1/tax-codes");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("BIRP_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);import os
import requests
response = requests.request(
"GET",
"https://api.birp.io/v1/tax-codes",
headers={
"Authorization": f"Bearer {os.environ['BIRP_API_KEY']}",
},
timeout=10,
)
response.raise_for_status()
result = response.json(){
"data": [
{
"code": "STANDARD",
"effective_from": "2015-01-01",
"effective_to": null,
"label": "Tax code, article on the standard rate",
"rate": "20.0000"
},
{
"code": "ZERO",
"effective_from": "2015-01-01",
"effective_to": null,
"label": "Tax code, article on zero-rated supplies",
"rate": "0.0000"
}
],
"date": "2026-10-05"
}The codes depend on the rules of the company's country and change over time. Read them from this operation; never copy the codes of an example.
Create a draft
POST /v1/invoices creates a draft. Each line has a product or a description, a quantity, a unit price and its tax code.
curl -X POST "https://api.birp.io/v1/invoices" \
-H "Authorization: Bearer $BIRP_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"currency":"EUR","customer":{"id":"00000000-0000-4000-8000-000000000031"},"issue_date":"2026-09-28","lines":[{"product":{"sku":"FP-BREAD-500"},"quantity":"20","tax_code":"STANDARD","unit_price":"3.20"},{"product":{"sku":"SP-FLOUR-25"},"quantity":"4","tax_code":"ZERO","unit_price":"12.50"}],"order":{"id":"00000000-0000-4000-8000-000000000051"},"payment_terms_days":30}'const response = await fetch("https://api.birp.io/v1/invoices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BIRP_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
"currency": "EUR",
"customer": {
"id": "00000000-0000-4000-8000-000000000031"
},
"issue_date": "2026-09-28",
"lines": [
{
"product": {
"sku": "FP-BREAD-500"
},
"quantity": "20",
"tax_code": "STANDARD",
"unit_price": "3.20"
},
{
"product": {
"sku": "SP-FLOUR-25"
},
"quantity": "4",
"tax_code": "ZERO",
"unit_price": "12.50"
}
],
"order": {
"id": "00000000-0000-4000-8000-000000000051"
},
"payment_terms_days": 30
}),
});
if (!response.ok) throw new Error(`BIRP API error ${response.status}`);
const result = await response.json();<?php
$ch = curl_init("https://api.birp.io/v1/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("BIRP_API_KEY"),
"Idempotency-Key: " . bin2hex(random_bytes(16)),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"currency": "EUR",
"customer": {
"id": "00000000-0000-4000-8000-000000000031"
},
"issue_date": "2026-09-28",
"lines": [
{
"product": {
"sku": "FP-BREAD-500"
},
"quantity": "20",
"tax_code": "STANDARD",
"unit_price": "3.20"
},
{
"product": {
"sku": "SP-FLOUR-25"
},
"quantity": "4",
"tax_code": "ZERO",
"unit_price": "12.50"
}
],
"order": {
"id": "00000000-0000-4000-8000-000000000051"
},
"payment_terms_days": 30
}
JSON,
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);import os
import uuid
import requests
response = requests.request(
"POST",
"https://api.birp.io/v1/invoices",
json={
"currency": "EUR",
"customer": {
"id": "00000000-0000-4000-8000-000000000031"
},
"issue_date": "2026-09-28",
"lines": [
{
"product": {
"sku": "FP-BREAD-500"
},
"quantity": "20",
"tax_code": "STANDARD",
"unit_price": "3.20"
},
{
"product": {
"sku": "SP-FLOUR-25"
},
"quantity": "4",
"tax_code": "ZERO",
"unit_price": "12.50"
}
],
"order": {
"id": "00000000-0000-4000-8000-000000000051"
},
"payment_terms_days": 30
},
headers={
"Authorization": f"Bearer {os.environ['BIRP_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
timeout=10,
)
response.raise_for_status()
result = response.json()What BIRP computes for you
Each line total: quantity times unit price, rounded to cents.
Each line VAT: the line total times the rate of its tax code, rounded to cents.
The subtotal, the VAT total and the total of the invoice.
The VAT breakdown per rate, with the tax code of each rate.
Totals and VAT are never accepted from you: a body that sends them answers 422 validation_failed. A draft has no number yet: number is null.
Change a draft
PATCH /v1/invoices/{id} changes a draft. When you send lines, they replace all the lines, and BIRP computes everything again.
Issue it
POST /v1/invoices/{id}/issue gives the invoice its number from the company numbering and sets its issue date: the one you send, or today. BIRP computes the VAT again with the rules of that date.
Warning. An issued invoice never changes through the API. A correction is a credit note, made in BIRP.
Record a payment
POST /v1/invoices/{id}/payments records a payment on an issued invoice. The invoice then shows its payment_status, amount_paid and amount_due.
curl -X POST "https://api.birp.io/v1/invoices/00000000-0000-4000-8000-000000000061/payments" \
-H "Authorization: Bearer $BIRP_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount":"64.50","method":"card","paid_on":"2026-10-07","reference":"EXAMPLE-PAY-0042"}'const response = await fetch("https://api.birp.io/v1/invoices/00000000-0000-4000-8000-000000000061/payments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BIRP_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
"amount": "64.50",
"method": "card",
"paid_on": "2026-10-07",
"reference": "EXAMPLE-PAY-0042"
}),
});
if (!response.ok) throw new Error(`BIRP API error ${response.status}`);
const result = await response.json();<?php
$ch = curl_init("https://api.birp.io/v1/invoices/00000000-0000-4000-8000-000000000061/payments");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("BIRP_API_KEY"),
"Idempotency-Key: " . bin2hex(random_bytes(16)),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"amount": "64.50",
"method": "card",
"paid_on": "2026-10-07",
"reference": "EXAMPLE-PAY-0042"
}
JSON,
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);import os
import uuid
import requests
response = requests.request(
"POST",
"https://api.birp.io/v1/invoices/00000000-0000-4000-8000-000000000061/payments",
json={
"amount": "64.50",
"method": "card",
"paid_on": "2026-10-07",
"reference": "EXAMPLE-PAY-0042"
},
headers={
"Authorization": f"Bearer {os.environ['BIRP_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
timeout=10,
)
response.raise_for_status()
result = response.json()Dates and rules
Tax rules apply at the document date: the issue date, or today for a draft without one. When a rule changes between the draft and the issue, the issue applies the rule of its own date.
Check the result
GET /v1/invoices/{id} returns the invoice with its status, its number once issued, its totals and its payment state.
What can go wrong
Related
- invoices-resource
- formats
- me-and-tax-codes
- idempotency