curl --request GET \
--url https://api.zivio.net/api/v4/invoices/summary \
--header 'Authorization: Bearer <token>' \
--header 'zivio-tenant-id: <api-key>'import requests
url = "https://api.zivio.net/api/v4/invoices/summary"
headers = {
"Authorization": "Bearer <token>",
"zivio-tenant-id": "<api-key>"
}
response = requests.get(url, headers=headers)
print(response.text)const options = {
method: 'GET',
headers: {Authorization: 'Bearer <token>', 'zivio-tenant-id': '<api-key>'}
};
fetch('https://api.zivio.net/api/v4/invoices/summary', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zivio.net/api/v4/invoices/summary",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"zivio-tenant-id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.zivio.net/api/v4/invoices/summary"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("zivio-tenant-id", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.zivio.net/api/v4/invoices/summary")
.header("Authorization", "Bearer <token>")
.header("zivio-tenant-id", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zivio.net/api/v4/invoices/summary")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
request["zivio-tenant-id"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"summary": {
"resource": "invoices",
"group_by": [
"category",
"supplier"
],
"measures": [
"net_total"
],
"filters": {
"dated_on": {
"gte": "2025-01-01",
"lte": "2025-12-31"
},
"status": "paid"
},
"defaults_applied": [
"status"
],
"currency": {
"mode": "converted",
"convert_to": "GBP",
"rates_as_of": "2026-06-11"
},
"meta": {
"group_count": 1,
"returned": 1,
"truncated": false,
"order": "net_total desc"
},
"groups": [
{
"dimensions": [
{
"key": "category",
"id": 42,
"label": "Construction"
},
{
"key": "supplier",
"id": 88,
"label": "Acme Build Ltd"
}
],
"net_total": {
"amount": "1250000.00",
"currency": "GBP",
"cents": 125000000
},
"share": 100,
"invoice_count": 87,
"by_currency": [
{
"currency": "GBP",
"net_total_cents": 125000000,
"invoice_count": 87
}
]
}
],
"totals": {
"net_total": {
"amount": "1250000.00",
"currency": "GBP",
"cents": 125000000
},
"invoice_count": 87
}
}
}{
"error": "invalid_token",
"error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons"
}{
"error": "insufficient_scope",
"error_description": "The request requires higher privileges than provided by the access token",
"required_scope": "invoices:read",
"provided_scopes": [
"welcome:read"
]
}{
"error": "measure 'net_total' is not permitted"
}Aggregate invoice spend grouped by one or more dimensions
Pagination-free spend aggregation computed in SQL. Groups the invoices you are permitted to see by one or more dimensions and (always) currency, returning exact per-currency subtotals plus optional converted roll-ups. Reuses the standard filter[...] grammar — including filter[type]=invoice|credit_note|all and projects-backed drill-down filters filter[supplier|category|subcategory|tier3|owner|org]=<id>. The role-aware measure default and the paid-status default are echoed in defaults_applied. Large cross-products are bounded — see meta (group_count / returned / truncated). Forbidden or unknown measures/dimensions/filters return 422 (never a silently wrong number).
curl --request GET \
--url https://api.zivio.net/api/v4/invoices/summary \
--header 'Authorization: Bearer <token>' \
--header 'zivio-tenant-id: <api-key>'import requests
url = "https://api.zivio.net/api/v4/invoices/summary"
headers = {
"Authorization": "Bearer <token>",
"zivio-tenant-id": "<api-key>"
}
response = requests.get(url, headers=headers)
print(response.text)const options = {
method: 'GET',
headers: {Authorization: 'Bearer <token>', 'zivio-tenant-id': '<api-key>'}
};
fetch('https://api.zivio.net/api/v4/invoices/summary', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zivio.net/api/v4/invoices/summary",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"zivio-tenant-id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.zivio.net/api/v4/invoices/summary"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("zivio-tenant-id", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.zivio.net/api/v4/invoices/summary")
.header("Authorization", "Bearer <token>")
.header("zivio-tenant-id", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zivio.net/api/v4/invoices/summary")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
request["zivio-tenant-id"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"summary": {
"resource": "invoices",
"group_by": [
"category",
"supplier"
],
"measures": [
"net_total"
],
"filters": {
"dated_on": {
"gte": "2025-01-01",
"lte": "2025-12-31"
},
"status": "paid"
},
"defaults_applied": [
"status"
],
"currency": {
"mode": "converted",
"convert_to": "GBP",
"rates_as_of": "2026-06-11"
},
"meta": {
"group_count": 1,
"returned": 1,
"truncated": false,
"order": "net_total desc"
},
"groups": [
{
"dimensions": [
{
"key": "category",
"id": 42,
"label": "Construction"
},
{
"key": "supplier",
"id": 88,
"label": "Acme Build Ltd"
}
],
"net_total": {
"amount": "1250000.00",
"currency": "GBP",
"cents": 125000000
},
"share": 100,
"invoice_count": 87,
"by_currency": [
{
"currency": "GBP",
"net_total_cents": 125000000,
"invoice_count": 87
}
]
}
],
"totals": {
"net_total": {
"amount": "1250000.00",
"currency": "GBP",
"cents": 125000000
},
"invoice_count": 87
}
}
}{
"error": "invalid_token",
"error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons"
}{
"error": "insufficient_scope",
"error_description": "The request requires higher privileges than provided by the access token",
"required_scope": "invoices:read",
"provided_scopes": [
"welcome:read"
]
}{
"error": "measure 'net_total' is not permitted"
}Authorizations
OAuth 2.0 client credentials. The access token from POST /oauth/token is sent as a bearer token. Request only the scopes you need.
Your Zivio organisation identifier. The regional API endpoints serve every Zivio organisation, so each request must identify yours.
Query Parameters
One or more dimensions, comma-separated (max 4), combined into a composite grouping. Permitted: category, subcategory, tier3, supplier, owner, org, status, month, quarter, year.
Comma-separated measures (defaults per role if omitted): net_total, tax_total, total, sales_net_total, sales_tax_total, sales_total, invoice_count.
Date column used for month/quarter/year buckets.
dated_on, paid_at, approved_at, due_on Optional ISO currency code for an indicative converted roll-up (per-currency subtotals remain exact).
Standard filter grammar, e.g. filter[dated_on][gte]=2025-01-01&filter[status]=paid. Also filter[type]=invoice|credit_note|all and projects-backed drill-down filter[supplier]=88 / filter[category]=42 (comma for multiple).
Filter by the parent Project's tenant-defined custom-field values (cost centre, PO, department, …). Same operator grammar as cf: cf_project[cost_center]=CC-001, cf_project[department][in]=a,b, cf_project[po][exists]=. (cf[...] continues to filter the invoice's own custom fields.)
Response
Spend summary grouped by the requested dimensions and currency
The response is of type object.

