Capturia measures its ads and site traffic with the Meta pixel and Google Analytics. You can decline at any time. Learn more

FR

Changelog

History of changes to the Capturia API v1, dated and categorized.

  1. Addedv1

    The API reference and the OpenAPI spec are available in English

    The English reference pages (/en/developers/api/v1/reference) describe every endpoint, parameter, schema and response in English, including in the docs search. The English spec is published next to the French one: /openapi.en.yaml, /openapi.en.json and the Postman collection /postman-collection.en.json. The contract does not change: same paths, same fields, same values; only the descriptive text is translated. The French Postman collection (/postman-collection.json) is now served on api.capturia.io, like the spec.

  2. Changedv1

    AI assistant connector: get_todays_bookings says who booked and who cancelled each booking (breaking change)

    Breaking change to the MCP connector. get_todays_bookings accepts range (today by default, upcoming for the next ones, recent for the most recent ones, cancellations included) and contact_id (one contact's bookings, recent by default). Each booking carries origin (who booked it), cancel_origin and cancelled_at (who cancelled it, and when), meeting_type, and under untrusted_user_content the booking type, the host and the names of whoever booked or cancelled it. The source field is no longer returned: it labelled "automation" a booking the agent took in the conversation, and origin replaces it. date is null outside range: today. The tool now follows the plan like the Bookings screen: an account whose plan does not include the Bookings and calendars module gets a refusal that points to “My Plan”, and an unreadable plan is to be retried. The result is still capped at 20 bookings, with total_count and has_more. search_contacts, get_pipeline_summary and get_todays_bookings also publish their output schema (outputSchema).

  3. Changedv1

    Pipeline stages: each stage names its pipeline, hidden stages are marked, archived contacts no longer count

    GET /v1/pipeline/stages now returns the stages of every pipeline in the account with their pipeline: pipeline_id and pipeline (id, name, is_default). An account with two pipelines can have two stages with the same name; the pipeline tells them apart. Stages are sorted by pipeline, in the dashboard's order, then by stage_order. A stage hidden on the dashboard pipeline is still listed, with is_hidden: true: its contacts exist and count. contact_count no longer counts archived or merged contacts, like the Pipeline screen. A missing stage flag (is_default, is_won, is_lost) now reads false instead of null. The MCP connector (get_pipeline_summary) returns the same fields.

  4. Changedv1

    Webhooks: an event's author says whether it came from an AI assistant or the API, and no longer invents a member

    The events' data.actor field gains via: ai_assistant when the action comes from an AI assistant connected to the connector (Claude, ChatGPT), api_key when it comes from the public API, null otherwise. When no member acted (system, automation, integration), type and id are now null; they previously announced a team member without an identifier. A write made with an API key is named after its key in name, a value written in French (for example Clé API « Zapier », or Assistant IA — clé API « Zapier » when the key goes through the connector), never Système.

  5. Changedv1

    POST /v1/contacts: email or phone, never a second record, no implied consent

    A contact is now created with an email or a phone number: either one is enough. The email is stored in lowercase, and the person is searched across every identity of the account (primary and secondary emails and phone numbers, case-insensitive). A person who already exists returns 409 duplicate_contact with the existing record in data and the matching fields in details.conflicting_fields, whether the match comes from the email or the phone; an email and a phone held by two different records return 409 duplicate_resource. The new contact has the source api, and no implied consent (text or email) is recorded for it anymore: an integration names no one who met this contact. For a lead who just filled in your form, with their consent, use POST /v1/leads/capture.

  6. Changedv1

    DELETE /v1/contacts/{id} requires the contacts:delete scope

    Deleting a contact now requires the contacts:delete scope, separate from contacts:write, as in the dashboard where deleting is a permission of its own. Only someone allowed to delete prospects in Capturia can grant this scope to a key; it is only included in the Full integration preset. A key with only contacts:write gets 403 insufficient_scope on this route.

  7. Changedv1

    /v1/bookings now serves your account's calendar (breaking change)

    Breaking change. The /v1/bookings routes now serve your account's calendar: the bookings your team holds on your calendars (booking page, dashboard, AI agents, automations). They used to read and write the meetings of your delivery projects with Capturia.

    GET /v1/bookings lists the calendar's bookings (archived ones excluded). The project_id filter is gone; event_type_id and contact_id join from, to and status (confirmed, cancelled, completed, no_show). An unreadable filter is refused with 400 validation_error. Each booking carries event_type_id, contact_id, host_id and meeting_link; project_id, assigned_to_id, video_provider and zoom_join_url are no longer returned.

    POST /v1/bookings books through the same service as your booking page, under the calendar's rules (hosts, hours, booking window, the host's Google calendar, required questions, one upcoming booking per contact per calendar). The body requires event_type_id (one of your account's calendars) and start_datetime, plus contact_id or guest_name + guest_email. Optional: guest_phone, host_id, timezone, notes, form_responses (one entry per question id, shaped { value }; a form refusal lists the calendar's questions in details.questions). project_id no longer exists, and end_datetime is refused: the calendar sets the duration. The requested time is booked or refused with its reason in details.reason, never replaced: 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 (retry after Retry-After).

    DELETE /v1/bookings/{id} cancels the way the dashboard does: the event is removed from the host's calendar and the host is notified. An already cancelled booking now answers 422 business_rule_violation (already_cancelled) instead of 409 already_cancelled; a completed or no-show booking, 422 (booking_not_cancellable). An unknown booking answers 404 resource_not_found.

    The AI assistant connector follows the same change: get_todays_bookings reads the account's calendar, and book_meeting books in it (start_datetime, then contact_id or guest_name + guest_email, optional event_type_id and form_responses; project_id and end_datetime are no longer accepted). The connector never cancels a booking.

  8. Addedv1

    GET /v1/usage: new status_unknown counter

    Every bucket and the totals of GET /v1/usage now carry status_unknown: the number of requests whose outcome was not recorded. That is a response still in flight when the bucket was aggregated (the current hourly bucket is recomputed every few minutes, and the request then gets its real outcome back), or an outcome whose recording failed. These requests are neither successes nor errors: leave them out of any error-rate calculation. The status_2xx, status_4xx, status_5xx, rate_limited_count and status_unknown families are disjoint: a request is never counted in two of them.

  9. Changedv1

    GET /v1/usage: status_2xx now only counts confirmed successes

    Until now, a request whose outcome was never recorded could be counted as a success. It is now counted in status_unknown. The affected historical buckets are corrected once: their status_2xx drops by that amount, and status_unknown picks up the difference. Refusals by the per-IP limit of /v1/events and /v1/leads/capture also move from status_4xx to rate_limited_count, like refusals by the key's hourly quota: a refusal by a rate limiter is always in rate_limited_count and does not consume the quota. A business refusal returned as 429 (trigger called too often, test-send quota, webhook subscription cap) stays a status_4xx error and consumes the quota. Finally, the reference wrongly said that rate_limited_count was a subset of status_4xx: the two families have always been distinct, and this documentation fix changes no calculation.

  10. Changedv1

    GET /v1/conversations/{id}/messages: content no longer carries internal agent control markers

    AI agents pace their replies with internal markers ([PAUSE:3s] to send in several bubbles, [INPUT:email] to request a field from the visitor). They were never part of the contract, yet they surfaced in the content field. They are now stripped: a reply the agent sent as several bubbles reaches you as paragraphs separated by a blank line. This applies to messages already stored, so the value of content changes once for existing agent messages. Worth knowing: a message that carried ONLY control markers becomes an empty string. The row stays in the page, with its id, role and created_at, so that total_count, has_more and the pagination cursor stay accurate. If your integration checks for a message with if (!content), switch it to checking that the object is present.

  11. Addedv1

    Dedicated api.capturia.io subdomain + legacy host sunset 2027-05-03

    The public API is now served at https://api.capturia.io/v1/* (enterprise convention aligned with Stripe/Twilio/GitHub). OpenAPI bundle auto-discoverable at api.capturia.io/openapi.{yaml,json}. The legacy host app.capturia.io/api/v1/* remains active until 2027-05-03; every legacy response carries IETF Deprecation, Sunset and Link successor-version headers. At sunset, the legacy host switches to HTTP 308 Permanent Redirect to the new host. No 410 Gone — your integrations will keep working even if you forget to migrate (with one round-trip of overhead).

  12. Addedv1

    OpenAPI 3.1 public spec + Postman + TS types

    The API now exposes an OpenAPI 3.1 spec at /openapi.yaml (235KB) and /openapi.json (289KB). Postman collection (/postman-collection.json) downloadable, importable in Insomnia 2023+ via the OpenAPI spec directly. TypeScript types generated at build (lib/api/v1/openapi-types.ts). Request validation against spec in shadow mode by default.

  13. Addedv1

    GET /v1/usage endpoint + observability headers

    Programmatic /v1/usage endpoint returns consumption aggregations (filterable by bucket_type and range). Every response now emits X-Request-ID ULID, Capturia-Request-ID (alias), Capturia-Version: v1, and Vary: Accept-Language, Accept-Encoding.

  14. Addedv1

    Opaque cursor pagination + Deprecation/Sunset legacy headers

    All GET collection endpoints now accept ?cursor= opaque base64url with id tiebreaker for stable sort. Legacy total_count + cursor ISO format accepted alongside for 12 months (sunset 2027-05-03). Deprecation (RFC 9745) + Sunset (RFC 8594) + Link rel=deprecation headers announce the removal.

  15. Addedv1

    Idempotency-Key header on POST/PUT/DELETE

    All mutating routes accept an optional Idempotency-Key header (regex [a-zA-Z0-9_-]{1,64}). TTL 24h via Vercel KV. Verbatim replay returns Idempotent-Replayed: true. Conflict (different payload or concurrent request) → 409 idempotency_conflict.

  16. Addedv1

    RFC 9331 headers + enriched 429 body

    Parallel emission of RateLimit-Limit/Remaining/Reset/Policy headers (RFC 9331) and X-RateLimit-* (legacy, sunset 2027-05-03). 429 body now includes actionable details.limit_type (per_key or brute_force) and details.retry_after_seconds.