API publique

Les systèmes tiers (Zapier / Make, back-offices, scripts) lisent et écrivent les données du CRM en HTTPS, sur l'origine du déploiement Convex :

https://<deployment>.convex.site/api/v1/
Authorization: Bearer wap_<keyId>_<secret>

Les clés se créent dans Paramètres → Clés d'API (nom, portées, expiration optionnelle). Le secret n'est affiché qu'une fois ; seule son empreinte salée est stockée. Une clé se révoque sans délai ; sa création, sa modification et sa révocation sont journalisées dans auditLogs (entityType: 'apiKey').

Une clé voit et modifie toutes les fiches de l'organisation. L'API est une surface serveur-à-serveur : les portées limitent les ressources, pas le périmètre — la grille rôles × modules et les équipes ne s'y appliquent pas. Pas d'en-têtes CORS : une clé ne doit jamais vivre dans un navigateur.

Ressources et portées

RessourceRoutesPortées
SondeGET /me (nom, portées, expiration de la clé)n'importe quelle clé valide
Contacts (leads)GET /contacts[?email=], GET /contacts/:id, POST /contacts, POST /contacts/upsert, PATCH /contacts/:id, DELETE /contacts/:idcontacts:read / contacts:write
EntreprisesGET /companies[?domain=], GET /companies/:id, POST, PATCH, DELETEcompanies:read / companies:write
TransactionsGET /deals[?leadId=], GET /deals/:id, POST, PATCH, DELETEdeals:read / deals:write
ActivitésGET /activities[?leadId=], GET /activities/:id, POST, PATCH, DELETEactivities:read / activities:write
ListesGET /lists, GET /lists/:id/memberslists:read (+ contacts:read pour les membres, qui sont des contacts complets)
PropriétésGET /properties?entityType=lead|company|deal|activityproperties:read

Une portée d'écriture inclut la lecture de la même ressource (contacts:write suffit pour lire les contacts et pour recevoir la fiche renvoyée par une écriture). Les réponses sont des DTO explicites (id, createdAt, updatedAt, champs publics) : jamais de document brut, jamais de jeton de consentement ni de champ interne. Les fiches supprimées (soft delete) sont invisibles partout.

Lecture

Écriture

Idempotence

Les clients qui rejouent leurs requêtes (Zapier, Make) envoient un en-tête Idempotency-Key (1 à 255 caractères, unique par clé d'API) sur les POST. Pendant 24 h, la même clé avec le même corps renvoie la réponse enregistrée (en-tête Idempotent-Replayed: true) sans réécrire ; la même clé avec un corps différent renvoie 422 idempotency_key_reused ; une requête encore en cours renvoie 409 idempotency_in_progress. Sans cet en-tête, un POST rejoué crée un doublon (sauf /contacts/upsert).

Limites de débit

LimiteValeurClé
Requêtes600 / minpar clé d'API
Écritures (POST, PATCH, DELETE)300 / min, en plus de la précédentepar clé d'API
Échecs d'authentification10 / minpar adresse IP

Dépassement : 429 avec Retry-After (secondes). Une adresse IP à court de budget d'authentification est refusée avant toute recherche de clé en base.

Erreurs

{ "error": { "code": "invalid_fields", "message": "…", "details": { "path": ".email" } } }

Codes HTTP : 400 (corps ou champ invalide ou absent, id malformé, référence inconnue — le code est le code d'erreur métier, ex. field_required, invalid_owner, invalid_address, unknown_stage), 401 (clé absente, malformée, inconnue, révoquée ou expirée — toujours le même corps), 403 missing_scope, 404 not_found, 409 (conflit d'état : duplicate_email, company_domain_exists, lifecycle_regression_blocked, deal_transition_forbidden…), 422 idempotency_key_reused, 429 rate_limited, 500 internal_error.

Journal et traçabilité

Chaque écriture par l'API produit une ligne auditLogs portant apiKeyId (et pas d'userId) ; le fil d'activité des fiches l'affiche comme API · <nom de la clé>. Les lectures ne sont pas journalisées : lastUsedAt de la clé (rafraîchi au plus toutes les 5 min) et les logs Convex suffisent.

Documentation OpenAPI

Le contrat est décrit dans docs/openapi.yaml (OpenAPI 3.1, relu comme du code) et servi sans clé par le déploiement :

Après une modification du YAML, bun run openapi régénère le module servi (convex/lib/api/openapi.generated.ts, commité) ; tests/backend/openapi.test.ts vérifie que le module est à jour, que le document est un OpenAPI valide et qu'il décrit exactement les routes du routeur avec leurs portées.