Inbox API and data coverage
Bring contact conversations, calls, bookings and usage into your CRM. Start with Watch contacts and prepare replies for an end-to-end integration recipe; use this guide for field semantics, permissions and coverage.
The read API includes Inbox, Messages, Calls, Bookings and Usage resource groups. Public message sending, assignment changes and AI/escalation controls are not available yet. sendingAvailable=false is intentional. Reads never send a message or change conversation state.
Access and permissions
Use an active company API key in the X-API-Key header. Keep the key on your server. Read permission is independent of subscription sending eligibility, so an integration can still inspect an account whose sending is blocked.
| Resource | Required scope for a restricted key |
|---|---|
| Inbox, conversation, messages, full message bodies, send methods | inbox:read |
| Combined timeline | inbox:read and calls:read and bookings:read |
| Calls and transcripts | calls:read |
| Recording links | calls:media:read |
| Bookings and booking-change requests | bookings:read |
| Account usage | usage:read |
Null scopes on existing keys and ["ALL_SCOPES"] grant all current and future scopes. New keys default to ["ALL_SCOPES"]; [] grants none. Omitting scopes when editing a key preserves its permissions. Scope restrictions apply to these Inbox operations; legacy API routes retain their existing permissions. No creation-date cutoff applies.
Find contacts and automation state
Use GET /v2/pub/inbox. Results default to unarchived contacts, newest activity first; view=all includes archived contacts. Activity uses indexed conversation/call/escalation events and inquiry/contact creation. Internal notes and read/bookkeeping events do not advance it. Search covers names, phone and email. Filter by assignment, lead status, acquisition source, original source, conversation channel, AI controls or human escalation. Repeat query keys for OR within a filter, for example source=WEBCHAT&source=YELP_LEAD; different filter families are combined with AND. Acquisition source is independent of conversation medium. Original source uses the latest resolved inquiry provenance.
automation.controlState reports enabled, paused, mixed or unavailable across existing reply threads, independently of sender availability. An open human escalation takes precedence: automation.aiState becomes paused while the underlying control state stays visible. These are reply-control settings, not a guarantee that provider connections or account-wide automation are ready. humanEscalation=open finds contacts needing takeover; an ordinary AI pause is not an escalation.
Booking filters apply across the contact's recorded bookings before pagination:
GET /v2/pub/inbox?bookingOrigin=ai&bookingView=upcoming
GET /v2/pub/inbox?bookingOrigin=ai&bookingView=recently-booked
GET /v2/pub/inbox?hasOpenBookingChangeRequest=true&bookingChangeRequestType=RESCHEDULE
upcoming requires a confirmed future appointment with BOOKED status. recently-booked uses booking creation time, defaults to the preceding seven days, and accepts bookedFrom / bookedTo overrides. appointmentStartFrom / appointmentStartTo concern confirmed appointment times. All booking predicates must match the same booking. matchedBookingIds contains at most 25 matches; matchedBookingsHaveMore signals more results. Use the contact booking list to retrieve them. hasUpcomingAiBooking=null means no confirmed upcoming appointment was found but external AI booking status is unknown; inspect unknownAiBookingCount and bookingCoverage. Do not interpret null as false.
Read a conversation and discover reply targets
GET /v2/pub/clients/{clientId}/conversation returns the public contact profile, opt-outs, assignment/status, AI/escalation and booking state, up to 25 inquiry summaries, 10 recent messages and 25 reply targets. Follow the nested message/target cursors through their respective endpoints. inquiriesHaveMore marks a partial inquiry preview; use the existing lead-inquiry endpoints for full inquiry records. Call summaries/details and bookings have separate endpoints and scopes.
Use GET /v2/pub/clients/{clientId}/messages?order=asc to load stored message history oldest first. This combines existing SMS, email, Facebook Messenger, Google LSA, Thumbtack, Yelp and webchat records with messages retained only in the history index. coverage=stored_sources_and_index includes legacy records even if they were never indexed; reads do not backfill or contact providers. IDs are opaque strings (message:hash or history:bigint). Preserve them unchanged. Matching source/index identities appear once; equal text remains distinct. Legacy entries without message IDs use their stored array position, so reconcile if a thread is reordered. createdFrom / createdTo filter occurrence time.
Message lists include up to 20,000 Unicode code points per message. textCompleteness is complete, preview, truncated or unavailable. To retrieve all saved text, use:
GET /v2/pub/clients/{clientId}/messages/{messageId}/body?limit=10000
Concatenate text from each page in cursor order until nextCursor=null. limit counts Unicode code points (default 10,000; maximum 20,000). HTML-only email is converted to plain text; scripts and embedded assets are excluded. A 409 MESSAGE_CHANGED response means restart without the cursor. availability=preview means only the indexed preview survives; unavailable means no saved text. Deleted or never-stored content cannot be recovered by this API. Unknown outbound authors remain unknown; attachments and raw provider payloads are excluded.
Use GET /v2/pub/clients/{clientId}/timeline?order=asc for a combined chronological view of messages, call summaries, bookings, escalation decisions, inquiry creation, booking-change requests and supported indexed activity. Filter by kind=message|call|booking|escalation|activity and occurrence date bounds. Follow nextCursor to retrieve the full stored timeline. Booking-change activity includes bookingChangeRequestId for the request detail endpoint. Call, booking, escalation and request items show saved state at record creation time; this is not an exhaustive audit log of every transition. Calls do not include transcripts or recording links in this view.
Timeline access requires all three scopes: inbox:read, calls:read and bookings:read, even with a kind filter. Legacy null scopes and ALL_SCOPES continue to work. Message bodies require inbox:read. For narrower permissions, use the separate message, call or booking endpoints.
Use GET /v2/pub/clients/{clientId}/send-methods to discover existing targets for SMS, email, Facebook Messenger, Google LSA, Thumbtack, Yelp and webchat. The method list reports NO_REPLY_TARGET when no target exists. Each target includes its method, inquiry, AI choices, version, text-length limit and blockers. SMS requires an active approved 10DLC sender; opt-outs, billing limits, connection state and reply windows are checked. Messenger uses the configured enabled page and its saved reply window. Google LSA reports PROVIDER_REVIEW_REQUIRED: discovery does not perform its refresh/review handshake.
sendingAvailable=false: the public send endpoint is not available yet. Target enabled describes stored eligibility at asOf, not provider authorization or a reservation. Future sending must recheck eligibility and require explicit sendMethod, sendTargetId and aiReplies (off or on). Open escalations leave allowedAiReplies empty. There is no automatic channel fallback, shared SMS sender or implicit marketplace-to-SMS bridge. Attachments are unsupported. Conversation and target version values are read fingerprints, not mutation preconditions.
Poll from a CRM
- Poll
/v2/pub/inbox?view=allabout once every 30 seconds per company, sharing the result across CRM users. Back off while idle. - Follow
nextCursorwith unchanged filters, deduplicate contact IDs, then fetch conversations or messages for contacts of interest. Messages, targets, calls and bookings have their own pagination. - Rescan an overlapping activity window and periodically reconcile all contacts, including archives. Mutable activity ordering is not a snapshot or a lossless change feed; metadata-only changes may not advance activity.
asOfkeeps relative booking dates fixed during an inbox traversal. - Keep every cursor with its tenant, endpoint and filters. Cursors expire after 24 hours. Restart a traversal after
400 INVALID_CURSOR; useRetry-Afterand backoff on429, and bounded retries on temporary503errors.
Reads default to 25 records and cap at 100 unless a specific endpoint documents a different limit. Date bounds include From and exclude To. Company and key buckets refill at 120 and 60 requests/minute with bursts of 20 and 10; a company may have four concurrent reads. No GET sends messages, marks conversations read, changes AI controls or refreshes a provider.
Read usage and sending limits
Use GET /v2/pub/account/usage to inspect the billing period, lead usage, and separate SMS/email limits. SMS is measured in segments. Each limit uses its subscription or license override when present, otherwise the plan allowance. A null SMS/email override uses the plan default; zero means no sending capacity. These are not API request-rate allowances.
The API sending policy requires an active subscription, lead usage not above the lead limit, and sufficient capacity for the selected message method. Trial-only and past-due accounts do not meet that sending policy. Read permission remains separate so an integration can inspect usage when sending would be blocked.
apiBillingEligible and the per-method billingEligibility describe billing only. sendingAvailable is false in this initial implementation. There is no public Inbox message-send endpoint yet. These values do not grant permission to send through other endpoints, override opt-outs, or promise that a channel is connected. Reservations are zero until the durable send/reservation implementation is enabled.
Find requested booking changes
Use GET /v2/pub/appointment-change-requests. The default status is REQUESTED. Supported filters include clientId, requestType, bookingProvider, createdFrom and createdTo. Use status=ALL for resolved requests too.
curl --request GET \
--url 'https://pub-api.leadtruffle.com/api/v2/pub/appointment-change-requests?status=REQUESTED&limit=50' \
--header 'X-API-Key: YOUR_API_KEY'
Follow data.nextCursor until it is null. Keep the same filters on subsequent requests. Cursors expire after 24 hours; begin a new traversal if one expires. Dates use an inclusive lower bound and exclusive upper bound. Status can change while paging, so refresh recent pages and reconcile by request ID.
Retrieve one request with GET /v2/pub/appointment-change-requests/{requestId}. Request types are CANCEL and RESCHEDULE; statuses are REQUESTED, COMPLETED, DECLINED and SUPERSEDED. Some records have no linked contact or native appointment, and remain visible in the company queue.
A completed request means the request workflow was marked handled. These read endpoints do not cancel or reschedule an appointment. Internal notes, notification recipients and raw provider diagnostics are excluded.
Read bookings
Use GET /v2/pub/clients/{clientId}/bookings and follow nextCursor. Use origin=ai&view=upcoming to find confirmed future AI appointments for a contact. bookedFrom / bookedTo filter when a booking was created; appointmentStartFrom / appointmentStartTo filter its scheduled time. Retrieve one result with GET /v2/pub/bookings/{bookingId}.
Native appointments and recorded booking successes across the contact's inquiries are combined. Repeated successes for the same external booking are deduplicated. AI origin requires evidence; being associated with an AI-qualified contact is insufficient. openChangeRequestCount links this view to the requested-change queue.
For external bookings, status=UNKNOWN and statusFreshness=last_observed mean the API has not queried the CRM for current status. observedAt gives the evidence time. timeSource=requested_slot distinguishes a recorded requested slot from a native appointment time. External records are excluded from view=upcoming because a requested slot is not confirmation that an appointment remains booked. Use the unfiltered list to inspect them; they might since have been moved or canceled. Missing or malformed times stay null. Coverage is limited to local appointments and recorded booking actions, rather than every booking in your CRM.
Read calls and transcripts
Use GET /v2/pub/clients/{clientId}/calls for a bounded list, filtered by kind=ai|outbound|missed and optional creation-date bounds. Results are newest first by record creation time and ID. IDs such as ai:UUID, outbound:UUID, and missed:UUID identify LeadTruffle records, not provider IDs.
GET /v2/pub/calls/{callId} returns metadata, summary and saved-content flags. GET /v2/pub/calls/{callId}/transcript returns plain text in bounded pages; limit counts Unicode code points (default 10,000; maximum 20,000). Concatenate pages in cursor order. The API does not invent speakers or timestamps. not_available means no saved nonblank transcript. If the transcript changes during pagination, 409 TRANSCRIPT_CHANGED means restart without a cursor.
Recordings require the separate calls:media:read permission. GET /v2/pub/calls/{callId}/recordings returns five-minute proxy download links when configured. Each GET or HEAD of a link rechecks the API key, company, media scope and current call ownership. Treat the URL as a secret; request a fresh one when it expires. Call summaries and transcripts never embed recording URLs.
The proxy supports single byte ranges for audio players. Downloads use the same read rate budget and allow two concurrent downloads per company, eight per server, files up to 100 MiB and a 60-second request timeout. Resume a large transfer with byte ranges. A 429 response includes Retry-After; an expired or revoked link returns 403. Missing files return 404, invalid ranges 416, oversized files 422, and temporary failures 503.
proxy_pending means link issuance is not configured; not_available means there is no saved recording link; not_archived means only unsupported/provider links exist. These responses have an empty recordings array. Issuing a link does not verify the saved file still exists.
The API never falls back to bucket or provider URLs. Proxy expiry does not make a legacy public origin private. Storage permissions have not changed; private-origin rollout and deployed playback verification remain separate requirements.