Send an Invoice
Send invoices to the Peppol network with attachments, allowances, charges, and delivery details.
Send a Peppol invoice. The API validates the payload against BIS 3.0 business rules, generates UBL XML, and delivers it to the buyer's access point.
Parties
Your API key determines the seller/legal entity. The payload must include
to (buyer) with name, peppolId,
street, city, postalCode, and
country. The deprecated from field is ignored by
the gateway and kept only for local/offline compatibility.
Line Items
Each line needs description, quantity,
unitPrice, and vatRate.
Amounts are calculated automatically — no need for manual totals.
Payment
Add paymentTerms and paymentIban to include
payment instructions. Strongly recommended for Belgian and French mandates.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
number | string | Required | Unique invoice number (e.g. INV-2026-042) |
to | BuyerParty | Required | Buyer details: name, peppolId, street, city, postalCode, country |
lines | InvoiceLine[] | Required | Line items with description, quantity, unitPrice, vatRate |
from | Party | Optional | Deprecated local metadata. The gateway determines the seller from your API key. |
date | string | Optional | ISO 8601 invoice date (defaults to today) |
dueDate | string | Optional | ISO 8601 payment due date |
currency | string | Optional | ISO 4217 currency code (default: "EUR") |
paymentTerms | string | Optional | Payment terms description |
paymentIban | string | Optional | IBAN for bank transfer |
attachments | Attachment[] | Optional | Embedded or external file attachments |
allowances | AllowanceCharge[] | Optional | Document-level discounts |
charges | AllowanceCharge[] | Optional | Document-level surcharges |
delivery | Delivery | Optional | Delivery date and address |
invoicePeriod | InvoicePeriod | Optional | Billing period (start/end dates) |
note | string | Optional | Free-text note for the buyer |
import { Peppol } from "@getpeppr/sdk";
const peppol = new Peppol({ apiKey: "sk_live_..." });
const result = await peppol.invoices.send({
number: "INV-2026-042",
buyerReference: "PO-2026-007",
// Buyer
to: {
name: "Wayne Enterprises NV",
peppolId: "0208:BE0123456789",
street: "Avenue Louise 54",
city: "Brussels",
postalCode: "1050",
country: "BE",
},
// Line items
lines: [
{ description: "Arc Reactor Maintenance Q1", quantity: 1, unitPrice: 50_000, vatRate: 21 },
{ description: "Vibranium Shield Polish", quantity: 3, unitPrice: 250, vatRate: 21 },
],
// Payment
paymentTerms: "Net 30 days",
paymentIban: "BE68539007547034",
// Optional
date: "2026-03-01",
dueDate: "2026-03-31",
note: "Thank you for your business!",
});
console.log(`Sent! ID: ${result.id}, Status: ${result.status}`);Attachments
Attach supporting documents to your invoices — PDF copies, timesheets, contracts, or any file. Supports both embedded (base64) and external URL references.
Embedded
Provide filename, mimeType, and raw base64-encoded content without a data URI prefix.
The file is included directly in the UBL XML.
External URL
Provide a url instead. The buyer's access point will fetch the
document. Make sure the URL is publicly accessible.
import { Peppol } from "@getpeppr/sdk";
import { readFileSync } from "fs";
const peppol = new Peppol({ apiKey: "sk_live_..." });
const result = await peppol.invoices.send({
number: "INV-2026-043",
to: { name: "Globex NV", peppolId: "0208:BE0987654321", street: "Rue de la Loi 200", city: "Brussels", postalCode: "1000", country: "BE" },
lines: [
{ description: "Consulting Q1", quantity: 40, unitPrice: 125, vatRate: 21 },
],
// Embed a PDF copy of the invoice
attachments: [
{
id: "ATT-001",
filename: "invoice-2026-043.pdf",
mimeType: "application/pdf",
content: readFileSync("./invoice.pdf").toString("base64"),
},
// Or reference an external document
{
id: "ATT-002",
description: "Detailed timesheet",
url: "https://acme.com/docs/timesheet-q1.pdf",
},
],
});Allowances & Charges
Apply discounts (allowances) and surcharges to invoices at both the document level and line level.
Document-level
Use allowances for discounts and charges for surcharges
that apply to the entire invoice. Each requires reason, amount,
and vatRate.
Line-level
Add allowances or charges to individual line items.
Line-level adjustments only need reason and amount.
import { Peppol } from "@getpeppr/sdk";
const peppol = new Peppol({ apiKey: "sk_live_..." });
const result = await peppol.invoices.send({
number: "INV-2026-044",
to: { name: "Globex NV", peppolId: "0208:BE0987654321", street: "Rue de la Loi 200", city: "Brussels", postalCode: "1000", country: "BE" },
lines: [
{
description: "Consulting hours",
quantity: 40,
unitPrice: 125,
vatRate: 21,
// Line-level discount
allowances: [{ reason: "Loyalty discount", amount: 200 }],
},
],
// Document-level discount (applies to entire invoice)
allowances: [
{ reason: "Early payment discount (2%)", amount: 100, vatRate: 21 },
],
// Document-level surcharge
charges: [
{ reason: "Express delivery fee", amount: 50, vatRate: 21 },
],
});Delivery & Period
Specify delivery details and billing periods for invoices that cover goods delivery or service periods.
Delivery
Add a delivery object with an optional date and
address. Some EU countries (e.g. Italy, Spain) require delivery
information on invoices.
Invoice Period
Use invoicePeriod with startDate and endDate
for subscriptions, retainers, or any service billed over a time range.
invoicePeriod —
it's required by some national rules and improves reconciliation for your buyers.
import { Peppol } from "@getpeppr/sdk";
const peppol = new Peppol({ apiKey: "sk_live_..." });
const result = await peppol.invoices.send({
number: "INV-2026-045",
to: { name: "Globex NV", peppolId: "0208:BE0987654321", street: "Rue de la Loi 200", city: "Brussels", postalCode: "1000", country: "BE" },
lines: [
{ description: "Server hardware", quantity: 10, unitPrice: 2500, vatRate: 21 },
],
// Delivery details (mandatory in some EU countries)
delivery: {
date: "2026-03-15",
address: {
street: "Rue de la Science 14",
city: "Brussels",
postalCode: "1000",
country: "BE",
},
},
// Billing period (common for SaaS / subscriptions)
invoicePeriod: {
startDate: "2026-01-01",
endDate: "2026-03-31",
},
});Common Errors
Most failures on this endpoint fall into one of these categories. The API returns
structured JSON with a stable error field and (where available) a machine-readable
code for client-side branching. See Error Handling for the full reference.
Validation failed (422)
One or more fields failed Peppol BIS 3.0 validation. Common causes: missing buyer
address (BR-50/51/53), invalid VAT rate (BR-CO-17),
malformed Peppol ID, or unknown currency code.
{
"error": "Validation failed: street1 not provided, postalCode not provided",
"code": "buyer_address_incomplete"
}Recipient not on the Peppol network (422)
Returned when x-validate-recipient: strict is set and the recipient's
Peppol ID is not registered. Verify with GET /v1/directory/{scheme}/{id} first.
{
"error": "Recipient 0208:BE9999999999 not found in Peppol Directory",
"code": "recipient_not_in_directory"
}Rate limited (429)
Sandbox limit is 10 req/min per key. The response includes a Retry-After header
with the seconds to wait. The SDK retries automatically with exponential backoff.
{
"error": "Rate limit exceeded. Retry after 30 seconds.",
"code": "rate_limited"
}PeppolValidationError (carries the structured validation
object with field paths and suggestions) and PeppolApiError (carries
statusCode and retryAfterMs). Check err instanceof to
branch reliably on error type.