Hivebuy External API (v2)
The Hivebuy External API is a versioned API for machine-to-machine integrations: ERP sync, master-data management, archiving, BI, payment runs. The transactional resources (invoices, purchase orders) are read-only apart from order status and document writes; purchase requests can be created and read (pre-release, see that section); the master-data resources (categories, departments, projects, legal entities, suppliers, addresses, catalogues) offer full CRUD. The people directory at /users/ is read plus a batch upsert, built for syncing from an HR system; see the Users section. New resources are added over time; existing fields only change additively.
Old documentation
This site documents the new versioned External API (/api/v2/). The documentation for the previous API is still available here: Link to the old documentation.
Note: this new API requires a new API key. Keys used with the old API do not work here; create a new key in Hivebuy for /api/v2/.
Authentication
Every request needs an API key, sent as a Bearer token:
Authorization: Bearer <your-api-key>
API keys are created in Hivebuy. Browser sessions are not accepted here: a session-authenticated caller is rejected with 403 on this whole tree. It exists for machine callers.
Scopes
A key carries scopes, and they are checked per verb:
- Reads (list, retrieve) on master data need
master-data:read. - Writes (POST, PUT, PATCH, DELETE) on master data need
master-data:write. - Invoices and purchase orders need
transactional:read, and setting a purchase-order status needstransactional:write.
A key holding only master-data:read gets 200 on reads and 403 on writes. There is deliberately no admin scope: no key can change company settings.
Company scoping
Every resource lives under a company id:
/api/v2/{companyId}/{resource}/
/api/v2/{companyId}/{resource}/{id}/
The companyId in the URL is authoritative, not the key's default company. One key can be a member of several companies and addresses each by URL. A company field in a payload is ignored, and company is not part of any response on this tree.
Two conditions must hold for every company you touch, and each is a 403 when unmet: the key reaches the company, and the External API feature is enabled for it. A company-anchored key reaches the companies its user is an active member of; a tenant-wide key reaches every company of its tenant, including companies created after the key was issued. Legal entities and catalogues need one more feature flag each. A company with no flag row at all counts as disabled, not as an error.
Use GET /api/v2/companies/ to discover which company ids your key can use.
Bulk upserts
Five master-data resources (suppliers, departments, categories, legal entities, projects) also offer a machine-shaped bulk write, documented inside each resource's section: POST /api/v2/{companyId}/bulk-upsert/{resource}/. Post a JSON array of rows and get a per-row result; one bad row never fails the batch. These are the /api/v2/ successors to the legacy /api/company/bulk-upsert/… routes, with the same matching algorithm and response envelope, but each resource's v2 field policy: what a row may set is what the resource's own POST accepts.
Matching. Each row is matched against existing records in the URL company only by walking the resource's lookup fields in order; the first field present in the row and matching exactly one record decides. A match updates that record partially, like PATCH: only the keys you send change. No match creates. A field matching several records fails the row with ambiguous_match and the candidate ids. Each resource's bulk operation lists its lookup fields.
Response. Always 200 with two arrays, in row order. upserted echoes each stored row as you sent it plus id and action (created or updated). errors carries one entry per failed row: your row under data, a machine-readable errorCode, a message, and where possible errorField and details. Rows are processed independently, each in its own transaction.
Limits. At most 50 rows per request. A longer list, or a body that is not a JSON array, is refused whole with 400 and a plain {"error": …} body (this one predates the error envelope and existing clients parse it). All five need master-data:write; there is no read half and no other verb on these routes.
Pagination
All list endpoints return the same envelope:
{ "totalRecords": 137, "totalPages": 6, "currentPage": 1, "results": [ ] }
Control it with page (default 1) and pageSize (default 25, maximum 100). An empty result set reports "totalPages": 0, not 1. Note the key is totalRecords, not the count the in-app API uses; this is a different envelope.
Ordering
?ordering=name ascending
?ordering=-name descending
Each resource documents its own accepted keys. An unknown key is a 400 that lists the valid ones, never a silent fallback. A stable -id tiebreaker is always appended, so a paging client cannot see a row twice or skip one on a tie. Name ordering is case-insensitive, and for the locale-keyed names field (categories, catalogues) it sorts on the English value whatever language the key's user has. Categories default to newest first; every other resource defaults to name.
Narrowing a response
?fields= and ?omit= narrow a response. ?expand= is inert here; it cannot widen one, unlike on the in-app API.
Soft delete
DELETE soft-deletes: 204, and the row is gone from the list but still retrievable by id, with deleted set to a timestamp. That is deliberate: a caller holding an id can still read what it deleted. Writes against a deleted row return 404. deleted is null on live rows and read-only everywhere, so never treat a successful GET as proof a row is live: check deleted.
Errors
Every error body has the same shape:
{
"type": "validationError",
"errors": [
{ "code": "required", "detail": "This field is required.", "attr": "names" }
]
}
Payload keys are camelCase, but attr comes back snake_case: you send assignedCategories and an error about it names assigned_categories. This is pre-existing and tree-wide, and clients hard-code these values, so it is documented rather than glossed. attr is null for errors belonging to no single field.
A row belonging to another company is 404 on every verb, never 403: the API does not confirm that the id exists elsewhere.
Rate limiting
60 requests per minute per API key. Above that you receive 429 Too Many Requests; back off and retry.
Differences from the in-app API
Consistently across every resource here:
- No per-user filtering. The in-app endpoints narrow results by the calling user's departments, legal entities or assignments. An API key sees all of its company's rows.
- No per-user ordering (favourites first, defaults first) and no per-user actions such as favouriting.
- Suppliers include unlisted rows; addresses include personal and manual rows.
Contact: Hivebuy Support · support@hivebuy.de
On this page
- Hivebuy External API (v2)