Aller au contenu
Zentra
DémoEssai gratuit

Pour les développeurs

L’API Zentra.

Reliez votre site, votre boutique ou vos outils à Zentra. Lisez vos données, déposez des brouillons et soyez prévenu à chaque facture payée.

Lecture en JSON
Clients, articles, devis, commandes, factures et paiements, par une API REST simple.
Rien sans votre accord
Ce que vos outils envoient arrive en brouillon. Vous le validez dans l’app.
Inclus
Dans Solo, Équipe et Pro, sans supplément. 600 appels par heure et par clé.

Avant de commencer

  1. Dans l’app Zentra, ouvrez Paramètres › API, boutiques et courrier. Ou passez par votre compte en ligne.
  2. Créez une clé et donnez-lui un nom, par exemple « site internet ». Choisissez « Lecture », ou « Lecture et brouillons » si votre outil doit aussi déposer des demandes.
  3. Copiez la clé tout de suite : elle commence par zk_ et ne sera plus affichée. Gardez-la comme un mot de passe, côté serveur seulement.
  4. Ouvrez Zentra sur un poste de l’entreprise. L’app publie alors les données que l’API peut lire, puis les tient à jour pendant qu’elle est ouverte.

Seuls le titulaire et les administrateurs de l’entreprise créent et révoquent les clés. Une clé révoquée cesse de fonctionner immédiatement.

Vos données restent dans l’app : le serveur ne garde que la dernière version publiée de ces six types d’enregistrements, pour les entreprises qui ont créé une clé ou un webhook. Le champ publishedAt de chaque réponse dit quand l’app les a publiés.

Authentification

Chaque appel envoie la clé dans l’en-tête Authorization. Toutes les adresses commencent par :

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

Les réponses sont en JSON, en UTF-8, et ne sont jamais mises en cache. N’appelez pas l’API depuis un navigateur : la clé serait visible.

Ressources

Six ressources en lecture seule. Chacune se lit en liste, ou un enregistrement à la fois par son identifiant.

/customers
Clients
id, number, name, company, contactPerson, email, phone, addressLine1, addressLine2, postalCode, city, country, archived
/articles
Articles et prestations du catalogue
id, kind, sku, name, description, unit, unitPriceCents, vatBp, trackStock, stockQuantityMilli, archived
/quotes
Devis
id, number, customerId, title, status, currency, issueDate, validUntil, subtotalCents, discountCents, vatCents, totalCents, lines
/orders
Commandes clients
id, number, customerId, title, status, currency, orderDate, externalReference, totalCents, lines
/invoices
Factures
id, number, customerId, title, status, type, currency, issueDate, dueDate, subtotalCents, discountCents, vatCents, totalCents, paidCents, openCents, lines
/payments
Paiements reçus
id, invoiceId, date, amountCents, currency, method, reference

Les devis, commandes et factures contiennent leurs lignes, avec position, articleId, description, quantityMilli, unit, unitPriceCents, discountBp, vatBp et totalCents. Chaque enregistrement porte aussi updatedAt et changedAt.

Listes et pagination

Les listes sont triées par date de changement. Pour tout reprendre sans rien manquer, suivez nextCursor tant que hasMore vaut true.

limit
Nombre de résultats par page, de 1 à 100. 50 par défaut.
cursor
La valeur nextCursor de la page précédente.
updated_since
Seulement ce qui a changé depuis cette date (ISO 8601 ou horodatage Unix).
status
Filtre sur le statut, par exemple issued, paid ou draft.
customer_id
Seulement les documents d’un client.
include_deleted
true pour recevoir aussi les suppressions, sous la forme { id, deleted: true }.
Exemple de réponse
{
  "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"
}
Synchroniser les clients
// 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;
}

Montants et quantités

Tous les chiffres sont des entiers calculés par l’app. Le serveur ne recalcule rien.

…Cents
Montants en centimes : 129690 = CHF 1’296.90.
…Milli
Quantités en millièmes : 1000 = une unité.
vatBp, discountBp
Taux en points de base : 100 = 1 %. Le taux normal de TVA s’écrit 810.
currency
CHF ou EUR.
openCents
Solde encore dû sur une facture.

Déposer un brouillon

Un formulaire de votre site peut créer une demande de devis, une commande ou un nouveau client. Le brouillon arrive dans l’app, à valider ou à refuser. Rien n’est enregistré ni envoyé sans vous.

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" }
    ]
  }'
Réponse 202
{ "object": "draft", "id": "in_3b9e…", "kind": "quote", "state": "pending", "duplicate": false }
  • Le champ kind vaut customer, quote ou order. Un devis ou une commande compte de 1 à 200 lignes.
  • Indiquez un client existant par son id, ou ses coordonnées : name, company, email, phone, addressLine1, addressLine2, postalCode, city, country.
  • Chaque ligne a une quantité, et un sku, un articleId ou une description. Les prix éventuels ne sont que des indications : l’app applique votre catalogue et calcule les totaux.
  • Avec l’en-tête Idempotency-Key, un même envoi répété ne crée qu’un seul brouillon. La réponse indique alors duplicate: true.
  • Pour suivre un brouillon, lisez /drafts/ suivi de son id : l’état vaut pending, imported ou rejected, avec la référence du document créé.

Webhooks

Zentra prévient votre adresse https à chaque événement choisi. Ajoutez le webhook au même endroit que les clés. Son secret de signature commence par whsec_ et n’est montré qu’une fois.

customer.created
Un client est créé.
quote.created
Un devis est émis.
quote.accepted
Un devis est accepté.
order.created
Une commande est confirmée.
invoice.created
Une facture est émise.
invoice.paid
Une facture est entièrement payée.
payment.created
Un paiement est enregistré.
draft.imported
Un brouillon déposé par l’API a été validé dans l’app.
draft.rejected
Un brouillon déposé par l’API a été refusé.
Ce que reçoit votre serveur
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", … } }
}

La signature est un HMAC-SHA256 en hexadécimal, calculé avec votre secret sur l’horodatage t, un point, puis le corps reçu. Refusez un horodatage de plus de cinq minutes.

Vérifier la 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));
}

Abonnements par l’API

Zapier, Make, n8n ou votre propre outil peuvent créer et retirer leurs webhooks avec la clé d’API. GET /me vérifie la clé et donne le nom de l’entreprise ; GET /webhooks liste les abonnements.

S’abonner et se désabonner
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_…
  • Répondez par un code 2xx en moins de dix secondes. Les redirections ne sont pas suivies.
  • En cas d’échec, l’envoi est repris jusqu’à huit fois : après 1 minute, 5 minutes, 30 minutes, puis 2, 6, 12 et 24 heures.
  • Après vingt échecs de suite, le webhook se met en pause. Le bouton « Reprendre » le relance.
  • Un même événement peut arriver deux fois : dédoublonnez avec Zentra-Event-Id.
  • Le bouton « Envoyer un test » envoie un événement ping.

Assistants IA (MCP)

Claude, ChatGPT ou tout assistant compatible MCP peut lire vos données Zentra. Il répond à « Qui me doit encore de l’argent ? » à partir des montants calculés par l’app.

Dans Claude, sans clé

  1. Dans Claude, ouvrez Réglages, puis Connecteurs, et choisissez Ajouter un connecteur personnalisé. Sur une formule Team ou Enterprise, c’est un propriétaire de l’organisation Claude qui l’ajoute.
  2. Nommez-le Zentra et collez l’adresse du serveur. Laissez vides les réglages avancés.
  3. Cliquez sur Se connecter. Zentra s’ouvre : connectez-vous, choisissez l’entreprise et autorisez l’accès.
  4. Ouvrez une fois Zentra sur un poste de l’entreprise. L’app publie alors les données que l’assistant lit.
Adresse du serveur
https://zentraapp.ch/api/mcp

Seuls le titulaire et les administrateurs peuvent connecter un assistant, pour une entreprise à la fois. Les assistants connectés sont listés dans votre compte, sous Intégrations : un clic les déconnecte.

Dans Claude Code

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

Lancez ensuite /mcp dans Claude Code et choisissez Zentra pour vous connecter.

Questions prêtes à l’emploi

Le serveur propose aussi des questions toutes faites. Dans Claude, elles apparaissent dans le menu « + » de la conversation.

qui_me_doit
Qui me doit de l’argent ?
ma_journee
Qu’est-ce qui m’attend aujourd’hui ?
relances_semaine
Préparer mes relances de la semaine
resume_mois
Résumer mon mois
creer_devis
Créer un devis
nouveau_client
Ajouter un client

Avec une clé d’API

Un outil qui ne propose pas la connexion au compte peut utiliser une clé d’API, envoyée dans l’en-tête Authorization.

Configuration
{
  "mcpServers": {
    "zentra": {
      "type": "http",
      "url": "https://zentraapp.ch/api/mcp",
      "headers": { "Authorization": "Bearer zk_votre_cle" }
    }
  }
}
search_customers
Cherche des clients par nom, société ou e-mail.
get_customer
Un client et ses dernières factures.
list_invoices
Factures ouvertes, échues, payées ou en brouillon.
get_invoice
Une facture avec ses lignes et ses paiements.
list_quotes
Devis par statut et par client.
list_orders
Commandes clients.
search_articles
Articles du catalogue, avec prix et stock.
company_summary
Chiffres clés : factures ouvertes et échues, nombre d’enregistrements.
prepare_draft
Dépose à valider un client, un fournisseur, un article, un projet, une tâche de projet, un rendez-vous, un devis, une commande, une facture en brouillon ou une note. S’il manque un détail, répond par les questions à poser. Seulement si les brouillons sont autorisés.
draft_requirements
Les informations obligatoires de chaque brouillon, avec la question à poser.
search_projects
Projets et chantiers, pour y rattacher une tâche, un rendez-vous ou une note.
search_suppliers
Fournisseurs par nom ou contact.
list_autopilot_tasks
Les tâches en attente dans Autopilote, avec les actions que l’assistant peut demander.
act_on_autopilot_task
Confirme une tâche à votre demande : créer une fiche ou un brouillon reçu, ajouter un rendez-vous, reporter, ignorer, marquer une tâche faite. Seulement si vous avez coché « Confirmer des tâches Autopilote » à la connexion.
check_request
Dit si un brouillon ou une action demandée est déjà traité par l’app.

L’assistant ne peut rien envoyer, payer ni supprimer. Un brouillon qu’il prépare attend votre validation dans l’app, sauf si vous lui demandez de le confirmer.

Quand un détail manque, Zentra ne devine pas : un article sans prix, un client qui ressemble à plusieurs fiches ou un projet introuvable reviennent à l’assistant sous forme de questions, qu’il vous pose avant de créer.

Une action confirmée est exécutée par l’app Zentra dès qu’elle est ouverte sur un poste de l’entreprise, puis inscrite dans le journal d’Autopilote. Envoyer un mail ou une relance, enregistrer un paiement, émettre une facture et lancer une poursuite se font toujours dans l’app.

Erreurs et limites

Une erreur renvoie un code HTTP et un objet error avec un code stable et un message en français.

{ "error": { "code": "rate_limited", "message": "Trop d’appels pour cette clé. Réessayez dans une heure." } }
400 invalid_query, invalid_draft, invalid_body
La demande est mal formée. Le message dit quel champ corriger.
401 unauthorized
Clé absente, mal copiée ou révoquée.
402 subscription_inactive
L’abonnement de l’entreprise n’est plus actif.
403 forbidden
La clé ne peut que lire. Créez une clé « Lecture et brouillons ».
404 not_found
Ressource ou identifiant inconnu.
405 method_not_allowed
Seul GET est accepté, sauf POST sur drafts.
429 rate_limited
Plus de 600 appels dans l’heure pour cette clé. L’en-tête Retry-After donne l’attente en secondes.
503 unavailable
Service momentanément indisponible. Réessayez plus tard.

Limites : 600 appels par heure et par clé, 100 résultats par page, 64 Ko par brouillon.

Relier Shopify, WooCommerce ou le courrier postal

Essayez l’API sur vos données.

Quatorze jours gratuits, l’API et les webhooks compris.

Sans carte bancaire. Aucun paiement automatique.