Zinevu
Getting started

Authentication

One secret, sent as a bearer token, carrying exactly the scopes it was minted with.

The key#

Every request carries your key as a bearer token. Keys belong to the account, not to the person who created them, so removing a colleague never breaks a running integration.

curl https://api.zinevu.com/api/public/v1/me \  -H "Authorization: Bearer zv_live_..."

If your HTTP client cannot set an Authorization header, send the key as X-Api-Key instead — it is the same secret either way, and neither is accepted in a query string. A missing, unknown, revoked or expired key is 401.

Never in the browser

A key in front-end JavaScript is a key in everyone's hands: it reads the whole account, and CORS does not protect a secret that has already been shipped to the page. Call us from your server.

Scopes#

A key only reaches what its scopes allow. Asking for an endpoint outside them returns 403 with the scope you are missing named in the body, so the fix is in the refusal rather than in a support ticket.

Read and write of the same resource are separate scopes and neither implies the other. Write scopes are also never granted implicitly: a key minted before prices:write existed stays read-only for its whole life, which is what makes an old integration safe to leave running.

ScopeWhat it allows
leads:readList and read leads, including their configuration answers and source.
leads:writeCreate a lead with its customer, as a form would. Never prices, never sends, never updates an existing one.
offers:readList and read offers with their line items and totals.
customers:readList and read customer records and their addresses.
prices:readRead price matrices, materials and offer items.
prices:writeCreate and update price matrices, including the prices in them. Does not reprice existing offers.
promotions:readList and read promotions, their discount and their window.
promotions:writeCreate, change and end promotions, and apply one to offers still open.
webhooks:manageList, subscribe and unsubscribe webhook endpoints.
403 — the scope is named
{  "meta": { "message": "This API key does not have the 'offers:read' scope." },  "errors": { "code": "insufficient_scope", "required_scope": "offers:read" }}

Rotating and revoking#

We store only a SHA-256 hash of your key, so it cannot be shown to you again after it is created. That leaves two different remedies, and picking the wrong one either costs you an outage or leaves the leak open:

Lost it — rotate

Rotation issues a new key and keeps the old one working for seven more days, so you can deploy the new secret without a gap.

Leaked it — revoke

Revocation takes effect immediately. Every request with that key answers 401 from the next one onward, including the request already in flight when you pressed the button.

Expiry#

A key may be given an expiry date when it is created. GET /me reports it as expires_at, and an expired key is refused exactly like a revoked one — so a monitor that reads /me once a day is the cheapest way to never be surprised by it.

Practical hygiene#

  • One key per integration, named after it. When something has to be revoked in a hurry, you want to revoke one thing.
  • Keep the secret in your platform's secret store, never in the repository and never in a Postman environment you sync to a team workspace.
  • Log the prefix (zv_live_8f14), never the key. It is enough to tell two keys apart in a log and useless to anybody who finds it.
  • Read the limits from /me at start-up rather than hard-coding them: a trial key and a paid key do not get the same budget.