# LeadTruffle developer documentation Generated from the same guides and OpenAPI schema as https://api-docs.leadtruffle.com/. All local links below are relative to https://api-docs.leadtruffle.com. ## Guides # Make your first request Connect to LeadTruffle and read your first lead inquiry. This example only reads data. ## Get your API key Open [Settings → API Keys](https://app.leadtruffle.com/settings/api-keys) in your LeadTruffle dashboard. Use a key for the company you want to access. If you cannot see this setting, ask your company administrator for access. Keep this key on your server. Do not put it in website JavaScript, a public repository, or a widget embed. ## Send a request Replace `YOUR_API_KEY` with your key and run this in your terminal: ```bash curl --request GET \ --url 'https://pub-api.leadtruffle.com/api/v2/pub/leads-created?limit=1' \ --header 'X-API-Key: YOUR_API_KEY' ``` The base URL is `https://pub-api.leadtruffle.com/api`. Every REST API request needs the `X-API-Key` header. ## Check the result A successful request returns HTTP `200`. The response contains `success`, a `data.leads` array, and `data.hasMore`. An empty leads array can be a valid result when the company has no matching lead inquiries. See [List leads created](/reference/#tag/lead-inquiries/GET/v2/pub/leads-created) for the complete response schema. If you receive `401`, check the key and header. If you receive `429`, slow down your requests before trying again. See [Errors and pagination](/guides/troubleshooting/). ## Choose your next step - [Receive lead events](/guides/webhooks/) when a lead arrives or a conversation finishes. - [Explore the API reference](/reference/) to work with contacts, conversations, and calendar data. - [Add a website widget](/guides/widgets/) using the browser widget API. - [Give your AI agent the docs](/guides/ai-agents/) as a single text document. --- # How the API fits together A contact is the person or business you work with. A lead inquiry is a specific interaction with that contact. Understanding the difference helps you update the right record. ## Contacts and lead inquiries **Contacts** hold details such as names, phone numbers, email addresses, notes, and pipeline status. Existing API paths and payloads use `clients` and `clientId` for these records. **Lead inquiries** represent interactions such as a website conversation, phone call, or marketplace lead. A contact can have multiple lead inquiries. Use the IDs returned by the API rather than assuming a contact ID and a lead ID are interchangeable. ## Created and completed events A **lead created** payload contains the information available when the lead first arrives. A **conversation completed** payload contains the information collected through the conversation. Choose the event that matches your workflow. An initial lead event does not mean qualification or booking has finished. ## API versions The version is part of each endpoint path. V1 and V2 endpoints coexist; a V1 endpoint is not automatically obsolete. Check the specific operation's request and response schema. For new webhook integrations, use **V2 webhooks**. V1 webhook operations remain documented for existing integrations under Legacy webhooks. ## REST API and browser widgets Use the REST API from your server with an API key. Website widgets use their own browser JavaScript APIs and company or agency identifiers. They do not require you to expose your REST API key. See [Website widgets](/guides/widgets/) for the available methods and their differences. ## Operations that take action Read the description and prerequisites before calling an endpoint. Some operations update contacts, register webhook destinations, initiate messages, or trigger review requests. The lead qualifier trigger requires account approval and the prerequisites listed in its reference. A successful acceptance response does not guarantee that a message has already been delivered. See [Trigger lead qualifier agent](/reference/#tag/widgets/POST/v1/pub/chat-widget/trigger-lead-qualifier). --- # Receive lead events Have LeadTruffle send events to your application when a lead arrives, a conversation finishes, or another supported event occurs. ## Prepare your receiver Create an HTTPS endpoint in your application that accepts JSON POST requests. Keep the raw request body available so you can verify signed requests before parsing JSON. Use V2 webhooks for new integrations. You will need your LeadTruffle API key, your receiver's URL, and a shared secret for signature verification. ## Choose an event | Event | Use it to | | --- | --- | | `LEAD_CREATED` | Capture a lead as soon as it arrives | | `CONVERSATION_COMPLETED` | Receive the completed conversation and collected details | | `MESSAGE_REPLY` | React to a message reply | | `CLIENT_STATUS_CHANGED` | Track changes to a contact's pipeline status | | `NEW_APPOINTMENT` | Receive a new calendar booking | | `YELP_MESSAGE_OUTBOUND` | Connect outbound Yelp messages to your integration | The [Create V2 webhook reference](/reference/#tag/webhooks/POST/v2/pub/webhooks) includes payload examples and the full set of supported event values. ## Register your webhook This request creates a webhook subscription. Replace the example URL with your receiver and use your own shared secret. ```bash curl --request POST \ --url 'https://pub-api.leadtruffle.com/api/v2/pub/webhooks' \ --header 'X-API-Key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "eventType": "LEAD_CREATED", "url": "https://your-app.example/webhooks/leadtruffle", "secret": "YOUR_WEBHOOK_SECRET" }' ``` A successful registration returns HTTP `201`. Keep the webhook ID from the response so you can update or remove the subscription later. ## Verify incoming deliveries Follow [Verify webhook signatures](/guides/webhook-signatures/) to check the timestamp and signature against the raw request body. The shared secret is separate from your LeadTruffle API key. Design your receiver so a duplicate event cannot create duplicate downstream actions. Save the incoming event before handing off slow work. ## Confirm the integration Use [List V2 webhooks](/reference/#tag/webhooks/GET/v2/pub/webhooks) to verify the registration. Then confirm that a matching event reaches your receiver and that your application processes it successfully. Registration alone does not verify delivery. Use [Update V2 webhook](/reference/#tag/webhooks/PUT/v2/pub/webhooks/{id}) to change a destination or disable a subscription. --- # Verify webhook signatures If you set a `secret` on a V2 webhook, LeadTruffle signs each delivery with HMAC SHA-256 headers: - `x-leadtruffle-timestamp`: Unix timestamp in seconds - `x-leadtruffle-signature`: `v1=` followed by the hex HMAC SHA-256 signature - `x-leadtruffle-signature-algorithm`: `hmac-sha256` The signature is computed over `${timestamp}.${rawRequestBody}` using the webhook secret as the HMAC key. Verify the signature against the raw request body before parsing JSON. Python verification example: ```python import hmac, hashlib, time raw_body = request.get_data() timestamp = request.headers["x-leadtruffle-timestamp"] signature = request.headers["x-leadtruffle-signature"].removeprefix("v1=") try: timestamp_seconds = int(timestamp) except ValueError: raise Exception("invalid timestamp") if abs(time.time() - timestamp_seconds) > 5 * 60: raise Exception("stale webhook") expected = hmac.new( b"YOUR_WEBHOOK_SECRET", timestamp.encode("utf-8") + b"." + raw_body, hashlib.sha256, ).hexdigest() if not hmac.compare_digest(expected, signature): raise Exception("invalid signature") ``` PHP verification example: ```php $rawBody = file_get_contents('php://input'); $timestamp = $_SERVER['HTTP_X_LEADTRUFFLE_TIMESTAMP']; $signature = preg_replace('/^v1=/', '', $_SERVER['HTTP_X_LEADTRUFFLE_SIGNATURE']); if (!ctype_digit($timestamp) || abs(time() - intval($timestamp)) > 5 * 60) { http_response_code(401); exit; } $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, 'YOUR_WEBHOOK_SECRET'); if (!hash_equals($expected, $signature)) { http_response_code(401); exit; } ``` --- # Website widgets LeadTruffle also provides browser-embedded web clients (chat widgets). These JavaScript APIs are separate from the REST API and are available after each widget script loads. ## Install your widget 1. Copy the installation snippet for your company from the widget setup in your [LeadTruffle dashboard](https://app.leadtruffle.com). 2. Paste the snippet before the closing `` tag on each page where the widget should appear, or use your website builder's equivalent footer-code setting. 3. Publish the website change, open the page, and confirm the widget appears. Keep your REST API key out of the embed code. The snippet loads and initializes the widget. The methods below are for additional customization after its script has loaded. ## Choose the right browser API ### Standard Chat Widget (`window.LTWidget`) Methods: - `initialize({ companyId, initialMessage? })` - `open({ initialMessage? })` - `setAttribution({ gclid?, utm_source?, utm_medium?, utm_campaign?, utm_term?, utm_content?, ... })` - `destroy()` Example: ```js if (window.LTWidget && typeof window.LTWidget.initialize === 'function') { window.LTWidget.initialize({ companyId: 'YOUR_COMPANY_UUID', initialMessage: 'Hi! How can we help today?', }) } if (window.LTWidget && typeof window.LTWidget.open === 'function') { window.LTWidget.open({ initialMessage: 'Need help with pricing or scheduling?', }) } if (window.LTWidget && typeof window.LTWidget.setAttribution === 'function') { window.LTWidget.setAttribution({ gclid: 'GOOGLE_CLICK_ID', utm_source: 'google', utm_medium: 'cpc', utm_campaign: 'spring-service', }) } ``` ### Franchise Widget (`window.FranchiseLeadtruffle`) Methods: - `initialize({ agencyId, initialMessage? })` - `open({ initialMessage? })` - `setAttribution({ gclid?, utm_source?, utm_medium?, utm_campaign?, utm_term?, utm_content?, ... })` - `destroy()` ### Popup Widget (`window.TPOPWidget`) Methods: - `initialize({ companyId })` - `show()` - `reset()` - `setAttribution({ gclid?, utm_source?, utm_medium?, utm_campaign?, utm_term?, utm_content?, ... })` - `destroy()` ### Other Web Clients - `window.LTWebchat`: `initialize({ companyId, initialMessage? })`, `prefillLead({ name?, email?, phone?, metadata? })`, `setAttribution({ gclid?, utm_source?, utm_medium?, utm_campaign?, utm_term?, utm_content?, ... })`, `open({ initialMessage? })`, `destroy()` - `window.TJSFormWidget`: `initialize({ companyId, targetElement? })`, `setAttribution({ gclid?, utm_source?, utm_medium?, utm_campaign?, utm_term?, utm_content?, ... })`, `show()`, `hide()`, `destroy()` `prefillLead(...)` is only available on the webchat widget. It lets you pre-populate the initial lead form and attach custom metadata that will be submitted with that webchat lead. This does not apply to the standard website texting widget, popup widget, franchise widget, or JS form widget. `setAttribution(...)` is available on all current JavaScript widget clients. LeadTruffle automatically captures supported URL parameters and falls back to the Google Ads `_gcl_aw` first-party cookie for `gclid` when available. Use `setAttribution(...)` only when your site or tag manager already has attribution values that you want to push into the LeadTruffle widget context. Supported fields include `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`, `gclid`, `fbclid`, `msclkid`, `ttclid`, `snapcid`, `gbraid`, `wbraid`, `gad_source`, `igshid`, `gclsrc`, and `srsltid`. Example: ```js if (window.LTWebchat && typeof window.LTWebchat.initialize === 'function') { window.LTWebchat.initialize({ companyId: 'YOUR_COMPANY_UUID', }) } if (window.LTWebchat && typeof window.LTWebchat.prefillLead === 'function') { window.LTWebchat.prefillLead({ name: 'Jane Smith', email: 'jane@example.com', phone: '+15555550123', metadata: { userId: 'internal-user-123', sourceApp: 'tooldesk-next', }, }) } ``` ### Browser Compatibility Guidance Because embeds can run on unknown/older browsers and third-party pages: - Always check object and method existence before calling (`if (window.X && typeof window.X.method === 'function')`). - Wrap manual widget API calls in `try/catch` to avoid breaking host page JavaScript. - Call `initialize(...)` only after the widget script has loaded (for example in the script `onload` handler). - Use `destroy()` before re-initializing with a different `companyId`/`agencyId`. --- # Errors and pagination Start with the endpoint's response schema and the HTTP status code. Different endpoints can use different response shapes and pagination cursors. ## Authentication errors For a `401` response, check that you sent the `X-API-Key` header and that its value is a valid key for the intended company. Keep the key private when sharing logs or asking for support. ## Request errors For a `400` response, compare your request with the endpoint's required fields, enum values, and examples. For a `404`, check the record ID and the company associated with your key. ## Rate limits The API rate limit is company-based. Plan for roughly one request per second and slow down when you receive `429`. Contact [LeadTruffle support](mailto:support@leadtruffle.com) if your integration needs a higher rate. Before retrying a request that creates data or sends a message, check whether the first attempt took effect. A network timeout alone does not prove the operation failed. ## Pagination Follow the cursor documented on the specific endpoint. Do not assume every list operation uses the same parameters. For [List leads created](/reference/#tag/lead-inquiries/GET/v2/pub/leads-created): 1. Send the first request with your desired `limit` (up to 100). 2. Process the returned `data.leads`. 3. If `data.hasMore` is true, use the oldest returned lead's `timestamp` as the next request's `before` value. 4. Continue until `data.hasMore` is false. If a page is empty or its cursor does not advance, stop and investigate rather than repeating the same request indefinitely. ```bash curl --get \ --url 'https://pub-api.leadtruffle.com/api/v2/pub/leads-created' \ --header 'X-API-Key: YOUR_API_KEY' \ --data-urlencode 'limit=100' \ --data-urlencode 'before=2026-01-01T00:00:00.000Z' ``` The date above is an example. Use the timestamp from your previous response. ## Webhook issues Confirm the subscription's URL and event type. If signature verification fails, check that you use the exact raw request body and the webhook's shared secret. Parsing and re-serializing JSON can change the bytes used for signing. See [Receive lead events](/guides/webhooks/) and [Verify webhook signatures](/guides/webhook-signatures/). ## Get help Email [support@leadtruffle.com](mailto:support@leadtruffle.com) with the endpoint, time of the request, HTTP status, and a redacted response. Do not include API keys or webhook secrets. --- # Give your AI agent the docs Use one URL to give an AI agent the guides and complete API contract. These files are generated in the same build as this documentation. ## One document, everything included [Open llms-full.txt](/llms-full.txt) for the full text export: guides, endpoint descriptions, parameters, request and response definitions, examples, and shared schemas. Copy this prompt into your agent: ```text Read https://api-docs.leadtruffle.com/llms-full.txt to learn the LeadTruffle API. Use the documented endpoint schemas and prerequisites. Ask me for the intended company and workflow before making changes. Keep API keys and webhook secrets private. ``` ## Choose the right format | File | Best for | | --- | --- | | [llms.txt](/llms.txt) | A short index to locate relevant documentation | | [llms-full.txt](/llms-full.txt) | A single document with guides and the complete API contract | | [openapi.json](/openapi.json) | Tools that consume OpenAPI, including client generators | For agents with smaller context windows, start with `llms.txt` and load only the guides and operations needed for the task. ## Read before taking action The reference includes endpoints that change data, trigger messages, and register webhook destinations. Preserve the documented approval requirements and account restrictions. Do not treat an API acceptance response as proof that a message was delivered or a downstream workflow finished. Use placeholder credentials in generated code and load real secrets from the application's secure configuration. # Endpoint index - GET /v1/pub/leads — List leads (Lead inquiries) - GET /v1/pub/leads/{uuid} — Get lead by ID (Lead inquiries) - PUT /v1/pub/leads/{uuid} — Update lead status and notes (Lead inquiries) - GET /v1/pub/webhooks — List webhooks (Legacy webhooks) - POST /v1/pub/webhooks/missed-call-complete — Add missed call webhook (Legacy webhooks) - DELETE /v1/pub/webhooks/missed-call-complete — Remove missed call webhook (Legacy webhooks) - POST /v1/pub/webhooks/chat-widget-lead-complete — Add chat widget webhook (Legacy webhooks) - DELETE /v1/pub/webhooks/chat-widget-lead-complete — Remove chat widget webhook (Legacy webhooks) - POST /v1/pub/clients/upsert — Create or update a contact (Contacts) - GET /v1/pub/clients/by-phone/{phone} — Get contact by phone number (Contacts) - GET /v1/pub/leads-lite — List leads without conversation history (Lead inquiries) - GET /v2/pub/webhooks — List V2 webhooks (Webhooks) - POST /v2/pub/webhooks — Create V2 webhook (Webhooks) - PUT /v2/pub/webhooks/{id} — Update V2 webhook (Webhooks) - DELETE /v2/pub/webhooks/{id} — Delete V2 webhook (Webhooks) - GET /v2/pub/leads-created/{uuid} — Get single lead created by ID (V2) (Lead inquiries) - GET /v2/pub/leads-created — List leads created (V2) (Lead inquiries) - GET /v2/pub/leads-completed/{uuid} — Get single completed conversation by ID (V2) (Conversations) - GET /v2/pub/leads-completed — List completed conversations (V2) (Conversations) - GET /v2/pub/message-replies — List recent message replies (V2) (Conversations) - GET /v2/pub/employees — List employees (Team) - PUT /v2/pub/clients/update — Update contact (Contacts) - GET /v2/pub/clients/statuses — List contact pipeline statuses (Contacts) - GET /v2/pub/clients/{clientId}/notes — List contact notes (Contacts) - POST /v2/pub/clients/{clientId}/notes — Add contact note (Contacts) - GET /v1/pub/chat-widget/config — Get chat widget configuration (Widgets) - PUT /v1/pub/chat-widget/config — Update chat widget configuration (Widgets) - POST /v1/pub/chat-widget/trigger-lead-qualifier — Trigger lead qualifier agent (Widgets) - GET /v1/pub/default-calendar/appointments — Get Default Calendar Appointments (Default Calendar) - GET /v1/pub/default-calendar/info — Get Default Calendar Information (Default Calendar) - POST /v1/pub/email-gateway/submit — Submit email gateway message (Email gateway) - POST /v1/pub/yelp-lead-agent/process-incoming-message — Process incoming Yelp message with AI agent (New Consumer Message) (Yelp Lead Agent) - POST /v1/pub/yelp-lead-agent/process-new-lead — Capture Yelp New Lead payload (Yelp Lead Agent) - POST /v1/pub/yelp-lead-agent/process-phone-availability — Capture Yelp Phone Number Available payload (Yelp Lead Agent) - POST /v1/pub/yelp-lead-agent/process-business-message — Process incoming business message from Yelp (Yelp Lead Agent) - POST /v1/pub/yelp-lead-agent/send-message — Send a follow-up message to a Yelp lead (Yelp Lead Agent) - GET /v1/pub/yelp-lead-agent/conversations — List Yelp AI conversations (Yelp Lead Agent) - GET /v1/pub/yelp-lead-agent/conversations/lead/{externalLeadId} — Get Yelp conversation by Yelp lead ID (Yelp Lead Agent) - GET /v1/pub/yelp-lead-agent/outbound-messages — List outbound messages in webhook payload format (Yelp Lead Agent) - PUT /v2/pub/leads/conversion — Upsert (create or update) lead conversion data (Lead inquiries) - PUT /v2/pub/leads/update — Update lead (Lead inquiries) - POST /_EXPERIMENTAL/v2/pub/leads/review-gathering — Trigger review gathering for a lead (Experimental) # Complete OpenAPI contract The JSON below includes every operation, authentication scheme, request and response definition, example, and shared schema. Resolve local $ref values against this document. Endpoint descriptions include prerequisites and restrictions; preserve them when using the API. ```json { "openapi": "3.0.3", "info": { "title": "LeadTruffle Public API", "description": "Connect your contacts, lead inquiries, conversations, and workflows to LeadTruffle.\n\n[Make your first request](/guides/quickstart/) · [Set up webhooks](/guides/webhooks/) · [Website widgets](/guides/widgets/)\n\nUse the resource groups to find an endpoint. Each endpoint documents its own API version, parameters, and response format.\n", "version": "1.0.0", "contact": { "name": "LeadTruffle Support", "url": "https://www.leadtruffle.co", "email": "support@leadtruffle.com" } }, "servers": [ { "url": "https://pub-api.leadtruffle.com/api", "description": "Production API" } ], "security": [ { "ApiKeyAuth": [] } ], "tags": [ { "name": "Contacts", "description": "Create and update contacts, manage notes, and look up pipeline statuses." }, { "name": "Lead inquiries", "description": "Retrieve lead inquiries and update their status and conversion data." }, { "name": "Conversations", "description": "Read completed conversations and recent message replies." }, { "name": "Webhooks", "description": "Subscribe to events. Use V2 webhooks for new integrations." }, { "name": "Default Calendar", "description": "Read your default calendar and appointments." }, { "name": "Widgets", "description": "Configure website widgets and access the approved lead qualifier workflow." }, { "name": "Email gateway", "description": "Submit email messages to the lead capture gateway." }, { "name": "Yelp Lead Agent", "description": "Connect Yelp lead messages and conversations." }, { "name": "Team", "description": "Look up employees in your company." }, { "name": "Experimental", "description": "Experimental operations. Review endpoint requirements before using them." }, { "name": "Legacy webhooks", "description": "V1 webhook operations for existing integrations. Use V2 for new integrations." } ], "paths": { "/v1/pub/leads": { "get": { "tags": [ "Lead inquiries" ], "summary": "List leads", "description": "Retrieve a paginated list of leads for your company", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "query", "name": "before", "schema": { "type": "string", "format": "date-time" }, "description": "Pagination cursor - get leads created before this timestamp (ISO8601). Defaults to current time if not provided.", "example": "2024-01-01T00:00:00Z" }, { "in": "query", "name": "limit", "schema": { "type": "integer", "minimum": 1, "maximum": 10, "default": 10 }, "description": "Maximum number of leads to return (max 10)" } ], "responses": { "200": { "description": "Successfully retrieved leads", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadList" } } } }, "401": { "description": "Authentication error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/v1/pub/leads/{uuid}": { "get": { "tags": [ "Lead inquiries" ], "summary": "Get lead by ID", "description": "Retrieve a specific lead by its UUID", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "uuid", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The UUID of the lead to retrieve" } ], "responses": { "200": { "description": "Successfully retrieved lead", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/WebhookPayload" } } } } } }, "401": { "description": "Authentication error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Lead not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } }, "put": { "tags": [ "Lead inquiries" ], "summary": "Update lead status and notes", "description": "Update the conversion status and notes for a specific lead.\n\nThis endpoint allows you to track the progress of leads through your sales funnel\nby updating their conversion status and adding notes for internal tracking.\n\n**Conversion Status Values:**\n- **NEW**: Just qualified (default status)\n- **CONTACTED**: Sales team has reached out to the lead\n- **QUOTED**: Quote or estimate has been provided\n- **WON**: Lead converted to customer/sale closed\n- **LOST**: Lead did not convert/sale was lost\n- **NURTURING**: Lead is in long-term follow-up process\n- **CLOSED**: Lead is closed and no longer being pursued\n\nThe status update timestamp is automatically recorded for tracking purposes.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "uuid", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The UUID of the lead to update", "example": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "conversionStatus" ], "properties": { "conversionStatus": { "type": "string", "enum": [ "NEW", "CONTACTED", "QUOTED", "WON", "LOST", "NURTURING", "CLOSED" ], "description": "The new conversion status for the lead", "example": "CONTACTED" }, "notes": { "type": "string", "maxLength": 1000, "nullable": true, "description": "Optional notes about the lead status update", "example": "Called customer, interested in scheduling estimate for next week" } } }, "examples": { "status_update": { "summary": "Update status only", "value": { "conversionStatus": "QUOTED" } }, "status_with_notes": { "summary": "Update status and add notes", "value": { "conversionStatus": "CONTACTED", "notes": "Left voicemail, customer will call back tomorrow" } }, "won_conversion": { "summary": "Mark as won conversion", "value": { "conversionStatus": "WON", "notes": "Customer signed contract for $5,000 HVAC installation. Project starts next month." } }, "lost_conversion": { "summary": "Mark as lost", "value": { "conversionStatus": "LOST", "notes": "Customer decided to go with competitor due to pricing" } } } } } }, "responses": { "200": { "description": "Lead status successfully updated", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "example": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b" }, "conversionStatus": { "type": "string", "enum": [ "NEW", "CONTACTED", "QUOTED", "WON", "LOST", "NURTURING", "CLOSED" ], "example": "CONTACTED" }, "conversionStatusUpdatedAt": { "type": "string", "format": "date-time", "example": "2024-01-15T14:30:00Z", "description": "Timestamp when the status was last updated" }, "leadNotes": { "type": "string", "nullable": true, "example": "Called customer, interested in scheduling estimate for next week", "description": "Current notes for the lead" } } } } }, "examples": { "success_response": { "summary": "Successful status update", "value": { "success": true, "data": { "id": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "conversionStatus": "QUOTED", "conversionStatusUpdatedAt": "2024-01-15T14:30:00Z", "leadNotes": "Provided estimate for $3,500. Customer reviewing with spouse." } } } } } } }, "400": { "description": "Invalid request data", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Error" }, { "type": "object", "properties": { "validStatuses": { "type": "array", "items": { "type": "string" }, "description": "List of valid conversion status values" } } } ] }, "examples": { "invalid_status": { "summary": "Invalid conversion status", "value": { "success": false, "error": "Valid conversion status is required", "validStatuses": [ "NEW", "CONTACTED", "QUOTED", "WON", "LOST", "NURTURING", "CLOSED" ] } }, "notes_too_long": { "summary": "Notes too long", "value": { "success": false, "error": "Notes must be at most 1000 characters" } }, "invalid_notes_type": { "summary": "Invalid notes type", "value": { "success": false, "error": "Notes must be a string" } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Lead not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "Lead not found" } } } }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/webhooks": { "get": { "tags": [ "Legacy webhooks" ], "summary": "List webhooks", "description": "Retrieve all configured webhooks for both chat widget and missed call lead completion", "security": [ { "ApiKeyAuth": [] } ], "responses": { "200": { "description": "Successfully retrieved webhooks", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookList" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/webhooks/missed-call-complete": { "post": { "tags": [ "Legacy webhooks" ], "summary": "Add missed call webhook", "description": "Add a new webhook URL for missed call lead completion notifications.\n\nWhen a lead conversation is completed, we will POST the payload described below to your webhook URL.\nThe webhook will timeout after 10 seconds and we will retry failed deliveries up to 3 times with exponential backoff.\n\n### Webhook Payload Example\n```json\n{\n \"type\": \"conversation_completed\",\n \"clientId\": \"456\",\n \"leadId\": \"123\",\n \"companyId\": \"789\",\n \"leadQualificationStatus\": \"COMPLETED\",\n \"leadInformation\": {\n \"name\": \"John Doe\",\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"+18001234567\",\n \"additionalData\": {\n \"source\": \"popup\",\n \"message\": \"I need help with...\"\n }\n },\n \"trackingData\": {\n \"source\": \"popup\",\n \"utm_source\": \"google\",\n \"utm_medium\": \"cpc\",\n \"utm_campaign\": \"home_renovation\",\n \"pageInfo\": {\n \"title\": \"Home - Best HVAC Services\",\n \"referrer\": \"https://www.google.com/\",\n \"currentUrl\": \"http://localhost:3002/index.html\"\n }\n },\n \"qualifyingData\": {\n \"example_budget\": \"$5000\",\n \"example_timeline\": \"Within 3 months\",\n \"example_projectType\": \"Home Renovation\"\n },\n \"commonFields\": {\n \"fullAddress\": \"111 main st, Austin TX 73301\",\n \"address\": \"111 main st\",\n \"zipcode\": \"73301\",\n \"state\": \"TX\",\n \"city\": \"Austin\",\n \"country\": \"US\",\n \"isHomeowner\": true,\n \"customerName\": \"John\"\n },\n \"qualifyingDataSummary\": \"The client lives in a 3 bedroom house.\\nHas a budget of $2000.\\nzipcode is 45150.\",\n \"contactReason\": \"Client is interested in a home renovation project...\",\n \"timestamp\": \"2024-01-01T00:00:00Z\",\n \"messageHistory\": [\n {\n \"direction\": \"outbound\",\n \"name\": \"AI Agent\",\n \"message\": \"How can we help you...\",\n \"date\": \"2024-01-01T00:00:00Z\"\n },\n {\n \"direction\": \"inbound\",\n \"name\": \"John Doe\",\n \"message\": \"I need help with...\",\n \"date\": \"2024-01-01T00:01:00Z\"\n }\n ],\n \"userMedia\": [\n {\n \"type\": \"image/jpeg\",\n \"url\": \"https://tooldesk-public-user-uploads.s3.us-west-2.amazonaws.com/email-assets/leadtruffle-Wordmark-white.png\"\n }\n ]\n}\n```\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "url" ], "properties": { "url": { "type": "string", "format": "uri", "example": "https://api.example.com/webhooks/leads" } } } } } }, "responses": { "200": { "description": "Webhook successfully added", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookResponse" } } } }, "400": { "description": "Invalid request or webhook limit reached", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "$ref": "#/components/responses/NotFoundError" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "delete": { "tags": [ "Legacy webhooks" ], "summary": "Remove missed call webhook", "description": "Remove an existing webhook URL for missed call lead completion notifications", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "url" ], "properties": { "url": { "type": "string", "format": "uri", "example": "https://api.example.com/webhooks/leads" } } } } } }, "responses": { "200": { "description": "Webhook successfully removed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookResponse" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "$ref": "#/components/responses/NotFoundError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/webhooks/chat-widget-lead-complete": { "post": { "tags": [ "Legacy webhooks" ], "summary": "Add chat widget webhook", "description": "Add a new webhook URL for chat widget lead completion notifications.\n\nWhen a lead conversation is completed, we will POST the payload described below to your webhook URL.\nThe webhook will timeout after 10 seconds and we will retry failed deliveries up to 3 times with exponential backoff.\n\n### Webhook Payload Example\n```json\n{\n \"type\": \"conversation_completed\",\n \"clientId\": \"456\",\n \"leadId\": \"123\",\n \"companyId\": \"789\",\n \"leadQualificationStatus\": \"COMPLETED\",\n \"leadInformation\": {\n \"name\": \"John Doe\",\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"+18001234567\",\n \"additionalData\": {\n \"source\": \"popup\",\n \"message\": \"I need help with...\"\n }\n },\n \"trackingData\": {\n \"source\": \"popup\",\n \"utm_source\": \"google\",\n \"utm_medium\": \"cpc\",\n \"utm_campaign\": \"home_renovation\",\n \"pageInfo\": {\n \"title\": \"Home - Best HVAC Services\",\n \"referrer\": \"https://www.google.com/\",\n \"currentUrl\": \"http://localhost:3002/index.html\"\n }\n },\n \"qualifyingData\": {\n \"example_budget\": \"$5000\",\n \"example_timeline\": \"Within 3 months\",\n \"example_projectType\": \"Home Renovation\"\n },\n \"commonFields\": {\n \"fullAddress\": \"111 main st, Austin TX 73301\",\n \"address\": \"111 main st\",\n \"zipcode\": \"73301\",\n \"state\": \"TX\",\n \"city\": \"Austin\",\n \"country\": \"US\",\n \"isHomeowner\": true,\n \"customerName\": \"John\"\n },\n \"qualifyingDataSummary\": \"The client lives in a 3 bedroom house.\\nHas a budget of $2000.\\nzipcode is 45150.\",\n \"contactReason\": \"Client is interested in a home renovation project...\",\n \"timestamp\": \"2024-01-01T00:00:00Z\",\n \"messageHistory\": [\n {\n \"direction\": \"outbound\",\n \"name\": \"AI Agent\",\n \"message\": \"How can we help you...\",\n \"date\": \"2024-01-01T00:00:00Z\"\n },\n {\n \"direction\": \"inbound\",\n \"name\": \"John Doe\",\n \"message\": \"I need help with...\",\n \"date\": \"2024-01-01T00:01:00Z\"\n }\n ],\n \"userMedia\": [\n {\n \"type\": \"image/jpeg\",\n \"url\": \"https://tooldesk-public-user-uploads.s3.us-west-2.amazonaws.com/email-assets/leadtruffle-Wordmark-white.png\"\n }\n ]\n}\n```\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "url" ], "properties": { "url": { "type": "string", "format": "uri", "example": "https://api.example.com/webhooks/leads" } } } } } }, "responses": { "200": { "description": "Webhook successfully added", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookResponse" } } } }, "400": { "description": "Invalid request or webhook limit reached", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "$ref": "#/components/responses/NotFoundError" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "delete": { "tags": [ "Legacy webhooks" ], "summary": "Remove chat widget webhook", "description": "Remove an existing webhook URL for chat widget lead completion notifications", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "url" ], "properties": { "url": { "type": "string", "format": "uri", "example": "https://api.example.com/webhooks/leads" } } } } } }, "responses": { "200": { "description": "Webhook successfully removed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookResponse" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "$ref": "#/components/responses/NotFoundError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/clients/upsert": { "post": { "tags": [ "Contacts" ], "summary": "Create or update a contact", "description": "Creates a new client record or updates an existing one based on the phone number.\n\nThe phone number is used as the unique identifier for finding existing clients. Phone numbers\nare normalized to E.164 format internally (+1XXXXXXXXXX for US numbers).\n\nWhen updating existing clients:\n- Only non-null fields in the request are updated\n- Existing data is preserved for fields not included in the request\n- The phone number cannot be changed once a client is created\n- Operations are restricted to the authenticated company's data\n\n### Notes\n- Phone numbers must be valid US numbers\n- All fields except phone are optional\n- Email addresses must be valid format\n- New clients are automatically marked as leads with status 'NEW'\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientUpsertRequest" } } } }, "responses": { "200": { "description": "Client successfully created or updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientUpsertResponse" }, "examples": { "created": { "summary": "New client created", "value": { "success": true, "data": { "id": "415f2b29-39e6-4182-9d6f-ec2d817f01c2", "companyId": "f096f9e3-001d-49ac-864c-3d73453bbe08", "primaryPhone": "+18001234567", "firstName": "John", "lastName": "Doe", "primaryEmail": "john.doe@example.com", "address": "123 Main St", "address2": "Unit 456", "city": "Austin", "state": "TX", "zipCode": "78701", "country": "US", "isLead": true, "leadStatus": "NEW", "createdAt": "2023-06-01T00:00:00Z", "updatedAt": "2023-06-01T00:00:00Z" }, "action": "CREATED" } }, "updated": { "summary": "Existing client updated", "value": { "success": true, "data": { "id": "415f2b29-39e6-4182-9d6f-ec2d817f01c2", "companyId": "f096f9e3-001d-49ac-864c-3d73453bbe08", "primaryPhone": "+18001234567", "firstName": "John", "lastName": "Smith", "primaryEmail": "john.smith@example.com", "address": "456 Oak St", "address2": null, "city": "Austin", "state": "TX", "zipCode": "78701", "country": "US", "isLead": true, "leadStatus": "NEW", "createdAt": "2023-06-01T00:00:00Z", "updatedAt": "2023-06-02T00:00:00Z" }, "action": "UPDATED" } }, "unchanged": { "summary": "No changes needed", "value": { "success": true, "data": { "id": "415f2b29-39e6-4182-9d6f-ec2d817f01c2", "companyId": "f096f9e3-001d-49ac-864c-3d73453bbe08", "primaryPhone": "+18001234567", "firstName": "John", "lastName": "Smith", "primaryEmail": "john.smith@example.com", "address": "456 Oak St", "address2": null, "city": "Austin", "state": "TX", "zipCode": "78701", "country": "US", "isLead": true, "leadStatus": "NEW", "createdAt": "2023-06-01T00:00:00Z", "updatedAt": "2023-06-01T00:00:00Z" }, "action": "UNCHANGED" } } } } } }, "400": { "description": "Invalid request data", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Error" }, { "type": "object", "properties": { "details": { "type": "object", "description": "Validation error details" } } } ] }, "example": { "success": false, "error": "Invalid input", "details": { "phone": { "_errors": [ "Phone number must be a valid US phone number in E.164 or national format" ] } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/clients/by-phone/{phone}": { "get": { "tags": [ "Contacts" ], "summary": "Get contact by phone number", "description": "Retrieves a client record by their phone number.\n\nThe phone number should be in E.164 format (+1XXXXXXXXXX) or a standard US format.\nThe system will normalize the phone number to E.164 format before searching.\n\nReturns a 404 error if no client with the specified phone number exists for the authenticated company.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "phone", "required": true, "schema": { "type": "string" }, "description": "Phone number in E.164 format or US national format (e.g., +18001234567 or 8001234567)", "example": "8001234567" } ], "responses": { "200": { "description": "Client successfully retrieved", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/Client" } } } } } }, "400": { "description": "Invalid phone number format", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Error" }, { "type": "object", "properties": { "details": { "type": "object", "description": "Validation error details" } } } ] }, "example": { "success": false, "error": "Invalid phone number format", "details": { "phone": { "_errors": [ "Phone number must be a valid US phone number in E.164 or national format" ] } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Client not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "Client not found" } } } }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/leads-lite": { "get": { "tags": [ "Lead inquiries" ], "summary": "List leads without conversation history", "description": "Retrieve a paginated list of leads for your company without full conversation history.\nThis endpoint is optimized for performance and can return up to 100 records at once.\n\nUse this endpoint when you need to fetch larger batches of leads and don't require\nthe full message history for each lead.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "query", "name": "before", "schema": { "type": "string", "format": "date-time" }, "description": "Pagination cursor - get leads created before this timestamp (ISO8601). Defaults to current time if not provided.", "example": "2024-01-01T00:00:00Z" }, { "in": "query", "name": "limit", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 100 }, "description": "Maximum number of leads to return (max 100)" } ], "responses": { "200": { "description": "Successfully retrieved leads", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "leads": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookPayload" }, "description": "List of leads, limited to a maximum of 100 per request. These leads will have empty messageHistory arrays." }, "hasMore": { "type": "boolean", "description": "Indicates if there are more results available. To fetch the next page, use the oldest lead's timestamp as the 'before' parameter." } } } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/webhooks": { "get": { "tags": [ "Webhooks" ], "summary": "List V2 webhooks", "description": "Retrieve all configured V2 webhooks for your company.\n\nV2 webhooks provide improved payload structure and support for multiple event types:\n- **LEAD_CREATED**: Fires instantly when a new lead is created\n- **CONVERSATION_COMPLETED**: Fires when AI qualification finishes with full data\n- **MESSAGE_REPLY**: Fires when leads reply via SMS\n- **LEAD_STATUS_CHANGED**: Legacy lead pipeline event. Use only if your account still uses the legacy Leads system.\n- **CLIENT_STATUS_CHANGED**: Fires when a contact Status changes in the new Contacts/client pipeline\n- **NEW_APPOINTMENT**: Fires when a calendar booking is created\n- **YELP_MESSAGE_OUTBOUND**: Fires to send messages to Yelp via Zapier\n\nEach webhook includes tracking information such as delivery success/failure rates and timestamps.\n\nSee the **POST** endpoint documentation below for full payload examples for each event type.\n", "security": [ { "ApiKeyAuth": [] } ], "responses": { "200": { "description": "Successfully retrieved webhooks", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookV2ListResponse" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "post": { "tags": [ "Webhooks" ], "summary": "Create V2 webhook", "description": "Create a new V2 webhook for your company.\n\n**Headers sent with every webhook:**\n- `Content-Type: application/json`\n- `x-origin: leadtruffle`\n- `x-event-type: [EVENT_TYPE]`\n\n**Signature headers when `secret` is set:**\n- `x-leadtruffle-timestamp`: Unix timestamp in seconds\n- `x-leadtruffle-signature`: `v1=` followed by the hex HMAC SHA-256 signature\n- `x-leadtruffle-signature-algorithm`: `hmac-sha256`\n\nTo verify a signed webhook, compute `HMAC_SHA256(secret, timestamp + \".\" + rawRequestBody)` and compare it to the value after `v1=` in `x-leadtruffle-signature`. Use the raw body exactly as received, before JSON parsing.\n\nPython example:\n```python\nimport hmac, hashlib, time\n\nraw_body = request.get_data()\ntimestamp = request.headers[\"x-leadtruffle-timestamp\"]\nsignature = request.headers[\"x-leadtruffle-signature\"].removeprefix(\"v1=\")\n\ntry:\n timestamp_seconds = int(timestamp)\nexcept ValueError:\n raise Exception(\"invalid timestamp\")\n\nif abs(time.time() - timestamp_seconds) > 5 * 60:\n raise Exception(\"stale webhook\")\n\nexpected = hmac.new(\n b\"YOUR_WEBHOOK_SECRET\",\n timestamp.encode(\"utf-8\") + b\".\" + raw_body,\n hashlib.sha256,\n).hexdigest()\n\nif not hmac.compare_digest(expected, signature):\n raise Exception(\"invalid signature\")\n```\n\nPHP example:\n```php\n$rawBody = file_get_contents('php://input');\n$timestamp = $_SERVER['HTTP_X_LEADTRUFFLE_TIMESTAMP'];\n$signature = preg_replace('/^v1=/', '', $_SERVER['HTTP_X_LEADTRUFFLE_SIGNATURE']);\n\nif (!ctype_digit($timestamp) || abs(time() - intval($timestamp)) > 5 * 60) {\n http_response_code(401);\n exit;\n}\n\n$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, 'YOUR_WEBHOOK_SECRET');\n\nif (!hash_equals($expected, $signature)) {\n http_response_code(401);\n exit;\n}\n```\n\n**Delivery & Retry:**\n- Webhooks timeout after 10 seconds\n- Failed deliveries are retried with exponential backoff\n- Maximum of 6 webhooks per company\n\n---\n\n## Event Types & Payload Examples\n\n### LEAD_CREATED\nFires instantly when a new lead is created. The fastest way to get notified.\n\n```json\n{\n \"eventType\": \"LEAD_CREATED\",\n \"eventTypeDetails\": \"NEW_CHAT_WIDGET_SUBMISSION\",\n \"leadId\": \"lead_87654321-dcba-4321-8765-987654321def\",\n \"clientId\": \"client_11111111-2222-3333-4444-555555555555\",\n \"companyId\": \"comp_99999999-8888-7777-6666-444444444444\",\n \"name\": \"John Doe\",\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"+15551234567\",\n \"message\": \"I need help with HVAC repair\",\n \"timestamp\": \"2024-01-15T14:30:00.000Z\",\n \"isRepeatLead\": false,\n \"qualificationSource\": \"WEBCHAT\",\n \"isManualTakeoverEnabled\": false,\n \"trackingData\": {\n \"source\": \"google_ads\",\n \"utm_source\": \"google\",\n \"utm_medium\": \"cpc\",\n \"gclid\": \"Cj0KCQjw...\"\n }\n}\n```\n\n### CONVERSATION_COMPLETED\nFires when AI qualification finishes. Contains full conversation history and extracted data.\n\n```json\n{\n \"eventType\": \"CONVERSATION_COMPLETED\",\n \"eventTypeDetails\": \"CONVERSATION_COMPLETE_CHAT_WIDGET\",\n \"qualificationSource\": \"WEBCHAT\",\n \"leadId\": \"lead_87654321-dcba-4321-8765-987654321def\",\n \"clientId\": \"client_11111111-2222-3333-4444-555555555555\",\n \"companyId\": \"comp_99999999-8888-7777-6666-444444444444\",\n \"leadInformation\": {\n \"name\": \"John Doe\",\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"+15551234567\"\n },\n \"qualifyingData\": {\n \"contactReason\": \"HVAC repair needed\",\n \"serviceType\": \"Air Conditioning Repair\",\n \"timeframe\": \"ASAP\"\n },\n \"qualifyingDataSummary\": \"Customer needs AC repair ASAP. Unit not cooling.\",\n \"contactReason\": \"HVAC repair needed\",\n \"timestamp\": \"2024-01-15T14:45:00.000Z\",\n \"messageHistory\": [\n {\"name\": \"John\", \"message\": \"Hi, my AC stopped working\", \"direction\": \"inbound\"},\n {\"name\": \"AI\", \"message\": \"I can help! What seems to be the issue?\", \"direction\": \"outbound\"}\n ],\n \"commonFields\": {\n \"address\": \"123 Main St\",\n \"city\": \"Springfield\",\n \"state\": \"IL\",\n \"zipcode\": \"62701\"\n }\n}\n```\n\n### MESSAGE_REPLY\nFires when a lead replies via SMS. Useful for Slack notifications.\n\n```json\n{\n \"eventType\": \"MESSAGE_REPLY\",\n \"eventTypeDetails\": \"NEW_MESSAGE_REPLY\",\n \"messageId\": \"msg_12345678-abcd-1234-5678-123456789abc\",\n \"leadId\": \"lead_87654321-dcba-4321-8765-987654321def\",\n \"clientId\": \"client_11111111-2222-3333-4444-555555555555\",\n \"companyId\": \"comp_99999999-8888-7777-6666-444444444444\",\n \"from\": \"+15551234567\",\n \"to\": \"+15559876543\",\n \"body\": \"Yes, I'm still interested in getting a quote!\",\n \"mediaUrl\": null,\n \"timestamp\": \"2024-01-15T15:00:00.000Z\",\n \"leadInformation\": {\n \"name\": \"John Doe\",\n \"phone\": \"+15551234567\",\n \"email\": \"john@example.com\"\n }\n}\n```\n\n### LEAD_STATUS_CHANGED\n**Legacy lead pipeline event.** Use this only if your account is still using\nthe legacy Leads system and you need status updates from individual\n`lead_form_submissions` records.\n\nIf your account is using the new Contacts/client pipeline, use\n`CLIENT_STATUS_CHANGED` instead. `LEAD_STATUS_CHANGED` does not represent\nthe contact-level pipeline status in the new system.\n\n```json\n{\n \"eventType\": \"LEAD_STATUS_CHANGED\",\n \"eventTypeDetails\": \"LEAD_STATUS_CHANGED\",\n \"leadId\": \"lead_87654321-dcba-4321-8765-987654321def\",\n \"clientId\": \"client_11111111-2222-3333-4444-555555555555\",\n \"companyId\": \"comp_99999999-8888-7777-6666-444444444444\",\n \"previousStatus\": \"NEW\",\n \"newStatus\": \"WON\",\n \"timestamp\": \"2024-01-16T10:00:00.000Z\",\n \"changedByUserId\": \"user_11111111-2222-3333-4444-555555555555\",\n \"changedByUserEmail\": \"team@example-hvac.com\",\n \"lead\": {\n \"id\": \"lead_87654321-dcba-4321-8765-987654321def\",\n \"companyId\": \"comp_99999999-8888-7777-6666-444444444444\",\n \"clientId\": \"client_11111111-2222-3333-4444-555555555555\",\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"+15551234567\",\n \"leadFormData\": {\n \"source\": \"google_ads\",\n \"serviceNeeded\": \"HVAC repair\"\n },\n \"conversionStatus\": \"WON\",\n \"conversionStatusUpdatedAt\": \"2024-01-16T10:00:00.000Z\"\n }\n}\n```\n\n### CLIENT_STATUS_CHANGED\nFires when a contact Status is changed in the new Contacts/client pipeline.\nUse this event for accounts using the new contact-centric pipeline.\n\n```json\n{\n \"eventType\": \"CLIENT_STATUS_CHANGED\",\n \"eventTypeDetails\": \"CLIENT_STATUS_CHANGED\",\n \"clientId\": \"client_11111111-2222-3333-4444-555555555555\",\n \"latestLeadSubmissionId\": \"lead_87654321-dcba-4321-8765-987654321def\",\n \"companyId\": \"comp_99999999-8888-7777-6666-444444444444\",\n \"previousStatus\": \"NEW\",\n \"newStatus\": \"WON\",\n \"timestamp\": \"2024-01-16T10:00:00.000Z\",\n \"changedByUserId\": \"user_11111111-2222-3333-4444-555555555555\",\n \"changedByUserEmail\": \"team@example-hvac.com\",\n \"client\": {\n \"id\": \"client_11111111-2222-3333-4444-555555555555\",\n \"companyId\": \"comp_99999999-8888-7777-6666-444444444444\",\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"primaryEmail\": \"john@example.com\",\n \"primaryPhone\": \"+15551234567\",\n \"leadStatus\": \"WON\",\n \"latestLeadSubmissionId\": \"lead_87654321-dcba-4321-8765-987654321def\",\n \"leadStatusUpdatedAt\": \"2024-01-16T10:00:00.000Z\"\n }\n}\n```\n\n### NEW_APPOINTMENT\nFires when a calendar booking is created.\n\n```json\n{\n \"eventType\": \"NEW_APPOINTMENT\",\n \"eventTypeDetails\": \"NEW_APPOINTMENT\",\n \"appointmentId\": \"apt_123e4567-e89b-12d3-a456-426614174000\",\n \"companyId\": \"comp_123e4567-e89b-12d3-a456-426614174000\",\n \"clientId\": \"client_123e4567-e89b-12d3-a456-426614174000\",\n \"appointmentDetails\": {\n \"title\": \"Service Appointment\",\n \"startAt\": \"2024-01-20T14:00:00.000Z\",\n \"endAt\": \"2024-01-20T15:00:00.000Z\",\n \"status\": \"BOOKED\"\n },\n \"clientInformation\": {\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"primaryEmail\": \"john@example.com\",\n \"primaryPhone\": \"+15551234567\",\n \"address\": \"123 Main St\"\n }\n}\n```\n\n### YELP_MESSAGE_OUTBOUND\nFires when sending a message to a Yelp lead via Zapier.\n\n**Setup:** Register a Zapier \"Webhooks by Zapier\" trigger, then connect to Yelp's \"Create Message\" action.\n\n**Trigger via:** `POST /v1/pub/yelp-lead-agent/send-message` or the LeadTruffle UI.\n\n```json\n{\n \"eventType\": \"YELP_MESSAGE_OUTBOUND\",\n \"eventTypeDetails\": \"YELP_FOLLOWUP_MESSAGE\",\n \"yelpLeadId\": \"yelp_lead_123456789\",\n \"yelpBusinessId\": \"yelp_biz_987654321\",\n \"conversationId\": \"conv_123e4567-e89b-12d3-a456-426614174000\",\n \"companyId\": \"comp_99999999-8888-7777-6666-444444444444\",\n \"message\": \"Hi! Thanks for reaching out. Are you still interested in a quote?\",\n \"attachmentUrls\": [],\n \"timestamp\": \"2024-01-16T10:00:00.000Z\",\n \"triggeredBy\": \"API\",\n \"leadInformation\": {\n \"name\": \"John Doe\",\n \"phone\": \"+15551234567\",\n \"email\": \"john@example.com\"\n }\n}\n```\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookV2CreateRequest" }, "examples": { "lead_created": { "summary": "Lead Created Webhook", "value": { "eventType": "LEAD_CREATED", "url": "https://api.example.com/webhooks/leads", "secret": "whsec_your_shared_secret" } }, "lead_status_changed": { "summary": "Legacy Lead Status Changed Webhook", "value": { "eventType": "LEAD_STATUS_CHANGED", "url": "https://api.example.com/webhooks/lead-status" } }, "client_status_changed": { "summary": "Contact Status Changed Webhook", "value": { "eventType": "CLIENT_STATUS_CHANGED", "url": "https://api.example.com/webhooks/contact-status" } }, "conversation_completed": { "summary": "Conversation Completed Webhook", "value": { "eventType": "CONVERSATION_COMPLETED", "url": "https://api.example.com/webhooks/completed" } }, "message_reply": { "summary": "Message Reply Webhook", "value": { "eventType": "MESSAGE_REPLY", "url": "https://api.example.com/webhooks/messages" } }, "new_appointment": { "summary": "New Appointment Webhook", "value": { "eventType": "NEW_APPOINTMENT", "url": "https://api.example.com/webhooks/appointments" } }, "yelp_message_outbound": { "summary": "Yelp Message Outbound (for Zapier)", "value": { "eventType": "YELP_MESSAGE_OUTBOUND", "url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/" } } } } } }, "responses": { "201": { "description": "Webhook successfully created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookV2Response" } } } }, "400": { "description": "Invalid request or webhook limit reached", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "validation_error": { "summary": "Validation Error", "value": { "success": false, "error": "Invalid event type. Must be one of: CONVERSATION_COMPLETED, MESSAGE_REPLY, LEAD_CREATED, LEAD_STATUS_CHANGED, CLIENT_STATUS_CHANGED, NEW_APPOINTMENT, YELP_MESSAGE_OUTBOUND" } }, "limit_reached": { "summary": "Webhook Limit", "value": { "success": false, "error": "Maximum of 6 webhooks allowed" } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/webhooks/{id}": { "put": { "tags": [ "Webhooks" ], "summary": "Update V2 webhook", "description": "Update an existing V2 webhook by ID.\n\nYou can partially update webhooks by including only the fields you want to change.\nAll fields are optional in the update request.\n\n**Special fields:**\n- `enabled`: Set to false to disable the webhook without deleting it\n- `clearErrors`: Set to true to reset the failed delivery counter\n- `secret`: Set a shared signing secret, or send `null`/an empty value to remove signing\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The UUID of the webhook to update", "example": "550e8400-e29b-41d4-a716-446655440000" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookV2UpdateRequest" }, "examples": { "update_url": { "summary": "Update webhook URL", "value": { "url": "https://api.example.com/webhooks/new-endpoint" } }, "disable_webhook": { "summary": "Disable webhook", "value": { "enabled": false } }, "clear_errors": { "summary": "Clear error history", "value": { "clearErrors": true } }, "full_update": { "summary": "Full update", "value": { "eventType": "MESSAGE_REPLY", "url": "https://api.example.com/webhooks/messages", "enabled": true, "clearErrors": true } } } } } }, "responses": { "200": { "description": "Webhook successfully updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookV2Response" } } } }, "400": { "description": "Invalid request data", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Webhook not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "Webhook not found" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } }, "delete": { "tags": [ "Webhooks" ], "summary": "Delete V2 webhook", "description": "Delete an existing V2 webhook by ID.\n\nThis action is permanent and cannot be undone. The webhook will immediately stop\nreceiving events and all associated delivery history will be preserved for audit purposes.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The UUID of the webhook to delete", "example": "550e8400-e29b-41d4-a716-446655440000" } ], "responses": { "200": { "description": "Webhook successfully deleted", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "message": { "type": "string", "example": "Webhook deleted successfully" } } } } } }, "400": { "description": "Invalid webhook ID", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Webhook not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "Webhook not found" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/leads-created/{uuid}": { "get": { "tags": [ "Lead inquiries" ], "summary": "Get single lead created by ID (V2)", "description": "Retrieve a specific lead in the \"lead created\" format by its UUID.\n\nThis endpoint returns the same payload format that is sent to LEAD_CREATED webhooks.\nDesigned specifically for Zapier integration requirements.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "uuid", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The UUID of the lead to retrieve", "example": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b" } ], "responses": { "200": { "description": "Successfully retrieved lead", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/WebhookV2PayloadLeadCreated" } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Lead not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "Lead not found" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/leads-created": { "get": { "tags": [ "Lead inquiries" ], "summary": "List leads created (V2)", "description": "Retrieve a paginated list of leads in the \"lead created\" format.\n\nThis endpoint returns the same payload format that is sent to LEAD_CREATED webhooks.\nReturns up to 100 records per request. Designed specifically for Zapier integration requirements.\n\nThis is the abbreviated format that gets delivered when we first receive a lead,\nbefore the conversation is completed.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "query", "name": "before", "schema": { "type": "string", "format": "date-time" }, "description": "Pagination cursor - get leads created before this timestamp (ISO8601). Defaults to current time if not provided.", "example": "2024-01-01T00:00:00Z" }, { "in": "query", "name": "limit", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 100 }, "description": "Maximum number of leads to return (max 100)" } ], "responses": { "200": { "description": "Successfully retrieved leads", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "leads": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookV2PayloadLeadCreated" }, "description": "List of leads in the \"lead created\" format, limited to a maximum of 100 per request" }, "hasMore": { "type": "boolean", "description": "Indicates if there are more results available. To fetch the next page, use the oldest lead's timestamp as the 'before' parameter." } } } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/leads-completed/{uuid}": { "get": { "tags": [ "Conversations" ], "summary": "Get single completed conversation by ID (V2)", "description": "Retrieve a specific lead in the \"conversation completed\" format by its UUID.\n\nThis endpoint returns the same payload format that is sent to CONVERSATION_COMPLETED webhooks.\nDesigned specifically for Zapier integration requirements.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "uuid", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The UUID of the lead to retrieve", "example": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b" } ], "responses": { "200": { "description": "Successfully retrieved completed conversation", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/WebhookV2PayloadConversationCompleted" } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Lead not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "Lead not found" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/leads-completed": { "get": { "tags": [ "Conversations" ], "summary": "List completed conversations (V2)", "description": "Retrieve a paginated list of leads in the \"conversation completed\" format.\n\nThis endpoint returns the same payload format that is sent to CONVERSATION_COMPLETED webhooks.\nReturns up to 10 records per request due to the larger payload size (includes full message history).\nDesigned specifically for Zapier integration requirements.\n\nThis is what gets delivered to the conversation complete webhook - the same lead data but\nwith full message history and completed qualification information.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "query", "name": "before", "schema": { "type": "string", "format": "date-time" }, "description": "Pagination cursor - get leads created before this timestamp (ISO8601). Defaults to current time if not provided.", "example": "2024-01-01T00:00:00Z" }, { "in": "query", "name": "limit", "schema": { "type": "integer", "minimum": 1, "maximum": 10, "default": 10 }, "description": "Maximum number of leads to return (max 10)" } ], "responses": { "200": { "description": "Successfully retrieved completed conversations", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "leads": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookV2PayloadConversationCompleted" }, "description": "List of leads in the \"conversation completed\" format, limited to a maximum of 10 per request" }, "hasMore": { "type": "boolean", "description": "Indicates if there are more results available. To fetch the next page, use the oldest lead's timestamp as the 'before' parameter." } } } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/message-replies": { "get": { "tags": [ "Conversations" ], "summary": "List recent message replies (V2)", "description": "Retrieve the latest inbound SMS replies (from leads/customers) in the exact same format that the MESSAGE_REPLY webhook delivers.\n\nReturns up to 10 entries per request and is designed to power Zapier \"Test trigger\" flows that need live-looking data.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "query", "name": "before", "schema": { "type": "string", "format": "date-time" }, "description": "Pagination cursor – fetch message replies created before this timestamp (ISO8601). When omitted, the newest replies are returned.", "example": "2024-01-15T00:00:00Z" }, { "in": "query", "name": "limit", "schema": { "type": "integer", "minimum": 1, "maximum": 10, "default": 10 }, "description": "Maximum number of message replies to return (capped at 10)." } ], "responses": { "200": { "description": "Successfully retrieved message replies", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "messageReplies": { "type": "array", "description": "List of inbound SMS replies formatted exactly like MESSAGE_REPLY webhook payloads.", "items": { "$ref": "#/components/schemas/WebhookV2PayloadMessageReply" } }, "hasMore": { "type": "boolean", "description": "Indicates if more replies exist beyond this page." }, "nextBeforeCursor": { "type": "string", "format": "date-time", "nullable": true, "description": "Pass this timestamp to the `before` query parameter to fetch the next (older) page of replies." } } } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/employees": { "get": { "tags": [ "Team" ], "summary": "List employees", "description": "Returns the limited employee details needed for public API integrations.\n\nUse the returned `id` as `assignedToUserId` when assigning leads through\n`PUT /v2/pub/leads/update`.\n", "security": [ { "ApiKeyAuth": [] } ], "responses": { "200": { "description": "Employees listed successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "employees": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "example": "Alexia Morgan" }, "email": { "type": "string", "format": "email", "example": "alexia@example.com" }, "role": { "type": "string", "enum": [ "ADMIN", "MANAGER", "USER" ], "example": "MANAGER" }, "disabled": { "type": "boolean", "example": false }, "createdAt": { "type": "string", "format": "date-time" } } } } } } } }, "example": { "success": true, "data": { "employees": [ { "id": "11111111-1111-4111-8111-111111111111", "name": "Alexia Morgan", "email": "alexia@example.com", "role": "MANAGER", "disabled": false, "createdAt": "2024-01-03T10:00:00.000Z" } ] } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/clients/update": { "put": { "tags": [ "Contacts" ], "summary": "Update contact", "description": "Update Contact pipeline fields in the new Contacts experience.\n\nThis endpoint controls the Contact-level pipeline state used by the\nLeadTruffle app's Contacts and Inbox screens. It does not update legacy\nper-inquiry Lead status fields.\n\nIdentify the Contact with `clientId`, `phone`, or `email`. If multiple\nidentifiers are provided, `clientId` is used first, then `phone`, then\n`email`.\n\nThe request field is named `leadStatus` for API compatibility with the\nstored field name, but it represents the Contact pipeline status shown in\nthe app. Use `GET /v2/pub/clients/statuses` to list valid status IDs for\nthe company.\n\n`assignedToUserId` controls the Contact assignee. Use\n`GET /v2/pub/employees` to find enabled employee IDs.\n\n`archived` controls the Contact's client-pipeline Inbox archive state. It\ndoes not delete the Contact.\n\nSuccessful status, assignment, review-state, and archive-state changes\nare recorded in Contact history.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "clientId": { "type": "string", "format": "uuid", "description": "LeadTruffle Contact ID.", "example": "33333333-3333-4333-8333-333333333333" }, "phone": { "type": "string", "description": "Contact primary phone number.", "example": "+15551234567" }, "email": { "type": "string", "format": "email", "description": "Contact primary email address.", "example": "customer@example.com" }, "leadStatus": { "type": "string", "description": "Contact pipeline status ID. Named leadStatus for API compatibility.", "example": "CONTACTED" }, "assignedToUserId": { "type": "string", "format": "uuid", "nullable": true, "description": "Enabled employee ID to assign the Contact to. Send null to unassign.", "example": "11111111-1111-4111-8111-111111111111" }, "archived": { "type": "boolean", "description": "Whether this Contact should be archived from the active client-pipeline Inbox.", "example": true } } }, "examples": { "updateStatusByPhone": { "summary": "Update Contact status by phone", "value": { "phone": "+15551234567", "leadStatus": "CONTACTED" } }, "assignContact": { "summary": "Assign a Contact", "value": { "clientId": "33333333-3333-4333-8333-333333333333", "assignedToUserId": "11111111-1111-4111-8111-111111111111" } }, "unassignAndUnarchive": { "summary": "Clear assignment and archive state", "value": { "clientId": "33333333-3333-4333-8333-333333333333", "assignedToUserId": null, "archived": false } } } } } }, "responses": { "200": { "description": "Contact updated successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "companyId": { "type": "string", "format": "uuid" }, "name": { "type": "string", "nullable": true }, "firstName": { "type": "string", "nullable": true }, "lastName": { "type": "string", "nullable": true }, "companyName": { "type": "string", "nullable": true }, "primaryPhone": { "type": "string", "nullable": true }, "primaryEmail": { "type": "string", "nullable": true }, "leadStatus": { "type": "string", "description": "Contact pipeline status ID." }, "leadStatusUpdatedAt": { "type": "string", "format": "date-time", "nullable": true }, "assignedToUserId": { "type": "string", "format": "uuid", "nullable": true }, "assignedAt": { "type": "string", "format": "date-time", "nullable": true }, "archived": { "type": "boolean" }, "archivedAt": { "type": "string", "format": "date-time", "nullable": true }, "latestLeadSubmissionId": { "type": "string", "format": "uuid", "nullable": true }, "updatedAt": { "type": "string", "format": "date-time" } } } } }, "example": { "success": true, "data": { "id": "33333333-3333-4333-8333-333333333333", "companyId": "f096f9e3-001d-49ac-864c-3d73453bbe08", "name": "Sarah Johnson", "firstName": "Sarah", "lastName": "Johnson", "companyName": null, "primaryPhone": "+15551234567", "primaryEmail": "sarah@example.com", "leadStatus": "CONTACTED", "leadStatusUpdatedAt": "2026-05-28T14:30:00.000Z", "assignedToUserId": "11111111-1111-4111-8111-111111111111", "assignedAt": "2026-05-28T14:30:00.000Z", "archived": true, "archivedAt": "2026-05-28T14:30:00.000Z", "latestLeadSubmissionId": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "updatedAt": "2026-05-28T14:30:00.000Z" } } } } }, "400": { "description": "Invalid request data", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Contact not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/clients/statuses": { "get": { "tags": [ "Contacts" ], "summary": "List contact pipeline statuses", "description": "List valid Contact pipeline status IDs for the authenticated company.\n\nUse these IDs as the `leadStatus` value in\n`PUT /v2/pub/clients/update`. The field is named `leadStatus` for API\ncompatibility with the stored field name, but these statuses control the\nnew Contact-level pipeline shown in Contacts and Inbox.\n", "security": [ { "ApiKeyAuth": [] } ], "responses": { "200": { "description": "Contact pipeline statuses listed successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "statuses": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Status ID to send as leadStatus.", "example": "CONTACTED" }, "label": { "type": "string", "example": "Contacted" }, "description": { "type": "string", "nullable": true }, "order": { "type": "integer" }, "color": { "type": "string", "nullable": true }, "visible": { "type": "boolean" }, "isDefault": { "type": "boolean" }, "locked": { "type": "boolean" } } } } } } } }, "example": { "success": true, "data": { "statuses": [ { "id": "NEW", "label": "New", "description": "Fresh leads awaiting first touch", "order": 0, "color": "INDIGO", "visible": true, "isDefault": true, "locked": false }, { "id": "CONTACTED", "label": "Contacted", "description": "Outbound call or text sent", "order": 1, "color": "BLUE", "visible": true, "isDefault": true, "locked": false } ] } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/clients/{clientId}/notes": { "get": { "tags": [ "Contacts" ], "summary": "List contact notes", "description": "List active notes for a Contact in the new Contacts experience.\n\nThese are Contact-level notes from the `client_notes` table. They are not\nlegacy per-inquiry Lead notes.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "clientId", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "in": "query", "name": "limit", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } }, { "in": "query", "name": "before", "required": false, "schema": { "type": "string", "format": "date-time" } } ], "responses": { "200": { "description": "Contact notes listed successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "notes": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "clientId": { "type": "string", "format": "uuid" }, "body": { "type": "string" }, "authorType": { "type": "string", "enum": [ "EMPLOYEE", "SYSTEM" ] }, "createdByCompanyUserId": { "type": "string", "format": "uuid", "nullable": true }, "createdByName": { "type": "string", "nullable": true }, "createdByEmail": { "type": "string", "format": "email", "nullable": true }, "source": { "type": "string" }, "metadata": { "type": "object", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } } }, "nextBefore": { "type": "string", "format": "date-time", "nullable": true } } } } }, "example": { "success": true, "data": { "notes": [ { "id": "44444444-4444-4444-8444-444444444444", "clientId": "33333333-3333-4333-8333-333333333333", "body": "Customer wants a call next week.", "authorType": "SYSTEM", "createdByCompanyUserId": null, "createdByName": null, "createdByEmail": null, "source": "SYSTEM", "metadata": { "source": "PUBLIC_API" }, "createdAt": "2026-05-28T14:30:00.000Z", "updatedAt": "2026-05-28T14:30:00.000Z" } ], "nextBefore": null } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Contact not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } }, "post": { "tags": [ "Contacts" ], "summary": "Add contact note", "description": "Add a new API-created note to a Contact in the new Contacts experience.\n\nNotes created here are Contact-level notes. They appear in the Contact\ndetails notes thread and do not modify legacy per-inquiry Lead notes.\nSuccessful note creation is recorded in Contact history.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "clientId", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "body" ], "properties": { "body": { "type": "string", "minLength": 1, "maxLength": 5000, "example": "Customer wants a call next week." } } } } } }, "responses": { "201": { "description": "Contact note created successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object" } } }, "example": { "success": true, "data": { "id": "44444444-4444-4444-8444-444444444444", "clientId": "33333333-3333-4333-8333-333333333333", "body": "Customer wants a call next week.", "authorType": "SYSTEM", "createdByCompanyUserId": null, "createdByName": null, "createdByEmail": null, "source": "SYSTEM", "metadata": { "source": "PUBLIC_API" }, "createdAt": "2026-05-28T14:30:00.000Z", "updatedAt": "2026-05-28T14:30:00.000Z" } } } } }, "400": { "description": "Invalid request data", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Contact not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/chat-widget/config": { "get": { "tags": [ "Widgets" ], "summary": "Get chat widget configuration", "description": "Retrieve the current chat widget configuration for your company.\n\nThis includes styling settings, AI agent configuration, and lead qualification settings.\nIf no configuration exists, a default configuration will be created automatically.\n", "security": [ { "ApiKeyAuth": [] } ], "responses": { "200": { "description": "Successfully retrieved chat widget configuration", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/ChatWidgetConfig" } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "put": { "tags": [ "Widgets" ], "summary": "Update chat widget configuration", "description": "Update the chat widget configuration for your company.\n\nYou can partially update the configuration by including only the fields you want to change.\nThis endpoint combines styling updates and AI agent configuration updates.\n\n**Key Configuration Options:**\n- **greetingMessage**: The initial message shown to visitors\n- **agentConfig**: AI agent styling, popup behavior, and appearance\n- **leadQualifierAgentConfig**: AI qualification logic and message limits\n- **Styling**: Primary/secondary colors, background, custom CSS\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatWidgetConfigUpdate" }, "examples": { "basic_styling": { "summary": "Update basic styling", "value": { "greetingMessage": "Welcome to our company! How can we help?", "primaryColor": "#007bff", "secondaryColor": "#6c757d" } }, "agent_config": { "summary": "Update AI agent configuration", "value": { "agentConfig": { "name": "Sarah", "avatarImageUrl": "https://example.com/avatar.png", "popupEnabled": true, "popupOpenDelay": 5 }, "leadQualifierAgentConfig": { "agentInstructions": "You are a helpful assistant for our HVAC company. Ask about their location, timeline, and budget.", "maximumMessagesLimit": 20 } } }, "full_config": { "summary": "Complete configuration update", "value": { "greetingMessage": "Welcome! Text us for immediate help.", "agentConfig": { "name": "Alex", "popupEnabled": true, "popupOpenDelay": 3, "popupHeader1": "Need HVAC Help?", "popupHeader2": "Get a quote in minutes!" }, "leadQualifierAgentEnabled": true, "leadQualifierAgentConfig": { "agentInstructions": "Qualify leads for our HVAC services by asking about their location, project timeline, and budget range.", "maximumMessagesLimit": 20 }, "primaryColor": "#ff6b35", "backgroundColor": "#ffffff" } } } } } }, "responses": { "200": { "description": "Configuration successfully updated", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/ChatWidgetConfig" } } } } } }, "400": { "description": "Invalid configuration data", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Error" }, { "type": "object", "properties": { "details": { "type": "object", "description": "Validation error details" } } } ] } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Widget configuration not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/chat-widget/trigger-lead-qualifier": { "post": { "tags": [ "Widgets" ], "summary": "Trigger lead qualifier agent", "description": "**⚠️ RESTRICTED ENDPOINT - APPROVAL REQUIRED ⚠️**\n\nThis endpoint allows you to programmatically submit leads and trigger the AI lead qualification process.\n\n## Prerequisites & Restrictions\n\n**This endpoint may ONLY be used if ALL of the following conditions are met:**\n\n1. **Active 10DLC Phone Number**: Your company must have an active 10DLC (Application-to-Person) phone number provisioned\n2. **Lead Qualifier Agent Enabled**: The lead qualification AI agent must be enabled in your widget configuration\n3. **Approved Usage**: LeadTruffle must have explicitly approved your specific use case and implementation\n4. **SMS Opt-in Compliance**: You must have proper SMS opt-in consent as part of your lead collection flow\n\n## Pre-Approved Use Cases\n\nThe following lead sources have been pre-approved for use with this endpoint:\n- **Facebook Lead Ads** - With proper SMS opt-in checkbox\n- **Google Lead Ads** - With SMS consent collection\n- **Thumbtack Lead Ads** - Following platform opt-in requirements \n- **Yelp Lead Ads** - With SMS permission collection\n\n## Compliance Requirements\n\n**SMS Opt-in Consent**: You MUST collect explicit SMS opt-in consent from leads before using this endpoint. This includes:\n- Clear disclosure that they will receive SMS messages\n- Explicit consent checkbox or opt-in mechanism\n- Compliance with TCPA regulations\n- Documentation of consent for audit purposes\n\n**Approval Process**: Contact support@leadtruffle.com to:\n1. Submit your lead collection workflow for review\n2. Demonstrate proper SMS opt-in implementation\n3. Receive written approval for your specific use case\n4. Get the permission flag enabled on your account\n\n## Important Notes\n\n- **Unauthorized usage will result in immediate API access suspension**\n- Only approved opt-in methods will be accepted\n- LeadTruffle reserves the right to audit and verify compliance\n- All lead submissions are logged and monitored\n- You are responsible for TCPA and SMS compliance\n- If the lead is submitted during the company's quiet hours, the lead is still created immediately, but the initial SMS is queued to retry the next morning starting at 9:00 AM in the company's local timezone\n- A successful `201` response means the lead record was accepted; it does not guarantee the first SMS was sent immediately\n\n## Usage Example\n\n```json\n{\n \"name\": \"John Doe\",\n \"phone\": \"+15551234567\",\n \"additionalData\": {\n \"leadSource\": \"facebook\",\n \"fbid\": \"923849028492034890283094\",\n \"campaignId\": \"summer_hvac_2024\",\n \"smsOptInConfirmed\": \"true\",\n \"optInTimestamp\": \"2024-01-01T12:00:00Z\"\n }\n}\n```\n\nThe lead qualifier AI will automatically initiate an SMS conversation with the provided phone number using your configured qualification questions and company branding.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatWidgetLeadTrigger" }, "examples": { "facebook_lead": { "summary": "Facebook Lead Ad", "value": { "name": "Sarah Johnson", "phone": "+15551234567", "email": "sarah.johnson@example.com", "message": "Interested in HVAC quote", "additionalData": { "leadSource": "facebook", "fbid": "923849028492034890283094", "campaignId": "summer_hvac_2024", "smsOptInConfirmed": "true" } } }, "google_lead": { "summary": "Google Lead Ad", "value": { "name": "Mike Rodriguez", "phone": "+15559876543", "additionalData": { "leadSource": "google", "gclid": "1234567890abcdef", "campaignId": "hvac_repair_campaign", "smsOptInConfirmed": "true" } } }, "yelp_lead": { "summary": "Yelp Lead", "value": { "name": "Lisa Chen", "phone": "+15555555555", "email": "lisa.chen@example.com", "additionalData": { "leadSource": "yelp", "yelpLeadId": "yelp_lead_12345", "serviceRequested": "AC Repair", "smsOptInConfirmed": "true" } } } } } } }, "responses": { "201": { "description": "Lead successfully created. SMS qualification is either started immediately or queued for the next local morning if the lead arrived during quiet hours.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatWidgetLeadTriggerResponse" } } } }, "400": { "description": "Invalid request data or missing required fields", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Error" }, { "type": "object", "properties": { "details": { "type": "object", "description": "Validation error details" } } } ] }, "examples": { "validation_error": { "summary": "Validation Error", "value": { "success": false, "error": "Invalid input", "details": { "phone": { "_errors": [ "Phone number is required" ] } } } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "403": { "description": "Insufficient permissions or approval required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "not_approved": { "summary": "Not Approved", "value": { "success": false, "error": "This endpoint requires explicit approval from LeadTruffle. Contact support@leadtruffle.com to request access." } }, "no_10dlc": { "summary": "No 10DLC Number", "value": { "success": false, "error": "Active 10DLC phone number required. Please provision a 10DLC number before using this endpoint." } }, "qualifier_disabled": { "summary": "Lead Qualifier Disabled", "value": { "success": false, "error": "Lead qualifier agent must be enabled in your widget configuration." } } } } } }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" }, "501": { "description": "Feature not yet implemented", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "Chat widget lead creation is not yet implemented" } } } } } } }, "/v1/pub/default-calendar/appointments": { "get": { "tags": [ "Default Calendar" ], "summary": "Get Default Calendar Appointments", "description": "Retrieve appointments for the company's default (primary) calendar with optional date filtering and pagination.\n\n**Default Behavior:**\n- If no date range is specified, returns appointments from today to 2 weeks in the future\n- Returns up to 100 appointments per request\n- Results are ordered by appointment start time (ascending)\n- Includes associated client information when available\n\n**Date Filtering:**\n- Use `startDate` and `endDate` query parameters to specify custom date ranges\n- Dates must be in ISO 8601 format (e.g., \"2024-01-15T00:00:00Z\")\n\n**Pagination:**\n- Use `limit` (1-100) and `offset` parameters for pagination\n- Response includes pagination metadata to help navigate through results\n", "parameters": [ { "name": "startDate", "in": "query", "required": false, "schema": { "type": "string", "format": "date-time" }, "description": "Start date for appointment filtering (ISO 8601 format). Defaults to today.", "example": "2024-01-15T00:00:00Z" }, { "name": "endDate", "in": "query", "required": false, "schema": { "type": "string", "format": "date-time" }, "description": "End date for appointment filtering (ISO 8601 format). Defaults to 2 weeks from today.", "example": "2024-01-29T23:59:59Z" }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 100 }, "description": "Maximum number of appointments to return", "example": 50 }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "default": 0 }, "description": "Number of appointments to skip for pagination", "example": 0 } ], "responses": { "200": { "description": "Appointments retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "calendar": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Calendar ID" }, "name": { "type": "string", "description": "Calendar name" }, "timeZone": { "type": "string", "description": "Calendar timezone" } }, "example": { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Primary", "timeZone": "America/New_York" } }, "appointments": { "type": "array", "items": { "$ref": "#/components/schemas/AppointmentCreatedWebhookPayload" } }, "pagination": { "type": "object", "properties": { "limit": { "type": "integer", "description": "Number of appointments requested" }, "offset": { "type": "integer", "description": "Number of appointments skipped" }, "total": { "type": "integer", "description": "Total number of appointments matching filter" }, "hasMore": { "type": "boolean", "description": "Whether there are more appointments beyond this page" } }, "example": { "limit": 100, "offset": 0, "total": 45, "hasMore": false } }, "filters": { "type": "object", "properties": { "startDate": { "type": "string", "format": "date-time", "description": "Applied start date filter" }, "endDate": { "type": "string", "format": "date-time", "description": "Applied end date filter" } }, "example": { "startDate": "2024-01-15T00:00:00.000Z", "endDate": "2024-01-29T23:59:59.999Z" } } } } } } } } }, "400": { "description": "Bad request - invalid query parameters or date range", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Error" }, { "type": "object", "properties": { "details": { "type": "object", "additionalProperties": true, "description": "Field validation errors when query parameters are invalid." } } } ] }, "examples": { "invalid_date_range": { "summary": "Invalid date range", "value": { "success": false, "error": "startDate must be before endDate" } }, "invalid_parameters": { "summary": "Invalid query parameters", "value": { "success": false, "error": "Invalid query parameters", "details": { "limit": { "_errors": [ "Number must be less than or equal to 100" ] } } } } } } } }, "404": { "description": "No primary calendar found for this company", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "No primary calendar found for this company" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/v1/pub/default-calendar/info": { "get": { "tags": [ "Default Calendar" ], "summary": "Get Default Calendar Information", "description": "Retrieve basic information about the company's default (primary) calendar including\nconfiguration settings like booking policies, time zones, and availability rules.\n", "responses": { "200": { "description": "Calendar information retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/Calendar" } } } } } }, "404": { "description": "No primary calendar found for this company", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "No primary calendar found for this company" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/v1/pub/email-gateway/submit": { "post": { "tags": [ "Email gateway" ], "summary": "Submit email gateway message", "description": "Submit email content to the email gateway so LeadTruffle can parse it and initiate follow-up emails.\n\nUse `customerEmail` when you already know the lead's email address to skip AI extraction.\nInclude `messageId` to safely retry without creating duplicates.\nOnly submit leads who have explicitly requested contact and follow applicable email compliance rules.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmailGatewaySubmitRequest" }, "examples": { "basic_submission": { "summary": "Basic submission", "value": { "emailGatewayAddress": "c3f3c4a2-1234-5678-9012-abcdefabcdef@m.leadtruffle.com", "emailBodyText": "New web form lead. Name: Jane Doe. Email: jane@example.com. Service: HVAC repair.", "emailSubject": "New website lead" } }, "with_customer_email": { "summary": "With known customer email", "value": { "emailGatewayAddress": "c3f3c4a2-1234-5678-9012-abcdefabcdef@m.leadtruffle.com", "emailBodyText": "Customer requested a quote for landscaping services.", "emailSubject": "Quote request", "customerEmail": "jane@example.com", "messageId": "ext-msg-12345" } } } } } }, "responses": { "200": { "description": "Submission accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmailGatewaySubmitResponse" }, "examples": { "queued": { "summary": "Queued for processing", "value": { "success": true, "data": { "inboundEmailId": "3c8b9b67-4b4a-4f2b-9f33-6f1e4e8b2f29", "status": "QUEUED", "queuedAction": "QUALIFY_OVER_EMAIL" } } }, "duplicate": { "summary": "Duplicate submission", "value": { "success": true, "data": { "inboundEmailId": "3c8b9b67-4b4a-4f2b-9f33-6f1e4e8b2f29", "status": "DUPLICATE" } } } } } } }, "400": { "description": "Invalid request payload or blocked by policy (opt-out, unsupported action)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "$ref": "#/components/responses/NotFoundError" }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/yelp-lead-agent/process-incoming-message": { "post": { "tags": [ "Yelp Lead Agent" ], "summary": "Process incoming Yelp message with AI agent (New Consumer Message)", "description": "Submit a Yelp inbox message to the LeadTruffle Yelp AI agent so it can continue the conversation,\nqualify the lead, and create CRM records if needed.\n\nThis endpoint mirrors the internal Yelp inbox workflow and should be called whenever a new\nmessage is received in Yelp or when you want to simulate a new consumer message. If the reply\nis null, do not send a response message; this can happen when manual takeover is enabled or\nwhen the initial lead reply was already sent by a concurrent event.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpLeadAgentRequest" }, "example": { "yelpLeadId": "lead_abc123", "yelpBusinessId": "business_456", "messageId": "msg_789", "userType": "CONSUMER", "userDisplayName": "Jane Smith", "timeCreated": "2024-12-01T15:04:05Z", "text": "Hi, can I get a quote for gutter cleaning?" } } } }, "responses": { "200": { "description": "Yelp agent processed the message", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpLeadAgentResponse" }, "examples": { "reply_available": { "summary": "Reply generated", "value": { "success": true, "data": { "reply": "Hi Jane! We'd love to help with your gutter cleaning. Can you confirm the best phone number to reach you and whether mornings or afternoons work better next week?", "leadId": "d6c6f5d1-6c17-4ab2-a41d-1e2a0b9acb1f", "conversationId": "ad10af01-6d8a-4b46-83aa-4a7d38c35172", "isDuplicate": false, "manualTakeoverEnabled": false, "qualificationResponse": { "response": "Hi Jane! We'd love to help with your gutter cleaning. Can you confirm the best phone number to reach you and whether mornings or afternoons work better next week?", "contactReason": "Customer needs gutter cleaning and would like it scheduled next week.", "allQuestionsAnswered": false, "isClientReadyToBook": false, "detectedAbuse": false, "shouldSkipReply": false, "hasImages": true, "dataFields": [ { "name": "customer_name", "value": "Jane Smith" }, { "name": "service", "value": "Gutter cleaning" }, { "name": "timeline", "value": "Next week" } ], "commonFields": { "phone": "+15551234567", "email": "jane@example.com", "customerName": "Jane Smith", "fullAddress": "123 Main St, Springfield, IL 62704", "address": "123 Main St", "city": "Springfield", "state": "IL", "zipcode": "62704", "isHomeowner": true } } } } }, "reply_suppressed": { "summary": "Reply suppressed (do not send response)", "value": { "success": true, "data": { "reply": null, "leadId": "d6c6f5d1-6c17-4ab2-a41d-1e2a0b9acb1f", "conversationId": "ad10af01-6d8a-4b46-83aa-4a7d38c35172", "isDuplicate": false, "manualTakeoverEnabled": true, "qualificationResponse": null } } } } } } }, "400": { "description": "Invalid payload", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/yelp-lead-agent/process-new-lead": { "post": { "tags": [ "Yelp Lead Agent" ], "summary": "Capture Yelp New Lead payload", "description": "Store the raw Yelp New Lead webhook payload, capture temporary contact info, and ensure the lead/client records are upserted. This endpoint now also generates the initial AI reply when possible and returns it so your Zap can forward it to Yelp. If the reply is null, do not send a response message. The reply may be null when the agent is disabled, manual takeover is enabled, or a duplicate first message is suppressed.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpLeadAgentLeadRequest" }, "example": { "yelpLeadId": "2XS0bA60kwwGv31HZ794eQ", "yelpBusinessId": "VSyxnREXHd5ZHFMSd6338Q", "payload": { "leadUserName": "Megha S.", "leadTimeCreated": "2025-11-11T00:11:07+00:00", "leadTimeUpdated": "2025-11-11T00:11:11+00:00", "temporaryEmail": "leadsapi+94768a8de2304b9b8f908fbf65d80489@messaging.yelp.com", "temporaryEmailExpiry": "2025-12-11T00:11:14+00:00", "temporaryPhone": "+15551230000", "temporaryPhoneExpiry": "2025-12-11T00:11:14+00:00", "project": { "projectSurveyAnswersFormatted": "Q: What do you need done?\nA: Fix a leak.\nQ: When do you need this?\nA: ASAP.\n", "location": { "postalCode": "95337" }, "details": "availability", "availabilityStatus": "ASAP", "jobNames": [ "Plumbing repair" ], "attachmentUrls": [ "https://example.com/attachment1.jpg", "https://example.com/attachment2.jpg" ] }, "business": { "name": "Discount Plumbing", "url": "https://www.yelp.com/biz/discount-plumbing-manteca-6", "formattedAddress": "787 Cottage Ave, Manteca, CA 95336" } } } } } }, "responses": { "200": { "description": "Payload stored.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpLeadAgentLeadResponse" }, "examples": { "reply_available": { "summary": "Reply generated", "value": { "success": true, "data": { "conversationId": "ad10af01-6d8a-4b46-83aa-4a7d38c35172", "leadId": "d6c6f5d1-6c17-4ab2-a41d-1e2a0b9acb1f", "clientId": "f3bbf751-0a6e-4ffb-8cb1-0bbca9698a7d", "reply": "Thanks for reaching out! Can you share the best number and a couple details about the leak?", "manualTakeoverEnabled": false, "qualificationResponse": { "response": "Thanks for reaching out! Can you share the best number and a couple details about the leak?", "contactReason": "Plumbing leak repair", "allQuestionsAnswered": false, "isClientReadyToBook": false, "detectedAbuse": false, "shouldSkipReply": false, "hasImages": true, "dataFields": [ { "name": "customer_name", "value": "Megha S." }, { "name": "phone", "value": "" } ], "commonFields": { "phone": null, "email": null, "fullAddress": null, "address": null, "zipcode": "95337", "state": null, "city": null, "isHomeowner": null, "customerName": "Megha S." } } } } }, "reply_suppressed": { "summary": "Reply suppressed (do not send response)", "value": { "success": true, "data": { "conversationId": "ad10af01-6d8a-4b46-83aa-4a7d38c35172", "leadId": "d6c6f5d1-6c17-4ab2-a41d-1e2a0b9acb1f", "clientId": "f3bbf751-0a6e-4ffb-8cb1-0bbca9698a7d", "reply": null, "manualTakeoverEnabled": true, "qualificationResponse": null, "initialReplySkipped": true } } } } } } }, "400": { "description": "Invalid payload", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/yelp-lead-agent/process-phone-availability": { "post": { "tags": [ "Yelp Lead Agent" ], "summary": "Capture Yelp Phone Number Available payload", "description": "Receive Yelp's \"Phone Number Available\" webhook from Zapier and attach the customer phone number to the matching LeadTruffle Yelp lead.\n\nFor the simplest Zapier setup, send only `yelpLeadId` and `temporaryPhone`. The endpoint also accepts `phone` and `temporaryPhoneNumber` as aliases for the same value. The phone is treated as the customer phone number after validation/normalization, even though Yelp/Zapier may still label it as temporary.\n\nThis endpoint enriches the existing Yelp conversation and LeadTruffle lead created by the earlier Yelp New Lead webhook. The linked Client primary phone is filled when blank. If the Client already has a different primary phone, LeadTruffle leaves it unchanged and still updates the lead record with the Yelp phone. If no matching conversation or lead exists, the endpoint returns a successful no-op response with `skippedReason`.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "yelpLeadId" ], "anyOf": [ { "required": [ "temporaryPhone" ] }, { "required": [ "phone" ] }, { "required": [ "temporaryPhoneNumber" ] } ], "properties": { "yelpLeadId": { "type": "string", "description": "Yelp Lead ID from the Phone Number Available trigger.", "example": "1RQghT7x6uLKiGhzgzJZXQ" }, "temporaryPhone": { "type": "string", "description": "Customer phone number from Yelp's Temporary Phone Number field.", "example": "+17206416322" }, "yelpBusinessId": { "type": "string", "description": "Yelp Business ID when available.", "example": "i4EmGWPneZPCiOJQHyfluQ" }, "phone": { "type": "string", "description": "Optional alias for `temporaryPhone`.", "example": "+17206416322" }, "temporaryPhoneNumber": { "type": "string", "description": "Optional alias for `temporaryPhone`.", "example": "+17206416322" }, "phoneExpiry": { "type": "string", "description": "Optional Yelp phone expiration timestamp.", "example": "2099-12-31T23:59:59+00:00" }, "leadUserName": { "type": "string", "description": "Optional Yelp lead display name.", "example": "Andrew B." }, "leadTimeCreated": { "type": "string", "description": "Optional Yelp lead creation timestamp.", "example": "2026-04-20T18:48:40+00:00" }, "leadTimeUpdated": { "type": "string", "description": "Optional Yelp lead update timestamp.", "example": "2026-04-20T18:49:01+00:00" }, "payload": { "type": "object", "additionalProperties": true, "description": "Optional raw Yelp/Zapier payload fields to merge into the stored Yelp metadata." } } }, "example": { "yelpLeadId": "1RQghT7x6uLKiGhzgzJZXQ", "temporaryPhone": "+17206416322" } } } }, "responses": { "200": { "description": "Phone captured and lead/client records synchronized.", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "conversationId": { "type": "string", "nullable": true, "format": "uuid" }, "leadId": { "type": "string", "nullable": true, "format": "uuid" }, "clientId": { "type": "string", "nullable": true, "format": "uuid" }, "phone": { "type": "string", "example": "+17206416322" }, "leadUpdated": { "type": "boolean" }, "clientUpdated": { "type": "boolean" }, "clientPhoneUpdateSkipped": { "type": "boolean" }, "skippedReason": { "type": "string", "nullable": true, "enum": [ "CONVERSATION_NOT_FOUND", "LEAD_NOT_FOUND" ] } } } } }, "example": { "success": true, "data": { "conversationId": "ad10af01-6d8a-4b46-83aa-4a7d38c35172", "leadId": "d6c6f5d1-6c17-4ab2-a41d-1e2a0b9acb1f", "clientId": "f3bbf751-0a6e-4ffb-8cb1-0bbca9698a7d", "phone": "+17206416322", "leadUpdated": true, "clientUpdated": true, "clientPhoneUpdateSkipped": false } } } } }, "400": { "description": "Invalid payload or phone number", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/yelp-lead-agent/process-business-message": { "post": { "tags": [ "Yelp Lead Agent" ], "summary": "Process incoming business message from Yelp", "description": "Receive a message sent by the business directly from their Yelp account (via Zapier's \"New Business Message\" trigger).\n\nThis endpoint stores the message in the conversation history but does NOT trigger any AI response.\nUse this when the business replies manually from Yelp and you want to keep the conversation\nhistory in sync.\n\n**Deduplication:** Messages are deduplicated based on content to prevent duplicates when\nZapier fires after the AI or webhook already sent the same message.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpBusinessMessageRequest" }, "example": { "yelpLeadId": "lead_abc123", "messageId": "msg_business_789", "yelpBusinessId": "business_456", "text": "Thanks for reaching out! We can schedule you for next Tuesday. Does 10am work?", "timeCreated": "2024-12-01T16:00:00Z", "userDisplayName": "ABC Plumbing" } } } }, "responses": { "200": { "description": "Business message processed successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpBusinessMessageResponse" }, "examples": { "stored": { "summary": "Message stored", "value": { "success": true, "data": { "conversationId": "ad10af01-6d8a-4b46-83aa-4a7d38c35172", "messageId": "msg_business_789" } } }, "skipped": { "summary": "Duplicate message skipped", "value": { "success": true, "data": { "conversationId": "ad10af01-6d8a-4b46-83aa-4a7d38c35172", "messageId": "msg_business_789", "skipped": true, "reason": "DUPLICATE_CONTENT" } } } } } } }, "400": { "description": "Invalid payload", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Conversation not found for the given yelpLeadId", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/yelp-lead-agent/send-message": { "post": { "tags": [ "Yelp Lead Agent" ], "summary": "Send a follow-up message to a Yelp lead", "description": "Send a message to a Yelp lead via webhook. This fires a `YELP_MESSAGE_OUTBOUND` webhook\nevent that Zapier can receive and relay to Yelp using the \"Create Message\" action.\n\n**Prerequisites:**\n1. Register a webhook for event type `YELP_MESSAGE_OUTBOUND` via `POST /v2/pub/webhooks`\n2. Point the webhook URL to a Zapier \"Webhooks by Zapier\" trigger\n3. Connect that trigger to Yelp's \"Create Message\" action\n\n**Identifying the conversation:**\nYou must provide either `conversationId` (our internal UUID) or `externalLeadId` (the Yelp lead ID).\n\n**What happens:**\n1. Message is appended to conversation history with `userType: BUSINESS_OUTBOUND`\n2. Webhook fires to all registered `YELP_MESSAGE_OUTBOUND` URLs\n3. Zapier receives the webhook and sends the message to Yelp\n\nIf manual takeover is enabled for the conversation, this endpoint returns `409`\nand no outbound message is appended or sent.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpSendMessageRequest" }, "example": { "externalLeadId": "lead_abc123", "message": "Hi! Just following up on your request. Are you still interested in getting a quote?", "attachmentUrls": [] } } } }, "responses": { "200": { "description": "Message sent successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpSendMessageResponse" }, "example": { "success": true, "data": { "conversationId": "ad10af01-6d8a-4b46-83aa-4a7d38c35172", "webhooksTriggered": 2 } } } } }, "400": { "description": "Invalid payload or missing conversationId/externalLeadId", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Conversation not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "Manual takeover is enabled for this conversation", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/yelp-lead-agent/conversations": { "get": { "tags": [ "Yelp Lead Agent" ], "summary": "List Yelp AI conversations", "description": "Retrieve paginated Yelp conversation history for the authenticated company.", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "query", "name": "limit", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, "description": "Maximum number of conversations to return." }, { "in": "query", "name": "offset", "schema": { "type": "integer", "minimum": 0, "default": 0 }, "description": "Number of records to skip for pagination." } ], "responses": { "200": { "description": "Conversations retrieved.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpLeadConversationListResponse" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/yelp-lead-agent/conversations/lead/{externalLeadId}": { "get": { "tags": [ "Yelp Lead Agent" ], "summary": "Get Yelp conversation by Yelp lead ID", "description": "Fetch a single Yelp conversation using the Yelp lead identifier (stored as externalLeadId).", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "path", "name": "externalLeadId", "required": true, "schema": { "type": "string" }, "description": "Yelp lead identifier to look up." } ], "responses": { "200": { "description": "Conversation retrieved.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpLeadConversationResponse" } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Conversation not found.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v1/pub/yelp-lead-agent/outbound-messages": { "get": { "tags": [ "Yelp Lead Agent" ], "summary": "List outbound messages in webhook payload format", "description": "Retrieve recent outbound messages (messages sent from business to Yelp leads) formatted\nas `YELP_MESSAGE_OUTBOUND` webhook payloads.\n\n**Purpose:** This endpoint is designed to provide test data for Zapier webhook registration.\nWhen setting up a Zapier trigger for `YELP_MESSAGE_OUTBOUND` webhooks, Zapier needs sample\ndata to configure the workflow. This endpoint returns real messages in the exact format\nthat webhooks deliver.\n\n**What's included:**\n- Messages sent via the `/send-message` endpoint\n- Messages triggered from the LeadTruffle UI\n- Messages with `triggeredBy` metadata (API, UI, or SCHEDULED)\n\n**Format:** Each item in the response array matches the `YELP_MESSAGE_OUTBOUND` webhook payload structure.\n", "security": [ { "ApiKeyAuth": [] } ], "parameters": [ { "in": "query", "name": "limit", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 }, "description": "Maximum number of messages to return." }, { "in": "query", "name": "offset", "schema": { "type": "integer", "minimum": 0, "default": 0 }, "description": "Number of records to skip for pagination." } ], "responses": { "200": { "description": "Outbound messages retrieved in webhook payload format.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YelpOutboundMessagesListResponse" }, "example": { "success": true, "data": [ { "eventType": "YELP_MESSAGE_OUTBOUND", "eventTypeDetails": "YELP_FOLLOWUP_MESSAGE", "yelpLeadId": "lead_abc123", "yelpBusinessId": "business_456", "conversationId": "ad10af01-6d8a-4b46-83aa-4a7d38c35172", "companyId": "comp_99999999-8888-7777-6666-444444444444", "message": "Hi! Thanks for reaching out. Are you still interested in getting a quote?", "attachmentUrls": [], "timestamp": "2024-01-16T10:00:00.000Z", "triggeredBy": "UI", "leadId": null, "clientId": null, "leadInformation": { "name": "John Doe", "phone": "+15551234567", "email": "john.doe@example.com" } } ], "pagination": { "total": 25, "limit": 10, "offset": 0, "hasMore": true } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/leads/conversion": { "put": { "tags": [ "Lead inquiries" ], "summary": "Upsert (create or update) lead conversion data", "description": "Report conversion data (revenue, status) for a specific lead.\n\nThis endpoint allows CRMs and external systems to report back when a lead converts to a customer.\nIf a conversion already exists for the lead, it will be updated. Otherwise, a new conversion record is created.\n\n**Upsert Behavior:**\n- First call for a lead → Creates new conversion\n- Subsequent calls → Updates existing conversion\n- Every successful call also updates the lead record's `conversionStatus`,\n which is the status shown on the Lead Workflow board.\n\n**Input**:\n- EITHER - leadId or phone is required to associate the conversion with.\n\n**Use Cases:**\n- CRM reports when a lead converts to a paying customer\n- Update revenue amount when final billing is processed\n- Track conversion status changes (QUOTED → WON)\n- Store integration-specific data in sourceData field\n\n**Important Notes:**\n- Revenue amount is optional (you can track conversions without revenue)\n- `conversionStatus` updates both the conversion record and the lead's\n workflow-board status.\n- Currency defaults to USD if not specified\n- Source field helps track which CRM/system reported the conversion\n- Source defaults to 'API' if not specified\n- sourceData field can store CRM-specific metadata (max 10KB)\n- Identify the lead by providing either leadId or phone in the request body. If both are provided, leadId is used.\n\n**Webhook Integration:**\nWhen you report conversions via this API, the conversion data will be automatically\nincluded in all future webhook payloads for this lead (LEAD_CREATED, CONVERSATION_COMPLETED).\nThis allows downstream systems to have complete lead lifecycle data including revenue tracking.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConversionUpsertRequest" }, "examples": { "wonWithRevenue": { "summary": "Won deal with revenue", "value": { "leadId": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "conversionStatus": "WON", "revenueAmount": "1500.00", "currency": "USD", "source": "HousecallPro", "notes": "Customer signed annual contract", "sourceData": { "crmId": "SF-12345", "salesRep": "John Doe", "closedDate": "2024-01-15" } } }, "lostDeal": { "summary": "Lost deal", "value": { "phone": "+15551234567", "conversionStatus": "LOST", "source": "Jobber", "notes": "Customer went with competitor" } }, "quotedNoRevenue": { "summary": "Quoted without revenue", "value": { "leadId": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "conversionStatus": "QUOTED", "source": "ServiceTitan", "notes": "Sent estimate via email" } } } } } }, "responses": { "200": { "description": "Successfully updated existing conversion", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConversionUpsertResponse" }, "example": { "success": true, "data": { "id": "conv_abc123", "companyId": "comp_xyz789", "leadFormSubmissionId": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "conversionStatus": "WON", "revenueAmount": "1500.00", "currency": "USD", "source": "HousecallPro", "notes": "Customer signed annual contract", "sourceData": { "crmId": "SF-12345", "salesRep": "John Doe" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T14:45:00Z", "isUpdate": true, "lead": { "id": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "conversionStatus": "WON", "conversionStatusUpdatedAt": "2024-01-15T14:45:00Z" } } } } } }, "201": { "description": "Successfully created new conversion", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConversionUpsertResponse" }, "example": { "success": true, "data": { "id": "conv_abc123", "companyId": "comp_xyz789", "leadFormSubmissionId": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "conversionStatus": "WON", "revenueAmount": "1500.00", "currency": "USD", "source": "HousecallPro", "notes": "Customer signed annual contract", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z", "isUpdate": false, "lead": { "id": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "conversionStatus": "WON", "conversionStatusUpdatedAt": "2024-01-15T10:30:00Z" } } } } } }, "400": { "description": "Invalid request - validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "missingIdentifier": { "summary": "Missing lead identifier", "value": { "success": false, "error": "You must provide either leadId or phone" } }, "invalidPhone": { "summary": "Invalid phone format", "value": { "success": false, "error": "Invalid phone number format" } }, "invalidStatus": { "summary": "Invalid conversion status", "value": { "success": false, "error": "Invalid conversion status. Must be one of: WON, LOST, QUOTED, PENDING, CONTACTED, NURTURING, CLOSED" } }, "invalidRevenue": { "summary": "Invalid revenue amount", "value": { "success": false, "error": "Invalid revenue amount. Must be a positive number with max 2 decimal places (0.01 - 999,999,999.99)" } }, "invalidCurrency": { "summary": "Invalid currency", "value": { "success": false, "error": "Invalid currency. Must be one of: USD, CAD" } }, "notesTooLong": { "summary": "Notes too long", "value": { "success": false, "error": "Notes must be 1000 characters or less" } }, "sourceDataTooLarge": { "summary": "Source data too large", "value": { "success": false, "error": "Source data is too large (max 10KB)" } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Lead not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "Lead not found" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/v2/pub/leads/update": { "put": { "tags": [ "Lead inquiries" ], "summary": "Update lead", "description": "Update mutable fields on a LeadTruffle lead.\n\nIdentify the lead with either `leadId` or `phone`. If both are provided,\n`leadId` is used. Provide at least one update field.\n\n`archived` controls whether the lead is hidden from active lead boards.\nIt does not delete the lead.\n\n`assignedToUserId` assigns the lead to an enabled employee. Use\n`GET /v2/pub/employees` to find employee IDs. Send null to unassign.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "leadId": { "type": "string", "format": "uuid", "description": "LeadTruffle lead ID. Used first when both leadId and phone are provided.", "example": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b" }, "phone": { "type": "string", "description": "Lead phone number. LeadTruffle normalizes US phone numbers before matching.", "example": "+15551234567" }, "conversionStatus": { "type": "string", "enum": [ "NEW", "WON", "LOST", "QUOTED", "PENDING", "CONTACTED", "NURTURING", "CLOSED" ], "description": "Optional lead conversion/status value.", "example": "LOST" }, "notes": { "type": "string", "nullable": true, "maxLength": 1000, "description": "Optional lead notes. Send null to clear notes.", "example": "Customer went with another provider." }, "leadNotes": { "type": "string", "nullable": true, "maxLength": 1000, "description": "Alias for notes. Send either notes or leadNotes, not both." }, "archived": { "type": "boolean", "description": "Whether this lead should be hidden from active lead boards.", "example": true }, "assignedToUserId": { "type": "string", "format": "uuid", "nullable": true, "description": "Enabled employee ID to assign the lead to. Send null to unassign.", "example": "11111111-1111-4111-8111-111111111111" } } }, "examples": { "updateStatusByPhone": { "summary": "Update status by phone", "value": { "phone": "+15551234567", "conversionStatus": "LOST" } }, "updateNotesAndArchive": { "summary": "Update notes and archive by lead ID", "value": { "leadId": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "notes": "Customer booked through Workiz.", "archived": true } }, "clearNotes": { "summary": "Clear notes", "value": { "phone": "+15551234567", "notes": null } }, "assignLead": { "summary": "Assign a lead to an employee", "value": { "leadId": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "assignedToUserId": "11111111-1111-4111-8111-111111111111" } }, "unassignLead": { "summary": "Clear lead assignment", "value": { "leadId": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "assignedToUserId": null } } } } } }, "responses": { "200": { "description": "Lead updated successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "companyId": { "type": "string", "format": "uuid" }, "phone": { "type": "string", "nullable": true }, "email": { "type": "string", "nullable": true }, "firstName": { "type": "string", "nullable": true }, "lastName": { "type": "string", "nullable": true }, "conversionStatus": { "type": "string", "nullable": true }, "conversionStatusUpdatedAt": { "type": "string", "format": "date-time", "nullable": true }, "leadNotes": { "type": "string", "nullable": true }, "archived": { "type": "boolean" }, "assignedToUserId": { "type": "string", "format": "uuid", "nullable": true }, "assignedAt": { "type": "string", "format": "date-time", "nullable": true }, "assignedToEmployee": { "type": "object", "nullable": true, "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "role": { "type": "string", "enum": [ "ADMIN", "MANAGER", "USER" ] }, "disabled": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" } } }, "updatedAt": { "type": "string", "format": "date-time" } } } } }, "example": { "success": true, "data": { "id": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b", "companyId": "f096f9e3-001d-49ac-864c-3d73453bbe08", "phone": "+15551234567", "email": "customer@example.com", "firstName": "Victor", "lastName": null, "conversionStatus": "LOST", "conversionStatusUpdatedAt": "2026-05-28T14:30:00.000Z", "leadNotes": "Customer went with another provider.", "archived": true, "assignedToUserId": "11111111-1111-4111-8111-111111111111", "assignedAt": "2026-05-28T14:30:00.000Z", "assignedToEmployee": { "id": "11111111-1111-4111-8111-111111111111", "name": "Alexia Morgan", "email": "alexia@example.com", "role": "MANAGER", "disabled": false, "createdAt": "2024-01-03T10:00:00.000Z" }, "updatedAt": "2026-05-28T14:30:00.000Z" } } } } }, "400": { "description": "Invalid request data", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "arrayWrapped": { "summary": "Zapier array wrapping is enabled", "value": { "success": false, "error": "Request body must be a single JSON object. If you are using Zapier, turn off \"Wrap in Array\"." } }, "typo": { "summary": "Wrong status field name", "value": { "success": false, "error": "Unknown field `conversationStatus`. Did you mean `conversionStatus`?" } }, "noUpdateFields": { "summary": "No update fields supplied", "value": { "success": false, "error": "At least one update field is required. Use one or more of: conversionStatus, notes, archived, assignedToUserId." } }, "disabledEmployee": { "summary": "Assignment target is disabled", "value": { "success": false, "error": "Assigned employee is disabled." } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Lead not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "leadNotFound": { "value": { "success": false, "error": "Lead not found" } }, "phoneNotFound": { "value": { "success": false, "error": "Lead not found for provided phone" } } } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/_EXPERIMENTAL/v2/pub/leads/review-gathering": { "post": { "tags": [ "Experimental" ], "summary": "Trigger review gathering for a lead", "description": "Creates a review request associated with a lead. If a review request already exists\nfor the lead, the call is idempotent and returns the existing request with `isDuplicate: true`.\n\nIdentify the lead by providing either `leadId` or `phone` in the request body. If both are provided,\n`leadId` is used.\n", "security": [ { "ApiKeyAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadReviewRequestCreate" }, "examples": { "byLeadId": { "summary": "Trigger by leadId", "value": { "leadId": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b" } }, "byPhone": { "summary": "Trigger by phone", "value": { "phone": "+15551234567" } } } } } }, "responses": { "200": { "description": "Review request already exists (idempotent)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadReviewRequestResponse" }, "example": { "success": true, "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "PENDING", "isDuplicate": true } } } } }, "201": { "description": "Review request created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadReviewRequestResponse" }, "example": { "success": true, "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "PENDING", "isDuplicate": false } } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "missingIdentifier": { "summary": "Missing lead identifier", "value": { "success": false, "error": "You must provide either leadId or phone" } }, "invalidPhone": { "summary": "Invalid phone format", "value": { "success": false, "error": "Invalid phone number format" } } } } } }, "401": { "$ref": "#/components/responses/UnauthorizedError" }, "404": { "description": "Lead not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "success": false, "error": "Lead not found" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } } }, "components": { "schemas": { "WebhookPayload": { "type": "object", "description": "Payload that will be sent to your webhook endpoints when a lead conversation is completed", "properties": { "type": { "type": "string", "enum": [ "conversation_completed" ], "example": "conversation_completed" }, "clientId": { "type": "string", "example": "456" }, "leadId": { "type": "string", "format": "uuid", "example": "123" }, "companyId": { "type": "string", "format": "uuid", "example": "789" }, "leadQualificationStatus": { "type": "string", "enum": [ "COMPLETED", "INCOMPLETE" ], "example": "COMPLETED" }, "leadInformation": { "type": "object", "properties": { "name": { "type": "string", "example": "John Doe" }, "firstName": { "type": "string", "example": "John" }, "lastName": { "type": "string", "example": "Doe" }, "email": { "type": "string", "format": "email", "example": "john@example.com" }, "phone": { "type": "string", "example": "+18001234567" }, "additionalData": { "type": "object", "properties": { "source": { "type": "string", "example": "popup" }, "message": { "type": "string", "example": "I need help with..." } } } } }, "trackingData": { "type": "object", "properties": { "source": { "type": "string", "example": "popup" }, "utm_source": { "type": "string", "example": "google" }, "utm_medium": { "type": "string", "example": "cpc" }, "utm_campaign": { "type": "string", "example": "home_renovation" }, "utm_term": { "type": "string", "example": "home_renovation_cost" }, "utm_content": { "type": "string", "example": "home_renovation_cost" }, "gclid": { "type": "string", "example": "1234567890" }, "fbclid": { "type": "string", "example": "1234567890" }, "msclkid": { "type": "string", "example": "1234567890" }, "ttclid": { "type": "string", "example": "1234567890" }, "snapcid": { "type": "string", "example": "1234567890" }, "gbraid": { "type": "string", "nullable": true, "example": "gbraid_example" }, "wbraid": { "type": "string", "nullable": true, "example": "wbraid_example" }, "gad_source": { "type": "string", "nullable": true, "example": "google_ads" }, "igshid": { "type": "string", "nullable": true, "example": "ig_share_id" }, "gclsrc": { "type": "string", "nullable": true, "example": "aw.ds" }, "srsltid": { "type": "string", "nullable": true, "example": "search_result_id" }, "ga_client_id": { "type": "string", "nullable": true, "description": "Google Analytics Client ID", "example": "123456789.987654321" }, "ga_session_id": { "type": "string", "nullable": true, "description": "Google Analytics Session ID", "example": "1700000000" }, "hubspotutk": { "type": "string", "nullable": true, "example": "1234567890" }, "pageInfo": { "type": "object", "properties": { "title": { "type": "string", "nullable": true, "example": "Home - Best HVAC Services" }, "referrer": { "type": "string", "nullable": true, "example": "https://www.google.com/" }, "currentUrl": { "type": "string", "nullable": true, "example": "http://localhost:3002/index.html" } } } } }, "qualifyingData": { "type": "object", "description": "Dynamic fields collected during the conversation", "additionalProperties": true, "example": { "example_budget": "$5000", "example_timeline": "Within 3 months", "example_projectType": "Home Renovation" } }, "commonFields": { "type": "object", "description": "Standardized fields extracted from the conversation", "properties": { "fullAddress": { "type": "string", "example": "111 main st, Austin TX 73301" }, "address": { "type": "string", "example": "111 main st" }, "zipcode": { "type": "string", "example": "73301" }, "state": { "type": "string", "example": "TX" }, "city": { "type": "string", "example": "Austin" }, "country": { "type": "string", "example": "US" }, "isHomeowner": { "type": "boolean", "example": true }, "customerName": { "type": "string", "example": "John" } } }, "qualifyingDataSummary": { "type": "string", "example": "The client lives in a 3 bedroom house.\\nHas a budget of $2000.\\nzipcode is 45150." }, "contactReason": { "type": "string", "example": "Client is interested in a home renovation project..." }, "timestamp": { "type": "string", "format": "date-time", "example": "2024-01-01T00:00:00Z" }, "isRepeatLead": { "type": "boolean", "description": "Indicates if this lead has previously submitted a form or contacted the company", "example": false }, "messageHistory": { "type": "array", "items": { "type": "object", "properties": { "direction": { "type": "string", "enum": [ "inbound", "outbound" ] }, "name": { "type": "string", "example": "AI Agent" }, "message": { "type": "string", "example": "How can we help you..." }, "date": { "type": "string", "format": "date-time", "example": "2024-01-01T00:00:00Z" } } } }, "userMedia": { "type": "array", "items": { "type": "object", "properties": { "type": { "type": "string", "example": "image/jpeg" }, "url": { "type": "string", "example": "https://tooldesk-public-user-uploads.s3.us-west-2.amazonaws.com/email-assets/leadtruffle-Wordmark-white.png" } } } } } }, "LeadList": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "leads": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookPayload" }, "description": "List of leads, limited to a maximum of 10 per request" }, "hasMore": { "type": "boolean", "description": "Indicates if there are more results available. To fetch the next page, use the oldest lead's timestamp as the 'before' parameter." } } } } }, "Error": { "type": "object", "properties": { "success": { "type": "boolean", "example": false }, "error": { "type": "string", "example": "Invalid API key" } } }, "WebhookList": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "chatWidgetWebhooks": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "List of webhook URLs for chat widget lead completion" }, "missedCallWebhooks": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "List of webhook URLs for missed call lead completion" } } } } }, "WebhookResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "webhooks": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "Updated list of webhook URLs" } } } } }, "ClientUpsertRequest": { "type": "object", "required": [ "phone" ], "properties": { "phone": { "type": "string", "description": "Phone number in E.164 format or US national format (e.g., +18001234567 or 8001234567)", "example": "8001234567" }, "firstName": { "type": "string", "example": "John" }, "lastName": { "type": "string", "example": "Doe" }, "email": { "type": "string", "format": "email", "example": "john.doe@example.com" }, "address1": { "type": "string", "example": "123 Main St" }, "address2": { "type": "string", "example": "Unit 456" }, "city": { "type": "string", "example": "Austin" }, "state": { "type": "string", "example": "TX" }, "zip": { "type": "string", "example": "78701" }, "country": { "type": "string", "example": "US" } } }, "Client": { "type": "object", "description": "Client record data", "properties": { "id": { "type": "string", "format": "uuid", "example": "415f2b29-39e6-4182-9d6f-ec2d817f01c2" }, "companyId": { "type": "string", "format": "uuid", "example": "f096f9e3-001d-49ac-864c-3d73453bbe08" }, "primaryPhone": { "type": "string", "example": "+17345520800", "description": "Phone number in E.164 format" }, "firstName": { "type": "string", "nullable": true, "example": "John" }, "lastName": { "type": "string", "nullable": true, "example": "Doe" }, "primaryEmail": { "type": "string", "nullable": true, "example": "john.doe@example.com" }, "address": { "type": "string", "nullable": true, "example": "123 Main St" }, "address2": { "type": "string", "nullable": true, "example": "Unit 456" }, "city": { "type": "string", "nullable": true, "example": "Austin" }, "state": { "type": "string", "nullable": true, "example": "TX" }, "zipCode": { "type": "string", "nullable": true, "example": "78701" }, "country": { "type": "string", "nullable": true, "example": "US" }, "isLead": { "type": "boolean", "example": true }, "leadStatus": { "type": "string", "nullable": true, "example": "NEW" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } }, "ClientUpsertResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/Client" }, "action": { "type": "string", "enum": [ "CREATED", "UPDATED", "UNCHANGED" ], "example": "CREATED", "description": "Indicates whether the client was created, updated, or left unchanged" } } }, "WebhookV2": { "type": "object", "description": "V2 Webhook configuration object", "properties": { "id": { "type": "string", "format": "uuid", "example": "550e8400-e29b-41d4-a716-446655440000" }, "companyId": { "type": "string", "format": "uuid", "example": "f096f9e3-001d-49ac-864c-3d73453bbe08" }, "eventType": { "type": "string", "enum": [ "CONVERSATION_COMPLETED", "MESSAGE_REPLY", "LEAD_CREATED", "LEAD_STATUS_CHANGED", "CLIENT_STATUS_CHANGED", "NEW_APPOINTMENT", "YELP_MESSAGE_OUTBOUND" ], "example": "LEAD_CREATED", "description": "Type of event that triggers this webhook. LEAD_STATUS_CHANGED is a legacy lead pipeline event; use CLIENT_STATUS_CHANGED for the new Contacts/client pipeline." }, "targetUrl": { "type": "string", "format": "uri", "example": "https://api.example.com/webhooks/leads", "description": "URL where webhook payloads will be sent" }, "enabled": { "type": "boolean", "example": true, "description": "Whether the webhook is active" }, "failedAttempts": { "type": "integer", "example": 0, "description": "Number of consecutive failed delivery attempts" }, "lastFailedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "Timestamp of last failed delivery attempt" }, "lastSuccessAt": { "type": "string", "format": "date-time", "nullable": true, "description": "Timestamp of last successful delivery" }, "integrationSource": { "type": "string", "example": "API", "description": "Source that created this webhook" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } }, "WebhookV2ListResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookV2" } } } }, "WebhookV2CreateRequest": { "type": "object", "required": [ "eventType", "url" ], "properties": { "eventType": { "type": "string", "enum": [ "CONVERSATION_COMPLETED", "MESSAGE_REPLY", "LEAD_CREATED", "LEAD_STATUS_CHANGED", "CLIENT_STATUS_CHANGED", "NEW_APPOINTMENT", "YELP_MESSAGE_OUTBOUND" ], "example": "LEAD_CREATED", "description": "Type of event that triggers this webhook:\n- `CONVERSATION_COMPLETED`: AI qualification finished\n- `MESSAGE_REPLY`: Lead replied via SMS\n- `LEAD_CREATED`: New lead created\n- `LEAD_STATUS_CHANGED`: Legacy lead pipeline status changed. Use only for accounts still using the legacy Leads system.\n- `CLIENT_STATUS_CHANGED`: Contact Status changed in the new Contacts/client pipeline\n- `NEW_APPOINTMENT`: Calendar booking created\n- `YELP_MESSAGE_OUTBOUND`: Send message to Yelp via Zapier\n" }, "url": { "type": "string", "format": "uri", "maxLength": 1000, "example": "https://api.example.com/webhooks/leads", "description": "URL where webhook payloads will be sent via HTTP POST" }, "secret": { "type": "string", "maxLength": 200, "nullable": true, "description": "Optional shared secret used to sign webhook deliveries with HMAC SHA-256. When set, deliveries include x-leadtruffle-signature and x-leadtruffle-timestamp headers." } } }, "WebhookV2Response": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/WebhookV2" } } }, "WebhookV2UpdateRequest": { "type": "object", "properties": { "eventType": { "type": "string", "enum": [ "CONVERSATION_COMPLETED", "MESSAGE_REPLY", "LEAD_CREATED", "LEAD_STATUS_CHANGED", "CLIENT_STATUS_CHANGED", "NEW_APPOINTMENT", "YELP_MESSAGE_OUTBOUND" ], "example": "LEAD_CREATED", "description": "Type of event that triggers this webhook. LEAD_STATUS_CHANGED is a legacy lead pipeline event; use CLIENT_STATUS_CHANGED for the new Contacts/client pipeline." }, "url": { "type": "string", "format": "uri", "maxLength": 1000, "example": "https://api.example.com/webhooks/leads", "description": "URL where webhook payloads will be sent via HTTP POST" }, "secret": { "type": "string", "maxLength": 200, "nullable": true, "description": "Optional shared secret used to sign webhook deliveries with HMAC SHA-256. Send null or an empty value to remove signing." }, "enabled": { "type": "boolean", "description": "Whether the webhook is active" }, "clearErrors": { "type": "boolean", "description": "Clear any existing error history for this webhook" } } }, "MissedCallWebhookV2": { "type": "object", "description": "Missed call information in V2 webhook payloads", "properties": { "id": { "type": "string", "format": "uuid" }, "originalDialedNumber": { "type": "string", "nullable": true }, "inboundCaller": { "type": "string", "nullable": true }, "voicemailFileLink": { "type": "string", "nullable": true }, "voicemailTranscription": { "type": "string", "nullable": true }, "voicemailSummary": { "type": "string", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" } } }, "AIHandledCallWebhookV2": { "type": "object", "description": "AI handled call information in V2 webhook payloads", "properties": { "id": { "type": "string", "format": "uuid" }, "transcriptSummary": { "type": "string", "nullable": true }, "status": { "type": "string" }, "fullTranscript": { "type": "string", "nullable": true }, "inboundCaller": { "type": "string" }, "originalDialedNumber": { "type": "string", "nullable": true }, "durationSeconds": { "type": "integer", "nullable": true }, "recordingUrl": { "type": "string", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" } } }, "WebhookV2PayloadLeadCreated": { "type": "object", "description": "V2 Webhook payload for LEAD_CREATED events", "properties": { "eventType": { "type": "string", "enum": [ "LEAD_CREATED" ], "example": "LEAD_CREATED" }, "eventTypeDetails": { "type": "string", "enum": [ "NEW_MISSED_CALL", "NEW_AI_CALL_HANDLED", "NEW_CHAT_WIDGET_SUBMISSION", "NEW_ANGI_LEAD", "NEW_CALENDAR_BOOKING", "NEW_INBOUND_EMAIL", "NEW_EMAIL_QUALIFICATION", "NEW_YELP_LEAD", "NEW_THUMBTACK_LEAD", "NEW_GOOGLE_LSA_LEAD" ], "example": "NEW_CHAT_WIDGET_SUBMISSION" }, "leadId": { "type": "string", "format": "uuid" }, "clientId": { "type": "string", "format": "uuid" }, "companyId": { "type": "string", "format": "uuid" }, "name": { "type": "string", "nullable": true, "example": "John Doe" }, "firstName": { "type": "string", "nullable": true, "example": "John" }, "lastName": { "type": "string", "nullable": true, "example": "Doe" }, "email": { "type": "string", "nullable": true, "example": "john@example.com" }, "phone": { "type": "string", "example": "+18001234567" }, "message": { "type": "string", "nullable": true, "example": "I need help with HVAC repair" }, "timestamp": { "type": "string", "format": "date-time" }, "isRepeatLead": { "type": "boolean" }, "qualificationSource": { "type": "string", "enum": [ "MISSED_CALL", "WEBSITE_TEXTING", "WEBCHAT", "COLD_TEXT_INBOUND", "AI_HANDLED_CALL", "CALENDAR_BOOKING", "INBOUND_EMAIL", "ANGI_LEAD", "EMAIL_QUALIFICATION", "YELP_LEAD", "THUMBTACK_LEAD", "GOOGLE_LSA_DIRECT" ], "description": "Source channel for the lead's qualification journey. Use this to differentiate between inbound channels like email, chat/SMS, third-party marketplaces, and AI handled calls.\n" }, "isManualTakeoverEnabled": { "type": "boolean" }, "trackingData": { "type": "object", "properties": { "source": { "type": "string", "nullable": true }, "utm_source": { "type": "string", "nullable": true }, "utm_medium": { "type": "string", "nullable": true }, "utm_campaign": { "type": "string", "nullable": true }, "utm_term": { "type": "string", "nullable": true }, "utm_content": { "type": "string", "nullable": true }, "gclid": { "type": "string", "nullable": true }, "fbclid": { "type": "string", "nullable": true }, "msclkid": { "type": "string", "nullable": true }, "ttclid": { "type": "string", "nullable": true }, "snapcid": { "type": "string", "nullable": true }, "gbraid": { "type": "string", "nullable": true }, "wbraid": { "type": "string", "nullable": true }, "gad_source": { "type": "string", "nullable": true }, "igshid": { "type": "string", "nullable": true }, "gclsrc": { "type": "string", "nullable": true }, "srsltid": { "type": "string", "nullable": true }, "ga_client_id": { "type": "string", "nullable": true }, "ga_session_id": { "type": "string", "nullable": true }, "hubspotutk": { "type": "string", "nullable": true }, "pageInfo": { "type": "object", "properties": { "currentUrl": { "type": "string", "nullable": true }, "referrer": { "type": "string", "nullable": true }, "title": { "type": "string", "nullable": true } } } } }, "missedCall": { "oneOf": [ { "$ref": "#/components/schemas/MissedCallWebhookV2" }, { "type": "object", "nullable": true, "enum": [ null ] } ], "description": "Only present for missed call webhooks" }, "aiHandledCall": { "oneOf": [ { "$ref": "#/components/schemas/AIHandledCallWebhookV2" }, { "type": "object", "nullable": true, "enum": [ null ] } ], "description": "Only present for AI handled call webhooks" } } }, "WebhookV2PayloadConversationCompleted": { "type": "object", "description": "V2 Webhook payload for CONVERSATION_COMPLETED events", "properties": { "eventType": { "type": "string", "enum": [ "CONVERSATION_COMPLETED" ], "example": "CONVERSATION_COMPLETED" }, "eventTypeDetails": { "type": "string", "enum": [ "CONVERSATION_COMPLETE_MISSED_CALL", "CONVERSATION_COMPLETE_CHAT_WIDGET", "CONVERSATION_COMPLETE_AI_CALL_HANDLED", "CONVERSATION_COMPLETE_CALENDAR_BOOKING", "CONVERSATION_COMPLETE_INBOUND_EMAIL", "CONVERSATION_COMPLETE_EMAIL_QUALIFICATION", "CONVERSATION_COMPLETE_YELP_LEAD", "CONVERSATION_COMPLETE_THUMBTACK_LEAD", "CONVERSATION_COMPLETE_GOOGLE_LSA_LEAD" ], "example": "CONVERSATION_COMPLETE_CHAT_WIDGET" }, "clientId": { "type": "string", "format": "uuid" }, "leadId": { "type": "string", "format": "uuid" }, "companyId": { "type": "string", "format": "uuid" }, "isRepeatLead": { "type": "boolean" }, "leadInformation": { "type": "object", "properties": { "name": { "type": "string" }, "firstName": { "type": "string", "nullable": true }, "lastName": { "type": "string", "nullable": true }, "email": { "type": "string", "nullable": true }, "phone": { "type": "string", "nullable": true }, "additionalData": { "type": "object", "additionalProperties": true } } }, "commonFields": { "type": "object", "description": "Standardized fields extracted from the conversation", "properties": { "fullAddress": { "type": "string", "nullable": true }, "address": { "type": "string", "nullable": true }, "zipcode": { "type": "string", "nullable": true }, "state": { "type": "string", "nullable": true }, "city": { "type": "string", "nullable": true }, "country": { "type": "string", "nullable": true }, "isHomeowner": { "type": "boolean", "nullable": true }, "customerName": { "type": "string", "nullable": true }, "email": { "type": "string", "nullable": true } } }, "trackingData": { "type": "object", "properties": { "source": { "type": "string", "nullable": true }, "utm_source": { "type": "string", "nullable": true }, "utm_medium": { "type": "string", "nullable": true }, "utm_campaign": { "type": "string", "nullable": true }, "utm_term": { "type": "string", "nullable": true }, "utm_content": { "type": "string", "nullable": true }, "gclid": { "type": "string", "nullable": true }, "fbclid": { "type": "string", "nullable": true }, "msclkid": { "type": "string", "nullable": true }, "ttclid": { "type": "string", "nullable": true }, "snapcid": { "type": "string", "nullable": true }, "gbraid": { "type": "string", "nullable": true }, "wbraid": { "type": "string", "nullable": true }, "gad_source": { "type": "string", "nullable": true }, "igshid": { "type": "string", "nullable": true }, "gclsrc": { "type": "string", "nullable": true }, "srsltid": { "type": "string", "nullable": true }, "ga_client_id": { "type": "string", "nullable": true }, "ga_session_id": { "type": "string", "nullable": true }, "hubspotutk": { "type": "string", "nullable": true }, "pageInfo": { "type": "object", "properties": { "currentUrl": { "type": "string", "nullable": true }, "referrer": { "type": "string", "nullable": true }, "title": { "type": "string", "nullable": true } } } } }, "leadQualificationStatus": { "type": "string", "enum": [ "COMPLETED", "IN_PROGRESS" ], "nullable": true }, "qualificationSource": { "type": "string", "enum": [ "MISSED_CALL", "WEBSITE_TEXTING", "WEBCHAT", "COLD_TEXT_INBOUND", "AI_HANDLED_CALL", "CALENDAR_BOOKING", "INBOUND_EMAIL", "ANGI_LEAD", "EMAIL_QUALIFICATION", "YELP_LEAD", "THUMBTACK_LEAD", "GOOGLE_LSA_DIRECT" ], "nullable": true, "description": "Channel where the conversation originated. Use this field to distinguish lead types in v2 webhooks (e.g., EMAIL_QUALIFICATION, YELP_LEAD, THUMBTACK_LEAD, GOOGLE_LSA_DIRECT).\n" }, "isManualTakeoverEnabled": { "type": "boolean" }, "qualifyingData": { "type": "object", "description": "Dynamic fields collected during the conversation", "properties": { "contactReason": { "type": "string" } }, "additionalProperties": true }, "qualifyingDataSummary": { "type": "string", "nullable": true }, "contactReason": { "type": "string" }, "timestamp": { "type": "string", "format": "date-time" }, "messageHistory": { "type": "array", "items": { "type": "object", "properties": { "direction": { "type": "string", "enum": [ "inbound", "outbound" ] }, "name": { "type": "string" }, "message": { "type": "string" }, "date": { "type": "string", "format": "date-time" }, "mediaUrl": { "type": "string", "nullable": true }, "additionalMediaUrls": { "type": "array", "items": { "type": "string" }, "nullable": true } } } }, "userMedia": { "type": "array", "items": { "type": "object", "properties": { "type": { "type": "string" }, "url": { "type": "string" } } } }, "aiHandledCall": { "oneOf": [ { "$ref": "#/components/schemas/AIHandledCallWebhookV2" }, { "type": "object", "nullable": true, "enum": [ null ] } ], "description": "Only present for AI handled call webhooks" }, "missedCall": { "oneOf": [ { "$ref": "#/components/schemas/MissedCallWebhookV2" }, { "type": "object", "nullable": true, "enum": [ null ] } ], "description": "Only present for missed call webhooks" } } }, "WebhookV2PayloadMessageReply": { "type": "object", "description": "V2 Webhook payload for MESSAGE_REPLY events", "properties": { "eventType": { "type": "string", "enum": [ "MESSAGE_REPLY" ], "example": "MESSAGE_REPLY" }, "eventTypeDetails": { "type": "string", "enum": [ "NEW_MESSAGE_REPLY" ], "example": "NEW_MESSAGE_REPLY" }, "messageId": { "type": "string", "format": "uuid" }, "leadId": { "type": "string", "format": "uuid" }, "clientId": { "type": "string", "format": "uuid" }, "companyId": { "type": "string", "format": "uuid" }, "from": { "type": "string", "example": "+18001234567" }, "to": { "type": "string", "example": "+18009876543" }, "body": { "type": "string", "example": "Yes, I am interested in getting a quote" }, "mediaUrl": { "type": "string", "nullable": true }, "additionalMediaUrls": { "type": "array", "items": { "type": "string" }, "nullable": true }, "timestamp": { "type": "string", "format": "date-time" }, "leadQualificationStatus": { "type": "string", "nullable": true }, "qualificationSource": { "type": "string", "enum": [ "MISSED_CALL", "WEBSITE_TEXTING", "WEBCHAT", "COLD_TEXT_INBOUND", "AI_HANDLED_CALL", "CALENDAR_BOOKING", "INBOUND_EMAIL", "ANGI_LEAD", "EMAIL_QUALIFICATION", "YELP_LEAD", "THUMBTACK_LEAD", "GOOGLE_LSA_DIRECT" ], "nullable": true, "description": "Source channel for the lead. Use this to differentiate reply context (e.g., EMAIL_QUALIFICATION vs YELP_LEAD).\n" }, "isRepeatLead": { "type": "boolean" }, "isManualTakeoverEnabled": { "type": "boolean" }, "leadInformation": { "type": "object", "properties": { "name": { "type": "string" }, "firstName": { "type": "string", "nullable": true }, "lastName": { "type": "string", "nullable": true }, "email": { "type": "string", "nullable": true }, "phone": { "type": "string", "nullable": true }, "additionalData": { "type": "object", "additionalProperties": true } } } } }, "ChatWidgetConfig": { "type": "object", "description": "Chat widget configuration settings", "properties": { "id": { "type": "string", "format": "uuid", "example": "550e8400-e29b-41d4-a716-446655440000" }, "name": { "type": "string", "example": "Default Widget" }, "greetingMessage": { "type": "string", "nullable": true, "example": "Welcome to our company! Text us now for immediate response.", "description": "The initial greeting message shown to visitors" }, "agentConfig": { "type": "object", "nullable": true, "description": "AI agent styling and behavior configuration", "properties": { "name": { "type": "string", "example": "Sarah" }, "avatarImageUrl": { "type": "string", "format": "uri", "example": "https://example.com/avatar.png" }, "popupEnabled": { "type": "boolean", "example": true }, "popupOpenDelay": { "type": "number", "example": 5, "description": "Delay in seconds before popup opens" }, "popupExitIntentEnabled": { "type": "boolean", "example": true }, "popupHeader1": { "type": "string", "example": "Need Help?" }, "popupHeader2": { "type": "string", "example": "Chat with us now!" }, "popupBannerImage": { "type": "string", "format": "uri" }, "popupLogoImage": { "type": "string", "format": "uri" }, "popupBgColor": { "type": "string", "example": "#ffffff" }, "popupTextColor": { "type": "string", "example": "#000000" }, "popupMinNumberMessagesToday": { "type": "number", "example": 0 }, "popupCustomStyles": { "type": "string", "description": "Custom CSS for popup styling" } } }, "leadQualifierAgentConfig": { "type": "object", "nullable": true, "description": "Lead qualification AI agent configuration", "properties": { "agentInstructions": { "type": "string", "example": "You are a helpful assistant that qualifies leads for our HVAC company...", "description": "Custom instructions for the AI agent" }, "maximumMessagesLimit": { "type": "number", "minimum": 4, "maximum": 30, "example": 20, "description": "Maximum number of messages the AI will send before ending conversation" } } }, "primaryColor": { "type": "string", "nullable": true, "example": "#007bff", "description": "Primary color for widget styling" }, "secondaryColor": { "type": "string", "nullable": true, "example": "#6c757d", "description": "Secondary color for widget styling" }, "backgroundColor": { "type": "string", "nullable": true, "example": "#ffffff", "description": "Background color for widget" }, "customCss": { "type": "string", "nullable": true, "description": "Custom CSS for advanced widget styling" }, "isActive": { "type": "boolean", "example": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } }, "ChatWidgetConfigUpdate": { "type": "object", "description": "Request payload for updating chat widget configuration", "properties": { "greetingMessage": { "type": "string", "maxLength": 1000, "nullable": true, "example": "Welcome to our company! How can we help you today?", "description": "The initial greeting message shown to visitors" }, "agentConfig": { "type": "object", "nullable": true, "description": "AI agent styling and behavior configuration", "properties": { "name": { "type": "string", "maxLength": 200, "example": "Sarah" }, "avatarImageUrl": { "type": "string", "maxLength": 1000, "format": "uri", "example": "https://example.com/avatar.png" }, "popupEnabled": { "type": "boolean", "example": true }, "popupOpenDelay": { "type": "number", "example": 5, "description": "Delay in seconds before popup opens" }, "popupExitIntentEnabled": { "type": "boolean", "example": true }, "popupHeader1": { "type": "string", "maxLength": 1000, "example": "Need Help?" }, "popupHeader2": { "type": "string", "maxLength": 1000, "example": "Chat with us now!" }, "popupBannerImage": { "type": "string", "maxLength": 1000, "format": "uri" }, "popupLogoImage": { "type": "string", "maxLength": 1000, "format": "uri" }, "popupBgColor": { "type": "string", "maxLength": 10, "example": "#ffffff" }, "popupTextColor": { "type": "string", "maxLength": 10, "example": "#000000" }, "popupMinNumberMessagesToday": { "type": "number", "example": 0 }, "popupCustomStyles": { "type": "string", "maxLength": 10000, "description": "Custom CSS for popup styling" } } }, "leadQualifierAgentEnabled": { "type": "boolean", "nullable": true, "example": true, "description": "Enable or disable the lead qualification AI agent" }, "leadQualifierAgentConfig": { "type": "object", "nullable": true, "description": "Lead qualification AI agent configuration", "properties": { "agentInstructions": { "type": "string", "maxLength": 10000, "example": "You are a helpful assistant for our HVAC company. Ask about their location, timeline, and budget.", "description": "Custom instructions for the AI agent" }, "maximumMessagesLimit": { "type": "number", "minimum": 4, "maximum": 20, "example": 8, "description": "Maximum number of messages the AI will send before ending conversation" } } }, "primaryColor": { "type": "string", "maxLength": 50, "nullable": true, "example": "#007bff", "description": "Primary color for widget styling" }, "secondaryColor": { "type": "string", "maxLength": 50, "nullable": true, "example": "#6c757d", "description": "Secondary color for widget styling" }, "backgroundColor": { "type": "string", "maxLength": 50, "nullable": true, "example": "#ffffff", "description": "Background color for widget" }, "customCss": { "type": "string", "maxLength": 10000, "nullable": true, "description": "Custom CSS for advanced widget styling" } } }, "ChatWidgetLeadTrigger": { "type": "object", "description": "Request payload for triggering lead qualifier", "required": [ "name", "phone" ], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 200, "example": "John Doe", "description": "Full name of the lead" }, "phone": { "type": "string", "minLength": 1, "maxLength": 50, "example": "+15551234567", "description": "Phone number in E.164 format or US national format" }, "email": { "type": "string", "format": "email", "nullable": true, "example": "john.doe@example.com", "description": "Email address of the lead (optional)" }, "message": { "type": "string", "maxLength": 1000, "nullable": true, "example": "Interested in HVAC services for my home", "description": "Initial message or inquiry from the lead" }, "additionalData": { "type": "object", "additionalProperties": { "type": "string", "maxLength": 1000 }, "example": { "leadSource": "facebook", "fbid": "923849028492034890283094", "campaignId": "summer_hvac_2024" }, "description": "Additional tracking data and lead source information" } } }, "ChatWidgetLeadTriggerResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "client": { "$ref": "#/components/schemas/Client" }, "leadSubmission": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "companyId": { "type": "string", "format": "uuid" }, "clientId": { "type": "string", "format": "uuid" }, "firstName": { "type": "string" }, "lastName": { "type": "string" }, "email": { "type": "string", "nullable": true }, "phone": { "type": "string" }, "qualificationSource": { "type": "string", "enum": [ "MISSED_CALL", "WEBSITE_TEXTING", "WEBCHAT", "COLD_TEXT_INBOUND", "AI_HANDLED_CALL", "CALENDAR_BOOKING", "INBOUND_EMAIL", "ANGI_LEAD", "EMAIL_QUALIFICATION", "YELP_LEAD", "THUMBTACK_LEAD", "GOOGLE_LSA_DIRECT" ], "example": "WEBSITE_TEXTING" }, "createdAt": { "type": "string", "format": "date-time" } } }, "wasExistingClient": { "type": "boolean", "example": false, "description": "Whether this phone number was already in the system" }, "isRepeatSubmission": { "type": "boolean", "example": false, "description": "Whether this lead has submitted recently" } } } } }, "AppointmentCreatedWebhookPayload": { "type": "object", "required": [ "eventType", "eventTypeDetails", "appointmentId", "companyId", "calendarId", "clientId", "appointmentDetails", "clientInformation", "calendarInformation", "companyInformation" ], "properties": { "eventType": { "type": "string", "enum": [ "NEW_APPOINTMENT" ], "description": "The webhook event type", "example": "NEW_APPOINTMENT" }, "eventTypeDetails": { "type": "string", "enum": [ "NEW_APPOINTMENT" ], "description": "Detailed event type", "example": "NEW_APPOINTMENT" }, "appointmentId": { "type": "string", "format": "uuid", "description": "Unique identifier for the appointment", "example": "apt_123e4567-e89b-12d3-a456-426614174000" }, "companyId": { "type": "string", "format": "uuid", "description": "Company identifier", "example": "comp_123e4567-e89b-12d3-a456-426614174000" }, "calendarId": { "type": "string", "format": "uuid", "description": "Calendar identifier", "example": "cal_123e4567-e89b-12d3-a456-426614174000" }, "clientId": { "type": "string", "format": "uuid", "description": "Client identifier", "example": "client_123e4567-e89b-12d3-a456-426614174000" }, "appointmentDetails": { "type": "object", "required": [ "title", "startAt", "endAt", "status", "source", "createdAt" ], "properties": { "title": { "type": "string", "description": "Appointment title", "example": "Appointment with ABC Home Services" }, "description": { "type": "string", "nullable": true, "description": "Appointment description", "example": "Initial consultation for kitchen renovation project" }, "startAt": { "type": "string", "format": "date-time", "description": "Appointment start time in ISO 8601 format", "example": "2024-01-15T14:00:00.000Z" }, "endAt": { "type": "string", "format": "date-time", "description": "Appointment end time in ISO 8601 format", "example": "2024-01-15T15:00:00.000Z" }, "status": { "type": "string", "enum": [ "BOOKED", "CANCELLED", "COMPLETED", "NO_SHOW" ], "description": "Appointment status", "example": "BOOKED" }, "source": { "type": "string", "enum": [ "WEB_PORTAL", "VOICE_AGENT", "ADMIN_UI", "SYNC" ], "description": "How the appointment was created", "example": "WEB_PORTAL" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the appointment was created", "example": "2024-01-10T10:30:00.000Z" }, "appointmentNotifications": { "type": "array", "description": "Notifications sent for this appointment", "items": { "type": "object", "properties": { "medium": { "type": "string", "enum": [ "EMAIL", "SMS", "PUSH", "WEBHOOK", "OTHER" ], "example": "EMAIL" }, "templateId": { "type": "string", "nullable": true, "example": "appt-confirmation-v1" }, "sentAt": { "type": "string", "format": "date-time", "nullable": true, "example": "2024-01-10T10:31:00.000Z" }, "status": { "type": "string", "enum": [ "QUEUED", "SENT", "FAILED" ], "example": "SENT" }, "recipients": { "type": "array", "items": { "type": "object", "properties": { "contact": { "type": "string", "nullable": true, "description": "Phone, email, or webhook URL", "example": "john.smith@example.com" }, "role": { "type": "string", "nullable": true, "enum": [ "CLIENT", "COMPANY_USER", "ADDITIONAL_CONTACT", "OTHER" ], "example": "CLIENT" }, "name": { "type": "string", "nullable": true, "example": "John Smith" }, "medium": { "type": "string", "nullable": true, "enum": [ "EMAIL", "SMS", "PUSH", "WEBHOOK", "OTHER" ], "example": "EMAIL" } } } }, "metadata": { "type": "object", "additionalProperties": true, "nullable": true, "example": { "messageId": "email-msg-1" } } } } }, "postBookingActions": { "type": "array", "description": "Follow-up actions taken after booking", "items": { "type": "object", "properties": { "action": { "type": "string", "example": "SYNCED_TO_GOOGLE_CAL" }, "performedAt": { "type": "string", "format": "date-time", "nullable": true, "example": "2024-01-10T10:31:10.000Z" }, "status": { "type": "string", "enum": [ "PENDING", "SUCCESS", "FAILED" ], "example": "SUCCESS" }, "metadata": { "type": "object", "additionalProperties": true, "nullable": true, "example": { "calendarEventId": "gcal-evt-123" } } } } } } }, "clientInformation": { "type": "object", "properties": { "firstName": { "type": "string", "nullable": true, "description": "Client's first name", "example": "John" }, "lastName": { "type": "string", "nullable": true, "description": "Client's last name", "example": "Smith" }, "fullName": { "type": "string", "nullable": true, "description": "Client's full name", "example": "John Smith" }, "primaryEmail": { "type": "string", "nullable": true, "description": "Client's primary email address", "example": "john.smith@example.com" }, "primaryPhone": { "type": "string", "nullable": true, "description": "Client's primary phone number", "example": "+15551234567" }, "companyName": { "type": "string", "nullable": true, "description": "Client's company name" }, "address": { "type": "string", "nullable": true, "description": "Client's address", "example": "123 Main Street" }, "city": { "type": "string", "nullable": true, "description": "Client's city", "example": "Springfield" }, "state": { "type": "string", "nullable": true, "description": "Client's state", "example": "IL" }, "zipCode": { "type": "string", "nullable": true, "description": "Client's ZIP code", "example": "62701" }, "country": { "type": "string", "nullable": true, "description": "Client's country", "example": "US" } } }, "calendarInformation": { "type": "object", "required": [ "name", "calendarType", "timeZone" ], "properties": { "name": { "type": "string", "description": "Calendar name", "example": "Primary" }, "calendarType": { "type": "string", "enum": [ "PRIMARY", "CREW", "USER" ], "description": "Type of calendar", "example": "PRIMARY" }, "timeZone": { "type": "string", "description": "Calendar timezone", "example": "America/Chicago" } } }, "companyInformation": { "type": "object", "required": [ "name", "timeZone" ], "properties": { "name": { "type": "string", "description": "Company name", "example": "ABC Home Services" }, "timeZone": { "type": "string", "description": "Company timezone", "example": "America/Chicago" } } }, "leadFormData": { "type": "object", "nullable": true, "description": "Optional lead form data if appointment was created via lead form", "properties": { "message": { "type": "string", "nullable": true, "description": "Lead message", "example": "I need help with a kitchen renovation. Looking for a consultation." }, "additionalData": { "type": "object", "additionalProperties": true, "description": "Additional form data", "example": { "projectType": "Kitchen Renovation", "budgetRange": "$15,000 - $25,000", "timeframe": "Next 3 months", "source": "website_form", "utm_source": "google", "utm_medium": "cpc", "utm_campaign": "kitchen_renovation" } } } } } }, "Calendar": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the calendar" }, "name": { "type": "string", "description": "Name of the calendar" }, "description": { "type": "string", "nullable": true, "description": "Optional description of the calendar" }, "calendarType": { "type": "string", "enum": [ "PRIMARY", "CREW", "USER" ], "description": "Type of calendar" }, "timeZone": { "type": "string", "description": "IANA timezone identifier for the calendar" }, "minBookAheadHours": { "type": "integer", "description": "Minimum hours required to book ahead" }, "maxBookAheadDays": { "type": "integer", "description": "Maximum days allowed to book ahead" }, "timeBlockMinutes": { "type": "integer", "description": "Duration of each appointment time block in minutes" }, "isDoubleBookingAllowed": { "type": "boolean", "description": "Whether double booking is allowed on this calendar" }, "bufferMinutes": { "type": "integer", "description": "Buffer time in minutes between appointments" }, "isActive": { "type": "boolean", "description": "Whether the calendar is active" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the calendar was created" }, "updatedAt": { "type": "string", "format": "date-time", "description": "When the calendar was last updated" } }, "required": [ "id", "name", "calendarType", "timeZone", "minBookAheadHours", "maxBookAheadDays", "timeBlockMinutes", "isDoubleBookingAllowed", "bufferMinutes", "isActive", "createdAt", "updatedAt" ], "example": { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Primary", "description": "Main company calendar", "calendarType": "PRIMARY", "timeZone": "America/New_York", "minBookAheadHours": 24, "maxBookAheadDays": 14, "timeBlockMinutes": 60, "isDoubleBookingAllowed": false, "bufferMinutes": 0, "isActive": true, "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-01-15T10:00:00Z" } }, "EmailGatewaySubmitRequest": { "type": "object", "description": "Payload for submitting an email gateway message", "required": [ "emailGatewayAddress", "emailBodyText" ], "properties": { "emailGatewayAddress": { "type": "string", "format": "email", "example": "c3f3c4a2-1234-5678-9012-abcdefabcdef@m.leadtruffle.com", "description": "The email gateway address assigned to your company" }, "emailBodyText": { "type": "string", "minLength": 1, "maxLength": 50000, "example": "Customer submitted a form requesting a quote for HVAC repair...", "description": "Plain text body that will be parsed by the email gateway" }, "emailSubject": { "type": "string", "maxLength": 500, "nullable": true, "example": "New website lead", "description": "Optional subject line used for context" }, "customerEmail": { "type": "string", "format": "email", "nullable": true, "example": "jane.doe@example.com", "description": "Optional known customer email to skip AI email extraction" }, "messageId": { "type": "string", "maxLength": 200, "nullable": true, "example": "ext-msg-12345", "description": "Optional idempotency key to prevent duplicate processing" } } }, "EmailGatewaySubmitResponse": { "type": "object", "description": "Response after submitting an email gateway payload", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "inboundEmailId": { "type": "string", "format": "uuid", "nullable": true, "example": "3c8b9b67-4b4a-4f2b-9f33-6f1e4e8b2f29", "description": "Identifier for the inbound email record (null for duplicate without lookup)" }, "status": { "type": "string", "enum": [ "QUEUED", "DUPLICATE" ], "example": "QUEUED", "description": "Processing status for this submission" }, "queuedAction": { "type": "string", "enum": [ "QUALIFY_OVER_EMAIL", "CREATE_LEAD" ], "nullable": true, "example": "QUALIFY_OVER_EMAIL", "description": "Action queued for processing (only present when status is QUEUED)" } } } } }, "YelpLeadAgentRequest": { "type": "object", "required": [ "yelpLeadId", "yelpBusinessId", "messageId", "text" ], "properties": { "yelpLeadId": { "type": "string", "description": "Yelp lead identifier provided by Yelp messaging platform." }, "yelpBusinessId": { "type": "string", "description": "Identifier for the Yelp business profile receiving the message." }, "messageId": { "type": "string", "description": "Unique identifier for the message event from Yelp." }, "userType": { "type": "string", "enum": [ "CONSUMER", "BUSINESS" ], "description": "Denotes whether the sender is the consumer or business. Defaults to `CONSUMER` when omitted." }, "userDisplayName": { "type": "string", "description": "Display name for the Yelp user if the source provides one." }, "timeCreated": { "type": "string", "format": "date-time", "description": "ISO8601 timestamp when the Yelp message was created. Defaults to the current time when omitted or invalid." }, "text": { "type": "string", "description": "Raw text content from the Yelp message." }, "attachmentUrls": { "oneOf": [ { "type": "string" }, { "type": "array", "items": { "type": "string" } } ], "description": "Optional attachment URLs from Yelp. You can provide a single string or an array of strings—single values will automatically be wrapped into an array.\n" }, "attachmentText": { "type": "string", "nullable": true, "description": "Optional OCR or caption content provided for attachments." } } }, "YelpLeadAgentQualificationResponse": { "type": "object", "required": [ "response", "contactReason", "allQuestionsAnswered", "detectedAbuse", "shouldSkipReply", "hasImages", "dataFields", "commonFields" ], "properties": { "response": { "type": "string", "description": "AI-generated reply that will be sent back to the Yelp user." }, "contactReason": { "type": "string", "description": "Summary of why the customer reached out." }, "allQuestionsAnswered": { "type": "boolean", "description": "Indicates whether all required qualification questions are answered." }, "isClientReadyToBook": { "type": "boolean", "description": "True when the customer clearly indicates they are ready to book service." }, "detectedAbuse": { "type": "boolean", "description": "True if abusive or inappropriate content was detected." }, "shouldSkipReply": { "type": "boolean", "description": "True if the AI recommends not replying (e.g., user requested no contact)." }, "hasImages": { "type": "boolean", "description": "True if attachments/images were included in the conversation." }, "dataFields": { "type": "array", "description": "List of structured data points gathered during the conversation.", "items": { "type": "object", "required": [ "name", "value" ], "properties": { "name": { "type": "string" }, "value": { "type": "string" } } } }, "commonFields": { "type": "object", "required": [ "phone", "fullAddress", "address", "zipcode", "state", "city", "isHomeowner", "customerName", "email" ], "properties": { "phone": { "type": "string", "nullable": true }, "fullAddress": { "type": "string", "nullable": true }, "address": { "type": "string", "nullable": true }, "zipcode": { "type": "string", "nullable": true }, "state": { "type": "string", "nullable": true }, "city": { "type": "string", "nullable": true }, "isHomeowner": { "type": "boolean", "nullable": true }, "customerName": { "type": "string", "nullable": true }, "email": { "type": "string", "nullable": true } } } } }, "YelpLeadAgentResponse": { "oneOf": [ { "type": "object", "required": [ "success", "data" ], "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "required": [ "reply", "conversationId", "qualificationResponse", "manualTakeoverEnabled" ], "properties": { "reply": { "type": "string", "nullable": true, "description": "AI response that should be sent back to Yelp. This is null when manualTakeoverEnabled\nis true or when the initial lead reply was already sent by a concurrent event. When null,\ndo not send a response message to Yelp.\n" }, "leadId": { "type": "string", "nullable": true, "description": "LeadTruffle lead ID if one was created." }, "conversationId": { "type": "string", "description": "Internal Yelp conversation identifier." }, "qualificationResponse": { "oneOf": [ { "$ref": "#/components/schemas/YelpLeadAgentQualificationResponse" }, { "type": "object", "nullable": true, "enum": [ null ] } ] }, "isDuplicate": { "type": "boolean", "description": "Returns true when the API replays the last reply because the inbound message matched a previous message." }, "manualTakeoverEnabled": { "type": "boolean", "description": "Indicates whether the conversation is in manual takeover mode (AI replies paused)." } } } } }, { "type": "object", "required": [ "success", "code" ], "properties": { "success": { "type": "boolean", "enum": [ false ] }, "code": { "type": "string", "description": "Machine readable error code.", "enum": [ "INVALID_INPUT", "AGENT_DISABLED", "MISSING_CONFIG", "AI_ERROR", "UNKNOWN_ERROR" ] }, "error": { "description": "Additional error detail.", "oneOf": [ { "type": "string" }, { "type": "object" } ] } } } ] }, "YelpLeadAgentLeadPayload": { "type": "object", "description": "Raw payload forwarded from Yelp's `NEW_LEAD` webhook. Every field is optional because Yelp may omit values depending on the template completed by the consumer.\n", "properties": { "yelpLeadId": { "type": "string", "description": "Optional lead identifier supplied inside the payload. Defaults to the root `yelpLeadId` when omitted." }, "yelpBusinessId": { "type": "string", "description": "Optional business identifier supplied inside the payload. Defaults to the root `yelpBusinessId` when omitted." }, "leadUserName": { "type": "string", "description": "Display name for the Yelp consumer." }, "leadTimeCreated": { "type": "string", "format": "date-time", "description": "Timestamp when Yelp recorded the lead card." }, "leadTimeUpdated": { "type": "string", "format": "date-time", "description": "Timestamp when Yelp last updated the card." }, "temporaryEmail": { "type": "string", "format": "email", "description": "Temporary Yelp relay email address for the consumer." }, "temporaryEmailExpiry": { "type": "string", "format": "date-time", "description": "Expiration timestamp of the temporary email address." }, "temporaryPhone": { "type": "string", "description": "Temporary Yelp relay phone number for the consumer." }, "temporaryPhoneExpiry": { "type": "string", "format": "date-time", "description": "Expiration timestamp of the temporary phone number." }, "project": { "type": "object", "description": "Nested project details supplied by Yelp. Contents are stored verbatim and not validated.\n", "additionalProperties": true }, "business": { "type": "object", "description": "Snapshot of the business profile returned with the lead.", "additionalProperties": true }, "metadata": { "type": "object", "description": "Additional metadata captured by any upstream integrations.", "additionalProperties": true } }, "additionalProperties": true }, "YelpLeadAgentLeadRequest": { "type": "object", "required": [ "yelpLeadId", "yelpBusinessId", "payload" ], "properties": { "yelpLeadId": { "type": "string", "description": "Yelp lead identifier supplied as the root payload ID." }, "yelpBusinessId": { "type": "string", "description": "Yelp business identifier tied to the inbox." }, "payload": { "$ref": "#/components/schemas/YelpLeadAgentLeadPayload" } } }, "YelpLeadAgentLeadResponse": { "oneOf": [ { "type": "object", "required": [ "success", "data" ], "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "required": [ "conversationId", "reply", "manualTakeoverEnabled", "qualificationResponse" ], "properties": { "conversationId": { "type": "string", "description": "Internal Yelp conversation identifier that stores the payload." }, "leadId": { "type": "string", "nullable": true, "description": "LeadTruffle lead ID when one exists for the conversation." }, "clientId": { "type": "string", "nullable": true, "description": "Client identifier created or updated from the payload." }, "reply": { "type": "string", "nullable": true, "description": "AI reply to send back to Yelp for the initial lead card. This can be null\nwhen manual takeover is enabled, the agent is disabled, or the initial\nreply was already sent by a concurrent consumer message. When null, do\nnot send a response message to Yelp.\n" }, "manualTakeoverEnabled": { "type": "boolean", "description": "Indicates whether the conversation is in manual takeover mode (AI replies paused)." }, "qualificationResponse": { "oneOf": [ { "$ref": "#/components/schemas/YelpLeadAgentQualificationResponse" }, { "type": "object", "nullable": true, "enum": [ null ] } ] }, "initialReplySkipped": { "type": "boolean", "description": "True when the initial reply was intentionally suppressed to avoid a duplicate response." } } } } }, { "type": "object", "required": [ "success", "code" ], "properties": { "success": { "type": "boolean", "enum": [ false ] }, "code": { "type": "string", "enum": [ "INVALID_INPUT", "UNKNOWN_ERROR" ] }, "error": { "description": "Additional error detail.", "oneOf": [ { "type": "string" }, { "type": "object" } ] } } } ] }, "YelpBusinessMessageRequest": { "type": "object", "required": [ "yelpLeadId", "messageId", "text" ], "properties": { "yelpLeadId": { "type": "string", "description": "Yelp lead identifier (maps to externalLeadId in our system)", "example": "lead_abc123" }, "messageId": { "type": "string", "description": "Unique message identifier from Yelp", "example": "msg_business_789" }, "yelpBusinessId": { "type": "string", "nullable": true, "description": "Yelp business identifier", "example": "business_456" }, "text": { "type": "string", "description": "The message content sent by the business", "example": "Thanks for reaching out! We can schedule you for next Tuesday." }, "timeCreated": { "type": "string", "format": "date-time", "nullable": true, "description": "Timestamp when the message was created in Yelp", "example": "2024-12-01T16:00:00Z" }, "attachmentUrls": { "type": "array", "items": { "type": "string", "format": "uri" }, "nullable": true, "description": "URLs of any attachments sent with the message" }, "attachmentText": { "type": "string", "nullable": true, "description": "Text description of attachments" }, "userDisplayName": { "type": "string", "nullable": true, "description": "Display name of the business user who sent the message", "example": "ABC Plumbing" } } }, "YelpBusinessMessageResponse": { "type": "object", "properties": { "success": { "type": "boolean", "description": "Whether the request was successful", "example": true }, "data": { "type": "object", "properties": { "conversationId": { "type": "string", "format": "uuid", "description": "Internal conversation identifier", "example": "ad10af01-6d8a-4b46-83aa-4a7d38c35172" }, "messageId": { "type": "string", "description": "The message ID that was processed", "example": "msg_business_789" }, "skipped": { "type": "boolean", "description": "If true, the message was skipped due to deduplication", "example": false }, "reason": { "type": "string", "description": "Reason for skipping (only present if skipped is true)", "enum": [ "DUPLICATE_CONTENT" ], "example": "DUPLICATE_CONTENT" } } }, "error": { "type": "string", "description": "Error message if success is false" }, "code": { "type": "string", "description": "Error code if success is false", "enum": [ "INVALID_INPUT", "CONVERSATION_NOT_FOUND", "UNKNOWN_ERROR" ] } } }, "YelpSendMessageRequest": { "type": "object", "required": [ "message" ], "properties": { "conversationId": { "type": "string", "format": "uuid", "description": "Internal conversation ID. Provide either this or externalLeadId.\n", "example": "ad10af01-6d8a-4b46-83aa-4a7d38c35172" }, "externalLeadId": { "type": "string", "description": "Yelp lead ID (yelpLeadId). Provide either this or conversationId.\n", "example": "lead_abc123" }, "message": { "type": "string", "minLength": 1, "maxLength": 5000, "description": "The message text to send to the Yelp lead", "example": "Hi! Just following up on your request. Are you still interested in getting a quote?" }, "attachmentUrls": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "Optional URLs of attachments to include with the message", "example": [] } } }, "YelpSendMessageResponse": { "type": "object", "properties": { "success": { "type": "boolean", "description": "Whether the request was successful", "example": true }, "data": { "type": "object", "properties": { "conversationId": { "type": "string", "format": "uuid", "description": "Internal conversation identifier", "example": "ad10af01-6d8a-4b46-83aa-4a7d38c35172" }, "webhooksTriggered": { "type": "integer", "description": "Number of registered webhook endpoints triggered for this outbound message", "example": 2 } } }, "error": { "type": "string", "description": "Error message if success is false" }, "code": { "type": "string", "description": "Error code if success is false", "enum": [ "INVALID_INPUT", "CONVERSATION_NOT_FOUND", "MANUAL_TAKEOVER_ENABLED", "NO_WEBHOOKS_CONFIGURED", "UNKNOWN_ERROR" ] } } }, "YelpConversationMedia": { "type": "object", "required": [ "type", "url" ], "properties": { "type": { "type": "string", "description": "Media type provided by Yelp.", "enum": [ "image", "video", "audio" ] }, "url": { "type": "string", "format": "uri", "description": "Direct URL to the media asset." }, "thumbnailUrl": { "type": "string", "format": "uri", "nullable": true, "description": "Optional thumbnail preview URL." } } }, "YelpConversationMessage": { "type": "object", "required": [ "role", "timestamp", "content" ], "properties": { "role": { "type": "string", "enum": [ "user", "assistant" ], "description": "Indicates whether the message was sent by the consumer or the AI agent." }, "timestamp": { "type": "string", "format": "date-time", "description": "ISO8601 timestamp when the message was logged." }, "content": { "type": "string", "description": "Text content of the message." }, "additionalData": { "type": "object", "nullable": true, "description": "Supplemental metadata captured for the message." }, "media": { "type": "array", "items": { "$ref": "#/components/schemas/YelpConversationMedia" }, "description": "Optional media attachments included with the message." } } }, "YelpConversationHistory": { "type": "object", "required": [ "messages" ], "properties": { "messages": { "type": "array", "description": "Ordered message history captured for the Yelp lead.", "items": { "$ref": "#/components/schemas/YelpConversationMessage" } } } }, "YelpLeadConversationActionMetadata": { "type": "object", "properties": { "actionsTaken": { "type": "array", "description": "Downstream system actions triggered for the conversation.", "items": { "type": "object", "required": [ "action", "actionStatus", "actionDataResult" ], "properties": { "action": { "type": "string", "enum": [ "EMAIL_SENT", "LEAD_CREATED", "TEXT_SENT" ] }, "actionStatus": { "type": "string", "enum": [ "SUCCESS", "FAILURE" ] }, "actionDataResult": { "type": "object", "description": "Arbitrary payload describing the action result." } } } } } }, "YelpLeadConversation": { "type": "object", "required": [ "id", "companyId", "eventType", "createdAt", "updatedAt", "manualTakeoverEnabled" ], "properties": { "id": { "type": "string", "format": "uuid" }, "companyId": { "type": "string", "format": "uuid" }, "eventType": { "type": "string", "description": "Yelp webhook event that opened the conversation (e.g., NEW_CONSUMER_MESSAGE)." }, "externalLeadId": { "type": "string", "description": "Lead identifier from Yelp." }, "conversationHistory": { "$ref": "#/components/schemas/YelpConversationHistory" }, "latestQualificationResponse": { "$ref": "#/components/schemas/YelpLeadAgentQualificationResponse" }, "actionMetadata": { "$ref": "#/components/schemas/YelpLeadConversationActionMetadata" }, "manualTakeoverEnabled": { "type": "boolean", "description": "Indicates if the company has paused AI replies and is handling the conversation manually." }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } }, "YelpLeadConversationListResponse": { "type": "object", "required": [ "success", "data", "pagination" ], "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/YelpLeadConversation" } }, "pagination": { "type": "object", "required": [ "total", "limit", "offset", "hasMore" ], "properties": { "total": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "offset": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" } } } } }, "YelpLeadConversationResponse": { "type": "object", "required": [ "success", "data" ], "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "$ref": "#/components/schemas/YelpLeadConversation" } } }, "WebhookV2PayloadYelpMessageOutbound": { "type": "object", "description": "Webhook payload for YELP_MESSAGE_OUTBOUND events. Fired when you want to send\na follow-up message to a Yelp lead. Configure Zapier to receive this webhook\nand relay the message to Yelp via their \"Create Message\" action.\n", "required": [ "eventType", "eventTypeDetails", "yelpLeadId", "conversationId", "companyId", "message", "timestamp", "triggeredBy" ], "properties": { "eventType": { "type": "string", "enum": [ "YELP_MESSAGE_OUTBOUND" ], "description": "Event type identifier", "example": "YELP_MESSAGE_OUTBOUND" }, "eventTypeDetails": { "type": "string", "enum": [ "YELP_FOLLOWUP_MESSAGE" ], "description": "Specific event detail", "example": "YELP_FOLLOWUP_MESSAGE" }, "yelpLeadId": { "type": "string", "description": "Yelp lead identifier. Use this in Zapier to identify which conversation\nto send the message to in Yelp.\n", "example": "lead_abc123" }, "yelpBusinessId": { "type": "string", "nullable": true, "description": "Yelp business identifier", "example": "business_456" }, "conversationId": { "type": "string", "format": "uuid", "description": "Internal LeadTruffle conversation ID", "example": "ad10af01-6d8a-4b46-83aa-4a7d38c35172" }, "companyId": { "type": "string", "format": "uuid", "description": "Company identifier", "example": "comp_99999999-8888-7777-6666-444444444444" }, "message": { "type": "string", "description": "The message text to send to the Yelp lead", "example": "Hi! Thanks for reaching out. Are you still interested in getting a quote?" }, "attachmentUrls": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "Optional attachment URLs", "example": [] }, "timestamp": { "type": "string", "format": "date-time", "description": "When the webhook was triggered", "example": "2024-01-16T10:00:00.000Z" }, "triggeredBy": { "type": "string", "enum": [ "API", "UI", "SCHEDULED" ], "description": "How the message send was triggered", "example": "API" }, "leadId": { "type": "string", "format": "uuid", "nullable": true, "description": "Associated lead ID if available", "example": "lead_87654321-dcba-4321-8765-987654321def" }, "clientId": { "type": "string", "format": "uuid", "nullable": true, "description": "Associated client ID if available", "example": "client_11111111-2222-3333-4444-555555555555" }, "leadInformation": { "type": "object", "nullable": true, "description": "Optional lead context for filtering/logging in Zapier", "properties": { "name": { "type": "string", "nullable": true, "example": "John Doe" }, "phone": { "type": "string", "nullable": true, "example": "+15551234567" }, "email": { "type": "string", "nullable": true, "example": "john.doe@example.com" } } } } }, "YelpOutboundMessagesListResponse": { "type": "object", "description": "Response containing a list of outbound messages in YELP_MESSAGE_OUTBOUND webhook payload format.", "properties": { "success": { "type": "boolean", "description": "Whether the request was successful", "example": true }, "data": { "type": "array", "description": "List of outbound messages in webhook payload format", "items": { "$ref": "#/components/schemas/WebhookV2PayloadYelpMessageOutbound" } }, "pagination": { "type": "object", "properties": { "total": { "type": "integer", "description": "Total number of outbound messages available", "example": 25 }, "limit": { "type": "integer", "description": "Maximum number of messages per page", "example": 10 }, "offset": { "type": "integer", "description": "Number of messages skipped", "example": 0 }, "hasMore": { "type": "boolean", "description": "Whether there are more messages beyond this page", "example": true } } }, "error": { "type": "string", "description": "Error message if success is false" } } }, "ConversionUpsertRequest": { "type": "object", "required": [ "conversionStatus" ], "properties": { "leadId": { "type": "string", "format": "uuid", "description": "Lead ID to associate the conversion with. If provided, this is used directly.\nYou may provide either leadId or phone. If both are provided, leadId is used.\n", "example": "123e4567-e89b-12d3-a456-426614174000" }, "phone": { "type": "string", "description": "Phone number of the lead to resolve the most recent lead submission for this company.\nE.164 format is recommended (e.g., +15551234567); US 10-digit formats are also accepted and normalized.\nYou may provide either phone or leadId. If both are provided, leadId is used.\n", "example": "+15551234567" }, "conversionStatus": { "type": "string", "enum": [ "WON", "LOST", "QUOTED", "PENDING", "CONTACTED", "NURTURING", "CLOSED" ], "description": "The status of the lead conversion. Required field.", "example": "WON" }, "revenueAmount": { "type": "string", "pattern": "^\\d+(\\.\\d{1,2})?$", "description": "Revenue amount as a decimal string with max 2 decimal places.\nCan be a string like \"150.00\" or a number.\nValid range: 0.01 to 999,999,999.99\nOptional field.\n", "example": "1500.00" }, "currency": { "type": "string", "enum": [ "USD", "CAD" ], "default": "USD", "description": "Currency code. Defaults to USD if not specified. Optional field.", "example": "USD" }, "notes": { "type": "string", "maxLength": 1000, "description": "Optional notes about the conversion. Maximum 1000 characters.", "example": "Customer signed annual contract via email" }, "source": { "type": "string", "maxLength": 100, "description": "Optional source identifier indicating where this conversion came from.\nExamples: 'Jobber', 'HousecallPro', 'ServiceTitan', 'Salesforce', 'Manual Entry'\n\nDefaults to 'API' if not specified.\nMax length: 100 characters.\n", "example": "HousecallPro" }, "sourceData": { "type": "object", "description": "Optional JSON object to store integration-specific data.\nCan contain any valid JSON data (e.g., CRM IDs, sales rep info, custom fields).\nMaximum size: 10KB when serialized.\n", "example": { "crmId": "SF-12345", "salesRep": "John Doe", "closedDate": "2024-01-15", "dealValue": 1500 }, "additionalProperties": true } }, "anyOf": [ { "required": [ "leadId" ] }, { "required": [ "phone" ] } ] }, "ConversionUpsertResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true, "description": "Indicates whether the operation was successful" }, "data": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the conversion record", "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, "companyId": { "type": "string", "format": "uuid", "description": "Company ID that owns this conversion", "example": "comp_xyz789" }, "leadFormSubmissionId": { "type": "string", "format": "uuid", "description": "The lead ID this conversion is associated with", "example": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b" }, "conversionStatus": { "type": "string", "enum": [ "WON", "LOST", "QUOTED", "PENDING", "CONTACTED", "NURTURING", "CLOSED" ], "description": "The conversion status", "example": "WON" }, "revenueAmount": { "type": "string", "nullable": true, "description": "Revenue amount as a decimal string. Null if not provided.", "example": "1500.00" }, "currency": { "type": "string", "description": "Currency code (USD or CAD)", "example": "USD" }, "source": { "type": "string", "nullable": true, "description": "Source of the conversion data (e.g., \"HousecallPro\", \"Jobber\", \"API\", \"MANUAL\"). Defaults to \"API\" if not specified.", "example": "HousecallPro" }, "notes": { "type": "string", "nullable": true, "description": "Optional notes about the conversion", "example": "Customer signed annual contract" }, "sourceData": { "type": "object", "nullable": true, "description": "Integration-specific metadata", "example": { "crmId": "SF-12345", "salesRep": "John Doe" }, "additionalProperties": true }, "createdByCompanyUserId": { "type": "string", "format": "uuid", "nullable": true, "description": "User ID who created the conversion (null for API calls)", "example": null }, "createdAt": { "type": "string", "format": "date-time", "description": "Timestamp when the conversion was created", "example": "2024-01-15T10:30:00Z" }, "updatedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the conversion was last updated", "example": "2024-01-15T14:45:00Z" }, "isUpdate": { "type": "boolean", "description": "Indicates whether this was an update (true) or a new creation (false)", "example": true }, "lead": { "type": "object", "description": "Lead record status updated for the Lead Workflow board.", "properties": { "id": { "type": "string", "format": "uuid", "description": "Lead ID whose workflow-board status was updated" }, "conversionStatus": { "type": "string", "description": "Current lead workflow-board status", "example": "WON" }, "conversionStatusUpdatedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the lead workflow-board status was updated" } } } } } } }, "LeadReviewRequestCreate": { "type": "object", "properties": { "leadId": { "type": "string", "format": "uuid", "description": "Lead ID to associate the review request with. If provided, this is used directly.\nYou may provide either leadId or phone. If both are provided, leadId is used.\n", "example": "123e4567-e89b-12d3-a456-426614174000" }, "phone": { "type": "string", "description": "Phone number of the lead to resolve the most recent lead submission for this company.\nE.164 format is recommended (e.g., +15551234567); US 10-digit formats are also accepted and normalized.\nYou may provide either phone or leadId. If both are provided, leadId is used.\n", "example": "+15551234567" } }, "anyOf": [ { "required": [ "leadId" ] }, { "required": [ "phone" ] } ] }, "LeadReviewRequestResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "ID of the review request", "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, "status": { "type": "string", "description": "Status of the review request (PENDING, SENT, COMPLETED, FAILED)", "example": "PENDING" }, "isDuplicate": { "type": "boolean", "description": "True if a review request already existed and was not duplicated", "example": false } } } } }, "Lead": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "example": "db8db5a7-2f26-4e88-9c2a-969bad0f6a5b" }, "companyId": { "type": "string", "format": "uuid", "example": "f096f9e3-001d-49ac-864c-3d73453bbe08" }, "leadFormId": { "type": "string", "format": "uuid", "nullable": true }, "clientId": { "type": "string", "format": "uuid", "example": "415f2b29-39e6-4182-9d6f-ec2d817f01c2" }, "phone": { "type": "string", "example": "+17345520800", "description": "Phone number in E.164 format" }, "email": { "type": "string", "nullable": true, "example": "john.doe@example.com" }, "firstName": { "type": "string", "nullable": true }, "lastName": { "type": "string", "nullable": true }, "leadFormData": { "type": "object", "properties": { "source": { "type": "string", "example": "missed-call" }, "message": { "type": "string", "example": "Just calling to see how this would work. Thanks." } }, "additionalProperties": false }, "userAgent": { "type": "string", "nullable": true }, "ipAddress": { "type": "string", "nullable": true }, "leadStatus": { "type": "string", "nullable": true }, "leadNotes": { "type": "string", "nullable": true }, "leadQualificationResult": { "type": "object", "nullable": true, "properties": { "response": { "type": "string", "example": "Thank you for this! Bryan our Co-Founder & CEO will be reviewing the conversation shortly and be in touch if there's a fit." }, "hasImages": { "type": "boolean", "default": false }, "dataFields": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "example": "customer_name" }, "value": { "type": "string", "example": "Marcus" } } } }, "commonFields": { "type": "object", "properties": { "city": { "type": "string", "nullable": true }, "state": { "type": "string", "nullable": true }, "address": { "type": "string", "nullable": true }, "zipcode": { "type": "string", "nullable": true }, "fullAddress": { "type": "string", "nullable": true }, "isHomeowner": { "type": "boolean", "nullable": true }, "customerName": { "type": "string", "nullable": true } } }, "contactReason": { "type": "string", "nullable": true }, "detectedAbuse": { "type": "boolean", "default": false }, "shouldSkipReply": { "type": "boolean", "default": false }, "allQuestionsAnswered": { "type": "boolean" }, "isClientReadyToBook": { "type": "boolean", "default": false } } }, "leadQualificationStatus": { "type": "string", "enum": [ null, "COMPLETED" ], "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "qualificationSource": { "type": "string", "enum": [ "MISSED_CALL", "WEBSITE_TEXTING", "WEBCHAT", "COLD_TEXT_INBOUND", "AI_HANDLED_CALL", "CALENDAR_BOOKING", "INBOUND_EMAIL", "ANGI_LEAD", "YELP_LEAD", "THUMBTACK_LEAD", "EMAIL_QUALIFICATION", "GOOGLE_LSA_DIRECT" ], "example": "EMAIL_QUALIFICATION" }, "qualificationStartedAt": { "type": "string", "format": "date-time", "nullable": true }, "qualificationCompletedAt": { "type": "string", "format": "date-time", "nullable": true }, "isRepeatLead": { "type": "boolean", "default": false }, "previousLeadSubmissionId": { "type": "string", "format": "uuid", "nullable": true }, "conversionStatus": { "type": "string", "nullable": true }, "conversionStatusUpdatedAt": { "type": "string", "format": "date-time", "nullable": true }, "notifiedCompanyOn": { "type": "string", "format": "date-time", "example": "2025-02-16T15:20:51.422Z" }, "missedCallId": { "type": "string", "format": "uuid", "example": "99bd4118-7d7b-4376-9ecf-dc5c0fa37962" }, "lineType": { "type": "string", "enum": [ "mobile", "landline", "voip" ], "example": "mobile" }, "manualTakeoverEnabled": { "type": "boolean", "default": false } } }, "WebhookV2PayloadLeadStatusChanged": { "type": "object", "description": "V2 webhook payload for LEAD_STATUS_CHANGED events.\n\nLegacy lead pipeline event. Use only if your account still uses the legacy\nLeads system. For the new Contacts/client pipeline, use CLIENT_STATUS_CHANGED.\n", "properties": { "eventType": { "type": "string", "enum": [ "LEAD_STATUS_CHANGED" ], "example": "LEAD_STATUS_CHANGED" }, "eventTypeDetails": { "type": "string", "enum": [ "LEAD_STATUS_CHANGED" ], "example": "LEAD_STATUS_CHANGED" }, "leadId": { "type": "string", "format": "uuid" }, "clientId": { "type": "string", "format": "uuid", "nullable": true }, "companyId": { "type": "string", "format": "uuid" }, "previousStatus": { "type": "string", "nullable": true, "example": "NEW" }, "newStatus": { "type": "string", "example": "WON" }, "timestamp": { "type": "string", "format": "date-time" }, "changedByUserId": { "type": "string", "nullable": true, "description": "User who changed the status in LeadTruffle, when available." }, "changedByUserEmail": { "type": "string", "nullable": true, "description": "Email for the user who changed the status, when available." }, "lead": { "type": "object", "additionalProperties": true, "description": "Full lead submission payload after the status change." } } }, "WebhookV2PayloadClientStatusChanged": { "type": "object", "description": "V2 webhook payload for CLIENT_STATUS_CHANGED events in the new Contacts/client pipeline.", "properties": { "eventType": { "type": "string", "enum": [ "CLIENT_STATUS_CHANGED" ], "example": "CLIENT_STATUS_CHANGED" }, "eventTypeDetails": { "type": "string", "enum": [ "CLIENT_STATUS_CHANGED" ], "example": "CLIENT_STATUS_CHANGED" }, "clientId": { "type": "string", "format": "uuid" }, "latestLeadSubmissionId": { "type": "string", "format": "uuid", "nullable": true, "description": "Latest lead inquiry/submission associated with the contact, when available." }, "companyId": { "type": "string", "format": "uuid" }, "previousStatus": { "type": "string", "nullable": true, "example": "NEW" }, "newStatus": { "type": "string", "example": "WON" }, "timestamp": { "type": "string", "format": "date-time" }, "changedByUserId": { "type": "string", "nullable": true, "description": "User who changed the contact status in LeadTruffle, when available." }, "changedByUserEmail": { "type": "string", "nullable": true, "description": "Email for the user who changed the contact status, when available." }, "client": { "type": "object", "additionalProperties": true, "description": "Full contact/client payload after the status change." } } }, "LeadConversionPayload": { "type": "object", "description": "Conversion data for a lead. This object is included in webhook payloads\nwhen conversion tracking has been enabled and conversion data exists for the lead.\n\nThis will be null/undefined if no conversion has been recorded for the lead yet.\n", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the conversion record", "example": "conv_123e4567-e89b-12d3-a456-426614174000" }, "conversionStatus": { "type": "string", "enum": [ "WON", "LOST", "QUOTED", "PENDING", "CONTACTED", "NURTURING", "CLOSED" ], "description": "The status of the lead conversion", "example": "WON" }, "revenueAmount": { "type": "string", "nullable": true, "description": "Revenue amount as a decimal string.\nNull if no revenue was recorded.\n", "example": "1500.00" }, "currency": { "type": "string", "description": "Currency code for the revenue", "example": "USD" }, "source": { "type": "string", "nullable": true, "description": "Source of the conversion record.\nExamples: 'HousecallPro', 'Jobber', 'ServiceTitan', 'API', 'Manual Entry'\n", "example": "HousecallPro" }, "sourceData": { "type": "object", "nullable": true, "description": "Additional data from the integration source.\nStructure varies based on the source system.\n", "example": { "crmRecordId": "REC-12345", "invoiceNumber": "INV-2024-001" } }, "notes": { "type": "string", "nullable": true, "description": "Optional notes about the conversion", "example": "Customer signed contract for kitchen remodel" } } } }, "responses": { "UnauthorizedError": { "description": "Authentication error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "InternalError": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "NotFoundError": { "description": "Resource not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key", "description": "Your LeadTruffle API key. Obtain yours from the LeadTruffle dashboard under Settings → API Keys." } } } } ```