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 and contacts 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 voice-agent call data with Breesy recording links per call.

Query Parameters

NameRequiredDescription
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, callback number, or customer phone (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.

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",
"caller_id": "+15551234567",
"timestamp": "2026-06-22T18:04:11+00:00",
"duration": "182",
"agent_type": "afterhours",
"call_category": "new_urgent_service_request",
"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"
},
"recording_url": "https://api.breesy.app/data/recordings/CA0123456789abcdef0123456789abcdef",
"recording_breesy_url": "https://api.breesy.app/insights?tab=calldata&callId=conv_CA0123456789abcdef0123456789abcdef"
}
]
}

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

Streams or downloads a single call recording. 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)

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 (recording_url, breesy_url, content_type) without streaming audio. Useful when you want to confirm a recording exists before playing it.
  • 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://api.breesy.app/insights?tab=calldata&callId=conv_CA0123456789abcdef0123456789abcdef",
"content_type": "audio/mpeg"
}

Returns 404 Recording not found when the call does not exist or belongs to another franchise.

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