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 |