# API publique

> Authentification Bearer (clé API, jeton OAuth ou clé de projet), /me, embed-context, les façades par installation, l'API de projet (clé cpk_… de la CLI), les quotas et les conventions d'erreur.
> Catégorie : API & intégration · mis à jour le 2026-09-04 · plateforme v1

## Base et authentification

L'API publique est servie sur le domaine principal de l'application :

```
https://capibara.fr/api/public/v1/…
```

Un seul mécanisme d'authentification, trois porteurs possibles dans l'en-tête
`Authorization` :

```
Authorization: Bearer <clé API ck_live_… OU jeton d'accès OAuth>
```

- **Clé API** (`ck_live_…`) — générée depuis `/dev/keys`, section « Clés
  API ». C'est le chemin le plus simple pour une app hybride : la clé ne
  change pas tant que vous ne la révoquez pas.
- **Jeton d'accès OAuth** — obtenu via l'un des deux grants décrits dans
  [Sign in with Capibara (OAuth)](/dev/docs/oauth). Valable 1 heure ; à
  renouveler en refaisant le flux (il n'y a pas de `refresh_token` à ce
  stade).
- **Clé de projet** (`cpk_…`) — générée sur la page d'une app (section
  **Code**, carte « Clé de projet & CLI »), bornée à CE projet : elle n'ouvre
  que l'[API de projet](#lapi-de-projet-cle-de-projet) ci-dessous (celle de la
  [CLI](/dev/docs/cli)), jamais `/me` ni les façades.

Aucune requête n'aboutit sans l'un des trois : une absence ou une valeur
invalide renvoie `401` avec un en-tête `WWW-Authenticate: Bearer`.

---

## `GET /api/public/v1/me`

L'endpoint de test d'authentification et d'identité (clé API ou jeton
OAuth) :

```
GET https://capibara.fr/api/public/v1/me
Authorization: Bearer ck_live_VOTRE_CLE
```

Réponse (`200`) :

```json
{
  "developer": {
    "slug": "studio-forge",
    "displayName": "Studio Forge",
    "partner": false
  },
  "scopes": ["crm:read", "shop:read"],
  "auth": "api_key"
}
```

`auth` vaut `"api_key"` ou `"oauth"` selon le porteur utilisé — utile pour
distinguer vos deux modes d'intégration dans vos propres journaux. `scopes`
reflète exactement ceux de la clé (ou du jeton) utilisé, pas l'ensemble des
scopes possibles.

> Cet endpoint ne coûte pas de quota différent des autres : il compte comme
> n'importe quel appel dans la limite ci-dessous. Utilisez-le pour vérifier
> qu'une intégration est bien configurée avant de construire dessus, pas en
> polling répété.

---

## Scopes disponibles

Les scopes d'une clé API (ou d'un client OAuth) sont des **familles de
module** : ils disent ce que la CLÉ du développeur peut toucher. Pour agir sur
une organisation, il faut en plus qu'une installation de votre app y ait
accordé le [scope feuille](/dev/docs/permissions) correspondant.

| Scope de clé | Couvre |
|---|---|
| `crm:read` / `crm:write` | Façades du CRM (contacts, sociétés, opportunités, activités). |
| `billing:read` / `billing:write` | Façades de la facturation (devis, factures, avoirs). |
| `shop:read` / `shop:write` | Façades de la boutique (produits, commandes). |
| `projects:read` / `projects:write` | Façades des projets (projets, tâches, temps). |
| `planning:read` / `planning:write` | Façades du planning (ressources, réservations). |
| `site:read` | Membres du site. |
| `formation:read` | Catalogue et sessions de formation. |
| `media:read` | Réservé (médiathèque, à venir). |

## `GET /api/public/v1/embed-context` (mode hybride)

Une page montée par `embedUrl` (entrée de menu ou widget embed) reçoit
`?capibara_token=<jwt>` dans son URL. Votre backend l'échange ici — avec
SA clé API ou son jeton OAuth — contre le contexte de la visite :

```
GET https://capibara.fr/api/public/v1/embed-context?token=<jwt>
Authorization: Bearer ck_live_VOTRE_CLE
```

```json
{
  "addonKey": "sync-compta-externe",
  "tenant": { "id": "cm9x…", "slug": "mon-organisation" },
  "userAccountId": "acc_…",
  "grantedPermissions": ["billing-fr.documents:read"],
  "installId": "inst_…",
  "expiresAt": "2026-09-04T10:05:00.000Z"
}
```

Le jeton vaut **5 minutes**, n'est résolu que par le développeur de l'app
visée (`403 forbidden` sinon), et ne contient jamais l'e-mail de la
personne. `installId` est la valeur à passer ensuite dans
`X-Capibara-Install`. Erreurs propres : `missing_token` et
`invalid_token` (400).

## Façades par installation

`POST /api/public/v1/<module>/<entité>/<op>` avec `X-Capibara-Install:
<installId>` exécute une [façade](/dev/docs/facades) au nom du compte de
service de votre app dans cette organisation — lectures en `GET` possibles,
écritures idempotentes par `Idempotency-Key`, 120 appels / minute par
installation (en plus du quota de la clé). `GET /api/public/v1/facades`
renvoie le catalogue complet (schémas d'entrée et de sortie) ; les codes
d'erreur sont sur la page des [façades](/dev/docs/facades). Le SDK expose la
même chose via `client.install(installId)`.

## L'API de projet (clé de projet)

`/api/public/v1/project/…` est l'API que la [CLI](/dev/docs/cli) utilise —
et que vous pouvez appeler directement. Elle s'authentifie par une **clé de
projet** `cpk_…` (`Authorization: Bearer cpk_…`) et n'agit que sur le
projet qui l'a émise. Les installations de test qu'elle pose ne visent qu'une
organisation dont le créateur de la clé est propriétaire. Chaque réponse est
enveloppée : `{ "ok": true, "result": … }` ou `{ "ok": false, "error":
"<code>", "message": "…", …détails }`.

| Méthode et chemin | Rôle |
|---|---|
| `GET /project` | État du projet : versions (canal, bundle), fiche, installations de test, organisations cibles, runtime. |
| `GET /project/scopes` | Scopes de la dernière version avec leur phrase de consentement + ce que chaque organisation de test a accordé. |
| `GET /project/logs?org=&installId=&after=&limit=` | Journal des exécutions sur les installations de test (curseur `after` = dernier id reçu). |
| `GET /project/runtime?route=<clé>[@<versionId>]&hash=<sha256>` | L'isolate de cette route est-il monté, et avec quel bundle ? Renvoie `state` (nommé) + `message` + `expectedHash`/`mountedHash` — ce sur quoi `capibara dev` attend et que `capibara status` affiche. |
| `POST /project/dev` `{ manifest, files, org? }` | Valide le manifeste, scanne et bundle les sources, enregistre le brouillon et pose/actualise l'installation de test (deploy-on-save). |
| `POST /project/deploy` `{ manifest, files }` | Même construction, puis la version part en review (build automatisé). |
| `POST /project/publish` `{ semver? }` | Publie une version `APPROVED` (la dernière approuvée sans `semver`). |
| `POST /project/install` `{ org?, semver? }` · `POST /project/uninstall` `{ org? }` | Pose ou retire une installation de test (retirer purge ses données). |
| `POST /project/kit-check` `{ doc, surface? }` | Valide un document (ou un nœud) du Kit avec le validateur réel. |

`manifest` est le texte brut de `add-on.json` ; `files` un objet
`{ "src/index.ts": "…" }` (80 fichiers, 1,5 Mo au total, 512 Ko par
fichier, corps ≤ 3 Mo). Codes d'erreur propres à cette API :
`version_locked` (409 : la version du manifeste est en review, approuvée
ou publiée — incrémentez `version`), `invalid_manifest` (400, avec
`issues[]`), `key_mismatch` / `author_mismatch` / `main_missing` (400),
`invalid_sources` et `forbidden_pattern` (400, avec `findings[]` du
scan), `bundle_failed` (400), `no_test_org` (412, avec `targets[]`),
`org_required` / `org_not_found`, `no_version` / `not_approved` (412),
`version_not_found` (404), `invalid_route` (400), `invalid_json` (400),
`payload_too_large` (413), `unauthorized` (401), `rate_limited` (429).
Quota dédié : **240 appels par minute** et **60 déploiements par minute**
par clé de projet.

---

## Quotas

**120 requêtes par minute**, par identifiant authentifié — pas par adresse
IP. L'identifiant est votre clé API (ou votre compte développeur, si vous
appelez via OAuth) : plusieurs backends utilisant la même clé partagent le
même quota, et à l'inverse, une même IP appelant pour plusieurs devs ne les
fait pas se gêner entre eux.

Au-delà du quota :

```
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{ "error": "rate_limited" }
```

Attendez le délai indiqué par `Retry-After` (en secondes) avant de
réessayer — un retry immédiat en boucle prolonge votre propre exclusion sans
bénéfice.

---

## Conventions d'erreur

Deux formes, selon la famille d'endpoints — le champ `error` est la valeur
stable dans les deux cas, le message peut évoluer :

- `/me` et `/embed-context` : `{ "error": "<code>" }`.
- Les **façades** et l'**API de projet** : une enveloppe
  `{ "ok": false, "error": "<code>", "message": "…" }` (+ `issues`,
  `findings`, `targets`… selon le cas), et `{ "ok": true, "result": … }`
  en succès.

Codes communs :

| HTTP | `error` | Sens |
|---|---|---|
| 401 | `unauthorized` | en-tête `Authorization` absent, mal formé, ou clé/jeton invalide (`WWW-Authenticate: Bearer`). |
| 403 | `forbidden` / `key_scope_missing` | authentification valide mais scope insuffisant, ou ressource d'un autre développeur. |
| 404 | `not_found` / `unknown_facade` / `install_not_found` | ressource absente ou hors de portée du compte authentifié. |
| 429 | `rate_limited` | quota dépassé (voir ci-dessus), `Retry-After` fourni. |

Les codes propres aux façades (`scope_not_granted`, `consent_outdated`,
`killed`…) sont listés sur [Les façades](/dev/docs/facades) ; ceux de l'API
de projet ci-dessus.

---

## Et pour le reste de la plateforme ?

Si votre app a besoin d'une ressource qui n'est pas encore exposée
publiquement, décrivez le besoin depuis `/dev/support` — la priorité des
prochains endpoints suit les demandes réelles des développeurs du portail,
et sera documentée ici au fur et à mesure de sa sortie.
