Back to Help Home
Last updated: 2026-10-25

Public API Appointment Status Endpoint

This endpoint lets an external system change the status of an appointment that already exists in a brayv.ai account: mark it completed, mark it a no-show, or cancel it. The change is handled exactly as if a team member made it in brayv.ai. Journey stages triggered by the status run, reminders and calendar sync behave the same way, and connected field-service systems are updated. The one thing the caller decides is whether the calendar's own webhook is sent for the change.

Any system with a brayv.ai API key can call it. A common setup: a calendar's External Webhook sends appointments to another system, such as a CRM or field-service tool. Your team records the outcome there, and that system reports the status back to brayv.ai through this endpoint.

Endpoint

POST https://api.brayv.ai/v1/accounts/{account_id}/appointments/status
Authorization: Bearer {api_key}
Content-Type: application/json

Replace {account_id} with the brayv.ai account ID that owns the appointment.

Authentication

Send the API key in the Authorization header:

Authorization: Bearer brayv_live_...

The same keys used for lead submission and contact updates work here. A key is scoped to either one account or an organization that has access to the target account. Rate limits are shared with the other endpoints, per key and per account.

Request

{
  "appointment_id": "8d3f6c1e-2a4b-4f7e-9c51-0b6a7d2e9f13",
  "status": "no_show",
  "suppress_webhook": true,
  "idempotency_key": "crm-deal-48213-no-show"
}
FieldTypeRequiredNotes
appointment_idstringYesThe brayv.ai appointment ID. Every calendar webhook payload includes it as appointment.id.
statusstringYescompleted, no_show, or cancelled. See Allowed Status Changes. Case doesn't matter, canceled is accepted for cancelled, and no-show or no show for no_show.
suppress_webhookbooleanYestrue skips the calendar's webhook for this change. false sends it as usual. The strings "true" and "false" are also accepted. See Avoiding Webhook Echoes.
cancellation_reasonstringNoSaved on the appointment when status is cancelled, and used as the note on a ServiceTitan job cancellation. Trimmed to 1000 characters. Ignored for other statuses and reported in ignored_fields.
idempotency_keystringNoSee Idempotency.

Unknown fields are ignored and reported in ignored_fields.

Allowed Status Changes

Current statusCan change to
scheduledcompleted, no_show, cancelled
completedno_show
no_showcompleted
cancelledNothing. A cancelled appointment is final.
  • An appointment can't be moved back to scheduled through the API. Sending scheduled for an appointment that is still scheduled is accepted and changes nothing.
  • completed and no_show are only accepted once the appointment's start time has passed. A future appointment can still be cancelled.
  • A change that isn't allowed is refused with 409, and nothing is updated. Team members can still edit the appointment in brayv.ai.

What Happens After a Change

The new status runs the same automations as a change made in brayv.ai:

StatusWhat runs
cancelledPending reminders are cancelled, and cancellation notices go to the appointment owner and team if the calendar has them turned on. The Google or Outlook calendar event is removed. A linked ServiceTitan job is cancelled with the calendar's ServiceTitan cancellation reason, and a linked HouseCall Pro appointment or JobTread task is deleted. Journey stages triggered by Appointment Cancelled on that calendar run.
completedJourney stages triggered by Appointment Completed on that calendar run.
no_showJourney stages triggered by Appointment No-Show on that calendar run.

A journey whose cancellation or no-show stage can start an opportunity may enroll the contact and begin that stage's outreach. Switching between completed and no_show runs the new status's automations, but doesn't undo what the earlier status already did: messages that were sent stay sent, and journey moves stay in place.

Each change is recorded in the appointment's history and the contact's activity as made by Public API, followed by the name of the API key.

Avoid sending historical outcomes in bulk. Each status change runs the automations above, so backfilling months of cancellations or no-shows can enroll many contacts in re-engagement outreach at once.

Avoiding Webhook Echoes

If the system calling this endpoint also receives the calendar's webhook, a status it sends here would normally come straight back to it as an appointment.cancelled, appointment.completed, or appointment.no_show webhook. Send suppress_webhook: true to skip the calendar webhook for this one change. Nothing else is skipped, and changes made in brayv.ai still send the webhook as usual. The appointment history notes when a webhook was skipped.

A calendar has a single webhook URL. If that URL feeds more than one system, for example a Zapier zap that updates both a CRM and a spreadsheet, suppressing the webhook hides the change from all of them.

Sending a status the appointment already has changes nothing and never sends a webhook, so an echo that does reach your system can't start a loop.

Response

{
  "status": "success",
  "appointment_id": "8d3f6c1e-2a4b-4f7e-9c51-0b6a7d2e9f13",
  "previous_status": "scheduled",
  "appointment_status": "no_show",
  "changed": true,
  "webhook_suppressed": true,
  "ignored_fields": [],
  "idempotent_replay": false,
  "account_status": "active",
  "notice": null
}
FieldMeaning
previous_statusThe appointment's status before this request.
appointment_statusThe appointment's status now.
changedfalse when the appointment already had the requested status. Nothing was written, and no automations or webhook ran.
webhook_suppressedtrue when the status changed and the calendar webhook was skipped at your request.
ignored_fieldsFields that were not applied, each with a reason.
account_statusactive normally. frozen when the account is paused for a payment issue (see below).
noticenull normally. A human-readable explanation when account_status is not active.

Idempotency

idempotency_key is optional. Repeated requests with the same API key, target account, and idempotency key within about 5 minutes return the original result with idempotent_replay: true instead of applying the change again.

Account Paused for a Payment Issue

If the target account is paused for a payment issue, the status change still applies. The response reports account_status: "frozen" and a notice explaining that no outbound outreach will occur until billing is fixed.

Account Inactive

If the account's brayv.ai subscription has been cancelled, the request returns 403 Forbidden and nothing is changed. Have an account admin reactivate the subscription, then retry.

Errors

StatuscodeWhen
401Missing or invalid API key.
403Key is not authorized for this account.
403account_inactiveThe account's subscription is cancelled.
404appointment_not_foundNo appointment with that ID exists in this account.
409invalid_status_transitionThe change isn't allowed from the appointment's current status. current_status says what it is now.
409appointment_not_startedcompleted or no_show was sent before the appointment's start time.
422appointment_id, status, or suppress_webhook is missing or has a value that isn't accepted.
429Rate limit exceeded for this key and account.

Refused change example:

{
  "detail": {
    "code": "invalid_status_transition",
    "message": "Appointment status cannot change from cancelled to completed",
    "appointment_id": "8d3f6c1e-2a4b-4f7e-9c51-0b6a7d2e9f13",
    "current_status": "cancelled",
    "requested_status": "completed"
  }
}

Validation error example:

{
  "detail": {
    "message": "Invalid appointment status update",
    "validation_errors": [
      {
        "field": "suppress_webhook",
        "reason": "Required. Send true to skip the calendar webhook for this change, or false to send it as usual"
      }
    ],
    "ignored_fields": []
  }
}

Example cURL

curl -X POST "https://api.brayv.ai/v1/accounts/ACCOUNT_ID/appointments/status" \
  -H "Authorization: Bearer brayv_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "appointment_id": "8d3f6c1e-2a4b-4f7e-9c51-0b6a7d2e9f13",
    "status": "cancelled",
    "cancellation_reason": "Customer called to cancel",
    "suppress_webhook": true
  }'