For the complete documentation index, see llms.txt.

POST /v1/proposals/{id}/response

Respond to Proposal

Accept, counter, or reject a proposal. - **accept**: Requires `selected_slot_id`. Proposal transitions to `accepted` when ALL required participants accept. - **counter**: Requires `counter_slots`. Current proposal transitions to `countered`, a new proposal is created in the same thread. - **reject**: Any required participant rejecting transitions the whole proposal to `rejected`. ## For browser embeds: use the widget, not this endpoint If you are embedding the recipient-response flow in a third-party app, use [`@vennio/proposals-widget`](https://www.npmjs.com/package/@vennio/proposals-widget) (vanilla JS, 2 lines of HTML) or [`<VennioProposal>` from `@vennio/react`](https://www.npmjs.com/package/@vennio/react). The widget embeds `vennio.app/p/:token` in an iframe and surfaces accept / counter / reject as `postMessage` events / React callbacks. Your app never calls this endpoint directly — which means it cannot accidentally call it with the organizer's API key and hit `403 organizer_cannot_respond`. Call this endpoint directly only for server-side flows where there is no browser context (webhooks, schedulers, integration adapters). ## Authentication Either a Bearer JWT / API key in the `Authorization` header, **or** a magic-link `token` in the request body. JWT wins when both are present (the token is left intact for later out-of-band use). When the body-token path is used, the token is consumed atomically with the status flip; if the saga rolls back, the token remains valid for retry. **The proposal organizer cannot respond to their own proposal.** Responses come from recipients, not from the principal who created the proposal. To test end-to-end with a single API key, create the proposal with the key and then call this endpoint with the recipient's magic-link `token` in the body — your own Bearer auth on the response side will be rejected with 403. ## Error codes - `400 poll_mode_only_supports_accept` — `mode='poll'` proposals only accept `action: accept` (vote). `counter` and `reject` are rejected. - `401 missing_credentials` — no `Authorization` header and no `token` in the body. One of the two is required. - `401 invalid_token` — body-token is malformed, expired, or already consumed - `401 participant_unresolved` — the body-token authenticated but did not resolve to a participant row (open token still missing the self-identify fields the API expects, or named token with a deleted participant). - `401 token_proposal_mismatch` — body-token is valid but belongs to a different proposal than `:id` - `403 not_a_participant` — caller authenticated via JWT/API key but is not a participant of this proposal. - `403 organizer_cannot_respond` — caller is the proposal's organizer. Organizers cannot respond to their own proposals; use the magic-link token path or call this endpoint as a different principal. - `409 you_have_already_responded` — the resolved participant row has a non-`pending` response. Each participant gets one response; reconsideration is not supported. - `409 consent_revoked` — the recipient revoked `booking:propose` consent between create and accept; proposal is expired - `409 calendar_disconnected_recipient` — accepting recipient lost their calendar connection between create and accept; reconnect and retry - `409 token_already_consumed` — the magic-link token was consumed by a concurrent request - `400 participant_calendar_not_pinned` — at accept time, the responding participant has no calendar to write the event to. They must connect (or reconnect) a calendar and retry. Distinct from `409 calendar_disconnected_recipient` (create-time pre-flight); this fires when the participant connected post-create but lazy pin resolution still couldn't find a primary calendar. - `429 rate_limited` — per-link rate limit exceeded. Open-poll shareable tokens are multi-use; the API applies a per-token rate limit (default 20 votes per 60 seconds, env-tunable via `OPEN_VOTE_RATE_LIMIT_MAX` / `OPEN_VOTE_RATE_LIMIT_WINDOW_MS`) as a basic anti-flood floor. Builders are responsible for stricter voter identity / dedup on open polls. - `502 calendar_unavailable` — upstream calendar provider failed during event creation; saga rolled back, safe to retry

Auth required: Yes

Parameters

Request Body

Responses

Authentication

Requires authentication. Pass a Bearer token (Supabase JWT) or an API key (`Authorization: Bearer vennio_sk_live_*`) in the request headers.

Base URL: https://api.vennio.app