Resources
Invoicing
A service company's invoice basis, made when a technician approves a service report from speech, and its catalogue. The fields are the same as in the CSV and Excel export, so an accounting system is set up once. When the company sets a webhook address in the app, each approved basis is posted to it as invoice_draft.approved, with the body signed in the header X-Maci-Signature as sha256= and the HMAC-SHA256 of the body with the webhook's secret.
- GET
/invoice-draftsList invoice bases - POST
/invoice-drafts/mark-exportedMark invoice bases exported - GET
/invoice-drafts/{draft}Get an invoice basis - GET
/price-list-itemsList price list items - PUT
/price-list-itemsSave price list items
Webhook: invoice_draft.approved is posted to your own address as it happens. See webhooks.
The invoice draft model
Properties
-
idstring (uuid) -
statusstringOne of
draft,approved -
service_report_idstring (uuid) -
unit_idstring (uuid) -
unit_public_idstring, nullable -
unitstring, nullable -
customerobject -
visit_datestring (date) -
technicianstring, nullable -
currencystring -
total_excl_vatnumber -
approved_atstring (date-time), nullable -
exported_atstring (date-time), nullable -
linesarray
The price list item model
Properties
-
idstring (uuid) -
item_numberstring -
descriptionstring -
unit_of_measurestring -
price_excl_vatnumber -
kindstringOne of
part,refrigerant,labour,travel,other -
activeboolean -
updated_atstring (date-time), nullable
GET/invoice-drafts
List invoice bases
The organisation's invoice basis.
For an accounting system: status=approved&exported=false gives what is ready to invoice and not yet taken.
Scope: invoices:read
Query parameters
-
statusstringOne of
draft,approved -
exportedbooleanfalsefor only those not marked exported,truefor only those marked. -
sincestring (date-time)Only those changed at or after this time.
-
limitintegerRecords per page. At most 100, default 50.
-
cursorstringThe value of
next_cursorfrom the page before.
Response
200 A page of invoice bases with their lines. The fields are those of the invoice draft model.
curl https://api.maci.energy/v1/invoice-drafts \
-H "Authorization: Bearer $MACI_API_KEY"
const response = await fetch('https://api.maci.energy/v1/invoice-drafts', {
headers: {
Authorization: `Bearer ${process.env.MACI_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.maci.energy/v1/invoice-drafts",
headers={"Authorization": f"Bearer {os.environ['MACI_API_KEY']}"},
)
data = response.json()
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.maci.energy/v1/invoice-drafts', [
'headers' => [
'Authorization' => 'Bearer '.getenv('MACI_API_KEY'),
],
]);
$data = json_decode($response->getBody(), true);
{
"data": [
{
"id": "3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93",
"status": "draft",
"service_report_id": "9a4f0e2b-7c13-4d68-b5e1-2d9c6a3f8b07",
"unit_id": "0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11",
"unit_public_id": "MC-0482",
"unit": "Cold room 2",
"customer": {
"name": "Cold room 2",
"cvr": "Cvr",
"vat_number": null
},
"visit_date": "2026-10-30",
"technician": "Jonas L.",
"currency": "DKK",
"total_excl_vat": 1890,
"approved_at": "2026-10-30T09:15:00Z",
"exported_at": null,
"lines": [
{
"type": "part",
"item_number": "1000",
"description": "Labour",
"quantity": 1,
"unit_of_measure": "hours",
"unit_price": 700,
"amount_excl_vat": 1050,
"source": "speech",
"match_sure": true
}
]
}
],
"next_cursor": null
}
POST/invoice-drafts/mark-exported
Mark invoice bases exported
Mark invoice bases exported.
Only approved ones are marked, and only the first time; calling it again with the same IDs is no error.
Scope: invoices:write
Body
-
idsarray required
Response
200 How many were marked now, the bases as they are, and the IDs not found. The fields are those of the invoice draft model.
curl -X POST https://api.maci.energy/v1/invoice-drafts/mark-exported \
-H "Authorization: Bearer $MACI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93"
]
}'
const response = await fetch('https://api.maci.energy/v1/invoice-drafts/mark-exported', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MACI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"ids": [
"3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93"
]
}),
});
const data = await response.json();
import os
import requests
response = requests.post(
"https://api.maci.energy/v1/invoice-drafts/mark-exported",
headers={"Authorization": f"Bearer {os.environ['MACI_API_KEY']}"},
json={
"ids": [
"3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93"
]
},
)
data = response.json()
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.maci.energy/v1/invoice-drafts/mark-exported', [
'headers' => [
'Authorization' => 'Bearer '.getenv('MACI_API_KEY'),
],
'json' => [
'ids' => [
'3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93',
],
],
]);
$data = json_decode($response->getBody(), true);
{
"marked": 1,
"data": [
{
"id": "3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93",
"status": "draft",
"service_report_id": "9a4f0e2b-7c13-4d68-b5e1-2d9c6a3f8b07",
"unit_id": "0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11",
"unit_public_id": "MC-0482",
"unit": "Cold room 2",
"customer": {
"name": "Cold room 2",
"cvr": "Cvr",
"vat_number": null
},
"visit_date": "2026-10-30",
"technician": "Jonas L.",
"currency": "DKK",
"total_excl_vat": 1890,
"approved_at": "2026-10-30T09:15:00Z",
"exported_at": null,
"lines": [
{
"type": "part",
"item_number": "1000",
"description": "Labour",
"quantity": 1,
"unit_of_measure": "hours",
"unit_price": 700,
"amount_excl_vat": 1050,
"source": "speech",
"match_sure": true
}
]
}
],
"not_found": [
"9bcd14b2-18ed-41bf-a2d2-1978a263be9d"
]
}
GET/invoice-drafts/{draft}
Get an invoice basis
One invoice basis with all its lines and sources.
Scope: invoices:read
Path parameters
-
draftstring (uuid) requiredThe ID of the invoice basis.
Response
200 The invoice basis. The fields are those of the invoice draft model.
curl https://api.maci.energy/v1/invoice-drafts/3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93 \
-H "Authorization: Bearer $MACI_API_KEY"
const response = await fetch('https://api.maci.energy/v1/invoice-drafts/3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93', {
headers: {
Authorization: `Bearer ${process.env.MACI_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.maci.energy/v1/invoice-drafts/3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93",
headers={"Authorization": f"Bearer {os.environ['MACI_API_KEY']}"},
)
data = response.json()
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.maci.energy/v1/invoice-drafts/3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93', [
'headers' => [
'Authorization' => 'Bearer '.getenv('MACI_API_KEY'),
],
]);
$data = json_decode($response->getBody(), true);
{
"id": "3c8d2a71-6e0b-4f95-a2d4-8b1e7c5f0a93",
"status": "draft",
"service_report_id": "9a4f0e2b-7c13-4d68-b5e1-2d9c6a3f8b07",
"unit_id": "0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11",
"unit_public_id": "MC-0482",
"unit": "Cold room 2",
"customer": {
"name": "Cold room 2",
"cvr": "Cvr",
"vat_number": null
},
"visit_date": "2026-10-30",
"technician": "Jonas L.",
"currency": "DKK",
"total_excl_vat": 1890,
"approved_at": "2026-10-30T09:15:00Z",
"exported_at": null,
"lines": [
{
"type": "part",
"item_number": "1000",
"description": "Labour",
"quantity": 1,
"unit_of_measure": "hours",
"unit_price": 700,
"amount_excl_vat": 1050,
"source": "speech",
"match_sure": true
}
]
}
GET/price-list-items
List price list items
The organisation's catalogue.
Scope: price-list:read
Query parameters
-
limitintegerRecords per page. At most 100, default 50.
-
cursorstringThe value of
next_cursorfrom the page before.
Response
200 A page of items. The fields are those of the price list item model.
curl https://api.maci.energy/v1/price-list-items \
-H "Authorization: Bearer $MACI_API_KEY"
const response = await fetch('https://api.maci.energy/v1/price-list-items', {
headers: {
Authorization: `Bearer ${process.env.MACI_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.maci.energy/v1/price-list-items",
headers={"Authorization": f"Bearer {os.environ['MACI_API_KEY']}"},
)
data = response.json()
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.maci.energy/v1/price-list-items', [
'headers' => [
'Authorization' => 'Bearer '.getenv('MACI_API_KEY'),
],
]);
$data = json_decode($response->getBody(), true);
{
"data": [
{
"id": "f6ec47fe-e08d-464d-aba0-eaf6b286a073",
"item_number": "1000",
"description": "Labour",
"unit_of_measure": "hours",
"price_excl_vat": 700,
"kind": "part",
"active": true,
"updated_at": "2026-10-30T09:15:00Z"
}
],
"next_cursor": null
}
PUT/price-list-items
Save price list items
Create or update items from the accounting system.
Each item is found by its item number, whatever its case or punctuation, and created when it is new.
Scope: price-list:write
Body
-
itemsarray required
Response
200 How many were created, updated and skipped.
curl -X PUT https://api.maci.energy/v1/price-list-items \
-H "Authorization: Bearer $MACI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"item_number": "1000",
"description": "Labour",
"unit_of_measure": "hours",
"price_excl_vat": 700,
"kind": "part",
"active": true
}
]
}'
const response = await fetch('https://api.maci.energy/v1/price-list-items', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.MACI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"items": [
{
"item_number": "1000",
"description": "Labour",
"unit_of_measure": "hours",
"price_excl_vat": 700,
"kind": "part",
"active": true
}
]
}),
});
const data = await response.json();
import os
import requests
response = requests.put(
"https://api.maci.energy/v1/price-list-items",
headers={"Authorization": f"Bearer {os.environ['MACI_API_KEY']}"},
json={
"items": [
{
"item_number": "1000",
"description": "Labour",
"unit_of_measure": "hours",
"price_excl_vat": 700,
"kind": "part",
"active": True
}
]
},
)
data = response.json()
$client = new \GuzzleHttp\Client();
$response = $client->request('PUT', 'https://api.maci.energy/v1/price-list-items', [
'headers' => [
'Authorization' => 'Bearer '.getenv('MACI_API_KEY'),
],
'json' => [
'items' => [
[
'item_number' => '1000',
'description' => 'Labour',
'unit_of_measure' => 'hours',
'price_excl_vat' => 700,
'kind' => 'part',
'active' => true,
],
],
],
]);
$data = json_decode($response->getBody(), true);
{
"created": 1,
"updated": 1,
"skipped": 1
}