# Les façades — l'API des modules

> Ce que votre app peut lire et écrire dans le CRM, la facturation, la boutique, les projets, le planning, les membres du site et la formation — depuis le code hébergé (`ctx.capibara`) ou depuis votre backend (REST), sous les scopes accordés.
> Catégorie : Référence · mis à jour le 2026-09-04 · plateforme v1

## Le principe

Une façade = une opération nommée `module.entité.op` avec un contrat
d'entrée et de sortie fixé par la plateforme. Il n'y a **pas de requête
libre** : ce que le registre expose, rien d'autre. Chaque façade exige un
[scope feuille](/dev/docs/permissions) et passe par les services de Capibara
sous **l'acteur** (la personne qui interagit, ou le compte de service de votre
app) — droits, journal, notifications et événements sont ceux d'un humain.

## Depuis le code hébergé — `ctx.capibara`

```ts
import { defineApp, ui } from '@capibara-dev/sdk';

export default defineApp({
  pages: {
    home: async ({ ctx }) => {
      const { items } = await ctx.capibara.crm.contacts.list({ q: 'dupont', take: 10 });
      return ui.list({ items: items.map((c) => ({ title: c.displayName, subtitle: c.email ?? '' })) });
    },
  },
  actions: {
    'quote.create': async ({ ctx, form }) => {
      const doc = await ctx.capibara.billing.documents.createDraft({
        type: 'QUOTE',
        partyId: String(form?.partyId),
        lines: [{ description: 'Prestation', quantity: 1, unitPriceCents: 45000 }],
      });
      return { toast: { tone: 'success', text: `Brouillon ${doc.id} créé` }, navigate: `/facturation/documents/${doc.id}` };
    },
  },
});
```

`ctx.capibara` est typé par le SDK (`AppApi`). Un scope manquant renvoie une
erreur `scope_not_granted` qui nomme le scope à ajouter.

## Depuis votre backend — REST

```http
POST /api/public/v1/crm/contacts/list
Authorization: Bearer ck_live_…            (clé API ou access token OAuth du dev)
X-Capibara-Install: <installId>            (l'installation visée)
Content-Type: application/json

{ "q": "dupont", "take": 10 }
```

- `installId` vous est donné par [embed-context](/dev/docs/api-publique)
  (jeton d'iframe) ou par le portail ; il désigne l'organisation ET votre app.
  Seul le développeur de l'app peut agir au nom de ses installations.
- Les façades de **lecture** s'appellent aussi en `GET` (l'entrée en query
  string) ; les **écritures** en `POST`, avec un en-tête `Idempotency-Key`
  recommandé (rejouer la même clé ne crée pas deux fois).
- Réponse : `{ "ok": true, "result": … }` ou
  `{ "ok": false, "error": "<code>", "message": "…" }` (+ `issues` sur une
  entrée invalide).
- La clé du dev doit couvrir la famille du module (`crm:read`…) en plus du
  scope feuille accordé à l'installation.
- Catalogue lisible par machine : `GET /api/public/v1/facades` (schémas JSON
  d'entrée et de sortie).
- Deux quotas se cumulent : **120 requêtes / minute par clé** (comme le reste
  de l'API publique) et **120 appels / minute par installation**.

Avec le SDK : `new CapibaraClient({ token }).install(installId).crm.contacts.list({ q })`.

## Codes d'erreur

Communs aux deux transports (`ctx.capibara` lève une exception portant le
code ; REST renvoie `{ ok: false, error, message }`) :

| Code | HTTP | Sens |
|---|---|---|
| `unknown_facade` | 404 | Le nom n'existe pas dans le registre. |
| `scope_not_granted` | 403 | Scope absent du manifeste ou non accordé par l'organisation (le message nomme le scope). |
| `forbidden` | 403 | L'acteur (personne ou compte de service) n'a pas le droit dans l'organisation. |
| `invalid_input` | 400 | L'entrée ne respecte pas le contrat (`issues` détaille). |
| `conflict` / `not_found` / `precondition_failed` / `bad_request` | 409 / 404 / 412 / 400 | Règles métier du module (e-mail déjà connu, document émis…) — les codes des services de Capibara, en minuscules. |
| `service_account_missing` | 412 | Compte de service absent (organisation en cours de mise à jour) : réinstallez. |
| `bad_output` | 502 | La réponse du module ne correspond plus au contrat de la façade (à signaler). |
| `internal` | 500 | Erreur interne (journalisée côté plateforme). |

Propres au transport REST (avant même d'atteindre la façade) :

| Code | HTTP | Sens |
|---|---|---|
| `unauthorized` | 401 | `Authorization` absent ou invalide. |
| `key_scope_missing` | 403 | Votre clé ne couvre pas la famille du module (`crm:read`…). |
| `missing_install` | 400 | En-tête `X-Capibara-Install` absent. |
| `install_not_found` | 404 | Installation inconnue, inactive ou en pause. |
| `forbidden` | 403 | L'installation n'est pas celle d'une de **vos** apps. |
| `no_version` | 412 | Aucune version publiée pour cette installation. |
| `consent_outdated` | 412 | La version demande de nouveaux scopes qu'un administrateur n'a pas encore acceptés. |
| `killed` | 423 | Incident actif sur la version (kill switch). |
| `method_not_allowed` | 405 | Façade d'écriture appelée en `GET`. |
| `rate_limited` | 429 | Quota de la clé ou de l'installation dépassé (`Retry-After: 60`). |

## Pagination

Les listes renvoient `items` et `nextCursor` : repassez `cursor` pour la
page suivante ; `take` est borné (100 au plus).

## Référence (générée depuis le registre)

### CRM

#### `crm.contacts.list`

Lecture · scope `crm.contacts:read`

Liste paginée des contacts (recherche nom / e-mail / téléphone, étiquettes, vue contacts ou clients).

| Entrée | Type | Requis |
|---|---|:-:|
| `q` | texte |  |
| `view` | `contacts` · `clients` · `all` (défaut `"all"`) |  |
| `tags` | liste de texte |  |
| `cursor` | texte |  |
| `take` | nombre (défaut `40`) |  |

Sortie : `items` (liste de objets), `nextCursor` (texte), `total` (nombre).

#### `crm.contacts.get`

Lecture · scope `crm.contacts:read`

Une fiche contact par identifiant.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |

Sortie : `id` (texte), `kind` (texte), `displayName` (texte), `firstName` (texte), `lastName` (texte), `email` (texte), `phone` (texte), `jobTitle` (texte), `website` (texte), `siret` (texte), `vatNumber` (texte), `addressLine1` (texte), `addressLine2` (texte), `postalCode` (texte), `city` (texte), `country` (texte), `isCustomer` (booléen), `tags` (liste de texte), `companyId` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `crm.contacts.create`

Écriture · scope `crm.contacts:write`

Crée un contact (au moins un nom ou un e-mail ; un e-mail déjà connu = conflit qui nomme la fiche existante).

| Entrée | Type | Requis |
|---|---|:-:|
| `firstName` | texte |  |
| `lastName` | texte |  |
| `email` | texte |  |
| `phone` | texte |  |
| `jobTitle` | texte |  |
| `companyId` | texte |  |
| `addressLine1` | texte |  |
| `addressLine2` | texte |  |
| `postalCode` | texte |  |
| `city` | texte |  |
| `country` | texte |  |
| `tags` | liste de texte |  |
| `notes` | texte |  |
| `isCustomer` | booléen |  |

Sortie : `id` (texte), `kind` (texte), `displayName` (texte), `firstName` (texte), `lastName` (texte), `email` (texte), `phone` (texte), `jobTitle` (texte), `website` (texte), `siret` (texte), `vatNumber` (texte), `addressLine1` (texte), `addressLine2` (texte), `postalCode` (texte), `city` (texte), `country` (texte), `isCustomer` (booléen), `tags` (liste de texte), `companyId` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `crm.contacts.update`

Écriture · scope `crm.contacts:write`

Met à jour un contact (seuls les champs fournis changent).

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `firstName` | texte |  |
| `lastName` | texte |  |
| `email` | texte |  |
| `phone` | texte |  |
| `jobTitle` | texte |  |
| `companyId` | texte |  |
| `addressLine1` | texte |  |
| `addressLine2` | texte |  |
| `postalCode` | texte |  |
| `city` | texte |  |
| `country` | texte |  |
| `tags` | liste de texte |  |
| `notes` | texte |  |
| `isCustomer` | booléen |  |

Sortie : `id` (texte), `kind` (texte), `displayName` (texte), `firstName` (texte), `lastName` (texte), `email` (texte), `phone` (texte), `jobTitle` (texte), `website` (texte), `siret` (texte), `vatNumber` (texte), `addressLine1` (texte), `addressLine2` (texte), `postalCode` (texte), `city` (texte), `country` (texte), `isCustomer` (booléen), `tags` (liste de texte), `companyId` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `crm.companies.list`

Lecture · scope `crm.companies:read`

Liste paginée des sociétés.

| Entrée | Type | Requis |
|---|---|:-:|
| `q` | texte |  |
| `view` | `contacts` · `clients` · `all` (défaut `"all"`) |  |
| `tags` | liste de texte |  |
| `cursor` | texte |  |
| `take` | nombre (défaut `40`) |  |

Sortie : `items` (liste de objets), `nextCursor` (texte), `total` (nombre).

#### `crm.companies.get`

Lecture · scope `crm.companies:read`

Une fiche société par identifiant.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |

Sortie : `id` (texte), `kind` (texte), `displayName` (texte), `firstName` (texte), `lastName` (texte), `email` (texte), `phone` (texte), `jobTitle` (texte), `website` (texte), `siret` (texte), `vatNumber` (texte), `addressLine1` (texte), `addressLine2` (texte), `postalCode` (texte), `city` (texte), `country` (texte), `isCustomer` (booléen), `tags` (liste de texte), `companyId` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `crm.companies.create`

Écriture · scope `crm.companies:write`

Crée une société.

| Entrée | Type | Requis |
|---|---|:-:|
| `name` | texte | oui |
| `email` | texte |  |
| `phone` | texte |  |
| `website` | texte |  |
| `siret` | texte |  |
| `vatNumber` | texte |  |
| `addressLine1` | texte |  |
| `addressLine2` | texte |  |
| `postalCode` | texte |  |
| `city` | texte |  |
| `country` | texte |  |
| `tags` | liste de texte |  |
| `notes` | texte |  |
| `isCustomer` | booléen |  |

Sortie : `id` (texte), `kind` (texte), `displayName` (texte), `firstName` (texte), `lastName` (texte), `email` (texte), `phone` (texte), `jobTitle` (texte), `website` (texte), `siret` (texte), `vatNumber` (texte), `addressLine1` (texte), `addressLine2` (texte), `postalCode` (texte), `city` (texte), `country` (texte), `isCustomer` (booléen), `tags` (liste de texte), `companyId` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `crm.companies.update`

Écriture · scope `crm.companies:write`

Met à jour une société (seuls les champs fournis changent).

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `name` | texte |  |
| `email` | texte |  |
| `phone` | texte |  |
| `website` | texte |  |
| `siret` | texte |  |
| `vatNumber` | texte |  |
| `addressLine1` | texte |  |
| `addressLine2` | texte |  |
| `postalCode` | texte |  |
| `city` | texte |  |
| `country` | texte |  |
| `tags` | liste de texte |  |
| `notes` | texte |  |
| `isCustomer` | booléen |  |

Sortie : `id` (texte), `kind` (texte), `displayName` (texte), `firstName` (texte), `lastName` (texte), `email` (texte), `phone` (texte), `jobTitle` (texte), `website` (texte), `siret` (texte), `vatNumber` (texte), `addressLine1` (texte), `addressLine2` (texte), `postalCode` (texte), `city` (texte), `country` (texte), `isCustomer` (booléen), `tags` (liste de texte), `companyId` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `crm.opportunities.list`

Lecture · scope `crm.opportunities:read`

Les opportunités visibles par l'acteur (≤ 1000, les plus récentes d'abord).

| Entrée | Type | Requis |
|---|---|:-:|
| `stage` | `NEW` · `QUALIFIED` · `PROPOSAL` · `NEGOTIATION` · `WON` · `LOST` |  |

Sortie : `items` (liste de objets).

#### `crm.opportunities.create`

Écriture · scope `crm.opportunities:write`

Crée une opportunité (référence OPP attribuée par la plateforme).

| Entrée | Type | Requis |
|---|---|:-:|
| `title` | texte | oui |
| `partyId` | texte |  |
| `contactPartyId` | texte |  |
| `amountCents` | nombre (défaut `0`) |  |
| `currency` | texte (défaut `"EUR"`) |  |
| `stage` | `NEW` · `QUALIFIED` · `PROPOSAL` · `NEGOTIATION` · `WON` · `LOST` (défaut `"NEW"`) |  |
| `probability` | nombre (défaut `50`) |  |
| `source` | texte |  |
| `closeDate` | date (ISO 8601) |  |
| `notes` | texte |  |

Sortie : `id` (texte), `reference` (texte), `title` (texte), `partyId` (texte), `contactPartyId` (texte), `ownerId` (texte), `amountCents` (nombre), `currency` (texte), `stage` (texte), `probability` (nombre), `source` (texte), `closeDate` (date (ISO 8601) ou null), `lostReason` (texte), `notes` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `crm.opportunities.moveStage`

Écriture · scope `crm.opportunities:write`

Fait avancer (ou perdre, avec motif) une opportunité.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `stage` | `NEW` · `QUALIFIED` · `PROPOSAL` · `NEGOTIATION` · `WON` · `LOST` | oui |
| `lostReason` | texte |  |

Sortie : `id` (texte), `reference` (texte), `title` (texte), `partyId` (texte), `contactPartyId` (texte), `ownerId` (texte), `amountCents` (nombre), `currency` (texte), `stage` (texte), `probability` (nombre), `source` (texte), `closeDate` (date (ISO 8601) ou null), `lostReason` (texte), `notes` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `crm.activities.list`

Lecture · scope `crm.activities:read`

Historique et tâches d'un contact ou d'une opportunité (≤ 200, récentes d'abord).

| Entrée | Type | Requis |
|---|---|:-:|
| `partyId` | texte |  |
| `opportunityId` | texte |  |

Sortie : `items` (liste de objets).

#### `crm.activities.create`

Écriture · scope `crm.activities:write`

Ajoute une note, un appel, un e-mail, un rendez-vous ou une tâche (rattaché à un contact ou une opportunité).

| Entrée | Type | Requis |
|---|---|:-:|
| `type` | `CALL` · `MEETING` · `EMAIL` · `NOTE` · `TASK` (défaut `"NOTE"`) |  |
| `subject` | texte | oui |
| `notes` | texte |  |
| `partyId` | texte |  |
| `opportunityId` | texte |  |
| `scheduledAt` | date (ISO 8601) |  |
| `dueAt` | date (ISO 8601) |  |

Sortie : `id` (texte), `type` (texte), `subject` (texte), `notes` (texte), `scheduledAt` (date (ISO 8601) ou null), `dueAt` (date (ISO 8601) ou null), `completedAt` (date (ISO 8601) ou null), `partyId` (texte), `opportunityId` (texte), `assigneeId` (texte), `createdAt` (date (ISO 8601)).

### Facturation

#### `billing.documents.list`

Lecture · scope `billing-fr.documents:read`

Liste paginée des devis, factures et avoirs (statut effectif, nom du client, totaux).

| Entrée | Type | Requis |
|---|---|:-:|
| `type` | `QUOTE` · `INVOICE` · `CREDIT_NOTE` · `DEPOSIT_INVOICE` |  |
| `status` | texte |  |
| `q` | texte |  |
| `cursor` | texte |  |
| `take` | nombre (défaut `30`) |  |

Sortie : `items` (liste de objets), `nextCursor` (texte).

#### `billing.documents.get`

Lecture · scope `billing-fr.documents:read`

Un document avec ses lignes, ses totaux et les avoirs qui le couvrent.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |

Sortie : `id` (texte), `type` (texte), `status` (texte), `number` (texte), `currency` (texte), `finalizedAt` (date (ISO 8601) ou null), `issuedAt` (date (ISO 8601) ou null), `sentAt` (date (ISO 8601) ou null), `dueDate` (date (ISO 8601) ou null), `paidAt` (date (ISO 8601) ou null), `validUntil` (date (ISO 8601) ou null), `acceptedAt` (date (ISO 8601) ou null), `totalHtCents` (nombre), `totalTvaCents` (nombre), `totalTtcCents` (nombre), `discountBps` (nombre), `notes` (texte), `partyId` (texte), `buyerName` (texte), `relatedDocumentId` (texte), `opportunityId` (texte), `projectId` (texte), `creditedCents` (nombre), `lines` (liste de objets), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `billing.documents.createDraft`

Écriture · scope `billing-fr.documents:write`

Crée un BROUILLON de devis ou de facture (client par fiche CRM ou saisi) — jamais émis d'office.

| Entrée | Type | Requis |
|---|---|:-:|
| `type` | `QUOTE` · `INVOICE` | oui |
| `partyId` | texte |  |
| `buyer` | objet |  |
| `lines` | liste de objets | oui |
| `notes` | texte |  |
| `dueDate` | date (ISO 8601) |  |
| `validUntil` | date (ISO 8601) |  |
| `discountBps` | nombre (défaut `0`) |  |
| `opportunityId` | texte |  |
| `projectId` | texte |  |

Sortie : `id` (texte), `type` (texte), `status` (texte), `number` (texte), `currency` (texte), `finalizedAt` (date (ISO 8601) ou null), `issuedAt` (date (ISO 8601) ou null), `sentAt` (date (ISO 8601) ou null), `dueDate` (date (ISO 8601) ou null), `paidAt` (date (ISO 8601) ou null), `validUntil` (date (ISO 8601) ou null), `acceptedAt` (date (ISO 8601) ou null), `totalHtCents` (nombre), `totalTvaCents` (nombre), `totalTtcCents` (nombre), `discountBps` (nombre), `notes` (texte), `partyId` (texte), `buyerName` (texte), `relatedDocumentId` (texte), `opportunityId` (texte), `projectId` (texte), `creditedCents` (nombre), `lines` (liste de objets), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `billing.documents.finalize`

Écriture · scope `billing-fr.documents:write`

Émet un brouillon (numéro attribué, archive PDF + Factur-X) ; e-mail au client si une adresse est connue.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `sendEmail` | booléen (défaut `true`) |  |

Sortie : `id` (texte), `type` (texte), `status` (texte), `number` (texte), `currency` (texte), `finalizedAt` (date (ISO 8601) ou null), `issuedAt` (date (ISO 8601) ou null), `sentAt` (date (ISO 8601) ou null), `dueDate` (date (ISO 8601) ou null), `paidAt` (date (ISO 8601) ou null), `validUntil` (date (ISO 8601) ou null), `acceptedAt` (date (ISO 8601) ou null), `totalHtCents` (nombre), `totalTvaCents` (nombre), `totalTtcCents` (nombre), `discountBps` (nombre), `notes` (texte), `partyId` (texte), `buyerName` (texte), `relatedDocumentId` (texte), `opportunityId` (texte), `projectId` (texte), `creditedCents` (nombre), `lines` (liste de objets), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `billing.documents.send`

Écriture · scope `billing-fr.documents:write`

Envoie un document ÉMIS au client par e-mail (adresse de la fiche, ou fournie).

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `email` | texte |  |

Sortie : `ok` (booléen), `toEmail` (texte).

#### `billing.documents.payLink`

Écriture · scope `billing-fr.documents:write`

Lien de paiement en ligne d'une facture ÉMISE (ou d'une échéance) sur la page de paiement de l'organisation ; `returnUrl` (domaine déclaré dans `returnHosts`) ajoute un bouton de retour vers votre site après règlement.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `installmentId` | texte |  |
| `returnUrl` | texte |  |

Sortie : `url` (texte), `path` (texte).

#### `billing.charges.create`

Écriture · scope `billing-fr.pos:write`

Crée un encaissement par carte (montant TTC + libellé) que le client règle sur la page de paiement de l'organisation — même rail que le TPE virtuel, valable 5 à 15 min ; `returnUrl` = bouton de retour vers votre site après règlement.

| Entrée | Type | Requis |
|---|---|:-:|
| `amountCents` | nombre | oui |
| `label` | texte |  |
| `ttlMinutes` | nombre |  |
| `returnUrl` | texte |  |

Sortie : `id` (texte), `code` (texte), `formattedCode` (texte), `amountCents` (nombre), `currency` (texte), `expiresAt` (date (ISO 8601)), `url` (texte).

#### `billing.charges.get`

Lecture · scope `billing-fr.pos:read`

État d'un encaissement par carte (en attente, payé, expiré, annulé) — à interroger après le retour du client, ou écouter l'événement `pos.charge.paid`.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |

Sortie : `id` (texte), `status` (texte), `amountCents` (nombre), `currency` (texte), `paidAt` (date (ISO 8601) ou null), `expiresAt` (date (ISO 8601)).

### Boutique

#### `shop.products.list`

Lecture · scope `shop.products:read`

Catalogue paginé (60 par page) avec le stock VENDABLE fusionné de chaque produit.

| Entrée | Type | Requis |
|---|---|:-:|
| `q` | texte |  |
| `status` | `all` · `published` · `draft` (défaut `"all"`) |  |
| `cursor` | texte |  |

Sortie : `items` (liste de objets), `nextCursor` (texte).

#### `shop.products.get`

Lecture · scope `shop.products:read`

Un produit avec ses déclinaisons et ses images.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |

Sortie : `id` (texte), `sku` (texte), `name` (texte), `slug` (texte), `description` (texte), `priceCents` (nombre), `vatRateBps` (nombre), `currency` (texte), `isPublished` (booléen), `isVirtual` (booléen), `ean` (texte), `categoryId` (texte), `brandId` (texte), `variants` (liste de objets), `images` (liste de objets), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `shop.products.update`

Écriture · scope `shop.products:write`

Modifie un produit : nom, description, prix HT, TVA, publication (jamais le stock — il vit dans les mouvements).

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `name` | texte |  |
| `description` | texte |  |
| `priceCents` | nombre |  |
| `vatRateBps` | nombre |  |
| `isPublished` | booléen |  |

Sortie : `id` (texte), `sku` (texte), `name` (texte), `slug` (texte), `priceCents` (nombre), `vatRateBps` (nombre), `currency` (texte), `isPublished` (booléen), `isVirtual` (booléen), `ean` (texte), `categoryId` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `shop.orders.list`

Lecture · scope `shop.orders:read`

Les commandes (récentes d'abord), filtrables par statut.

| Entrée | Type | Requis |
|---|---|:-:|
| `status` | `PENDING` · `PAID` · `SHIPPED` · `DELIVERED` · `CANCELED` · `REFUNDED` |  |
| `take` | nombre (défaut `50`) |  |

Sortie : `items` (liste de objets).

#### `shop.orders.get`

Lecture · scope `shop.orders:read`

Une commande avec ses lignes.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |

Sortie : `id` (texte), `number` (texte), `status` (texte), `channel` (texte), `currency` (texte), `customerEmail` (texte), `customerName` (texte), `customerPartyId` (texte), `totalCents` (nombre), `totalHtCents` (nombre), `totalVatCents` (nombre), `shippingCents` (nombre), `discountCents` (nombre), `paidAmountCents` (nombre), `paidAt` (date (ISO 8601) ou null), `trackingNumber` (texte), `trackingCarrier` (texte), `items` (liste de objets), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `shop.orders.setStatus`

Écriture · scope `shop.orders:write`

Passe une commande en expédiée ou livrée (le client est prévenu à l'expédition). Jamais payée/remboursée : ce sont des flux d'argent.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `status` | `SHIPPED` · `DELIVERED` | oui |

Sortie : `id` (texte), `number` (texte), `status` (texte), `channel` (texte), `currency` (texte), `customerEmail` (texte), `customerName` (texte), `customerPartyId` (texte), `totalCents` (nombre), `totalHtCents` (nombre), `totalVatCents` (nombre), `shippingCents` (nombre), `discountCents` (nombre), `paidAmountCents` (nombre), `paidAt` (date (ISO 8601) ou null), `trackingNumber` (texte), `trackingCarrier` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

#### `shop.orders.setTracking`

Écriture · scope `shop.orders:write`

Pose le numéro et le transporteur d'une expédition (repris dans l'e-mail « expédiée »).

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `trackingNumber` | texte ou null | oui |
| `trackingCarrier` | texte ou null |  |

Sortie : `id` (texte), `number` (texte), `status` (texte), `channel` (texte), `currency` (texte), `customerEmail` (texte), `customerName` (texte), `customerPartyId` (texte), `totalCents` (nombre), `totalHtCents` (nombre), `totalVatCents` (nombre), `shippingCents` (nombre), `discountCents` (nombre), `paidAmountCents` (nombre), `paidAt` (date (ISO 8601) ou null), `trackingNumber` (texte), `trackingCarrier` (texte), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)).

### Projets

#### `projects.list`

Lecture · scope `projects:read`

Les projets (≤ 300, récents d'abord) avec leur avancement.

| Entrée | Type | Requis |
|---|---|:-:|
| `status` | `OPEN` · `ALL` · `ACTIVE` · `ON_HOLD` · `DONE` · `ARCHIVED` (défaut `"OPEN"`) |  |
| `search` | texte |  |

Sortie : `items` (liste de objets).

#### `projects.get`

Lecture · scope `projects:read`

Un projet avec ses colonnes et ses tâches.

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |

Sortie : `id` (texte), `reference` (texte), `name` (texte), `status` (texte), `clientPartyId` (texte), `ownerId` (texte), `billingMode` (texte), `startDate` (date (ISO 8601) ou null), `endDate` (date (ISO 8601) ou null), `completedAt` (date (ISO 8601) ou null), `createdAt` (date (ISO 8601)), `updatedAt` (date (ISO 8601)), `stages` (liste de objets), `tasks` (liste de objets).

#### `projects.tasks.create`

Écriture · scope `projects.tasks:write`

Crée une tâche dans un projet (colonne d'entrée par défaut).

| Entrée | Type | Requis |
|---|---|:-:|
| `projectId` | texte | oui |
| `title` | texte | oui |
| `stageId` | texte |  |
| `description` | texte |  |
| `ownerId` | texte |  |
| `dueDate` | texte |  |
| `estimateMinutes` | nombre |  |

Sortie : `id` (texte).

#### `projects.tasks.move`

Écriture · scope `projects.tasks:write`

Déplace une tâche dans une autre colonne (le statut suit la colonne).

| Entrée | Type | Requis |
|---|---|:-:|
| `id` | texte | oui |
| `stageId` | texte | oui |

Sortie : `ok` (booléen).

#### `projects.tasks.comment`

Écriture · scope `projects.tasks:write`

Poste un commentaire sur une tâche (fil de la tâche, notifications habituelles).

| Entrée | Type | Requis |
|---|---|:-:|
| `taskId` | texte | oui |
| `body` | texte | oui |

Sortie : `ok` (booléen).

#### `projects.time.log`

Écriture · scope `projects.tasks:write`

Enregistre du temps sur une tâche, au nom de l'acteur (compte de service ou personne).

| Entrée | Type | Requis |
|---|---|:-:|
| `taskId` | texte | oui |
| `minutes` | nombre | oui |
| `date` | texte |  |
| `description` | texte |  |
| `billable` | booléen |  |

Sortie : `id` (texte).

### Planning

#### `planning.resources.list`

Lecture · scope `planning.resources:read`

Les ressources réservables (salles, personnes, matériel) et leur mode de paiement.

Aucune entrée.

Sortie : `items` (liste de objets).

#### `planning.bookings.list`

Lecture · scope `planning.bookings:read`

Les réservations qui croisent une fenêtre [from, to], filtrables par ressource et statut.

| Entrée | Type | Requis |
|---|---|:-:|
| `from` | date (ISO 8601) |  |
| `to` | date (ISO 8601) |  |
| `resourceId` | texte |  |
| `status` | `SCHEDULED` · `CONFIRMED` · `CANCELLED` · `DONE` |  |

Sortie : `items` (liste de objets).

#### `planning.bookings.create`

Écriture · scope `planning.bookings:write`

Crée une réservation (conflit de créneau refusé, confirmation e-mail au client rattaché).

| Entrée | Type | Requis |
|---|---|:-:|
| `title` | texte | oui |
| `resourceId` | texte |  |
| `clientPartyId` | texte |  |
| `startsAt` | date (ISO 8601) | oui |
| `endsAt` | date (ISO 8601) | oui |
| `notes` | texte |  |

Sortie : `id` (texte), `title` (texte), `resourceId` (texte), `clientPartyId` (texte), `assigneeUserId` (texte), `startsAt` (date (ISO 8601)), `endsAt` (date (ISO 8601)), `status` (texte), `notes` (texte), `priceCents` (nombre), `paymentStatus` (texte), `createdAt` (date (ISO 8601)).

### Site

#### `site.members.list`

Lecture · scope `site.members:read`

Les membres du site public (comptes clients), paginés, filtrables par statut et recherche.

| Entrée | Type | Requis |
|---|---|:-:|
| `search` | texte |  |
| `status` | `ACTIVE` · `PENDING` · `BLOCKED` |  |
| `cursor` | texte |  |
| `take` | nombre (défaut `30`) |  |

Sortie : `items` (liste de objets), `nextCursor` (texte).

### Formation

#### `formation.catalogue.list`

Lecture · scope `formation.catalogue:read`

Le catalogue de formations (actives ; archivées sur demande).

| Entrée | Type | Requis |
|---|---|:-:|
| `includeArchived` | booléen (défaut `false`) |  |

Sortie : `items` (liste de objets).

#### `formation.sessions.list`

Lecture · scope `formation.sessions:read`

Les sessions inter-entreprises (à venir par défaut) avec places restantes et inscriptions payées.

| Entrée | Type | Requis |
|---|---|:-:|
| `productId` | texte |  |
| `includePast` | booléen (défaut `false`) |  |

Sortie : `items` (liste de objets).
