Zinevu
Basics

Conventions

The things that are true of every endpoint, so no reference page has to repeat them.

The envelope#

Every response — success or failure — is an object with data and meta. A list puts its array in data and its cursor in meta; a single record puts the object in data. An error replaces data with errors. Nothing is ever a bare array, which is what lets us add a field to meta without breaking a parser.

{  "data": [ { "id": 14868, "status": "requested" } ],  "meta": { "message": "OK", "has_more": true, "next_cursor": "aWQ6MTQ4Njg", "limit": 25 }}

Versioning#

The version is in the path: https://api.zinevu.com/api/public/v1. Inside v1 we only ever add — new fields, new endpoints, new values of an existing enum. Read a response as an open object rather than a closed one and a new field is a non-event. A removal or a rename would be v2.

MCP is not under /v1

The assistant endpoint speaks somebody else's protocol, whose version travels in a header rather than in the path — so it sits at /api/mcp, outside this prefix.

Ids and codes#

Two kinds of identifier, and which one an endpoint takes is deliberate:

  • id is ours. It is stable, numeric, and what /leads/{id} takes. It also orders the cursor.
  • code is yours. Price matrices are addressed by it, because your catalogue decides what a grid is called and you should not have to store an identifier of ours to update your own prices.
  • article_number is your ERP's. Offer items can be fetched by it directly, in any case, and it is copied onto every offer line it writes.
  • uuid appears on leads and offers. It is the identifier customer-facing links carry; it is never a path parameter here.

Timestamps and time zones#

Everything we return is ISO 8601 with an offset. Send the same: a timestamp without an offset is read as Dutch local time rather than UTC — the same rule the portal uses, so a window saved through either surface lands on the same instant — but sending +02:00 or Z removes the question entirely.

Money and VAT#

priceA plain JSON number, never a string and never cents. 1176 is €1176,00.
nullA cell with no price. A gap in a grid is not a free product, so it is never 0.
currencyThe account's currency, lowercase ISO 4217. null means EUR.
vat_rateA percentage on the line, not on the matrix: one grid can feed lines at different rates.
total_includes_vatWhether the total beside it is gross. Read it before you add anything up.

Nulls, absences and empty lists#

A field we know nothing about is null and still present — we do not drop keys, so your parser can be strict. An empty list is [] with has_more: false, never a 404: "no leads changed since Tuesday" is a successful answer.

Filters#

Filters are query parameters, combined with AND, and an unknown one is ignored — with one deliberate exception. An unparseable updated_since is refused with 422, because a date filter that silently returns everything looks exactly like one that works, and you would only find out when the nightly sync started taking an hour.

Safe by default#

GET never changes anything. Writes carry their own budget and accept an idempotency key. Nothing in this API sends an e-mail to your customer — not creating a lead, not writing a price, not starting a promotion. Sending stays a deliberate act inside the portal.