Skip to content

Data Share API Reference

All endpoints require a franchise-scoped bearer token. See Data Share API Authentication.

The franchise is always derived from the token, so no franchise_id parameter is accepted.

Rate Limits

Each API key is limited to 60 requests per minute and 5,000 requests per day. Every response includes the current limit state:

HeaderDescription
X-RateLimit-LimitPer-minute request limit
X-RateLimit-RemainingRequests remaining in the current minute
X-RateLimit-ResetUnix epoch (seconds) when the minute window resets
X-RateLimit-Limit-DayPer-day request limit
X-RateLimit-Remaining-DayRequests remaining in the current day

When a limit is exceeded the API responds with 429 Too Many Requests and a Retry-After header (seconds to wait). Clients should honor Retry-After and back off.

Pagination

The calls, jobs, contacts, and calendar/events list endpoints use cursor (keyset) pagination. Each response returns a pagination object:

{ "page_size": 100, "has_more": true, "next_cursor": "eyJ0Ijoi..." }

To fetch the next page, pass the returned next_cursor value back as the cursor query parameter. When has_more is false, next_cursor is null and there are no more records. Cursors are opaque — do not parse or construct them. page_size accepts 1–200 (default 100).

GET https://api.breesy.app/data/locations

Returns the locations in your franchise so you can filter other endpoints by location_id.

Query Parameters

None.

Example Request

GET https://api.breesy.app/data/locations
Authorization: Bearer <api_token>

Example Response

{
"franchise_id": "0c8b2a1e-1111-2222-3333-444455556666",
"locations": [
{ "location_id": "aaaa1111-2222-3333-4444-555566667777", "location_name": "North Branch" },
{ "location_id": "bbbb1111-2222-3333-4444-555566667777", "location_name": "South Branch" }
]
}

GET https://api.breesy.app/data/calls

Returns call data with Breesy recording links per call. Two kinds of calls are returned, distinguished by call_type:

  • ai_agent — calls answered by the Breesy voice agent (daytime or after-hours). When the agent transfers the caller to a live person, the recorded “live patch” leg appears in the nested post_call object (transcript, AI summary, sentiment, and its own recording link).
  • breesy_phone — calls handled directly on Breesy Phone by your team: inbound, outbound, and internal (extension-to-extension, including transfers). Breesy Phone-specific data (AI summary, extracted entities, sentiment, who handled the call, direction) appears in the nested breesy_phone object.

Each call is returned once. When the voice agent answers a call, the phone system also records that leg against the agent’s own extension; that leg is not returned as a breesy_phone call because it is already the ai_agent record. When the voice agent transfers the caller to your office, the office leg is returned as a breesy_phone call, and the ai_agent record points to it through linked_breesy_phone_call_id when the link is known.

Query Parameters

NameRequiredDescription
call_typeNoai_agent or breesy_phone. Omit to receive both
directionNoBreesy Phone only: inbound, outbound, or internal. Implies call_type=breesy_phone; returns 400 when combined with call_type=ai_agent
location_idNoLimit to a single location UUID
start_dateNoInclusive start of the call timestamp range (ISO 8601 or YYYY-MM-DD)
end_dateNoInclusive end of the call timestamp range (ISO 8601 or YYYY-MM-DD)
call_idNoExact match on the call’s conversation_id
phoneNoMatch any of the caller ID, dialed number, callback number, or customer phone, including the Breesy Phone caller, dialed, and customer numbers (digits are normalized)
cursorNoOpaque pagination cursor from a previous response’s next_cursor
page_sizeNoRecords per page, 1–200 (default 100)
include_recording_urlNoSet to false to omit recording links (faster for bulk listing). Default true

Each call includes two stable, Breesy-branded links:

  • recording_url — a GET https://api.breesy.app/data/recordings/{conversation_id} endpoint that streams or downloads the recording through Breesy. Call it with your Authorization: Bearer <api_token> header. The response is the audio file directly (audio/mpeg); the upstream storage provider is never exposed. This link does not expire from your side — access is re-authorized on every request — so you can store it safely. See GET /data/recordings/{conversation_id} below.
  • recording_breesy_url — a permanent link that opens the call and its recording inside Breesy (sign-in required). Use this when a person should review the call.

Both fields may be null when no conversation_id is available, and are omitted entirely when include_recording_url=false.

When a call has a post-call (live patch) leg, post_call.recording_url links to that leg’s audio (?leg=post_call). It is null when post_call.recording_available is false.

Call Fields

FieldDescription
call_typeai_agent or breesy_phone
agent_typeUnderlying agent: daytime, afterhours, or breesy_phone
caller_id, dialed_phoneCalling and dialed numbers
transferred, transferred_numberWhether the voice agent transferred the caller, and to what number
call_ended_reasonVoice-agent end reason. Common values: customer-ended-call, assistant-forwarded-call, assistant-ended-call, assistant-said-end-call-phrase, silence-timeout, customer-did-not-answer, customer-misdialed, carrier-disconnect, transfer-failed, system-error. null for Breesy Phone calls
linked_breesy_phone_call_idFor a transferred ai_agent call, the conversation_id of the Breesy Phone call that received the transfer (when known)
transcriptPrimary transcript (voice agent, or the Breesy Phone conversation)
breesy_phonePresent on breesy_phone calls, otherwise null. See Breesy Phone Fields
post_callPresent when the call has a live patch leg, otherwise null. Contains transcript, ai_summary, entities, sentiment, recording_available, recording_url

breesy_phone.ai_summary, breesy_phone.entities, and breesy_phone.sentiment are null until Breesy’s analysis runs, which is normally within a few minutes of the call ending. post_call fields fill in as the live patch recording is processed.

Breesy Phone Fields

FieldDescription
directioninbound (outside caller reached your team), outbound (your team called out), or internal (extension to extension, including a call transferred from one team member to another). Uses the same rules as Call Insights in Breesy, so counts reconcile with what you see in the app
handled_by, handler_extensionTeam member and extension that handled this leg
origin_extensionExtension that placed the call, for outbound and internal calls. null for inbound
caller_phone, dialed_phoneNumbers as recorded by the phone system for this leg
customer_phoneThe outside party’s number on outbound calls. null otherwise
parent_call_idShared by every leg of the same phone call. When a call is transferred between team members, each leg is returned as its own breesy_phone call with the same parent_call_id; group on it to see the full path of the call
statePhone-system outcome, e.g. Connected or No answer. This is the Breesy Phone equivalent of call_ended_reason
started_at, duration_msStart time and talk duration of this leg in milliseconds (duration at the top level is in seconds)
ai_summary, entities, sentimentBreesy’s analysis of the conversation; sentiment is 1–5 as text

Example Request

GET https://api.breesy.app/data/calls?start_date=2026-06-01&end_date=2026-06-23&page_size=50
Authorization: Bearer <api_token>

Example Response

{
"scope": { "franchise_id": "0c8b2a1e-1111-2222-3333-444455556666", "location_id": null },
"pagination": { "page_size": 50, "has_more": true, "next_cursor": "eyJ0IjoiMjAyNi0wNi0yMlQxODowNDoxMSswMDowMCIsImkiOiJDQTAxMjMuLi4ifQ" },
"calls": [
{
"conversation_id": "CA0123456789abcdef0123456789abcdef",
"call_type": "ai_agent",
"caller_id": "+15551234567",
"dialed_phone": "+15550001111",
"timestamp": "2026-06-22T18:04:11+00:00",
"duration": "182",
"agent_type": "afterhours",
"call_category": "new_urgent_service_request",
"call_ended_reason": "assistant-forwarded-call",
"transferred": true,
"transferred_number": "+15552223333",
"linked_breesy_phone_call_id": null,
"customer_name": "Jordan Smith",
"customer_email": "jordan@example.com",
"call_summary": "Water damage in the basement after a pipe burst.",
"loss_type": "Water",
"referral_source": "Google",
"notes": "Caller requested a morning callback.",
"property_area": "Basement",
"request_type": "INITIAL_SERVICE_REQUEST",
"service_location": "123 Main St, Springfield",
"callback_number": "+15557654321",
"location_id": "aaaa1111-2222-3333-4444-555566667777",
"location": "North Branch",
"transcript": "Agent: Thanks for calling...",
"real_estate": {
"address": "123 Main St, Springfield",
"last_sale_value": "415000",
"current_value": "468000",
"owner_name1": "Jordan Smith",
"owner_name2": null,
"room_count": "7",
"square_footage": "2100",
"year_built": "1998"
},
"breesy_phone": null,
"post_call": {
"transcript": "Tech: Hi Jordan, this is Mike...",
"ai_summary": "Jordan confirmed the basement leak; Mike scheduled a visit for the morning.",
"entities": { "customer_name": "Jordan", "loss_type": "water", "next_steps": "Morning visit" },
"sentiment": "4",
"recording_available": true,
"recording_url": "https://api.breesy.app/data/recordings/CA0123456789abcdef0123456789abcdef?leg=post_call"
},
"recording_url": "https://api.breesy.app/data/recordings/CA0123456789abcdef0123456789abcdef",
"recording_breesy_url": "https://www.breesy.app/insights?tab=calldata&callId=conv_CA0123456789abcdef0123456789abcdef"
},
{
"conversation_id": "20260622180411000000-0123456789abcdef0123456789abcdef",
"call_type": "breesy_phone",
"caller_id": "5551234567",
"dialed_phone": "5550001111",
"timestamp": "2026-06-22T18:04:11+00:00",
"duration": "96",
"agent_type": "breesy_phone",
"call_summary": "Inbound call - Duration: 1m 36s",
"location_id": "aaaa1111-2222-3333-4444-555566667777",
"location": "North Branch",
"transcript": "Team member: Thanks for calling...",
"breesy_phone": {
"ai_summary": "Customer called to confirm the crew's arrival window for tomorrow's dry-out.",
"entities": { "customer_name": "Jordan Smith", "job_number": "2026-0042", "loss_type": "water" },
"sentiment": "4",
"handled_by": "Alex Chen",
"handler_extension": "151",
"origin_extension": null,
"direction": "inbound",
"state": "Connected",
"started_at": "2026-06-22T18:04:11+00:00",
"duration_ms": 96000,
"customer_phone": null,
"caller_phone": "5551234567",
"dialed_phone": "5550001111",
"parent_call_id": "20260622180411000000-0123456789abcdef0123456789abcdef"
},
"post_call": null,
"recording_url": "https://api.breesy.app/data/recordings/20260622180411000000-0123456789abcdef0123456789abcdef",
"recording_breesy_url": "https://www.breesy.app/insights?tab=calldata&callId=conv_20260622180411000000-0123456789abcdef0123456789abcdef"
}
]
}

GET https://api.breesy.app/data/recordings/{conversation_id}

Streams or downloads a single call recording. Works for both voice-agent (ai_agent) and Breesy Phone (breesy_phone) calls. The recording is resolved against your franchise on every request, so you can only fetch recordings for calls that belong to you.

Path / Query Parameters

NameRequiredDescription
conversation_idYesThe call’s conversation_id (path segment, or ?conversation_id= / ?call_id= query)
legNoprimary (default) for the voice-agent or Breesy Phone audio, or post_call for the live patch leg recorded after a transfer

Behavior

  • Default — responds with 200 OK and streams the .mp3 through Breesy (Content-Type: audio/mpeg, Accept-Ranges: bytes). Use this URL in an <audio> element, a download link, or any HTTP client. The storage provider is never shown to the client.
  • Accept: application/json — returns metadata only (leg, call_type, recording_url, breesy_url, content_type) without streaming audio. Useful when you want to confirm the call exists before playing it. Note that this does not confirm the audio file itself has been stored yet; issue a normal (or Range: bytes=0-0) request for that.
  • This Breesy link itself is stable and does not expire; authorization is checked on every request.

Example Request

GET https://api.breesy.app/data/recordings/CA0123456789abcdef0123456789abcdef
Authorization: Bearer <api_token>

Example Metadata Response (Accept: application/json)

{
"conversation_id": "CA0123456789abcdef0123456789abcdef",
"recording_url": "https://api.breesy.app/data/recordings/CA0123456789abcdef0123456789abcdef",
"breesy_url": "https://www.breesy.app/insights?tab=calldata&callId=conv_CA0123456789abcdef0123456789abcdef",
"content_type": "audio/mpeg"
}

Expected Errors

StatusreasonMeaning
404not_foundThe call does not exist in your franchise, has no recording on file, or (for leg=post_call) has no live patch leg
404not_syncedA Breesy Phone call whose audio has not been copied into Breesy storage yet. Retry later
404not_availableA live patch leg exists but its audio was never stored in Breesy, so it cannot be served
400leg is not primary or post_call, or conversation_id is missing

GET https://api.breesy.app/data/jobs

Returns job records for franchises with Breesy Jobs enabled, including live status, status history, loss details, referral source, insurance/carrier data, schedule, assigned team, and invoicing summary.

Responds with 403 when Breesy Jobs is not enabled for the franchise the token is scoped to.

Query Parameters

NameRequiredDescription
location_idNoLimit to a single location UUID
statusNoFilter by job status: lead, scheduled, assessment, estimate_sent, approved, in_progress, quality_check, completed, invoiced, paid_closed, cancelled
job_idNoExact match on the job’s UUID
job_numberNoExact match on the job number (e.g. 2026-0042)
start_dateNoInclusive start of the job created-at range (ISO 8601 or YYYY-MM-DD)
end_dateNoInclusive end of the job created-at range (ISO 8601 or YYYY-MM-DD)
updated_sinceNoOnly jobs updated at or after this timestamp — useful for incremental sync
cursorNoOpaque pagination cursor from a previous response’s next_cursor
page_sizeNoRecords per page, 1–200 (default 100)

Example Request

GET https://api.breesy.app/data/jobs?status=in_progress&page_size=50
Authorization: Bearer <api_token>

Example Response

{
"scope": { "franchise_id": "0c8b2a1e-1111-2222-3333-444455556666", "location_id": null },
"pagination": { "page_size": 50, "has_more": false, "next_cursor": null },
"jobs": [
{
"job_id": "dddd1111-2222-3333-4444-555566667777",
"job_number": "2026-0042",
"job_name": "Smith Residence — Basement Water Loss",
"job_status": "in_progress",
"status_updated_at": "2026-07-28T14:22:05+00:00",
"priority": "high",
"created_at": "2026-07-20T09:15:00+00:00",
"updated_at": "2026-07-30T16:40:12+00:00",
"customer": {
"name": "Jordan Smith",
"contact_id": 10422,
"property_address": "123 Main St, Springfield",
"property_type": "residential",
"commercial_property_name": null
},
"loss": {
"date_of_loss": "2026-07-19",
"source_of_loss": "Burst supply line",
"damage_type": "Water",
"loss_types": [ { "name": "Water Mitigation", "slug": "water_mitigation", "is_primary": true } ]
},
"referral": { "source": "Insurance Agent", "notes": null },
"insurance": {
"carrier": "State Farm",
"claim_number": "55-1234-X89",
"policy_number": "SF-99881122",
"deductible": 1000,
"claim_status": "approved",
"claim_status_updated_at": "2026-07-25T11:02:00+00:00",
"adjuster_name": "Pat Rivera",
"adjuster_phone": "+15550001111",
"adjuster_email": "pat.rivera@example.com",
"payment_type": "insurance"
},
"schedule": {
"start_date": "2026-07-21",
"target_completion_date": "2026-08-05",
"actual_completion_date": null
},
"team": { "project_manager": "Alex Chen", "estimator": "Sam Lee", "coordinator": null },
"accounting": {
"invoice_number": "INV-2081",
"invoice_date": "2026-07-29",
"invoice_amount": 18450.00,
"amount_paid": 9225.00,
"payment_status": "partially_paid",
"payment_due_date": "2026-08-28",
"estimate_total": 18450.00
},
"counts": { "attachments": 34, "notes": 12 },
"location": { "location_id": "aaaa1111-2222-3333-4444-555566667777", "location_name": "North Branch" },
"closure": { "closed_at": null, "closure_reason": null },
"status_history": [
{ "old_status": null, "new_status": "lead", "reason": null, "changed_at": "2026-07-20T09:15:00+00:00" },
{ "old_status": "lead", "new_status": "in_progress", "reason": null, "changed_at": "2026-07-28T14:22:05+00:00" }
]
}
]
}

GET https://api.breesy.app/data/contacts

Reads contacts from your CRM. Returns a paginated list, or a single contact when id is supplied.

Query Parameters

NameRequiredDescription
idNoReturn a single contact by its numeric ID
phoneNoMatch the primary or alternate phone (digits are normalized)
searchNoCase-insensitive match on name, organization, email, or phone
location_idNoLimit to contacts tracked at a location UUID
cursorNoOpaque pagination cursor from a previous response’s next_cursor
page_sizeNoRecords per page, 1–200 (default 100)

Example Request

GET https://api.breesy.app/data/contacts?search=jordan&page_size=25
Authorization: Bearer <api_token>

Example Response

{
"scope": { "franchise_id": "0c8b2a1e-1111-2222-3333-444455556666" },
"pagination": { "page_size": 25, "has_more": false, "next_cursor": null },
"contacts": [
{
"id": 4821,
"first_name": "Jordan",
"last_name": "Smith",
"organization_name": null,
"is_business": false,
"primary_phone": "+15551234567",
"primary_email": "jordan@example.com",
"alternate_phones": [],
"alternate_emails": [],
"tags": ["VIP"],
"primary_contact_tags": [],
"notes": "Prefers morning calls.",
"referred_by": "Google",
"address": {
"line1": "123 Main St",
"line2": null,
"city": "Springfield",
"state": "IL",
"postal": "62704",
"country": "US"
},
"location_id": ["aaaa1111-2222-3333-4444-555566667777"],
"total_calls": 3,
"last_contact_date": "2026-06-22T18:04:11+00:00",
"lifetime_value": 12500.0,
"is_verified": true,
"created_at": "2026-01-10T12:00:00+00:00",
"updated_at": "2026-06-22T18:10:00+00:00"
}
]
}

PATCH https://api.breesy.app/data/contacts

Updates an existing contact. Only the fields you include are changed.

Request Body

FieldRequiredDescription
idYesThe numeric contact ID to update
first_nameNo
last_nameNo
organization_nameNo
is_businessNoBoolean
primary_phoneNoNormalized to E.164 on save; must be a valid phone
primary_emailNo
alternate_phonesNoArray of phone strings
alternate_emailsNoArray of email strings
tagsNoArray of tag strings
primary_contact_tagsNoArray of tag strings
notesNo
referred_byNo
addr_line1, addr_line2, addr_city, addr_state, addr_postal, addr_countryNoAddress fields
location_idNoArray of location UUID strings

Example Request

PATCH https://api.breesy.app/data/contacts
Authorization: Bearer <api_token>
Content-Type: application/json
{
"id": 4821,
"tags": ["VIP", "Repeat Customer"],
"notes": "Signed annual maintenance agreement."
}

Example Response

{
"contact": {
"id": 4821,
"first_name": "Jordan",
"last_name": "Smith",
"tags": ["VIP", "Repeat Customer"],
"notes": "Signed annual maintenance agreement."
}
}

POST https://api.breesy.app/data/contacts

Creates a contact. Breesy matches on phone number to avoid duplicates; if a matching contact already exists, that contact is updated with the supplied fields and matched_existing is true.

Request Body

primary_phone is required. All other writable fields are optional.

Example Request

POST https://api.breesy.app/data/contacts
Authorization: Bearer <api_token>
Content-Type: application/json
{
"first_name": "Alex",
"last_name": "Doe",
"primary_phone": "555-987-6543",
"primary_email": "alex@example.com",
"tags": ["New Lead"]
}

Example Response

{
"contact": {
"id": 5099,
"first_name": "Alex",
"last_name": "Doe",
"primary_phone": "+15559876543",
"primary_email": "alex@example.com",
"tags": ["New Lead"]
},
"matched_existing": false
}

Expected Errors

StatusMeaning
400Missing required field, invalid JSON, invalid primary_phone, or invalid cursor
401Missing, invalid, expired, or revoked bearer token
404The contact id does not exist within this franchise (PATCH)
429Rate limit exceeded — honor the Retry-After header and retry later

POST https://api.breesy.app/data/sms

Sends an outbound SMS from a Breesy Phone number that belongs to your franchise. The franchise is derived from the API key. Before sending, Breesy confirms that Breesy Phone is enabled for the account and that the from number is both owned by the franchise and enabled for texting.

This endpoint requires Breesy Phone.

Request Body

NameRequiredDescription
toYesDestination phone number (10-digit US number, with or without formatting)
fromYesYour Breesy Phone number to send from. Must be assigned to this franchise
messageYesText to send. Maximum 1,600 characters

destination, from_number, and body are accepted as aliases.

Example Request

POST https://api.breesy.app/data/sms
Authorization: Bearer <api_token>
Content-Type: application/json
{
"to": "5207712736",
"from": "8156974275",
"message": "Your technician is on the way."
}

Example Response

{
"success": true,
"status": "Accepted",
"message_id": "m-6a8389e47e971",
"session_id": "sms_a1b2c3d4e5f6...",
"from": "+18156974275",
"to": "+15207712736"
}

A successful send returns 202 Accepted. message_id may be null until the carrier assigns one; status comes from Breesy Phone.

Expected Errors

StatusCodeMeaning
400invalid_jsonBody is not valid JSON
400missing_to / missing_from / missing_messageRequired field is missing
400invalid_to / invalid_fromPhone number is not a valid 10-digit US number
400same_from_and_tofrom and to are the same number
400message_too_longmessage is longer than 1,600 characters
401Missing, invalid, expired, or revoked bearer token
403from_number_not_ownedfrom is not a Breesy Phone number on this account
403from_number_disabledfrom belongs to this account but is currently disabled
403from_number_not_sms_enabledfrom is not enabled for texting
409breesy_phone_not_enabledBreesy Phone is not enabled for this account
409breesy_phone_not_configuredBreesy Phone is not set up for this account
429Rate limit exceeded — honor the Retry-After header and retry later
502sms_send_failedBreesy Phone accepted the request format but could not send the text

Calendar

The calendar endpoints expose the Breesy Calendar page — Job Scheduling and Service Routes — for your franchise. They require Breesy Calendar to be enabled on the account; otherwise every calendar endpoint returns 403.

The usual flow for booking is:

  1. GET /data/calendar/assignees once to learn the location ids, team members, teams, and scheduling rules.
  2. GET /data/calendar/availability to find open times for a person (or the whole location).
  3. POST /data/calendar/events to book, then PATCH / DELETE to change or cancel.

All times are ISO 8601. Send times in UTC (Z) or with an explicit offset; responses are returned in UTC. Each location has a timezone that its scheduling window is interpreted in.

Appointments booked through the API appear on the Calendar page immediately, are recorded in the Change Log as Data Share API - token name, and are pushed to the assigned person’s connected Outlook/Google calendar (when they have one) within about a minute, using the same sync the app uses. Customer email/SMS alerts are off by default for API bookings; pass notify_customer_email / notify_customer_sms as true to opt in.

GET https://api.breesy.app/data/calendar/assignees

Returns, per location: the scheduling settings, active team members, teams (groups of team members), and time blocks.

Query Parameters

NameRequiredDescription
location_idNoLimit to one location (UUID)
include_inactiveNotrue to include inactive team members and teams

Example Response

{
"scope": { "franchise_id": "79c038ba-...", "location_id": null },
"locations": [
{
"id": "48aac354-...",
"name": "Central-Town-60050",
"timezone": "America/Chicago",
"scheduling": {
"window_start": "08:00:00",
"window_end": "17:00:00",
"days_of_week": [1, 2, 3, 4, 5],
"allow_weekend_scheduling": false,
"default_appointment_duration_minutes": 60,
"fixed_travel_buffer_minutes": 15,
"assignment_strategy": "round_robin",
"skill_based_scheduling": true
},
"team_members": [
{
"id": "0b725dc6-...",
"name": "Kyle McDougall",
"email": "kyle@example.com",
"phone_number": "+15125550100",
"role": "Project Manager",
"is_active": true,
"skills": ["Plumbing", "Water Mitigation"],
"max_events_per_day": 5,
"has_connected_calendar": true
}
],
"teams": [
{ "id": "aff93bde-...", "name": "Mitigation Crew A", "color": "#2563eb", "is_active": true, "skills": [], "max_events_per_day": 5, "team_member_ids": ["0b725dc6-..."] }
],
"time_blocks": [
{ "id": "37af2e00-...", "name": "Morning Block", "start_time": "08:00:00", "end_time": "12:00:00", "days_of_week": [1, 2, 3, 4, 5], "max_concurrent_jobs": 5, "jobs_per_person": 3, "eligible_team_member_ids": ["0b725dc6-..."], "eligible_team_ids": [], "eligible_roles": [] }
]
}
]
}

days_of_week uses 0 = Sunday through 6 = Saturday. has_connected_calendar tells you whether a booking for that person will also be pushed to their Outlook/Google calendar.

GET https://api.breesy.app/data/calendar/availability

Computes open appointment slots for one location over a date range (up to 31 days). A slot is open for a person when it is inside the scheduling window, does not overlap anything already on their calendar (including buffers and travel time), and they are under their daily appointment limit.

Query Parameters

NameRequiredDescription
location_idYesLocation UUID
start_dateYesFirst day to check (YYYY-MM-DD, in the location’s timezone)
end_dateNoLast day to check (defaults to start_date; max 31 days)
duration_minutesNoAppointment length (defaults to the location’s default, usually 60)
slot_minutesNoSpacing between candidate start times (15–240, default 30)
team_member_idNoOnly check this person
team_idNoOnly check members of this team

Example Request

GET https://api.breesy.app/data/calendar/availability?location_id=48aac354-...&start_date=2026-10-22&team_member_id=0b725dc6-...&slot_minutes=60
Authorization: Bearer <api_token>

Example Response

{
"scope": { "franchise_id": "79c038ba-...", "location_id": "48aac354-..." },
"location_id": "48aac354-...",
"timezone": "America/Chicago",
"duration_minutes": 60,
"slot_minutes": 60,
"window_start": "08:00:00",
"window_end": "17:00:00",
"days_of_week": [1, 2, 3, 4, 5],
"team_members": [{ "id": "0b725dc6-...", "name": "Kyle McDougall" }],
"days": [
{
"date": "2026-10-22",
"slots": [
{ "start_time": "2026-10-22T13:00:00+00:00", "end_time": "2026-10-22T14:00:00+00:00", "available_team_member_ids": ["0b725dc6-..."] },
{ "start_time": "2026-10-22T14:00:00+00:00", "end_time": "2026-10-22T15:00:00+00:00", "available_team_member_ids": ["0b725dc6-..."] }
]
}
]
}

Only slots with at least one available person are returned. Days the location does not schedule on are omitted. Slots in the past are never returned.

GET https://api.breesy.app/data/calendar/events

Lists appointments (and other calendar entries) for the franchise, ordered by start time. Supports cursor pagination (page_size 1–500, default 100).

Query Parameters

NameRequiredDescription
location_idNoLimit to one location (UUID)
start_timeNoReturn entries that end at or after this time (ISO 8601 or YYYY-MM-DD)
end_timeNoReturn entries that start at or before this time
team_member_idNoOnly entries on this person’s calendar (assigned to them, on a team they belong to, or imported from their connected calendar)
event_typeNoservice_job (appointments), personal (imported from a connected calendar), buffer, or travel
page_sizeNoItems per page (1–500, default 100)
cursorNoOpaque pagination cursor from a previous response’s next_cursor

Event Fields

FieldDescription
idAppointment id (UUID). Use it with the single-event, PATCH, and DELETE endpoints
location_id, location_nameOwning location
event_typeservice_job, personal, buffer, or travel
title, descriptionAppointment title and notes
start_time, end_time, timezoneWhen; timezone is the location’s timezone
assigned_team_member_id, assigned_team_member_namePerson the appointment is assigned to (or null)
assigned_team_id, assigned_team_nameTeam the appointment is assigned to (or null)
calendar_owner_emailFor personal entries, whose connected calendar it came from
customer_addressService address
customer{ name, phone, email }
job{ dash_job_number, dash_job_id, dash_job_name, loss_description }
prioritylow, normal, high, or vip
job_typeFree-text job type
locked_schedule, allow_time_adjustmentRoute-optimization flags (a locked appointment is never moved; allow_time_adjustment lets the optimizer shift it)
notify_customer_email, notify_customer_sms, notifications_pendingCustomer alert settings
synced_to_external_calendar, sync_statusWhether the entry is on the assignee’s connected calendar (synced, pending_retry, failed, or null when not applicable)
created_at, updated_atTimestamps

Entries imported from a person’s connected calendar whose privacy setting is “busy only” are returned with the title Busy and no details.

Example Response

{
"scope": { "franchise_id": "79c038ba-...", "location_id": "48aac354-..." },
"pagination": { "page_size": 100, "has_more": false, "next_cursor": null },
"events": [
{
"id": "7855c806-...",
"location_id": "48aac354-...",
"location_name": "Central-Town-60050",
"event_type": "service_job",
"title": "Monitoring visit - Nora Espinoza",
"description": "Clean up debris",
"start_time": "2026-10-19T13:00:00+00:00",
"end_time": "2026-10-19T14:00:00+00:00",
"timezone": "America/Chicago",
"assigned_team_member_id": "0b725dc6-...",
"assigned_team_member_name": "Kyle McDougall",
"assigned_team_id": null,
"assigned_team_name": null,
"calendar_owner_email": null,
"customer_address": "616 Meadow Ln, Georgetown, TX 78626",
"customer": { "name": "Nora Espinoza", "phone": "+15125550136", "email": "nora@example.com" },
"job": { "dash_job_number": "26-BREESY-NE-REC-031", "dash_job_id": null, "dash_job_name": "Nora Espinoza - Laundry Room Rebuild", "loss_description": "Washer hose failure." },
"priority": "normal",
"job_type": null,
"locked_schedule": true,
"allow_time_adjustment": false,
"notify_customer_email": false,
"notify_customer_sms": false,
"notifications_pending": false,
"synced_to_external_calendar": true,
"sync_status": "synced",
"created_at": "2026-10-18T16:10:53+00:00",
"updated_at": "2026-10-19T14:16:48+00:00"
}
]
}

GET https://api.breesy.app/data/calendar/events/{event_id}

Returns one appointment as { "event": { ... } }, or 404 when the id does not exist within this franchise.

GET https://api.breesy.app/data/calendar/routes

The Service Routes view: for one location and one day, each team member’s stops in time order plus their route start address.

Query Parameters

NameRequiredDescription
location_idYesLocation UUID
dateYesDay (YYYY-MM-DD, in the location’s timezone)
team_member_idNoOnly this person’s route

Example Response

{
"scope": { "franchise_id": "79c038ba-...", "location_id": "48aac354-..." },
"location_id": "48aac354-...",
"date": "2026-10-19",
"timezone": "America/Chicago",
"routes": [
{
"team_member_id": "0b725dc6-...",
"team_member_name": "Kyle McDougall",
"start_address": "4949 E Van Buren St, Phoenix, AZ 85026",
"last_optimization": null,
"stops": [
{
"sequence": 1,
"event_id": "7855c806-...",
"title": "Monitoring visit - Nora Espinoza",
"start_time": "2026-10-19T13:00:00+00:00",
"end_time": "2026-10-19T14:00:00+00:00",
"address": "616 Meadow Ln, Georgetown, TX 78626",
"customer_name": "Nora Espinoza",
"customer_phone": "+15125550136",
"dash_job_number": "26-BREESY-NE-REC-031",
"job_type": null,
"locked_schedule": true,
"allow_time_adjustment": false
}
]
}
]
}

last_optimization is { source, created_at, notified } when a route optimization has run for that person and day, otherwise null. Active team members with no appointments that day are included with an empty stops list.

POST https://api.breesy.app/data/calendar/events

Books an appointment. Before saving, Breesy checks that the time is inside the location’s scheduling window, that the assigned person has nothing overlapping (including buffers and travel time), and that they are under their daily limit. A failed check returns 409 with details and nothing is saved.

Request Body

NameRequiredDescription
location_idYesLocation UUID (from assignees)
titleYesAppointment title
start_timeYesISO 8601 start
end_timeNoISO 8601 end. If omitted, duration_minutes or the location default (usually 60) is used
duration_minutesNoUsed only when end_time is omitted
assigned_team_member_idNoPerson to assign (UUID from assignees)
assigned_team_idNoTeam to assign (UUID from assignees). Provide a person or a team, not both
descriptionNoNotes
customer_addressNoService address (also used as the route stop)
customerNo{ "name", "phone", "email" }
jobNo{ "dash_job_number", "dash_job_id", "dash_job_name", "loss_description" }
priorityNolow, normal (default), high, or vip
job_typeNoFree-text job type
locked_scheduleNotrue to keep route optimization from moving this appointment (default false)
allow_time_adjustmentNotrue to let route optimization shift the time (default false; cannot be combined with locked_schedule)
notify_customer_email / notify_customer_smsNoCustomer alert opt-in (default false)
override_validationNotrue to skip the window, conflict, and daily-limit checks (recorded in the Change Log). Use sparingly

Example Request

POST https://api.breesy.app/data/calendar/events
Authorization: Bearer <api_token>
Content-Type: application/json
{
"location_id": "48aac354-...",
"title": "Water mitigation - initial inspection",
"start_time": "2026-10-22T14:00:00Z",
"duration_minutes": 60,
"assigned_team_member_id": "358248dd-...",
"customer_address": "616 Meadow Ln, Georgetown, TX 78626",
"customer": { "name": "Jane Doe", "phone": "+15125550199" },
"job": { "dash_job_number": "26-ABC-001" }
}

Example Response

201 Created

{
"event": { "id": "fad5718a-...", "title": "Water mitigation - initial inspection", "start_time": "2026-10-22T14:00:00+00:00", "end_time": "2026-10-22T15:00:00+00:00", "assigned_team_member_id": "358248dd-...", "assigned_team_member_name": "Bill Smith", "...": "..." },
"external_calendar_sync": "not_applicable"
}

external_calendar_sync is requested when the assignee has a connected calendar (the push happens in the background; watch sync_status on the event), otherwise not_applicable.

PATCH https://api.breesy.app/data/calendar/events/{event_id}

Reschedules, reassigns, or edits an appointment. Send only the fields you want to change; the same field names as POST are accepted. Sending start_time without end_time keeps the existing duration. Set assigned_team_member_id to null to unassign. When the time or the person changes, the same window / conflict / daily-limit checks run (excluding the appointment itself).

Entries imported from a person’s connected calendar (event_type = personal) cannot be changed or cancelled through the API.

Example Request

PATCH https://api.breesy.app/data/calendar/events/fad5718a-...
Authorization: Bearer <api_token>
Content-Type: application/json
{ "start_time": "2026-10-22T16:00:00Z", "assigned_team_member_id": "0b725dc6-..." }

Example Response

{
"event": { "id": "fad5718a-...", "start_time": "2026-10-22T16:00:00+00:00", "end_time": "2026-10-22T17:00:00+00:00", "assigned_team_member_name": "Kyle McDougall", "...": "..." },
"changed_fields": ["start_time", "end_time", "assigned_user_id"],
"external_calendar_sync": "requested"
}

external_calendar_sync is one of updated (the entry on the connected calendar was changed), moved / requested (reassigned; the old copy was removed and the new person’s calendar will be updated in the background), unchanged, not_applicable, or failed (the Breesy appointment was saved but the connected calendar could not be updated — Breesy keeps retrying).

DELETE https://api.breesy.app/data/calendar/events/{event_id}

Cancels an appointment and removes it from the assignee’s connected calendar. POST /data/calendar/events/{event_id}/cancel with an optional JSON body { "reason": "..." } does the same and records the reason in the Change Log.

Example Response

{ "deleted": true, "event_id": "fad5718a-...", "external_calendar_sync": "removed" }

Calendar Errors

StatusCodeMeaning
400title_required, start_time_required, invalid_time, invalid_time_range, invalid_priority, invalid_lock_flags, assignee_conflict, assignee_invalid, location_id_requiredRequest body problem (see message)
401Missing, invalid, expired, or revoked bearer token
403Breesy Calendar is not enabled for this franchise
404event_not_found, location_not_found, team_member_not_found, team_not_foundThe id does not exist within this franchise
409time_conflictThe person already has something overlapping. conflicts lists the overlapping entries
409daily_limit_reachedThe person is at max_events_per_day for that day
409outside_scheduling_windowOutside the location’s scheduling days/hours (window_start, window_end, timezone are included)
409event_not_editableThe entry came from a connected personal calendar and cannot be changed via the API
429Rate limit exceeded — honor the Retry-After header and retry later