Related Articles
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"
}
| Field | Type | Required | Notes |
|---|---|---|---|
appointment_id | string | Yes | The brayv.ai appointment ID. Every calendar webhook payload includes it as appointment.id. |
status | string | Yes | completed, 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_webhook | boolean | Yes | true 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_reason | string | No | Saved 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_key | string | No | See Idempotency. |
Unknown fields are ignored and reported in ignored_fields.
Allowed Status Changes
| Current status | Can change to |
|---|---|
scheduled | completed, no_show, cancelled |
completed | no_show |
no_show | completed |
cancelled | Nothing. A cancelled appointment is final. |
- An appointment can't be moved back to
scheduledthrough the API. Sendingscheduledfor an appointment that is still scheduled is accepted and changes nothing. completedandno_showare 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:
| Status | What runs |
|---|---|
cancelled | Pending 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. |
completed | Journey stages triggered by Appointment Completed on that calendar run. |
no_show | Journey 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
}
| Field | Meaning |
|---|---|
previous_status | The appointment's status before this request. |
appointment_status | The appointment's status now. |
changed | false when the appointment already had the requested status. Nothing was written, and no automations or webhook ran. |
webhook_suppressed | true when the status changed and the calendar webhook was skipped at your request. |
ignored_fields | Fields that were not applied, each with a reason. |
account_status | active normally. frozen when the account is paused for a payment issue (see below). |
notice | null 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
| Status | code | When |
|---|---|---|
401 | Missing or invalid API key. | |
403 | Key is not authorized for this account. | |
403 | account_inactive | The account's subscription is cancelled. |
404 | appointment_not_found | No appointment with that ID exists in this account. |
409 | invalid_status_transition | The change isn't allowed from the appointment's current status. current_status says what it is now. |
409 | appointment_not_started | completed or no_show was sent before the appointment's start time. |
422 | appointment_id, status, or suppress_webhook is missing or has a value that isn't accepted. | |
429 | Rate 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
}'