Skip to content
Zentra
DemoFree trial

For developers

The Zentra API.

Connect your website, your shop or your tools to Zentra. Read your data, submit drafts and get notified of every paid invoice.

JSON reads
Customers, items, quotes, orders, invoices and payments through a simple REST API.
Nothing without your approval
What your tools send arrives as a draft. You approve it in the app.
Included
In Solo, Team and Pro at no extra cost. 600 calls per hour per key.

Before you start

  1. In the Zentra app, open Settings › API, shops and post. Or use your online account.
  2. Create a key and give it a name, for example “website”. Choose “Read”, or “Read and drafts” if your tool should also submit requests.
  3. Copy the key right away: it starts with zk_ and will not be shown again. Keep it like a password, on the server side only.
  4. Open Zentra on one of the company’s computers. The app then publishes the data the API can read, and keeps it up to date while it is open.

Only the company’s owner and administrators create and revoke keys. A revoked key stops working immediately.

Your data stays in the app: the server only keeps the latest published version of these six record types, for companies that have created a key or a webhook. The publishedAt field of each response says when the app published them.

Authentication

Every call sends the key in the Authorization header. All addresses start with:

https://zentraapp.ch/api/v1
Example
curl https://zentraapp.ch/api/v1/invoices?status=issued&limit=20 \
  -H "Authorization: Bearer zk_votre_cle"

Responses are JSON in UTF-8 and are never cached. Do not call the API from a browser: the key would be visible.

Resources

Six read-only resources. Each can be read as a list, or one record at a time by its id.

/customers
Customers
id, number, name, company, contactPerson, email, phone, addressLine1, addressLine2, postalCode, city, country, archived
/articles
Catalogue items and services
id, kind, sku, name, description, unit, unitPriceCents, vatBp, trackStock, stockQuantityMilli, archived
/quotes
Quotes
id, number, customerId, title, status, currency, issueDate, validUntil, subtotalCents, discountCents, vatCents, totalCents, lines
/orders
Customer orders
id, number, customerId, title, status, currency, orderDate, externalReference, totalCents, lines
/invoices
Invoices
id, number, customerId, title, status, type, currency, issueDate, dueDate, subtotalCents, discountCents, vatCents, totalCents, paidCents, openCents, lines
/payments
Payments received
id, invoiceId, date, amountCents, currency, method, reference

Quotes, orders and invoices contain their lines, with position, articleId, description, quantityMilli, unit, unitPriceCents, discountBp, vatBp and totalCents. Every record also carries updatedAt and changedAt.

Lists and pagination

Lists are sorted by change date. To fetch everything without missing anything, follow nextCursor while hasMore is true.

limit
Results per page, from 1 to 100. 50 by default.
cursor
The nextCursor value of the previous page.
updated_since
Only what has changed since this date (ISO 8601 or Unix timestamp).
status
Filter by status, for example issued, paid or draft.
customer_id
Only one customer’s documents.
include_deleted
true to also receive deletions, as { id, deleted: true }.
Example response
{
  "object": "list",
  "entity": "invoices",
  "data": [
    {
      "object": "invoice",
      "id": "inv_8f2c",
      "number": "2026-0142",
      "customerId": "cus_51a0",
      "status": "issued",
      "currency": "CHF",
      "issueDate": "2026-10-20",
      "dueDate": "2026-11-19",
      "totalCents": 129690,
      "paidCents": 0,
      "openCents": 129690,
      "lines": [ … ],
      "changedAt": "2026-10-20T09:14:03.000Z"
    }
  ],
  "hasMore": true,
  "nextCursor": "c1_WzE3OTI…",
  "publishedAt": "2026-10-26T07:55:12.000Z"
}
Sync customers
// Node 18 ou plus récent : reprendre tout ce qui a changé depuis la dernière fois
const headers = { Authorization: `Bearer ${process.env.ZENTRA_API_KEY}` };
let url = 'https://zentraapp.ch/api/v1/customers?limit=100&include_deleted=true&updated_since=2026-10-01';

while (url) {
  const response = await fetch(url, { headers });
  if (!response.ok) throw new Error((await response.json()).error.message);
  const page = await response.json();
  for (const customer of page.data) console.log(customer.id, customer.name ?? 'supprimé');
  url = page.nextCursor ? `https://zentraapp.ch/api/v1/customers?limit=100&include_deleted=true&cursor=${page.nextCursor}` : null;
}

Amounts and quantities

All figures are integers calculated by the app. The server recalculates nothing.

…Cents
Amounts in cents: 129690 = CHF 1’296.90.
…Milli
Quantities in thousandths: 1000 = one unit.
vatBp, discountBp
Rates in basis points: 100 = 1%. The standard VAT rate is written 810.
currency
CHF or EUR.
openCents
Balance still due on an invoice.

Submit a draft

A form on your website can create a quote request, an order or a new customer. The draft arrives in the app, to approve or reject. Nothing is saved or sent without you.

POST /drafts
curl -X POST https://zentraapp.ch/api/v1/drafts \
  -H "Authorization: Bearer zk_votre_cle" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: site-demande-20261026-0042" \
  -d '{
    "kind": "quote",
    "customer": { "name": "Marie Rochat", "email": "marie.rochat@example.ch" },
    "reference": "Formulaire du site",
    "note": "Pose prévue en novembre",
    "lines": [
      { "sku": "RAD-600", "description": "Radiateur 600 W", "quantity": "3" },
      { "description": "Pose et mise en service", "quantity": "1.5" }
    ]
  }'
Response 202
{ "object": "draft", "id": "in_3b9e…", "kind": "quote", "state": "pending", "duplicate": false }
  • The kind field is customer, quote or order. A quote or an order has 1 to 200 lines.
  • Give an existing customer by their id, or their details: name, company, email, phone, addressLine1, addressLine2, postalCode, city, country.
  • Each line has a quantity, and a sku, an articleId or a description. Any prices are only hints: the app applies your catalogue and calculates the totals.
  • With the Idempotency-Key header, a repeated submission creates only one draft. The response then shows duplicate: true.
  • To follow a draft, read /drafts/ followed by its id: the state is pending, imported or rejected, with the reference of the document created.

Webhooks

Zentra notifies your https address at every event you choose. Add the webhook in the same place as the keys. Its signing secret starts with whsec_ and is shown only once.

customer.created
A customer is created.
quote.created
A quote is issued.
quote.accepted
A quote is accepted.
order.created
An order is confirmed.
invoice.created
An invoice is issued.
invoice.paid
An invoice is paid in full.
payment.created
A payment is recorded.
draft.imported
A draft submitted through the API was approved in the app.
draft.rejected
A draft submitted through the API was rejected.
What your server receives
POST https://exemple.ch/zentra
Zentra-Event: invoice.paid
Zentra-Event-Id: evt_…
Zentra-Delivery-Id: dlv_…
Zentra-Signature: t=1793091600,v1=5d41c0…

{
  "id": "evt_…",
  "type": "invoice.paid",
  "apiVersion": 1,
  "createdAt": "2026-10-26T08:20:00.000Z",
  "data": { "object": "invoice", "record": { "id": "inv_8f2c", "status": "paid", … } }
}

The signature is a hexadecimal HMAC-SHA256, computed with your secret over the timestamp t, a dot, then the body received. Reject a timestamp older than five minutes.

Verify the signature (Node)
import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody : le corps reçu, tel quel, avant tout JSON.parse
export function isFromZentra(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(signatureHeader.split(',').map((part) => part.split('=')));
  const timestamp = Number(parts.t);
  if (!Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
  return parts.v1?.length === expected.length && timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}

Subscriptions through the API

Zapier, Make, n8n or your own tool can create and remove their webhooks with the API key. GET /me checks the key and gives the company’s name; GET /webhooks lists the subscriptions.

Subscribe and unsubscribe
POST https://zentraapp.ch/api/v1/webhooks
Authorization: Bearer zk_votre_cle
Content-Type: application/json

{ "url": "https://hooks.zapier.com/hooks/catch/…", "events": ["invoice.paid"] }

201 { "object": "webhook", "id": "whe_…", "events": ["invoice.paid"], "secret": "whsec_…" }

DELETE https://zentraapp.ch/api/v1/webhooks/whe_…
  • Reply with a 2xx code within ten seconds. Redirects are not followed.
  • On failure, delivery is retried up to eight times: after 1 minute, 5 minutes, 30 minutes, then 2, 6, 12 and 24 hours.
  • After twenty failures in a row, the webhook pauses. The “Resume” button restarts it.
  • The same event can arrive twice: deduplicate with Zentra-Event-Id.
  • The “Send a test” button sends a ping event.

AI assistants (MCP)

Claude, ChatGPT or any MCP-compatible assistant can read your Zentra data. It answers “Who still owes me money?” from the amounts calculated by the app.

In Claude, without a key

  1. In Claude, open Settings, then Connectors, and choose Add custom connector. On a Team or Enterprise plan, an owner of the Claude organization adds it.
  2. Name it Zentra and paste the server address. Leave the advanced settings empty.
  3. Click Connect. Zentra opens: sign in, choose the company and allow access.
  4. Open Zentra once on one of the company’s computers. The app then publishes the data the assistant reads.
Server address
https://zentraapp.ch/api/mcp

Only the account holder and administrators can connect an assistant, one company at a time. Connected assistants are listed in your account under Integrations, where one click disconnects them.

In Claude Code

Terminal
claude mcp add --transport http zentra https://zentraapp.ch/api/mcp

Then run /mcp in Claude Code and choose Zentra to sign in.

Ready-made questions

The server also offers ready-made questions. In Claude, they appear in the conversation’s “+” menu.

qui_me_doit
Who owes me money?
ma_journee
What is waiting for me today?
relances_semaine
Prepare this week’s reminders
resume_mois
Summarise my month
creer_devis
Create a quote
nouveau_client
Add a customer

With an API key

A tool that does not offer account sign-in can use an API key, sent in the Authorization header.

Setup
{
  "mcpServers": {
    "zentra": {
      "type": "http",
      "url": "https://zentraapp.ch/api/mcp",
      "headers": { "Authorization": "Bearer zk_votre_cle" }
    }
  }
}
search_customers
Searches customers by name, company or email.
get_customer
A customer and their latest invoices.
list_invoices
Open, overdue, paid or draft invoices.
get_invoice
An invoice with its lines and payments.
list_quotes
Quotes by status and by customer.
list_orders
Customer orders.
search_articles
Catalogue items, with price and stock.
company_summary
Key figures: open and overdue invoices, number of records.
prepare_draft
Files for approval a customer, supplier, item, project, project task, appointment, quote, order, draft invoice or note. If a detail is missing, it answers with the questions to ask. Only if drafts are allowed.
draft_requirements
The required details of each draft, with the question to ask.
search_projects
Projects and sites, to attach a task, an appointment or a note.
search_suppliers
Suppliers by name or contact.
list_autopilot_tasks
The tasks waiting in Autopilot, with the actions the assistant may request.
act_on_autopilot_task
Confirms a task when you ask: create a record or a received draft, add an appointment, postpone, ignore, mark a task done. Only if you ticked “Confirm Autopilot tasks” when connecting.
check_request
Says whether a draft or a requested action has already been handled by the app.

The assistant cannot send, pay or delete anything. A draft it prepares waits for your approval in the app, unless you ask it to confirm it.

When a detail is missing, Zentra does not guess: an item without a price, a customer resembling several records or a project that cannot be found go back to the assistant as questions, which it asks you before creating.

A confirmed action is carried out by the Zentra app as soon as it is open on one of the company’s computers, then written to the Autopilot log. Sending an e-mail or a reminder, recording a payment, issuing an invoice and starting debt collection always happen in the app.

Errors and limits

An error returns an HTTP status and an error object with a stable code and a message in French.

{ "error": { "code": "rate_limited", "message": "Trop d’appels pour cette clé. Réessayez dans une heure." } }
400 invalid_query, invalid_draft, invalid_body
The request is malformed. The message says which field to fix.
401 unauthorized
Key missing, miscopied or revoked.
402 subscription_inactive
The company’s subscription is no longer active.
403 forbidden
The key can only read. Create a “Read and drafts” key.
404 not_found
Unknown resource or id.
405 method_not_allowed
Only GET is accepted, except POST on drafts.
429 rate_limited
More than 600 calls in the hour for this key. The Retry-After header gives the wait in seconds.
503 unavailable
Service temporarily unavailable. Try again later.

Limits: 600 calls per hour per key, 100 results per page, 64 KB per draft.

Connect Shopify, WooCommerce or postal mail

Try the API on your own data.

Fourteen days free, API and webhooks included.

No credit card. No automatic payment.