Guide d'intégration
YAOKA Partners API
Ce guide t'explique comment intégrer YAOKA dans Qualibox, étape par étape, sans supposer que tu connais déjà notre stack. Si tu sais faire un fetch() en JS, tu peux suivre.
Migration OAuth en cours : le modèle d'auth a changé. Les clés sk_qbx_master_* et le provisioning par master key ne fonctionnent plus. Les OFs s'inscrivent eux-mêmes sur YAOKA et autorisent Qualibox via OAuth 2.0. Voir /partners/qualibox section technique pour le nouveau flow. Les sections de ce guide sont en cours de réécriture — les endpoints REST (/api/v1/orgs/[id]/x) restent identiques, seul l'header Authorization change (access_token OAuth ou clé org-owned sk_org_*).
Base URL
app.yaoka.fr/api/v1Format
JSON · UTF-8Auth
OAuth 2.0 + API keysMise à jour
2026-05-25C'est quoi cette API ?
L'API YAOKA Partners est un service web qui s'occupe de toute la facturation française pour vos clients OFs : générer les factures, les rendre conformes à la loi 2026, les pousser dans Pennylane, suivre les paiements.
Votre code Qualibox envoie des messages chez nous (ex: "crée une facture pour le client X, montant 1200 €"), et on s'occupe du reste. C'est une API REST classique — pas de truc exotique.
Métaphore
Pensez à Stripe pour les paiements. Vous n'avez pas implémenté le protocole carte bancaire — vous appelez l'API Stripe. YAOKA, c'est pareil mais pour la facturation française conforme.
Prérequis avant de commencer
Avant de coder, assurez-vous d'avoir :
- Une master API key YAOKA — on vous la donne après le call de démo. Ressemble à
sk_qbx_test_xxxxxxxxxxxx. Latestdedans = mode sandbox (gratuit, sans effets réels). - Un client HTTP — n'importe lequel : fetch en JS, requests en Python, le client HTTP de Go, ou juste cURL pour tester.
- Capacité à recevoir des webhooks — une URL HTTPS publique côté Qualibox qui peut recevoir nos messages POST. Pour tester en local :
ngrokoucloudflare tunnel. - Une base de données chez vous pour stocker, pour chaque OF que vous provisionnez : son orgId YAOKA + son apiKey per-org.
Vocabulaire
Tous les termes qu'on utilise dans la doc, définis.
| Terme | Définition |
|---|---|
| Endpoint | Une URL précise dans notre API. Ex: POST /api/v1/orgs crée un OF, GET /api/v1/orgs/{id} récupère son status. |
| Master API key | Votre clé Qualibox principale (sk_qbx_*). Permet de créer des OFs. Ne donne PAS accès aux données métier d'un OF spécifique. |
| Per-org API key | Une clé par OF (sk_org_*), retournée à la création d'un OF. Sert à émettre ses factures, devis, etc. Scopée — ne peut accéder qu'à CET OF. |
| Idempotency-Key | Un identifiant unique que vous envoyez en header sur un POST. Si vous le rejouez (ex: timeout réseau, vous re-essayez), on ne fait pas l'action 2 fois. Sécurité essentielle pour éviter les doubles factures. |
| Webhook | Message HTTP qu'on push à une URL chez vous quand un événement important survient (ex: facture payée). Vous configurez l'URL une fois dans votre dashboard admin. |
| HMAC SHA-256 | Signature cryptographique des webhooks. À chaque message, on calcule une signature avec un secret partagé. Vous la vérifiez pour être sûr que ça vient de nous. |
| Sandbox | Mode test. Mêmes URLs, mêmes endpoints, mais aucun effet réel (pas d'envoi Pennylane, pas de facturation chez nous). Activé par le préfixe de la master key (sk_qbx_test_*). |
| Org (Organization) | Dans notre vocabulaire, un OF = une org. orgId = identifiant unique YAOKA de cet OF. |
Quickstart en 10 minutes
Du zéro à une vraie facture émise. Copiez-collez, ça marche.
Étape 0 — Stocker votre master key
Mettez votre clé dans une variable d'environnement. JAMAIS dans le code en clair, JAMAIS dans le browser.
bash
# Dans votre fichier .env du backend Qualibox YAOKA_MASTER_KEY=sk_qbx_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Étape 1 — Provisionner un OF
Premier appel. On crée l'OF dans notre DB et un compte Pennylane à son nom (en mode test, le compte Pennylane est mocké).
cURL
curl -X POST https://app.yaoka.fr/api/v1/orgs \
-H "Authorization: Bearer $YAOKA_MASTER_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "FormaPro Excellence",
"siret": "12345678901234",
"email": "contact@formapro.fr",
"firstName": "Marie",
"lastName": "Dupont",
"qualiboxRefId": "qbx-of-4567"
}'Ce que vous recevez :
JSON
{
"orgId": "org_abc123",
"apiKey": "sk_org_test_xyz789",
"slug": "formapro-excellence",
"isSandbox": true,
"pennylane": {
"companyId": 5184271,
"simulated": true
}
}Important
L'apiKey ne sera plus jamais affichée. Stockez-la dans votre DB Qualibox tout de suite, indexée par qualiboxRefId. Sans elle, vous ne pourrez plus rien faire pour cet OF (ni émettre des factures, ni rien).
Étape 2 — Créer un client
Avec la per-org API key, créez un client (le client de l'OF — c'est lui qui va recevoir la facture).
cURL
curl -X POST https://app.yaoka.fr/api/v1/orgs/org_abc123/customers \
-H "Authorization: Bearer sk_org_test_xyz789" \
-H "Content-Type: application/json" \
-d '{
"externalRef": "qbx-cust-9912",
"type": "company",
"name": "ACME Industries",
"siret": "98765432109876",
"emails": ["compta@acme.fr"]
}'Retour : { "customerId": "cust_qrs", "created": true }
Étape 3 — Émettre la facture (avec PDF Factur-X)
Le moment magique. On crée la facture, on génère le PDF Factur-X conforme, on push dans Pennylane. Le finalize: true dit "c'est pas un brouillon, attribue un numéro légal et fais tout le tralala".
cURL
curl -X POST https://app.yaoka.fr/api/v1/orgs/org_abc123/invoices \
-H "Authorization: Bearer sk_org_test_xyz789" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"customerId": "cust_qrs",
"lines": [{
"label": "Formation Excel niveau 2 — 14h",
"quantity": 1,
"unitPrice": 1200,
"vatRate": 0
}],
"dueInDays": 30,
"finalize": true
}'Retour :
JSON
{
"invoiceId": "inv_klm",
"number": "FAC-2026-0042",
"status": "finalized",
"totalTtc": 1200,
"pdfUrl": "https://utfs.io/f/xxxxx.pdf",
"pennylane": {
"synced": true,
"pennylaneImportId": "..."
}
}Le pdfUrl pointe vers le PDF Factur-X — c'est ce que vous attachez/linkez dans l'email que Qualibox envoie au client OF.
Bravo
Vous venez d'émettre votre première facture YAOKA en sandbox. En mode live (clé sk_qbx_live_*), la même séquence fonctionne identiquement mais avec des vrais effets : vrai compte Pennylane, vrais documents légaux.
Authentification
Comment prouver à YAOKA que c'est bien vous qui appelez.
Header HTTP
Toutes les requêtes utilisent un header standard Authorization: Bearer <votre-clé>. Comme partout en REST.
HTTP
Authorization: Bearer sk_qbx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Content-Type: application/json Idempotency-Key: <uuid> # recommandé sur tous les POST
Deux types de clés
Selon l'endpoint, vous utilisez l'une ou l'autre :
- Master key (
sk_qbx_*) — UNE seule pour tout Qualibox. Permet de créer des OFs. Ne donne PAS accès aux opérations métier d'un OF. À stocker en env var côté backend. - Per-org key (
sk_org_*) — UNE par OF, retournée à la création de l'OF. Permet TOUT pour CET OF (clients, devis, factures…). À stocker dans votre DB Qualibox, indexée par votre référence interne.
Sécurité
Les clés sont stockées chez nous sous forme de hash SHA-256 uniquement. Si vous perdez une clé, on ne peut PAS vous la redonner — il faudra la régénérer (l'ancienne devient invalide instantanément).
Sandbox vs Live
Le mode de votre master key détermine si c'est pour de vrai ou pour tester.
| Aspect | Sandbox (sk_qbx_test_*) | Live (sk_qbx_live_*) |
|---|---|---|
| URL | app.yaoka.fr/api/v1 | app.yaoka.fr/api/v1(même) |
| Org créée en DB ? | Oui (taggée sandbox) | Oui |
| Compte Pennylane réel ? | Non (mocké) | Oui (Reseller API) |
| Email Pennylane PA envoyé ? | Non | Oui (à l'OF) |
| PDF Factur-X généré ? | Oui (réel) | Oui (réel) |
| Push Pennylane réel ? | Non (mocké) | Oui |
| Facturé chez YAOKA ? | Non (gratuit) | Oui (20€/OF/mois) |
| Visibilité admin ? | Badge SANDBOX jaune | Standard |
Recommandation
Faites toute votre intégration en sandbox. Quand tout marche, on vous donne une live key et vous basculez juste la valeur de la variable d'environnement. Aucun changement de code.
Idempotency (anti-double-facture)
Comment éviter qu'un retry réseau crée 2 factures.
Imaginez : vous envoyez POST /invoices, votre dev timeout au bout de 30s sans réponse, votre code retry. Sans précaution, on crée 2 factures.
La solution : envoyez un header Idempotency-Key: <uuid-unique>. Si on voit le même key dans les 24h, on ne fait pas l'action 2 fois — on renvoie la réponse du 1er appel.
bash
# 1er appel
curl -X POST .../invoices \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '...'
# → { "invoiceId": "inv_aaa", "number": "FAC-2026-0042" }
# 2e appel (même Idempotency-Key, même body)
curl -X POST .../invoices \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '...'
# → { "invoiceId": "inv_aaa", "number": "FAC-2026-0042" } (idem)Bonne pratique
Générez un UUID v4 dans votre code (lib standard de JS / Python / Go) AVANT le 1er appel. Stockez-le. Si vous retry, réutilisez le MÊME UUID.
Format des erreurs
Toutes les erreurs ont le même format JSON.
JSON
{
"error": {
"code": "validation_error",
"message": "Le champ 'siret' doit contenir 14 chiffres",
"field": "siret"
}
}Le champ code est stable — vous pouvez switcher dessus dans votre code pour réagir différemment selon le type d'erreur. Le message est lisible humain et peut changer entre versions.
| HTTP | Code | Quand |
|---|---|---|
| 400 | validation_error | Body JSON invalide (champ manquant, mauvais type, etc.) |
| 401 | auth_required | Header Authorization manquant |
| 403 | auth_invalid | Clé API révoquée ou invalide |
| 403 | auth_wrong_scope | Vous utilisez une master key sur un endpoint per-org (ou inverse) |
| 404 | not_found | L'OF/client/facture n'existe pas |
| 409 | conflict | Idempotency-Key réutilisé avec un body différent |
| 422 | business_rule_violation | Ex: facture sans ligne, transition de status invalide |
| 429 | rate_limited | Trop de requêtes (voir headers X-RateLimit-*) |
| 500 | internal_error | Bug chez nous, on est notifiés en temps réel |
| 503 | pennylane_unreachable | Pennylane down, retry après 30s |
Rate limits
Combien d'appels par minute on accepte.
- Master key : 100 requêtes / minute
- Per-org key : 60 requêtes / minute
- Burst : 20 req/s pendant 5s autorisé (token bucket)
Chaque réponse contient les headers X-RateLimit-Remaining et X-RateLimit-Reset. En cas de dépassement, vous recevez un 429 — backoff exponentiel recommandé.
Provisioning — créer un OF
Master key requise.
/api/v1/orgsCrée un OF dans YAOKA. En mode live, crée aussi le compte Pennylane via notre Reseller API. Retourne une per-org API key (montrée 1 fois).
| Champ | Type | Description |
|---|---|---|
name* | string | Raison sociale de l'OF |
siretoptionnel | string(14) | SIRET sans espaces |
email* | string | Email de contact principal de l'OF |
firstName* | string | Prénom du contact (requis par Pennylane) |
lastName* | string | Nom du contact |
phoneoptionnel | string | Téléphone format +33... |
ibanoptionnel | string | IBAN affiché sur les factures |
logoUrloptionnel | url | URL HTTPS du logo de l'OF (PDF Factur-X) |
addressoptionnel | object | { street, city, zip, country } |
qualiboxRefIdoptionnel | string | Votre référence interne (pour mapper avec votre DB) |
Response
JSON
{
"orgId": "org_abc123",
"apiKey": "sk_org_test_xyz789",
"slug": "formapro-excellence",
"createdAt": "2026-05-23T14:30:00Z",
"externalRefId": "qbx-of-4567",
"isSandbox": true,
"mode": "test",
"pennylane": {
"companyId": 5184271,
"simulated": true,
"error": null
}
}/api/v1/orgs/{orgId}Status d'un OF (provisioning, Pennylane, stats 30j). Accepte master key OU per-org key.
OAuth Pennylane (post-provisioning)
Pour que YAOKA puisse pousser les factures dans le Pennylane de l'OF, l'OF doit autoriser l'accès via OAuth. Lancé depuis Qualibox. En sandbox, le flow est mocké (call /complete avec n'importe quel code suffit).
/api/v1/orgs/{orgId}/pennylane/oauth/startBody : { "redirectUri": "https://qualibox.fr/yaoka-callback" }. Renvoie { authUrl, state, sandbox }. Qualibox redirige l'OF vers authUrl.
/api/v1/orgs/{orgId}/pennylane/oauth/completeAprès que Pennylane ait redirigé l'OF avec un ?code=&state=, Qualibox POST le code ici. Body : { "code": "...", "state": "..." }. Réponse : { connected: true, pennylaneCompanyId }.
Clients (de l'OF)
Per-org key requise. Les clients sont les entreprises/personnes que l'OF facture.
| Method | Path | Description |
|---|---|---|
| POST | /orgs/:id/customers | Créer/upsert un client (idempotent sur externalRef) |
| GET | /orgs/:id/customers | Lister avec ?search=&type=company|individual |
| GET | /orgs/:id/customers/:id | Récupérer un client |
| PATCH | /orgs/:id/customers/:id | Modifier |
| DELETE | /orgs/:id/customers/:id | Supprimer (refusé s'il a des factures) |
Exemple POST
JSON
{
"externalRef": "qbx-cust-9912",
"type": "company",
"name": "ACME Industries",
"siret": "98765432109876",
"emails": ["compta@acme.fr"],
"phone": "+33102030405",
"address": {
"street": "1 rue Test",
"city": "Paris",
"postalCode": "75001",
"country": "FR"
}
}Devis
Per-org key requise. Numérotation DEV-YYYY-NNNN automatique.
| Method | Path | Description |
|---|---|---|
| POST | /orgs/:id/quotes | Créer un devis (draft) |
| GET | /orgs/:id/quotes | Lister avec filtres ?status=&customerId= |
| GET | /orgs/:id/quotes/:id | Récupérer un devis |
| PATCH | /orgs/:id/quotes/:id | Modifier (lignes, statut, etc.). Refusé si déjà invoiced. |
| DELETE | /orgs/:id/quotes/:id | Supprimer (draft seulement) |
| POST | /orgs/:id/quotes/:id/convert | Convertir en facture (full, deposit ou balance) |
Exemple POST
JSON
{
"customerId": "cust_qrs",
"lines": [
{
"label": "Formation Excel niveau 2 — 14h",
"quantity": 1,
"unitPrice": 1200,
"vatRate": 0
}
],
"validUntilDays": 30,
"paymentConditions": "30 jours fin de mois"
}Le status passe par : draft → finalized → sent → accepted → invoiced (ou declined / expired). Utilisez PATCH pour faire évoluer le status.
Factures clients
Per-org key requise. Numérotation FAC-YYYY-NNNN continue (jamais réinitialisée — art. 242 nonies A CGI).
| Method | Path | Description |
|---|---|---|
| POST | /orgs/:id/invoices | Créer (draft) ou finalize:true pour générer PDF + push Pennylane immédiatement |
| GET | /orgs/:id/invoices | Lister avec ?status=&customerId=&fromDate=&toDate= |
| GET | /orgs/:id/invoices/:id | Récupérer une facture |
| PATCH | /orgs/:id/invoices/:id | Status transitions (finalize, mark-paid, cancel) + éditions limitées |
| DELETE | /orgs/:id/invoices/:id | Supprimer (draft seulement — pour les autres, utiliser PATCH status=cancelled ou créer un avoir) |
| POST | /orgs/:id/invoices/:id/credit-note | Émettre un avoir (full ou partiel via amountTtc). L'original reste immuable. |
Créer + finaliser en 1 call
JSON
{
"customerId": "cust_qrs",
"lines": [
{
"label": "Formation Excel niveau 2 — 14h",
"quantity": 1,
"unitPrice": 1200,
"vatRate": 0
}
],
"dueInDays": 30,
"paymentMethods": ["transfer"],
"qualiopiRefId": "qbx-session-9988",
"finalize": true
}Marquer payée
JSON
PATCH /orgs/{id}/invoices/{invoiceId}
{
"status": "paid",
"paidAt": "2026-06-15T10:30:00Z",
"paymentMethods": ["transfer"]
}Marquer une facture payée déclenche le webhook invoice.paid chez vous.
Immutabilité
Une fois finalized, une facture ne peut PLUS être modifiée (lignes, montants). C'est la loi française. Pour corriger une erreur, créez un avoir (credit note) ou annulez (status: cancelled) puis réémettez.
Factures fournisseurs
Per-org key requise. Upload de PDFs émis par les fournisseurs de l'OF.
| Method | Path | Description |
|---|---|---|
| POST | /orgs/:id/supplier-invoices | Uploader (fileUrl ou fileBase64) |
| GET | /orgs/:id/supplier-invoices | Lister |
| GET | /orgs/:id/supplier-invoices/:id | Récupérer |
| PATCH | /orgs/:id/supplier-invoices/:id | Modifier (status, payment, etc.) |
| DELETE | /orgs/:id/supplier-invoices/:id | Supprimer (draft seulement) |
Exemple POST
JSON
{
"supplierName": "Imprimerie Martin",
"supplierSiren": "111222333",
"invoiceNumber": "F-2026-118",
"amountHt": 350,
"amountTax": 70,
"amountTtc": 420,
"date": "2026-05-18T00:00:00Z",
"dueDate": "2026-06-18T00:00:00Z",
"fileUrl": "https://your-storage.com/invoice.pdf",
"fileName": "facture-imprimerie-2026-05.pdf",
"createdByUserId": "qbx-user-123"
}Notes de frais
Per-org key requise. Notes de frais des employés de l'OF.
| Method | Path | Description |
|---|---|---|
| POST | /orgs/:id/expense-reports | Créer (status=submitted) |
| GET | /orgs/:id/expense-reports | Lister avec ?status=&submittedById= |
| GET | /orgs/:id/expense-reports/:id | Récupérer |
| PATCH | /orgs/:id/expense-reports/:id | Status workflow (submitted → approved → sent_to_pennylane) |
| DELETE | /orgs/:id/expense-reports/:id | Supprimer (sauf sent_to_pennylane) |
Exemple POST
JSON
{
"submittedById": "qbx-user-456",
"title": "Déplacement client ACME — mai 2026",
"amountHt": 23.75,
"amountTax": 4.75,
"amount": 28.50,
"date": "2026-05-18T00:00:00Z",
"category": "repas",
"fileUrl": "https://your-storage.com/recu.jpg",
"fileName": "recu-restaurant.jpg"
}C'est quoi un webhook ?
Un webhook est un message qu'on vous envoie (vs. l'API où c'est VOUS qui nous appelez). Quand un événement se passe chez nous (ex: facture payée), on POST un message JSON sur une URL HTTPS que vous nous donnez.
Cela évite que vous ayez à poller ("dis YAOKA, est-ce que la facture est payée ?" toutes les 5 min) — on vous prévient instantanément.
Métaphore
Vous donnez votre numéro de téléphone (URL webhook). Quand quelqu'un essaye de vous joindre (événement), on appelle directement votre numéro. Pas besoin d'appeler vous-même toutes les minutes pour demander.
Configurer les webhooks chez Qualibox
Étape 1 — Créer un endpoint chez vous
Côté Qualibox, créez une route HTTPS publique qui accepte des POST JSON. Exemple en Next.js :
TypeScript
// app/api/yaoka/webhooks/route.ts (côté Qualibox)
export async function POST(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get("x-yaoka-signature");
// 1. Vérifier la signature (voir section suivante)
if (!verifyYaokaSignature(rawBody, signature, process.env.YAOKA_WEBHOOK_SECRET)) {
return new Response("Invalid signature", { status: 401 });
}
// 2. Parser
const event = JSON.parse(rawBody);
// 3. Réagir selon le type
switch (event.event) {
case "invoice.paid":
await markInvoicePaidInQualibox(event.data.invoiceId);
break;
case "org.provisioned":
await markOFProvisioned(event.data.orgId);
break;
// ...
}
// 4. Répondre 200 RAPIDEMENT (sinon YAOKA va retry)
return new Response("OK", { status: 200 });
}Étape 2 — Nous donner l'URL
Dans votre dashboard admin YAOKA (ou par email pendant la phase sandbox), donnez-nous :
- L'URL : ex
https://api.qualibox.fr/yaoka/webhooks - Un secret partagé (qu'on génère ensemble) — sert à signer chaque message
Vérifier la signature HMAC
Critique pour la sécurité — sans ça, n'importe qui peut vous envoyer de faux messages.
À chaque webhook, on inclut un header X-Yaoka-Signature: t=<timestamp>,v1=<hmac_hex>. Le HMAC est calculé sur `${timestamp}.${rawBody}` avec votre secret partagé.
Vérification en Node.js
TypeScript
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyYaokaSignature(
rawBody: string,
header: string | null,
secret: string
): boolean {
if (!header) return false;
const [tsPart, sigPart] = header.split(",");
const ts = tsPart.split("=")[1];
const sig = sigPart.split("=")[1];
// 1. Recalculer la signature attendue
const signedPayload = `${ts}.${rawBody}`;
const expected = createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
// 2. Comparer en timing-safe (évite les attaques par mesure de temps)
return timingSafeEqual(
Buffer.from(sig),
Buffer.from(expected)
);
}Important
Utilisez rawBody (le string brut), pas JSON.stringify(parsedBody) qui pourrait re-sérialiser différemment (ordre des clés, espaces) et casser la signature.
Liste des événements
15 types d'événements envoyés en webhook.
Format du payload
HTTP
POST https://api.qualibox.fr/yaoka/webhooks
Content-Type: application/json
X-Yaoka-Signature: t=1748901234,v1=<hmac_hex>
X-Yaoka-Event-Id: evt_abc123
{
"event": "invoice.paid",
"eventId": "evt_abc123",
"timestamp": 1748901234,
"data": {
"orgId": "org_abc",
"invoiceId": "inv_klm",
"invoiceNumber": "FAC-2026-0042",
"totalTtc": 1200,
"paidAt": "2026-05-23T15:30:00Z"
}
}Tous les événements
| Event | Description |
|---|---|
org.provisioned | Un OF a été provisionné (org créée + Pennylane company) |
pennylane.pa_designated | L'OF a cliqué sur l'email Pennylane et désigné Pennylane comme PA |
pennylane.connected | OAuth Pennylane terminé — YAOKA peut push/lire |
pennylane.sync_failed | Push Pennylane échoué (retry recommandé après OAuth ou support) |
quote.accepted | Devis marqué accepté |
quote.converted | Devis converti en facture (totale ou acompte) |
invoice.created | Facture créée (en draft) |
invoice.finalized | Facture finalisée (PDF Factur-X généré + push Pennylane) |
invoice.paid | Facture marquée payée |
invoice.overdue | Facture en retard de paiement (J+1 après dueDate) |
supplier_invoice.approved | Facture fournisseur approuvée par l'OF |
supplier_invoice.paid | Facture fournisseur payée |
expense_report.submitted | Note de frais soumise par un employé |
expense_report.approved | Note de frais approuvée |
expense_report.reimbursed | Note de frais remboursée + push Pennylane |
Idempotency côté Qualibox
Stockez les eventId reçus pendant 7 jours et ignorez les doublons. En cas de timeout réseau, on peut renvoyer le même event — votre code doit être idempotent.
Retry policy
Si vous renvoyez 5xx, on retry avec backoff exponentiel (60s, 5 min, 30 min, 2h, 6h, 12h) — 6 tentatives sur 24h max. Si 4xx, on abandonne (votre code a rejeté). Répondez 200 rapidement (avant 10s) pour acquittér.
Serveur MCP — bonus pour vos devs IA
Si vos devs utilisent Cursor, Claude Code, Windsurf ou tout agent IA, on a un package npm qui transforme notre API en tools utilisables directement par l'IA.
MCP = Model Context Protocol. Un standard ouvert (créé par Anthropic) qui permet à n'importe quel agent IA d'utiliser des outils externes. Notre serveur MCP expose 9 tools (créer un OF, émettre une facture, marquer payée, etc.).
Installation
bash
# Pas d'installation locale — on lance directement via npx npx -y @yaoka/partners-mcp # Ou via pnpm dlx, bunx, peu importe
Configuration Claude Desktop
Édite ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) ou %APPDATA%\Claude\claude_desktop_config.json (Windows) :
JSON
{
"mcpServers": {
"yaoka": {
"command": "npx",
"args": ["-y", "@yaoka/partners-mcp"],
"env": {
"YAOKA_API_KEY": "sk_qbx_test_xxxxxxxxxxxx"
}
}
}
}Configuration Cursor
Édite ~/.cursor/mcp.json (ou .cursor/mcp.json à la racine de ton projet pour un scope projet) :
JSON
{
"mcpServers": {
"yaoka": {
"command": "npx",
"args": ["-y", "@yaoka/partners-mcp"],
"env": {
"YAOKA_API_KEY": "sk_qbx_test_xxxxxxxxxxxx"
}
}
}
}Configuration Windsurf / Cline / autres
La même config JSON fonctionne partout — c'est le standard MCP. Pour Windsurf : ~/.codeium/windsurf/mcp_config.json. Pour Cline (VS Code) : dans les settings de l'extension.
Redémarrez le client après config. Vous verrez les 9 tools YAOKA dans la liste disponible. Demandez par exemple : "Crée un OF FormaPro avec SIRET 12345678901234, email contact@formapro.fr" — l'IA appellera yaoka_provision_org toute seule.
Les 9 tools exposés
| Tool | Description | Clé |
|---|---|---|
| yaoka_provision_org | Crée un OF + sa company Pennylane en un appel. Master key requise. | Master |
| yaoka_get_org_status | Récupère provisioning, OAuth Pennylane, stats 30j d'un OF. | Master / Org |
| yaoka_create_quote | Crée un devis (DEV-YYYY-NNNN), PDF + JSON structuré. | Master / Org |
| yaoka_create_invoice | Émet une facture Factur-X v1.0.6 conforme. PDF/A-3 + XML CII, push Pennylane auto. | Master / Org |
| yaoka_mark_invoice_paid | Marque payée avec date, montant, mode, référence. Déclenche le webhook invoice.paid. | Master / Org |
| yaoka_list_invoices | Liste les factures d'un OF avec filtres (status, période). | Master / Org |
| yaoka_upload_supplier_invoice | Uploade une facture fournisseur (PDF base64 ou URL). Lecture automatique optionnelle pour extraire les données. | Master / Org |
| yaoka_create_expense_report | Crée l'entête d'une note de frais (user + période). Lignes ajoutées ensuite. | Master / Org |
| yaoka_add_expense_item | Ajoute une ligne de dépense (repas, essence, hôtel, péage…). Barème km pour fuel. | Master / Org |
Astuce
Les noms sont préfixés yaoka_ pour éviter les conflits si vous chargez plusieurs serveurs MCP en parallèle. Toutes les descriptions sont en français et incluent des hints sur quand les utiliser — l'IA les choisit beaucoup mieux comme ça.
Usage depuis votre backend Next.js
TypeScript
import { experimental_createMCPClient } from "ai";
import { Experimental_StdioMCPTransport } from "ai/mcp-stdio";
const yaoka = await experimental_createMCPClient({
transport: new Experimental_StdioMCPTransport({
command: "npx",
args: ["-y", "@yaoka/partners-mcp"],
env: { YAOKA_API_KEY: process.env.YAOKA_API_KEY! },
}),
});
const tools = await yaoka.tools();
// Passer à n'importe quel LLM (Claude, Perplexity, GPT, Gemini)Forker / patcher le serveur en local
Si vous voulez ajouter des tools custom, modifier le comportement, ou juste comprendre comment ça marche, le code source est ouvert sur npm et facile à lancer en local.
bash
# 1. Clone le package depuis npm npm pack @yaoka/partners-mcp tar -xzf yaoka-partners-mcp-*.tgz && cd package # 2. Installer les deps pnpm install # ou npm install # 3. Build TypeScript pnpm build # → dist/ # 4. Lancer en mode dev (rebuild auto) pnpm dev # 5. Tester avec MCP Inspector (UI graphique pour debug) npx @modelcontextprotocol/inspector node ./dist/index.js
MCP Inspector lance une UI sur http://localhost:5173 où vous voyez les tools, lancez des appels, et inspectez les requêtes/réponses. Pratique pour debug avant de wire dans Claude Desktop ou Cursor.
Pour pointer votre fork local sur la prod YAOKA : YAOKA_API_KEY=sk_qbx_test_… dans .env.local. Pour pointer sur votre dev local YAOKA (port 3000) : YAOKA_BASE_URL=http://localhost:3000.
Pourquoi c'est bien
Vos devs peuvent prototyper / debug / scripter avec l'IA sans écrire un seul fetch. Et en prod, vos agents IA Qualibox peuvent utiliser YAOKA naturellement (ex: chatbot OF qui peut créer des factures à la demande).
Erreurs courantes et solutions
401 auth_requiredCause : Header Authorization manquant ou mal formé
Fix : Vérifiez que vous envoyez `Authorization: Bearer sk_xxx_xxx_xxx`
403 auth_invalidCause : Clé révoquée, expirée, ou n'existe pas
Fix : Régénérez la clé depuis votre dashboard admin. Vérifiez que vous utilisez bien la live key en prod et test en sandbox.
403 auth_wrong_scopeCause : Vous utilisez une master key sur un endpoint qui demande une per-org key (ou inverse)
Fix : Master key = /orgs (provisioning). Per-org key = /orgs/{id}/* (opérations métier).
422 business_rule_violation 'invoice without lines'Cause : Vous avez envoyé un POST /invoices avec lines:[]
Fix : Une facture doit avoir au moins 1 ligne
422 business_rule_violation 'Invalid status transition'Cause : Vous essayez de passer un statut illégal (ex: paid → draft)
Fix : Voir le workflow des status dans la section Invoices
404 not_found 'Customer'Cause : Vous référencez un customerId qui n'existe pas dans CET OF
Fix : Vérifiez l'orgId et le customerId. Multi-tenant strict : un client d'un OF n'est pas visible par un autre OF.
409 conflict 'Idempotency-Key already used'Cause : Vous avez réutilisé un UUID avec un body différent
Fix : Soit utilisez un UUID frais, soit envoyez exactement le même body que la 1ère fois
Webhook ne reçoit rienCause : URL non configurée, ou réponse non-200 → on abandonne après 6 retries
Fix : Vérifiez l'URL dans le dashboard admin. Vérifiez que vous répondez 200 sous 10s. Logs détaillés dans /admin/partners/{id}.
Webhook signature ne matche pasCause : Vous calculez le HMAC sur JSON.stringify(parsed) au lieu du rawBody
Fix : Stockez le rawBody (req.text() avant tout parsing), calculez le HMAC dessus
FAQ
Je peux tester en local sur localhost ?
Oui — pointez la BASE URL sur localhost:3000/api/v1 si vous testez contre une instance YAOKA locale. En production, c'est app.yaoka.fr/api/v1. Pour les webhooks en local, utilisez ngrok ou cloudflare tunnel.
Combien de temps prend l'intégration ?
Un dev moyen, 1-2 semaines. 1ère semaine : auth + provisioning + customers + invoices basiques. 2ème : webhooks + edge cases + tests d'intégration.
Vous avez une lib TypeScript / SDK ?
Pas encore — l'API est volontairement simple, fetch suffit. Si vous voulez les types Zod, importez les schemas via npm @yaoka/partners-types (à venir).
Comment je teste les webhooks sans déployer ?
Installez ngrok (https://ngrok.com), lancez `ngrok http 3000`, donnez-nous l'URL https publique générée. On y push les webhooks comme en prod.
Mon OF a déjà un compte Pennylane. Qu'est-ce qui se passe ?
À la création, on vérifie d'abord par SIRET si une company Pennylane existe déjà. Si oui, on link. Sinon, on crée. Donc safe.
Le push Pennylane est-il garanti ?
Best-effort en V1. Si Pennylane est down ou si l'OF n'a pas encore fait OAuth, on stocke pennylane.synced: false et on log. Vous pouvez retry plus tard. Webhook pennylane.sync_failed vous prévient si problème.
Comment je supprime un OF de test ?
Sandbox = auto-purgée tous les 30 jours. Pour suppression manuelle, contact@yaoka.fr — on a un endpoint admin pour ça (pas exposé en API publique).
Versioning : comment je sais qu'il y a une nouvelle version ?
On annonce 60 jours à l'avance par email + sur cette page (changelog). /v1/ ne reçoit JAMAIS de breaking change — toute évolution incompatible passe par /v2/, et /v1/ reste maintenu 12 mois après v2 stable.
Support — qui je contacte ?
contact@yaoka.fr en V1. Slack partagé Qualibox ↔ YAOKA à partir du go-live. Status page : status.yaoka.fr (à venir).
Une question pas couverte ?
Envoyez-la à contact@yaoka.fr — on l'ajoute à cette FAQ si elle est utile à d'autres.