API reference/POST /v1/booking-proposals

POST /v1/booking-proposals

requires akidempotentaudited decision

Create a durable booking proposal for calendar-owner approval.

  • Proposal access is scoped to the internal agent. Multiple active API keys for the same agent can read the same proposal; a different agent cannot.
  • Proposals expire at the earlier of 24 hours after creation or the end of the requested window.
  • Broad requested windows return a curated review set, not exhaustive availability. Use preferred_times for up to 3 specific choices.

Request


POST /v1/booking-proposals HTTP/1.1
Authorization: Bearer ak_01HX7QQM…
Content-Type: application/json

{
  "title": "Demo call with Acme",
  "meeting_class": "recruiting_interview",
  "duration_minutes": 45,
  "requested_window": { "start": "2026-07-08T09:00:00Z", "end": "2026-07-08T17:00:00Z" },
  "owner_email": "alex@acme.com",
  "calendar_type": "work",
  "description": "…"
}
owner_email
string:emailEmail address.
calendar_type
string
titlereq
stringMust not be empty.
description
string
meeting_classreq
string
duration_minutesreq
integerMinimum 5. Maximum 480.
attendees
object[]Contains email, displayName.
attendees[].emailreq
string:emailEmail address.
attendees[].displayName
string
requested_windowreq
objectContains start, end.
requested_window.startreq
string:date-timeUTC date-time string.
requested_window.endreq
string:date-timeUTC date-time string.
preferred_times
object[]Up to 3 items. Contains start, end.
preferred_times[].startreq
string:date-timeUTC date-time string.
preferred_times[].endreq
string:date-timeUTC date-time string.

Response


Returns 201 Created with the response body below. Successful booking arbitration writes an audit row you can query through the audit log.

201 Created · example

HTTP/1.1 201 Created
Content-Type: application/json

{
  "proposal_id":            "7a8f9d77-c4b4-4f4d-96c6-4e41a82e408e",
  "status":                 "pending_owner_decision",
  "title":                  "Demo call with Acme",
  "description":            null,
  "meeting_class":          "demo",
  "duration_minutes":       30,
  "attendees":              [{ "email": "guest@example.com" }],
  "requested_window": {
    "start": "2026-06-10T09:00:00Z",
    "end":   "2026-06-10T17:00:00Z"
  },
  "expires_at":             "2026-06-11T09:00:00.000Z",
  "calendar_owner":         "owner@example.com",
  "requesting_agent":       "sales-agent",
  "resolved_calendar_type": "work",
  "candidate_limit":        20,
  "available_valid_candidate_count": 42,
  "valid_candidate_count":  20,
  "candidates_truncated":   true,
  "candidate_set":          "curated",
  "candidates": [
    {
      "id":                 "2479f9ad-96cb-4f15-a2aa-1ad14220d94b",
      "start":              "2026-06-10T10:00:00.000Z",
      "end":                "2026-06-10T10:30:00.000Z",
      "rank":               100,
      "agent_preferred":    false,
      "status":             "valid",
      "invalid_reasons":    [],
      "risk":               "free",
      "preemptable":        null
    }
  ],
  "decision":               null,
  "approved_candidate_id":  null,
  "rejection_reason":       null,
  "owner_note":             null,
  "booking_id":             null
}

// Approval happens in Dashboard → Approvals. Agents poll
// GET /v1/booking-proposals/{id} or read pending notifications.

Errors


CodeDescription
201Default Response
400Validation failed — body is malformed or missing required fields.
401Missing or invalid API key. Send the raw key as a Bearer token in the Authorization header.
403Agent does not hold the permission scope required for this call (e.g. preempt on a higher-priority class).
404Calendar owner or booking id not found.
409Conflict — the requested slot was already committed at higher priority. Inspect alternatives[] in the response body.
422Validation error — the request body is structurally valid but semantically rejected. Common causes: earliest_start is in the past (PAST_TIME), the window is shorter than the requested duration (WINDOW_TOO_NARROW), the chosen slot is outside the hold window (SLOT_OUTSIDE_HOLD), or an idempotency key was reused with a different body (IDEMPOTENCY_CONFLICT). The error code field names the specific cause.