The Sunventory API.
This is a courtesy translation. Only the German version is legally binding. Read the German version
Your data belongs to you, and your systems can collect it: the Sunventory API serves sites, bills of materials and deliveries as a REST interface. Your ERP can additionally write the bill of materials of a site and get notified by webhook as soon as deliveries arrive. Capture happens on site with the app, analysis happens wherever you want it.
The pattern is deliberately the one integrators already know: REST with an
OpenAPI specification, API keys with scopes, an idempotent BOM upsert via
external_ref, and signed webhooks with at-least-once delivery
and event deduplication, the way Stripe and GitHub do it.
https://app.sunventory.de/api/v1
Authentication
Every request carries an API key of your account as a bearer token. Keys are created and revoked by site management in the web platform under Settings, API. A key applies to the whole account, is shown exactly once when it is created, and stops working the moment it is revoked.
When creating a key you choose its access: read only, or read and write.
The write endpoints, meaning BOM import and webhook management, require a
key with write access. A read-only key receives
403 insufficient_scope there. Existing keys stay read-only,
unchanged.
curl https://app.sunventory.de/api/v1/sites \
-H "Authorization: Bearer svk_your_key" Endpoints
Reading is open to every key, writing only to keys with write access.
Lists are paginated (limit up to 500,
offset); deliveries can additionally be filtered by
site_id, supplier, from,
to (each YYYY-MM-DD) and status
(pending or confirmed).
-
GET /sitesAll sites of the account. -
GET /sites/{id}One site. -
GET /sites/{id}/bomThe bill of materials with planned, confirmed and open quantity per line item. -
PUT /sites/{id}/bomImport the bill of materials: create or update positions, idempotent via external_ref. Needs write access. -
GET /deliveriesDeliveries with line items and delivery note photos, filterable by site, supplier, period and status. -
GET /deliveries/{id}One delivery. -
GET /deliveries.csvThe same deliveries as CSV, one row per line item. -
GET/POST/DELETE /webhooksManage webhooks. Needs write access. -
POST /webhooks/{id}/testSend a signed test event to the endpoint.
The full description of every field comes from the machine itself: openapi.json in OpenAPI 3.1 format, available without a key.
Examples
Delivery progress of a site
curl "https://app.sunventory.de/api/v1/sites/{id}/bom" \
-H "Authorization: Bearer svk_…" {
"data": [{
"supplier": "Canadian Solar",
"product": "HiKu7 CS7L-590MS 590W bifacial",
"planned_quantity": 169500,
"delivered_quantity": 8297,
"pending_quantity": 1188,
"last_delivery_at": "2026-05-27",
…
}]
} Confirmed deliveries of one month as CSV
curl "https://app.sunventory.de/api/v1/deliveries.csv?status=confirmed&from=2026-07-01&to=2026-07-31" \
-H "Authorization: Bearer svk_…" -o deliveries.csv Every delivery carries its delivery note photos as signed, time-limited URLs. Whoever needs the document for posting downloads it straight from the response.
Importing the BOM
PUT /sites/{id}/bom takes order lines from your ERP
into the site's bill of materials, up to 1000 positions per request, all
in one transaction. The anchor is external_ref, for example
purchase order and line from Dynamics 365: positions with a known
reference are updated, new ones are created. Give every position a stable
external_ref and the same import can run any number of times
without ever producing duplicates; only positions without a reference are
created anew on every run. An update takes over the position as a whole,
so empty optional fields overwrite what is stored.
curl -X PUT "https://app.sunventory.de/api/v1/sites/{id}/bom" \
-H "Authorization: Bearer svk_…" \
-H "Content-Type: application/json" \
-d '{
"positions": [
{ "external_ref": "PO-4711/10", "supplier": "Canadian Solar",
"product": "PV module CS7N 700 MB-T", "category": "PV modules",
"planned_quantity": 196000, "unit": "Stück" },
{ "external_ref": "PO-4711/20", "supplier": "LAPP",
"product": "Solar cable H1Z2Z2-K 6 mm²",
"planned_quantity": 84000, "unit": "m" }
]
}' { "created": 2, "updated": 0, "data": [ … ] }
The response lists the touched positions with the same fields as
GET /sites/{id}/bom, including quantities already
delivered. On the second identical run it reports
"created": 0, "updated": 2. Two rules make the import safe:
positions with linked deliveries keep their id, so the delivery proof
stays intact. And the API never deletes. Positions missing from the
import are left untouched; removing is a deliberate act in the app.
Webhooks
Instead of polling, get notified: a webhook calls your system as soon as
a delivery is captured on site (delivery.created) or
confirmed by site management (delivery.confirmed). Register
with a write key:
curl -X POST https://app.sunventory.de/api/v1/webhooks \
-H "Authorization: Bearer svk_…" \
-H "Content-Type: application/json" \
-d '{ "url": "https://erp.example.com/hooks/sunventory",
"events": ["delivery.created", "delivery.confirmed"] }'
The response contains the signing secret (whsec_…) exactly
once. Every event arrives as a POST with this envelope;
data is the delivery with the same fields as
GET /deliveries/{id}, including freshly signed
photo URLs:
{
"event_id": "8f14e45f-…",
"event": "delivery.created",
"timestamp": "2026-08-26T09:41:00.000Z",
"webhook_id": "6512bd43-…",
"data": { … }
}
Delivery is at-least-once, the way Stripe and GitHub do it: the same
event can arrive more than once, but then carries the same
event_id. Your consumer therefore deduplicates on the
event_id and answers with a 2xx within 10 seconds. On
failure, Sunventory retries three more times, after 1, 10 and 60
minutes. After 20 consecutive failures the webhook is deactivated; you
can see its state under Settings, API and via GET /webhooks.
Every delivery is signed. The header
X-Sunventory-Signature: t=<unix>,v1=<hmac>
carries an HMAC-SHA256 over t + "." + raw body with your
secret. Verify the signature and reject timestamps older than about 5
minutes, which protects against replays:
# Python
import hmac, hashlib, time
def verify(header, secret, raw_body):
t, v1 = (part.split("=", 1)[1] for part in header.split(","))
mac = hmac.new(secret.encode(), (t + ".").encode() + raw_body, hashlib.sha256)
fresh = abs(time.time() - int(t)) < 300
return fresh and hmac.compare_digest(mac.hexdigest(), v1)
To set things up, POST /webhooks/{id}/test sends a
signed test event and reports straight back whether your endpoint
accepted it.
Business Central, SAP and others
The API is deliberately plain: HTTPS, JSON, CSV. That means it talks to any system that can record goods receipts, without a dedicated connector.
- Microsoft Dynamics 365 Business Central reads the endpoints via Power Automate or directly from AL through the HttpClient.
- SAP connects the interface through the Integration Suite (CPI) or any other HTTP-capable middleware.
- For everything else, the CSV export is the shortest route: one row per line item, UTF-8, importable as is.
Planning an integration and would rather not build it yourself? Write to us, and we set it up together as part of a pilot project.
Reliability
- Version 1 is a contract: fields are added, but never renamed and never removed.
- Only keys with write access can write, and only the bill of materials. Capture and confirmation stay in the app and the web platform, nothing is ever deleted via the API, and the proof stays unchanged.
- Each key is allowed 120 requests per minute, enough for syncing every minute. Responses carry the usual RateLimit headers.
- Operated in a data centre in Germany, like the whole platform. Details in the privacy policy.