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:
| Header | Description |
|---|---|
X-RateLimit-Limit | Per-minute request limit |
X-RateLimit-Remaining | Requests remaining in the current minute |
X-RateLimit-Reset | Unix epoch (seconds) when the minute window resets |
X-RateLimit-Limit-Day | Per-day request limit |
X-RateLimit-Remaining-Day | Requests 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/locationsAuthorization: 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
| Name | Required | Description |
|---|---|---|
location_id | No | Limit to a single location UUID |
start_date | No | Inclusive start of the call timestamp range (ISO 8601 or YYYY-MM-DD) |
end_date | No | Inclusive end of the call timestamp range (ISO 8601 or YYYY-MM-DD) |
call_id | No | Exact match on the call’s conversation_id |
phone | No | Match any of the caller ID, callback number, or customer phone (digits are normalized) |
cursor | No | Opaque pagination cursor from a previous response’s next_cursor |
page_size | No | Records per page, 1–200 (default 100) |
include_recording_url | No | Set to false to omit recording links (faster for bulk listing). Default true |
Notes On The Recording Links
Each call includes two stable, Breesy-branded links:
recording_url— aGET https://api.breesy.app/data/recordings/{conversation_id}endpoint that streams or downloads the recording through Breesy. Call it with yourAuthorization: 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. SeeGET /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=50Authorization: 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
| Name | Required | Description |
|---|---|---|
conversation_id | Yes | The call’s conversation_id (path segment, or ?conversation_id= / ?call_id= query) |
Behavior
- Default — responds with
200 OKand streams the.mp3through 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/CA0123456789abcdef0123456789abcdefAuthorization: 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
| Name | Required | Description |
|---|---|---|
id | No | Return a single contact by its numeric ID |
phone | No | Match the primary or alternate phone (digits are normalized) |
search | No | Case-insensitive match on name, organization, email, or phone |
location_id | No | Limit to contacts tracked at a location UUID |
cursor | No | Opaque pagination cursor from a previous response’s next_cursor |
page_size | No | Records per page, 1–200 (default 100) |
Example Request
GET https://api.breesy.app/data/contacts?search=jordan&page_size=25Authorization: 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
| Field | Required | Description |
|---|---|---|
id | Yes | The numeric contact ID to update |
first_name | No | |
last_name | No | |
organization_name | No | |
is_business | No | Boolean |
primary_phone | No | Normalized to E.164 on save; must be a valid phone |
primary_email | No | |
alternate_phones | No | Array of phone strings |
alternate_emails | No | Array of email strings |
tags | No | Array of tag strings |
primary_contact_tags | No | Array of tag strings |
notes | No | |
referred_by | No | |
addr_line1, addr_line2, addr_city, addr_state, addr_postal, addr_country | No | Address fields |
location_id | No | Array of location UUID strings |
Example Request
PATCH https://api.breesy.app/data/contactsAuthorization: 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/contactsAuthorization: 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
| Status | Meaning |
|---|---|
400 | Missing required field, invalid JSON, invalid primary_phone, or invalid cursor |
401 | Missing, invalid, expired, or revoked bearer token |
404 | The contact id does not exist within this franchise (PATCH) |
429 | Rate 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
| Name | Required | Description |
|---|---|---|
to | Yes | Destination phone number (10-digit US number, with or without formatting) |
from | Yes | Your Breesy Phone number to send from. Must be assigned to this franchise |
message | Yes | Text to send. Maximum 1,600 characters |
destination, from_number, and body are accepted as aliases.
Example Request
POST https://api.breesy.app/data/smsAuthorization: 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
| Status | Code | Meaning |
|---|---|---|
400 | invalid_json | Body is not valid JSON |
400 | missing_to / missing_from / missing_message | Required field is missing |
400 | invalid_to / invalid_from | Phone number is not a valid 10-digit US number |
400 | same_from_and_to | from and to are the same number |
400 | message_too_long | message is longer than 1,600 characters |
401 | Missing, invalid, expired, or revoked bearer token | |
403 | from_number_not_owned | from is not a Breesy Phone number on this account |
403 | from_number_disabled | from belongs to this account but is currently disabled |
403 | from_number_not_sms_enabled | from is not enabled for texting |
409 | breesy_phone_not_enabled | Breesy Phone is not enabled for this account |
409 | breesy_phone_not_configured | Breesy Phone is not set up for this account |
429 | Rate limit exceeded — honor the Retry-After header and retry later | |
502 | sms_send_failed | Breesy Phone accepted the request format but could not send the text |