Journal des changements
Historique des changements de l'API Capturia v1, daté et catégorisé.
- Ajouté
v1La référence de l'API et le spec OpenAPI existent en anglais
Les pages de référence en anglais (
/en/developers/api/v1/reference) décrivent chaque endpoint, paramètre, schéma et réponse en anglais, recherche de la doc comprise. Le spec anglais est publié à côté du spec français :/openapi.en.yaml,/openapi.en.jsonet la collection Postman/postman-collection.en.json. Le contrat ne change pas : mêmes chemins, mêmes champs, mêmes valeurs ; seuls les textes descriptifs sont traduits. La collection Postman française (/postman-collection.json) est maintenant servie surapi.capturia.io, comme le spec. - Modifié
v1Connecteur pour assistants IA : get_todays_bookings dit qui a pris et annulé chaque rendez-vous (changement non rétrocompatible)
Changement non rétrocompatible du connecteur MCP.
get_todays_bookingsaccepterange(todaypar défaut,upcomingpour les prochains,recentpour les plus récents, annulations comprises) etcontact_id(les rendez-vous d'une fiche,recentpar défaut). Chaque rendez-vous porteorigin(qui l'a pris),cancel_originetcancelled_at(qui l'a annulé, et quand),meeting_type, et sousuntrusted_user_contentle type de rendez-vous, l'hôte et les noms de qui l'a pris ou annulé. Le champsourcen'est plus retourné : il nommait « automation » un rendez-vous pris par l'agent dans la conversation, etoriginle remplace.datevautnullhors derange: today. L'outil suit maintenant le forfait comme l'écran des rendez-vous : un compte dont le forfait n'inclut pas le module Rendez-vous et calendriers reçoit un refus qui renvoie à « Mon forfait », et une lecture du forfait en panne est à retenter. Le résultat reste borné à 20 rendez-vous, avectotal_countethas_more.search_contacts,get_pipeline_summaryetget_todays_bookingspublient aussi leur schéma de sortie (outputSchema). - Modifié
v1Étapes du pipeline : chaque étape nomme son pipeline, les étapes masquées sont marquées, les fiches archivées ne comptent plus
GET /v1/pipeline/stagesrend désormais les étapes de tous les pipelines du compte avec leur pipeline :pipeline_idetpipeline(id,name,is_default). Un compte à deux pipelines peut avoir deux étapes du même nom ; le pipeline les distingue. Les étapes sont triées par pipeline, dans l'ordre du tableau de bord, puis parstage_order. Une étape masquée sur le pipeline du tableau de bord reste listée, avecis_hidden: true: ses contacts existent et comptent.contact_countne compte plus les contacts archivés ni fusionnés, comme l'écran Pipeline. Un drapeau d'étape absent (is_default,is_won,is_lost) se lit maintenantfalseau lieu denull. Le connecteur MCP (get_pipeline_summary) rend les mêmes champs. - Modifié
v1Webhooks : l'auteur d'un événement dit s'il vient d'un assistant IA ou de l'API, et n'invente plus de membre
Le champ
data.actordes événements gagnevia:ai_assistantquand le geste vient d'un assistant IA branché au connecteur (Claude, ChatGPT),api_keyquand il vient de l'API publique,nullsinon. Quand aucun membre n'a agi (système, automatisation, intégration),typeetidvalent désormaisnull; ils annonçaient auparavant un membre de l'équipe sans identifiant. Une écriture faite par une clé API est nommée par sa clé dansname(par exempleClé API « Zapier », ouAssistant IA — clé API « Zapier »quand la clé passe par le connecteur), jamaisSystème. - Modifié
v1POST /v1/contacts : courriel ou téléphone, jamais une deuxième fiche, aucun consentement implicite
Un contact se crée désormais avec un courriel ou un téléphone : l'un des deux suffit. Le courriel est enregistré en minuscules, et la personne est cherchée parmi toutes les identités du compte (courriels et téléphones principaux et secondaires, sans égard à la casse). Une personne déjà connue retourne
409 duplicate_contactavec la fiche existante dansdataet les champs concernés dansdetails.conflicting_fields, que la correspondance vienne du courriel ou du téléphone ; un courriel et un téléphone portés par deux fiches différentes retournent409 duplicate_resource. Le contact créé porte la sourceapi, et aucun consentement implicite (texto ou courriel) ne lui est plus inscrit : une intégration ne nomme personne qui aurait rencontré ce contact. Pour un lead qui vient de remplir ton formulaire, avec son consentement, utilisePOST /v1/leads/capture. - Modifié
v1DELETE /v1/contacts/{id} exige le scope contacts:delete
Supprimer un contact exige maintenant le scope
contacts:delete, distinct decontacts:write, comme dans le tableau de bord où la suppression est une permission à part. Seule une personne qui peut supprimer des prospects dans Capturia peut accorder ce scope à une clé ; il n'est inclus que dans le preset Intégration complète. Une clé qui n'a quecontacts:writereçoit403 insufficient_scopesur cette route. - Modifié
v1/v1/bookings sert l'agenda de ton compte (changement non rétrocompatible)
Changement non rétrocompatible. Les routes
/v1/bookingsservent désormais l'agenda de ton compte : les rendez-vous que ton équipe tient sur tes calendriers (page de réservation, tableau de bord, agents IA, automatisations). Elles lisaient et écrivaient auparavant les rencontres de tes projets de livraison avec Capturia.GET /v1/bookingsliste les rendez-vous de l'agenda (archivés exclus). Le filtreproject_iddisparaît ;event_type_idetcontact_ids'ajoutent àfrom,toetstatus(confirmed,cancelled,completed,no_show). Un filtre illisible est refusé en400 validation_error. Chaque rendez-vous porteevent_type_id,contact_id,host_idetmeeting_link;project_id,assigned_to_id,video_provideretzoom_join_urlne sont plus retournés.POST /v1/bookingsréserve par le même service que ta page de réservation, avec les règles du calendrier (hôtes, heures, fenêtre de réservation, agenda Google de l'hôte, questions requises, un seul rendez-vous à venir par fiche et par calendrier). Le corps exigeevent_type_id(un calendrier de ton compte) etstart_datetime, pluscontact_idouguest_name+guest_email. Facultatifs :guest_phone,host_id,timezone,notes,form_responses(une entrée par identifiant de question, de la forme{ value }; un refus de formulaire liste les questions du calendrier dansdetails.questions).project_idn'existe plus, etend_datetimeest refusé : la durée est celle du calendrier. L'heure demandée est tenue ou refusée avec sa raison dansdetails.reason, jamais remplacée :400 validation_error(slot_in_past,outside_window,off_cadence,contact_or_guest_required,contact_without_email),404 resource_not_found(details.field:event_type_id,contact_id,host_id),409 duplicate_resource(already_booked),422 business_rule_violation(calendar_without_hosts,host_not_on_calendar,host_unavailable,slot_unavailable,contact_archived,lead_not_qualified),503 service_unavailable(réessaie aprèsRetry-After).DELETE /v1/bookings/{id}annule comme au tableau de bord : l'événement est retiré de l'agenda de l'hôte, qui est avisé. Un rendez-vous déjà annulé répond maintenant422 business_rule_violation(already_cancelled) au lieu de409 already_cancelled; un rendez-vous tenu ou marqué absent,422(booking_not_cancellable). Un rendez-vous introuvable répond404 resource_not_found.Le connecteur pour assistants IA suit le même changement :
get_todays_bookingslit l'agenda du compte, etbook_meetingy réserve (start_datetime, puiscontact_idouguest_name+guest_email,event_type_idetform_responsesfacultatifs ;project_idetend_datetimene sont plus acceptés). Le connecteur n'annule jamais un rendez-vous. - Ajouté
v1GET /v1/usage : nouveau compteur status_unknown
Chaque bucket et les totaux de
GET /v1/usageportent désormaisstatus_unknown: le nombre de requêtes dont l'issue n'a pas été enregistrée. C'est le cas d'une réponse encore en cours au moment de l'agrégation (le bucket horaire courant est recalculé toutes les quelques minutes, et la requête y reprend alors son issue réelle), ou d'une issue dont l'enregistrement a échoué. Ces requêtes ne sont ni des succès ni des erreurs : exclus-les de tout calcul de taux d'erreur. Les famillesstatus_2xx,status_4xx,status_5xx,rate_limited_countetstatus_unknownsont disjointes : une requête n'est jamais comptée dans deux d'entre elles. - Modifié
v1GET /v1/usage : status_2xx ne compte plus que les succès confirmés
Jusqu'ici, une requête dont l'issue n'avait jamais été enregistrée pouvait être comptée comme un succès. Elle est maintenant comptée dans
status_unknown. Les buckets historiques concernés sont corrigés une fois : leurstatus_2xxbaisse d'autant, etstatus_unknownreprend l'écart. Les refus de la limite par adresse IP de/v1/eventset/v1/leads/capturepassent aussi destatus_4xxàrate_limited_count, comme les refus du quota horaire de la clé : un refus d'un limiteur de débit est toujours dansrate_limited_countet ne consomme pas le quota. Un refus métier en 429 (déclencheur appelé trop souvent, quota d'envois de test, plafond d'abonnements webhook) reste une erreurstatus_4xxet consomme le quota. Enfin, la référence disait à tort querate_limited_countétait un sous-ensemble destatus_4xx: les deux familles ont toujours été distinctes, cette correction de la documentation ne change aucun calcul. - Modifié
v1GET /v1/conversations/{id}/messages : content ne contient plus les marqueurs de pilotage internes
Les agents IA rythment leurs réponses avec des marqueurs internes ([PAUSE:3s] pour envoyer en plusieurs bulles, [INPUT:email] pour demander un champ au visiteur). Ils n'ont jamais fait partie du contrat et se retrouvaient pourtant dans le champ content. Ils en sont retirés : une réponse envoyée en plusieurs bulles t'arrive maintenant en paragraphes séparés par une ligne vide. Ce changement s'applique aussi aux messages déjà en base, donc la valeur de content change une fois pour les messages d'agent existants. Conséquence à connaître : un message qui ne portait QUE du pilotage devient une chaîne vide. La ligne reste présente dans la page, avec ses id, role et created_at, pour ne pas fausser total_count, has_more ni le curseur de pagination. Si ton intégration teste la présence d'un message avec if (!content), ajuste-la pour tester la présence de l'objet.
- Ajouté
v1Sous-domaine dédié api.capturia.io + sunset legacy 2027-05-03
L'API publique est désormais servie sur https://api.capturia.io/v1/* (convention enterprise alignée Stripe/Twilio/GitHub). Bundle OpenAPI auto-discoverable à api.capturia.io/openapi.{yaml,json}. L'ancien host app.capturia.io/api/v1/* reste actif jusqu'au 2027-05-03 ; chaque réponse legacy porte les headers IETF Deprecation, Sunset et Link successor-version. Au sunset, le legacy bascule en HTTP 308 Permanent Redirect vers le nouveau host. Pas de 410 Gone — tes intégrations continueront à fonctionner même si tu oublies de migrer (avec un round-trip d'overhead).
- Ajouté
v1Spec OpenAPI 3.1 publique + Postman + types TS
L'API expose désormais une spec OpenAPI 3.1 servable à
/openapi.yaml(235KB) et/openapi.json(289KB). Collection Postman (/postman-collection.json) téléchargeable, importable dans Insomnia 2023+ via la spec OpenAPI directement. Types TypeScript générés au build (lib/api/v1/openapi-types.ts). Validation requêtes contre la spec en mode shadow par défaut. - Ajouté
v1Endpoint GET /v1/usage + observability headers
Endpoint programmatique
/v1/usageretourne les aggregations consommation (filtrables parbucket_typeet range). Chaque réponse émet désormaisX-Request-IDULID,Capturia-Request-ID(alias),Capturia-Version: v1, etVary: Accept-Language, Accept-Encoding. - Ajouté
v1Pagination cursor opaque + headers Deprecation/Sunset legacy
Tous les
GETcollection acceptent désormais?cursor=opaque base64url avec tiebreakeridpour tri stable. Format legacytotal_count+cursorISO accepté en parallèle 12 mois (sunset 2027-05-03). HeadersDeprecation(RFC 9745) +Sunset(RFC 8594) +Link rel=deprecationannoncent le retrait. - Ajouté
v1Header Idempotency-Key sur POST/PUT/DELETE
Toutes les routes mutantes acceptent un header optionnel
Idempotency-Key(regex[a-zA-Z0-9_-]{1,64}). TTL 24h via Vercel KV. Replay verbatim retourneIdempotent-Replayed: true. Conflit (payload différent ou requête concurrente) →409 idempotency_conflict. - Ajouté
v1Headers RFC 9331 + body 429 enrichi
Émission parallèle des headers
RateLimit-Limit/Remaining/Reset/Policy(RFC 9331) etX-RateLimit-*(legacy, sunset 2027-05-03). Body 429 inclut désormaisdetails.limit_type(per_keyoubrute_force) etdetails.retry_after_secondsactionnable.