Invoices
Electricity invoices generated from meter readings and created as DRAFT invoices in Xero. Usage is the difference between a pitch's two most recent readings; price is the site's configured GBP/kWh.
The invoice object
{
"id": "inv_9f8e7d",
"object": "invoice",
"source": "electricity",
"status": "draft",
"pitch": "pitch-101",
"pitch_number": "101",
"pitch_accommodation_type": "Super Pitch",
"period": {
"from_reading": "mR4nd0m",
"to_reading": "mR9th3r",
"from_date": "2026-10-31",
"to_date": "2026-11-30"
},
"usage_kwh": 420.5,
"price_per_kwh": 0.25,
"amount_due": 10513,
"currency": "gbp",
"xero": { "invoice_id": "...", "invoice_number": "BF-MR-A1B2C3D4" },
"created": 1793061121,
"duplicate": false
}
amount_due is minor units. Xero invoice numbers carry the BF- system
marker so system-generated invoices are identifiable in Xero.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /v1/invoices | List (pitch and source filters) |
| POST | /v1/invoices | Generate one pitch's invoice |
| POST | /v1/invoices/generate_all | Round operation — invoice every eligible pitch |
| GET | /v1/invoices/{id} | Retrieve |
Generate one invoice
curl -s $API/v1/invoices -H "Authorization: Bearer $KEY" \
-H "Idempotency-Key: inv-pitch-101-1130" -H "Content-Type: application/json" \
-d '{"pitch":"pitch-101"}'
Naturally idempotent: if the pitch's latest reading has already been
invoiced, the existing invoice is returned with "duplicate": true — no
second Xero invoice is ever created for the same closing reading. This holds
even without an Idempotency-Key, so "generate invoices" is safe to run
repeatedly (including generate_all after every meter round).
Errors: configuration_missing (Xero not configured / no price per kWh / no
access token), insufficient_readings (fewer than 2 readings),
negative_usage (meter rolled over or misread — check the readings),
pitch_not_found, resource_missing.
Generate all (round operation)
curl -s $API/v1/invoices/generate_all -H "Authorization: Bearer $KEY" \
-H "Idempotency-Key: invoices-2026-11-30" -H "Content-Type: application/json" \
-d '{"since":"2026-11-30"}'
For every non-deleted pitch with ≥ 2 readings and an un-invoiced latest
reading (on/after since when given), generates the invoice. Response:
{
"object": "bulk_invoice_generation",
"generated": 18, "duplicates": 3, "skipped": 5, "failed": 1,
"results": [
{"pitch": "pitch-101", "pitch_number": "101", "status": "generated",
"invoice": { ... }, "duplicate": false},
{"pitch": "pitch-104", "pitch_number": "104", "status": "skipped",
"reason": "fewer than 2 readings"},
{"pitch": "pitch-109", "pitch_number": "109", "status": "error",
"reason": "Xero API error: ..."}
]
}
One failing pitch never aborts the round. Invoices are DRAFT in Xero; an operator approves them there.