/external-api — publier une API pour un système tiers/external-api — publish an API for a third-party system
Le skill /external-api publie une partie d'une application cliente générée sous la forme d'une API consommée en machine-to-machine : un ERP partenaire, un portail client, un batch nocturne. Le socle possède déjà toute la mécanique d'accès (identité tierce, échange de jeton, accords, quotas, audit) ; ce skill y raccorde l'extension du client — sans modifier une ligne du socle.
The /external-api skill publishes part of a generated client app as a machine-to-machine API: a partner ERP, a customer portal, a nightly batch. The platform already owns the whole access mechanism (third-party identity, token exchange, grants, quotas, audit); this skill wires the client extension into it — without changing a single line of the platform.
Ce que le socle fournit déjàWhat the platform already provides
| BriqueBuilding block | DétailDetail |
|---|---|
| Identité tierceThird-party identity | ExternalApplication — clientId, secret primaire + secondaire (rotation sans coupure), liste d'IP autorisées, révocation immédiate |
| Échange de jetonToken exchange | POST /api/auth/external-app/token, assertion JWT HS256 (exp ≤ 5 min) |
| Garde de routesRoute guard | Une application externe n'atteint que /api/v1/export/* — tout le reste répond 403 route_blockedAn external app only reaches /api/v1/export/* — everything else answers 403 route_blocked |
| Catalogue + accordsCatalogue + grants | Un accord par application × endpoint, avec liste de tenants et quotaOne grant per application × endpoint, with a tenant list and a quota |
| AuditAudit | Chaque appel journalisé, corps d'erreur inclusEvery call journalled, error body included |
Le point d'accroche est une donnée, pas du code : le middleware prend le 4ᵉ segment de l'URL comme code de catalogue et le résout en base. Publier une API revient donc à servir /api/v1/export/{code} et à semer une ligne.
The seam is data, not code: the middleware takes the 4th URL segment as a catalogue code and resolves it in the database. Publishing an API therefore means serving /api/v1/export/{code} and seeding one row.
Un code de catalogue par opérationOne catalogue code per operation
Le catalogue ne porte qu'une permission par code, et ce sont exactement ces chaînes qui deviennent les droits de l'application. Un code unique servant GET+POST+PUT+DELETE imposerait donc un joker, qui élargit tout accord existant dès qu'une action est ajoutée — et rend impossible un partenaire en lecture seule. D'où le découpage par défaut : The catalogue holds one permission per code, and those exact strings become the application's rights. A single code serving GET+POST+PUT+DELETE would therefore force a wildcard, which widens every existing grant as soon as an action is added — and makes a read-only partner impossible. Hence the default split:
| OpérationOperation | Code | VerbeVerb | Permission |
|---|---|---|---|
| read | {app}-{section} |
GET | {app}.{module}.{section}.read |
| create | {app}-{section}-create |
POST | ….create |
| update | {app}-{section}-update |
PUT | ….update |
| delete | {app}-{section}-delete |
DELETE | ….delete |
Chaque code s'accorde séparément : donner la lecture ne donne pas l'écriture, et retirer l'écriture ne coupe pas la lecture. Each code is granted separately: giving read does not give write, and revoking write does not cut read.
Où se déclare la décisionWhere the decision is declared
Un endpoint public n'a pas d'écran : aucune pagespec ne peut le porter, puisqu'une action de pagespec est un bouton. La déclaration vit donc dans entité.md, à côté de l'opt-out API : none :
A public endpoint has no screen: no pagespec can carry it, since a pagespec action is a button. The declaration therefore lives in entité.md, beside the API : none opt-out:
- **API externe** : read, create
EnchaînementChain
derive-external-api-spec— lit la déclaration, produit la spec du scaffolder ; en modewrite, enregistre la décision validéereads the declaration, produces the scaffolder spec; inwritemode, records the approved decisionscaffold-external-api— un contrôleur par code, le semeur de catalogue, son enregistrement DIone controller per code, the catalogue seeder, its DI registrationaudit-dev-external-api— 14 règles déterministes (DEV-XAPI-001..014)14 deterministic rules (DEV-XAPI-001..014)publish-api-contract— OpenAPI + Postman + guide FR versionnés, avec refus des rupturesversioned OpenAPI + Postman + FR guide, breaking changes refusedprovision-external-app— un client de test, secret rendu une seule foisa test client, secret returned once
Trois pièges que le skill fermeThree traps the skill closes
- Pas de ligne de catalogue → 404 pour toute application externe, alors qu'un utilisateur connecté voit la même URL fonctionner. L'endpoint a l'air livré et reste injoignable.No catalogue row → 404 for every external app, while a signed-in user sees the same URL work. The endpoint looks shipped and stays unreachable.
- Permission du catalogue ≠ constante compilée → 403 sur un appel parfaitement formé, qu'aucun ré-accord ne corrige.Catalogue permission ≠ compiled constant → 403 on a perfectly formed call, which no re-granting fixes.
?tenantId=obligatoire : un appelant machine ne porte aucun tenant implicite, donc un endpoint qui l'oublierait lirait à travers les tenants.?tenantId=mandatory: a machine caller carries no implicit tenant, so an endpoint that forgot it would read across tenants.