# Add tags to an agent Source: https://developer.lofty.com/api-reference/agents/add-tags-to-an-agent openapi/openapi.json POST /v1.0/agent/{agentId}/tag/add Attaches one or more tags to the specified agent in batch. Notes: - tagNames must not be empty; up to 20 entries per call. - When autoCreate is true, tag names not yet defined on the team are created; when false, unknown tag names cause the call to fail. - The response envelope carries data = true and errorCode = 0 on success. # Create a new agent Source: https://developer.lofty.com/api-reference/agents/create-a-new-agent openapi/openapi.json POST /v1.0/agent/profile/add Creates a new agent under the caller's team using the supplied profile. Notes: - accountInfo.phoneNumber and accountInfo.emailAddress are required. - When phoneCode is omitted, '1' (US) is used by default. - When roleName is supplied, it is resolved to the team's matching role; unknown names are ignored and the agent is created without a role binding. - Long text fields (address, licenseId, position, personalWebsite, personalIntroduction, social-media item values) are silently truncated to their individual limits. - The response envelope carries errorCode = 0 on success; non-zero values indicate validation failure or upstream error, with errorMsg describing the cause. # Deactivate an agent Source: https://developer.lofty.com/api-reference/agents/deactivate-an-agent openapi/openapi.json POST /v2.0/agents/{agentId}/deactivate Deactivates the specified agent of the caller's team. Effects: - The agent's license seat is released immediately. - All CRM sessions and access tokens of the agent are revoked; subsequent API calls with the agent's tokens are rejected. - The agent's leads, deals and data remain untouched and stay assigned to the agent. - The operation is reversible via the reactivate endpoint; it is NOT a deletion. Constraints: - The caller must have team-agent management permission (MANAGE_TEAM_AGENT or MANAGE_TEAM_AGENT_DEPARTMENT). - The target agent must belong to the caller's team, must currently be active, and must not be the team owner, an office owner or the caller themselves. # Reactivate an agent Source: https://developer.lofty.com/api-reference/agents/reactivate-an-agent openapi/openapi.json POST /v2.0/agents/{agentId}/reactivate Reactivates a previously deactivated agent of the caller's team. Effects: - The agent re-occupies a license seat (the team must have a free seat available). - The agent's access is restored without data loss; the agent stays in the original office with the original seat type. Constraints: - The caller must have team-agent management permission (MANAGE_TEAM_AGENT or MANAGE_TEAM_AGENT_DEPARTMENT). - The target agent must belong to the caller's team and must currently be deactivated. - When the team has no free seat, the call fails with SEAT_INSUFFICIENT (310111); additional seats must be purchased in the Lofty UI first. # Create Lead Analysis Task Source: https://developer.lofty.com/api-reference/ai/create-leadanalysis-task openapi/openapi.json POST /v2.0/ai/lead-analysis Triggers an AI analysis task for the specified lead list, executed asynchronously. # Generate Call Script Source: https://developer.lofty.com/api-reference/ai/generate-call-script openapi/openapi.json POST /v2.0/ai/call-script Generates a call script based on lead information and an associated task. # Prepare Insight Source: https://developer.lofty.com/api-reference/ai/generate-prepare-insight openapi/openapi.json POST /v2.0/ai/prepare-insight Generates pre-meeting insight based on lead and appointment information. # Get Call Summary Source: https://developer.lofty.com/api-reference/ai/get-call-summary openapi/openapi.json GET /v2.0/ai/call-summary Queries and returns the generated call summary for a call recording. # List Lead Analysis Tasks Source: https://developer.lofty.com/api-reference/ai/query-leadanalysis-task-list openapi/openapi.json GET /v2.0/ai/lead-analysis Paginates current user's LeadAnalysis tasks (last 3 months, excluding canceled/finished). # Generate Call Summary Source: https://developer.lofty.com/api-reference/ai/trigger-call-summary-generation openapi/openapi.json POST /v2.0/ai/call-summary/generate Triggers async generation of a call summary from a call recording. # Available Meetings Source: https://developer.lofty.com/api-reference/calendar/v2/available-meetings openapi/openapi.json GET /v2.0/calendar/meetings/available Returns available meeting time slots within the given time range, sorted by startTime ascending. Notes: - Either startTime (ISO 8601) or startTimeMs (millisecond timestamp) must be provided; same for endTime / endTimeMs. When both forms are provided for a field, the string form takes precedence. - The resolved start time must be later than the current server time. - The resolved end time must be later than the resolved start time. - The range between start and end must not exceed 90 days. - At most 'limit' slots are returned; the envelope also includes the total count. # Create Calendar Event Source: https://developer.lofty.com/api-reference/calendar/v2/create-event openapi/openapi.json POST /v2.0/calendar Creates either a Task or an Appointment for the target lead, selected by the 'type' field (case-insensitive TASK / APPOINTMENT). Notes: - Caller must have manage permission on the lead; otherwise 400 PERMISSION_DENIED (20017). - type, leadId, content, startAt and endAt are required. taskWay is additionally required when type = TASK. - startAt and endAt must be later than the current server time. - The returned data.id is a composite string ('``-task' or '``-appointment') and must be used as-is when updating, finishing or deleting this entry. - When startAt and endAt are both exactly midnight (00:00:00) in their declared offsets, an appointment is automatically marked as all-day. # Delete Calendar Event Source: https://developer.lofty.com/api-reference/calendar/v2/delete-event openapi/openapi.json DELETE /v2.0/calendar/{calendarId} Deletes a Task or Appointment by its composite ID. The response body is a simple message envelope. # Finish Calendar Event Source: https://developer.lofty.com/api-reference/calendar/v2/finish-event openapi/openapi.json POST /v2.0/calendar/{calendarId}/finish Marks a Task or Appointment as finished. Caller must have manage permission on the entry's lead. # List Calendar Events Source: https://developer.lofty.com/api-reference/calendar/v2/list-events openapi/openapi.json GET /v2.0/calendar Returns a page of calendar entries (Tasks and Appointments) for the caller, optionally filtered by leadId, type and completion status. Notes: - At least one of startTime or endTime is required; requests missing both return 400 INVALID_PARAMETER. - includeFinished=true returns both finished and unfinished entries; includeFinished=false returns only unfinished. - Results are wrapped under the 'data' key, which also carries pagination metadata. # Unfinish Calendar Event Source: https://developer.lofty.com/api-reference/calendar/v2/unfinish-event openapi/openapi.json POST /v2.0/calendar/{calendarId}/unfinish Reverts a Task or Appointment back to the not-completed state. Caller must have manage permission on the entry's lead. # Update Calendar Event Source: https://developer.lofty.com/api-reference/calendar/v2/update-event openapi/openapi.json PUT /v2.0/calendar/{calendarId} Partially updates a Task or Appointment. Only non-empty fields in the request body are applied; omitted fields keep their current values. Notes: - calendarId must be the composite string returned by POST /v2.0/calendar (e.g. '12345-task' or '12345-appointment'). Invalid format returns 400. - When startAt or endAt is supplied, it must be later than the current server time. # Get a call by ID Source: https://developer.lofty.com/api-reference/calls/get-a-call-by-id openapi/openapi.json GET /v1.0/calls/{callId} Returns a single call record by ID. Caller must have manage permission on the lead the call belongs to; otherwise 400 PERMISSION_DENIED. # Get a call recording URL Source: https://developer.lofty.com/api-reference/calls/get-a-call-recording-url openapi/openapi.json GET /v1.0/call/url/{callId} Returns a temporary download URL for the audio recording of a call. Notes: - The URL is valid for 1 hour. - Caller must have manage permission on the lead this call belongs to; otherwise 400 PERMISSION_DENIED is returned. - When the call has no recording available, the 'url' field is an empty string. # List calls of a lead Source: https://developer.lofty.com/api-reference/calls/list-calls-of-a-lead openapi/openapi.json GET /v1.0/calls Returns call records attached to the given lead, with pagination metadata. Notes: - leadId is required and must be > 0; otherwise 404 LEAD_NOT_FOUND. - Caller must have manage permission on the lead; otherwise 400 PERMISSION_DENIED. - total in _metadata reflects the full result size before pagination. # Call History Source: https://developer.lofty.com/api-reference/communication/call-history openapi/openapi.json GET /v1.0/communication/call Returns call history entries for the given lead. Lead must belong to the caller's team. # Calls Overview Source: https://developer.lofty.com/api-reference/communication/calls-overview Understand the two sets of call APIs and when to use each. Lofty provides two groups of call-related endpoints that serve different purposes. ## Communication API vs Calls API | | Communication API | Calls API | | ------------- | ------------------------------------------ | --------------------------------------------------- | | **Endpoints** | `GET /v1.0/communication/call` (V1, V2) | `GET /v1.0/calls`, `GET /v1.0/calls/{callId}` | | **Purpose** | Query call **history** for a specific lead | Query call **records** across leads | | **Scope** | Lead-scoped (requires `leadUserId`) | Team-scoped (filter by lead, agent, date) | | **Data** | Communication timeline entries | Call metadata + recording access | | **Recording** | Not included | `GET /v1.0/call/url/{callId}` returns recording URL | ## Which should I use? Use when you need a lead's **call history** as part of their communication timeline. Best for CRM views showing all interactions with a lead. Use when you need to **list calls**, **get call details**, or **access recordings**. Best for call analytics, logging, and playback features. ## V1 vs V2 (Communication API) The Communication API has two versions: * **V1** (`GET /v1.0/communication/call`) — Returns basic call history. * **V2** (`GET /v1.0/communication/call/v2`) — Returns additional event types and richer metadata. **Recommended** for new integrations. # Create Note Source: https://developer.lofty.com/api-reference/communication/create-note openapi/openapi.json POST /v1.0/notes Creates a note on the target lead. Notes: - leadId, content and isPin are required; content must not be blank. - content is silently truncated to 2000 characters. - Caller must have access to the lead; otherwise 404 LEAD_NOT_EXIST. - The response contains only the new note's ID under the 'noteId' key. - Persistence is asynchronous: subsequent GET and PUT on the returned noteId may briefly return 404 immediately after create. DELETE is unaffected. # Delete Note Source: https://developer.lofty.com/api-reference/communication/delete-note openapi/openapi.json DELETE /v1.0/notes/{noteId} Deletes a note by ID. Caller must have manage permission on the lead that owns the note. The response body is empty. # Get a note by ID Source: https://developer.lofty.com/api-reference/communication/get-a-note-by-id openapi/openapi.json GET /v1.0/notes/{noteId} Returns a single note by its ID. The note must belong to a lead visible to the caller. # Get Call Source: https://developer.lofty.com/api-reference/communication/get-call openapi/openapi.json GET /v2.0/communication/call/{communicationId} Returns the call identified by the given communication ID. The communication must be a call record, and the caller must have manage permission on the owning lead; otherwise 400 PERMISSION_DENIED is returned (no existence leak). Manually logged calls (those with no backing dialer record) are populated from the communication content itself — leadPhoneNumber from the logged number and direction inferred from the channel; dialer-only fields (elapsedTime, status, etc.) are omitted. Webhook follow-up: when a Call webhook is delivered, pass each `createdCall[].timelineId` from the payload as the `communicationId` path parameter to fetch the full record. The legacy `createdCall[].callId` field is kept for backward compatibility but is not interchangeable across communicationType (it points at the dialer record for MANUAL calls) — prefer `timelineId`. # Get Email Source: https://developer.lofty.com/api-reference/communication/get-email openapi/openapi.json GET /v2.0/communication/email/{communicationId} Returns the email identified by the given communication ID. The communication must be an email record, and the caller must have manage permission on the owning lead; otherwise 400 PERMISSION_DENIED is returned (no existence leak). Webhook follow-up: when an Email webhook is delivered, pass each `createdEmail[].timelineId` from the payload as the `communicationId` path parameter to fetch the full record. The legacy `createdEmail[].emailId` field is kept for backward compatibility but is not interchangeable across communicationType — prefer `timelineId`. # Get Text Source: https://developer.lofty.com/api-reference/communication/get-text openapi/openapi.json GET /v2.0/communication/text/{communicationId} Returns the text message identified by the given communication ID. The communication must be a text record, and the caller must have manage permission on the owning lead; otherwise 400 PERMISSION_DENIED is returned (no existence leak). Webhook follow-up: when a Text webhook is delivered, pass each `createdText[].timelineId` from the payload as the `communicationId` path parameter to fetch the full record. The legacy `createdText[].textId` field is kept for backward compatibility but is not interchangeable across communicationType — prefer `timelineId`. # List call history of a lead (v2) Source: https://developer.lofty.com/api-reference/communication/list-call-history-of-a-lead-v2 openapi/openapi.json GET /v1.0/communication/call/v2 Returns call history entries for the given lead, including additional event types not covered by the v1 variant. Lead must belong to the caller's team. # Email History Source: https://developer.lofty.com/api-reference/communication/list-emails openapi/openapi.json GET /v1.0/communication/email Returns email history entries for the given lead. Lead must belong to the caller's team. # List Notes Source: https://developer.lofty.com/api-reference/communication/list-notes openapi/openapi.json GET /v1.0/notes Returns all notes attached to the given lead. Notes: - Caller must have access to the lead; otherwise 404 LEAD_NOT_EXIST. - By default only user-created notes are returned. Pass includeSystemNote=true to also include automated / system-generated notes. - Response order is not guaranteed. # SMS History Source: https://developer.lofty.com/api-reference/communication/list-sms openapi/openapi.json GET /v1.0/communication/text Returns text message history entries for the given lead. Lead must belong to the caller's team. # Search by Agent Source: https://developer.lofty.com/api-reference/communication/search-communications-by-agent openapi/openapi.json POST /v1.0/agent/communication Returns a page of communication timeline entries for the caller (or the caller's team when teamView=true, requiring ACCESS_ALL_TEAM_LEADS permission), filtered by type and optional time range. The response is a page wrapper with totalCount, offset, limit, and pageData. # Search by Agent (cursor paged) Source: https://developer.lofty.com/api-reference/communication/search-communications-by-agent-v2 openapi/openapi.json GET /v2.0/communication/agent Returns communication timeline entries for the caller (or the caller's team when teamView=true, requiring ACCESS_ALL_TEAM_LEADS permission) within a time range. - The time window (endTime - startTime) may span up to 90 days in a single query. - type accepts CALL, TEXT, EMAIL, or ALL to fetch all three together in one request. - limit controls the page size and must be between 1 and 1000 (default 100). - Cursor pagination: the response returns a nextCursor token; pass it back as the 'cursor' parameter to fetch the next page. Keep paging while nextCursor is non-null and stop when it is null. Because a full page cannot tell whether more entries exist, the last page may come back with an empty data array and nextCursor=null - treat that as end-of-data, not an error. This is the recommended way to sync large or historical ranges. # Send Email Source: https://developer.lofty.com/api-reference/communication/send-email openapi/openapi.json POST /v1.0/message/email/send Sends an email to the specified lead using the caller's outgoing email configuration. Notes: - leadId, subject and content are required. - toEmail selects a specific email on the lead; when it is not present on the lead, the lead's primary email is used. - Caller must have manage permission on the lead. # Send SMS Source: https://developer.lofty.com/api-reference/communication/send-sms openapi/openapi.json POST /v1.0/message/sms/send Sends an SMS to the specified lead from the caller's virtual number. Notes: - leadId and content are required. - phoneNumber + phoneCode select a specific phone on the lead; when omitted, the lead's primary phone is used. - Caller must have manage permission on the lead and must have an active virtual number. # Update Note Source: https://developer.lofty.com/api-reference/communication/update-note openapi/openapi.json PUT /v1.0/notes/{noteId} Updates the content and pin state of an existing note. Notes: - content must not be blank; isPin must not be null. - content is silently truncated to 2000 characters. - If leadId is supplied in the body, it must match the note's lead; otherwise 400 INVALID_UPDATE_OPERATION_LEADID. - Caller must have access to the lead; otherwise 404 LEAD_NOT_EXIST. - The response body is empty. # Lead Activities Source: https://developer.lofty.com/api-reference/leads/activities openapi/openapi.json GET /v1.0/leads/{leadId}/activities Returns the lead's site-tracked activities (browse / search / favorite / showing request / submission) across tracking types 1, 2, 3, 23 and 39, merged and sorted by created time descending. Notes: - Caller must have manage permission on the lead; otherwise 404 LEAD_NOT_EXIST. - Page size is fixed at 100; use curPage to step through pages. # Add activity Source: https://developer.lofty.com/api-reference/leads/add-activity openapi/openapi.json POST /v1.0/leads/{leadId}/activity Add activity to a lead. # Assign Lead Source: https://developer.lofty.com/api-reference/leads/assign-lead openapi/openapi.json POST /v1.0/leads/{leadId}/assignment Place assignee to a lead. # Brokermint — Sync Lead Source: https://developer.lofty.com/api-reference/leads/brokermint-webhook-update-a-synced-lead openapi/openapi.json PUT /v1.0/brokermint/lead Callback endpoint for Brokermint to push contact updates into Lofty. Not intended for general external callers. The target contact must have been previously linked to a Brokermint record through the Lofty UI's Brokermint Integration. Without this link the response body contains a non-zero code despite HTTP 200. # Create Lead Source: https://developer.lofty.com/api-reference/leads/create-lead openapi/openapi.json POST /v1.0/leads ## Lead Assignment & Routing **Assignment priority:** 1. If `assignedUserId` is provided, the lead is directly assigned to that agent. No routing rules are applied. 2. If `assignedUserId` is empty or omitted, the lead goes through automatic lead routing based on the configured routing rules. **Routing rule scope** (when routing is triggered): - `assignGroupId = -1` → Apply company-level routing rules. - `assignGroupId = ` → Apply routing rules for the specified office. - `assignGroupId` not set → Default routing rules will be applied according to the lead’s ownership scope. **Routing location matching:** - For Seller (1) type leads, routing is determined by the `property` field (property address/location). - For all other lead types (Buyer, Renter, etc.), routing is determined by `inquiry.locations[]` (interested areas). ## Lead Ownership Controls the visibility scope of the lead: - `ownershipScope = TEAM` → Account-wide: visible to all users in this Lofty client account. - `ownershipScope = OFFICE` → Visible to a specific office. Must provide `ownershipId` with the office ID. - `ownershipScope = PERSONAL` → Visible only to the assigned agent. Must provide `ownershipId` with the agent's user ID. - If not specified, defaults to PERSONAL scope owned by the API caller. > Note: `ownershipScope = TEAM` refers to the entire client account (the organization), not the Lofty "Team add-on" product. ## Eventual consistency Persistence is asynchronous. The lead ID is returned immediately, but subsequent GET /v1.0/leads/{leadId} and similar read endpoints may briefly return 404 right after create. Follow-up writes (PUT, POST inquiry, POST property, POST transaction) are unaffected. ## Property block The optional `property` block is persisted together with the lead: - Omit it entirely if you don't need to attach an address — the lead is still created. - If provided, at least one of `city`, `state`, `zipCode`, `streetAddress` must be non-blank; otherwise the request is rejected with **200057 PROPERTY_ADDRESS_EMPTY** and no lead is created. **Silent truncation** — `firstName`, `lastName`, `groups`, `segments` are truncated to **30 characters**; `tags` / `tagsAdd` to **64 characters**. The API returns success with no error. Validate input length before sending. # Delete Lead Source: https://developer.lofty.com/api-reference/leads/delete-lead openapi/openapi.json DELETE /v1.0/leads/{leadId} Moves a lead to the trash. A reason is required and is stored for audit. The response body is empty. # Get Lead Source: https://developer.lofty.com/api-reference/leads/get-lead openapi/openapi.json GET /v1.0/leads/{leadId} Returns a single lead by ID. The response is wrapped under the 'lead' key. Notes: - By default, trashed leads are not returned. Pass withTrash=true to include them. - Caller must have access to the lead; otherwise 404 LEAD_NOT_EXIST. # List lead activities Source: https://developer.lofty.com/api-reference/leads/list-lead-activities openapi/openapi.json GET /v2.0/leads/{leadId}/activities Returns a unified list of call, text and email activities for the given lead, including both auto-captured and agent-logged entries, sorted by activityTime ascending. Notes: - Caller must have access to the lead; otherwise 404 LEAD_NOT_EXIST. - Use currentId for cursor-based pagination or offset for offset-based pagination. # List Leads Source: https://developer.lofty.com/api-reference/leads/list-leads openapi/openapi.json GET /v1.0/leads Returns a page of leads matching the supplied filters with pagination metadata. Notes: - limit must be in [1, 100]; otherwise 400 LIMIT_NOT_VALID. - Use offset for offset-based pagination or scrollId for cursor-based pagination. - Multiple filters are combined with AND logic; multi-value filters within a single parameter (e.g. allTags, anyTags) apply their documented AND / OR semantics. # List saved searches Source: https://developer.lofty.com/api-reference/leads/list-saved-searches openapi/openapi.json GET /v1.0/leads/{leadId}/savedSearches Returns every saved search of the lead, including its conditions and alert delivery settings. Returns an empty collection when the lead has none. # Overview Source: https://developer.lofty.com/api-reference/leads/overview Full reference for the Lofty Leads API: list, create, retrieve, update, delete, assign leads, and access activity timelines and system logs. The Leads API gives you programmatic access to every stage of the lead lifecycle in Lofty. Use it to import leads from external sources, sync CRM data, automate assignment workflows, and retrieve activity history for reporting and compliance. All endpoints require a valid Bearer token in the `Authorization` header. See [Authentication](/authentication/overview) for details. Lead IDs (`leadId`) are 64-bit integers. Use a `long` or `BigInt` type in your language of choice — standard 32-bit integers will overflow. ## Endpoints | Method | Path | Description | | -------- | --------------------------------- | ------------------------------------------------------- | | `GET` | `/v1.0/leads` | List and filter leads with pagination | | `POST` | `/v1.0/leads` | Create a new lead | | `GET` | `/v1.0/leads/{leadId}` | Get a single lead by ID | | `PUT` | `/v1.0/leads/{leadId}` | Update an existing lead | | `DELETE` | `/v1.0/leads/{leadId}` | Move a lead to the trash | | `POST` | `/v1.0/leads/{leadId}/assignment` | Assign a lead to one or more agents | | `POST` | `/v1.0/leads/assignee` | Preview routing assignees without creating a lead | | `GET` | `/v2.0/leads/{leadId}/activities` | List call, text, and email activity for a lead | | `GET` | `/v1.0/leads/{leadId}/activities` | List site-tracked activities (browse, search, favorite) | | `POST` | `/v1.0/leads/{leadId}/activity` | Log an external communication as a site activity | | `GET` | `/v1.0/systemLogs` | Retrieve the system-generated audit trail for a lead | ## Base URL ``` https://api.lofty.com ``` ## Related resources * [Managing leads guide](/guides/lead-management) — end-to-end walkthrough of lead operations * [Authentication](/authentication/overview) — how to obtain an access token # Update Property Info Source: https://developer.lofty.com/api-reference/leads/place-a-property openapi/openapi.json POST /v1.0/leads/{leadId}/property Attach a property (or mailing address) to the specified lead. The behaviour depends on the `property.id` field in the request body: - **`id` omitted or `<= 0`** — the service first tries to merge with an existing property on the lead that has the same street/city/state/zip; if no match is found, a new property record is created. - **`id > 0`** — the named property is updated in place. The property must belong to this lead (its `leadUserId` must match the lead's); otherwise the request is rejected with 200108 LEAD_PROPERTY_NOT_EXIST. Validation: - At least one of `city`, `state`, `zipCode`, `streetAddress` must be non-blank; otherwise 200057 PROPERTY_ADDRESS_EMPTY is returned. - Caller must have manage permission on the lead. # Add Inquiry Source: https://developer.lofty.com/api-reference/leads/place-an-inquiry openapi/openapi.json POST /v1.0/leads/{leadId}/inquiry Place an inquiry for a lead. # Preview Routing Source: https://developer.lofty.com/api-reference/leads/resolve-assignees-by-lead-info openapi/openapi.json POST /v1.0/leads/assignee Returns the assignees that would be selected by routing rules for a hypothetical lead matching the supplied name, email and other attributes. Useful for previewing routing before creating the lead. Notes: - firstName and email are required. - The response lists one entry per role matched by routing. # System Logs Source: https://developer.lofty.com/api-reference/leads/system-logs openapi/openapi.json GET /v1.0/systemLogs Returns system-event timeline entries for the lead, sorted by timelineTime descending (newest first), with offset-based pagination. Only system / change-log activity events are returned (e.g. stage changes, assignment/routing, subscriptions, smart-plan tasks). Call, text, email and site-activity (search / view / favorite) entries are NOT included, nor is note content (e.g. adding a note); edit/delete-note actions (types 17/18) are included. Each entry includes both the `timelineType` integer and a human-readable `timelineTypeDesc` name, so you do not need to map the integer yourself. Note: `startTime`/`endTime` query parameters are epoch milliseconds, but the `timelineTime`/`createTime`/`updateTime` fields in the response are ISO-8601 strings. Authorization: the authenticated user must be the lead's owner or the shared agent; otherwise 403 is returned. # Update Lead Source: https://developer.lofty.com/api-reference/leads/update-lead openapi/openapi.json PUT /v1.0/leads/{leadId} Updates an existing lead. Caller must have manage permission on the lead. ## Property block If the request body carries a `property` block, it is persisted alongside the lead update with the same semantics as `POST /v1.0/leads/{leadId}/property`: - **`property.id` omitted or `<= 0`** — merge with an existing property by address; if no match, create a new property record. (Prior versions of this endpoint rejected this case with `UPDATE_LEAD_PROPERTY_FAILED`; that restriction has been lifted.) - **`property.id > 0`** — update the named property in place. The property must belong to this lead (its `leadUserId` must match); otherwise the request is rejected with 200108 LEAD_PROPERTY_NOT_EXIST. Validation: - When a `property` block is supplied, at least one of `city`, `state`, `zipCode`, `streetAddress` must be non-blank; otherwise 200057 PROPERTY_ADDRESS_EMPTY is returned (the lead update is also aborted). **Silent truncation** — `firstName`, `lastName`, `groups`, `segments` are truncated to **30 characters**; `tags` / `tagsAdd` to **64 characters**. The API returns success with no error. Validate input length before sending. # Get Published Listings Source: https://developer.lofty.com/api-reference/listings/get-published openapi/openapi.json GET /v1.0/getPublishedListings Returns the caller team's published listings as an XML feed. Intended for external syndication partners; the response content type is application/xml. # Overview Source: https://developer.lofty.com/api-reference/listings/overview Understand the different listing search endpoints and when to use each. Lofty provides two listing endpoints for different use cases. ## Endpoint comparison | Endpoint | Method | Purpose | Response | | ---------------------------- | ------ | ------------------------------------ | -------- | | `/v1.0/listing` | GET | Search by agent, office, or MLS ID | JSON | | `/v1.0/getPublishedListings` | GET | Export team listings for syndication | XML | ## Which should I use? Look up active and sold listings for a specific agent, their office, or by MLS listing IDs. Best for agent portfolio pages and listing management. Export the listings your team has published to a site feed. Returns XML and is intended for syndication partners. **Get Published Listings** returns XML (not JSON) and is intended for syndication partners. Use `/v1.0/listing` for application integrations. # Search by Agent / Office Source: https://developer.lofty.com/api-reference/listings/search-listings-by-agent-office-or-mls-id openapi/openapi.json GET /v1.0/listing Searches active and sold listings for a specific agent (the caller by default), their office, or a set of MLS listing IDs. Notes: - limit must be in (0, 1000]; otherwise 400 LISTING_LIMIT_NOT_VALID. - listingId and soldListingId cannot be supplied together; doing so returns an error envelope on HTTP 200. - At least one of agentListings, officeListings, listingId, soldListingId, mlsListingIds, buildingName, or buildingAddress must identify the search target. - When userId differs from the caller, caller must have MANAGE_TEAM_AGENT permission. # Add a manual log entry Source: https://developer.lofty.com/api-reference/manual-logs/add-a-manual-log-entry openapi/openapi.json POST /v1.0/logType Creates a manual log entry (logCall, logEmail or logText) for the target lead. Notes: - Persistence is asynchronous. The response returns the new entry's ID once the write is enqueued; the entry becomes readable after a short delay. - Caller must have access to the lead (owner, shared agent, or assigned lender), and the lead must not be trashed. Otherwise 404 LEAD_NOT_EXIST. - leadId, logType and content are required for all channels. - content is silently truncated to 5000 characters. - outboundOrInbound must be exactly 'outbound' or 'inbound' when supplied; omitted values default to 'outbound'. - For logCall: leadPhoneNumber is required (max 20 characters). callingOutcome is optional (max 20 characters); values outside the known set are silently normalized to 'Talked' during persistence. - For logEmail: emailSubject / toEmail / fromEmail are optional and accepted as-is. # Delete a manual log entry Source: https://developer.lofty.com/api-reference/manual-logs/delete-a-manual-log-entry openapi/openapi.json DELETE /v1.0/logType/{logTypeId} Deletes a manual log entry by ID. Only entries whose logType is logCall, logEmail or logText can be deleted through this endpoint; deletion of other timeline types returns 404 RESOURCE_NOT_EXIST. Caller must have manage permission on the lead that owns the entry. The response body is empty. # Get a manual log entry by ID Source: https://developer.lofty.com/api-reference/manual-logs/get-a-manual-log-entry-by-id openapi/openapi.json GET /v1.0/logType/{logTypeId} Returns a single manual log entry by its ID. Returns 404 RESOURCE_NOT_EXIST when: - The ID does not exist or has been deleted. - The ID exists but its logType is not one of logCall, logEmail or logText. An ID returned by POST /v1.0/logType may briefly return 404 here until the asynchronous write has been applied. # List manual logs of a lead Source: https://developer.lofty.com/api-reference/manual-logs/list-manual-logs-of-a-lead openapi/openapi.json GET /v1.0/logType Returns the lead's manual-log entries for a single channel (logCall, logEmail or logText), paginated via offset+limit or cursor (currentId). Notes: - Only entries whose logType equals logCall, logEmail or logText are returned. Auto-captured communications are not included. - Caller must have access to the lead; otherwise 404 LEAD_NOT_EXIST. - Default mode (currentId=0): offset + limit pagination, ordered by sort and order. - Cursor mode (currentId > 0): the sort parameter is ignored; entries are ordered by id, with timelineTime as secondary key, matching the direction in order. - limit outside [1, 1000] is silently reset to 10; offset < 0 is silently reset to 0; offset + limit > 20000 returns 400 OFFSET_EXCEED_LIMIT. - sort=createTime (default) orders by the entry time as logged by the agent (usually equal to the record's creation time). sort=id orders by the entry's ID. - order=asc (default) returns oldest first; pass order=desc for newest first. # Send Task Reminder Push Source: https://developer.lofty.com/api-reference/notifications/send-app-push-for-taskappointment-reminder openapi/openapi.json POST /v2.0/sales-agent/notification/app-push/send-task-reminder Sends an in-app push notification to the agent assigned to the given task or appointment. # Send Email to Agent Source: https://developer.lofty.com/api-reference/notifications/send-system-email-to-agent openapi/openapi.json POST /v2.0/sales-agent/message/email/send-to-agent Sends a system email to the current logged-in user's email address. # Send SMS to Agent Source: https://developer.lofty.com/api-reference/notifications/send-system-sms-to-agent openapi/openapi.json POST /v2.0/sales-agent/message/sms/send-to-agent Sends a system SMS message to the current logged-in user's phone number. # Send Opportunity Alert Source: https://developer.lofty.com/api-reference/opportunity/send-an-opportunity-notification-to-the-leads-assi openapi/openapi.json POST /v1.0/agent/send-notification Sends an in-app notification to the agent(s) assigned to the given lead, representing a buyer/seller activity signal (e.g. viewed a listing, saved a search, requested CMA). Certain notificationType values also trigger SMS and Email delivery. **Use case:** a partner system observes a user action on its own site and wants that action to surface as an opportunity in the assigned agent's Lofty inbox. ## Delivery channels | Channel | Trigger | |---|---| | In-app push | All notificationType values | | SMS + Email | Browse (11/48/138), Saved listing (9/52/136), Saved search (27/140), Mortgage calculator (89), Return-to-site (166) | If the lead belongs to a lead pond, the notification is broadcast to all pond members instead of the single assignee. ## Important notes - The leadId **must** belong to the caller's own team. - Agents whose team has the OPPORTUNITY resource disabled are silently skipped. - This endpoint is **not idempotent**. Repeated calls produce repeated deliveries. - The `data` field in the response is a best-effort success hint, not a strict delivery confirmation. See the 200 response description for details. # Add Office Source: https://developer.lofty.com/api-reference/org/add-an-office-to-the-team openapi/openapi.json PUT /v1.0/org/office Creates a new office (organizational sub-unit) under the caller's team. Caller must have permission to manage team organization. # Get Organization Source: https://developer.lofty.com/api-reference/org/get-the-callers-organization-info openapi/openapi.json GET /v1.0/org Returns the organization structure the caller belongs to. Notes: - When the caller is part of a multi-team group (brokerage), orgType is MULTI_TEAM and multiTeamInfo carries the parent group's ID and name; enterpriseInfo is absent. - Otherwise orgType is SIGNAL_INSTANCE and enterpriseInfo describes the caller's office, visible sub-offices, and assigned permission profile. - visibleOrgInfoList depends on the caller's permissions: callers with MANAGE_ORGANIZATION see all offices in the company; callers with MANAGE_ORGANIZATION_DEPARTMENT see their own office's sub-offices; others receive an empty list. # List Permission Profiles Source: https://developer.lofty.com/api-reference/org/list-permission-profiles-available-to-the-team openapi/openapi.json GET /v1.0/org/permission/profiles Returns the permission profiles (role templates) defined on the caller's team. Use these IDs when assigning roles to team members. Caller must have MANAGE_TEAM_AGENT or MANAGE_PERMISSION_PROFILE permission; otherwise 400 VENDOR_HAS_NO_PERMISSION. # Update Office Source: https://developer.lofty.com/api-reference/org/update-an-office openapi/openapi.json POST /v1.0/org/office Updates an existing office under the caller's team. Caller must have permission to manage team organization. # Update Company Source: https://developer.lofty.com/api-reference/org/update-the-teams-company-info openapi/openapi.json POST /v1.0/org/company Updates the company record associated with the caller's team. Caller must have permission to manage team organization. # Get default (supplement) routing rule Source: https://developer.lofty.com/api-reference/routing/get-default-supplement-routing-rule openapi/openapi.json GET /v1.0/routing/rule/supplement/{type} Returns the default / fallback routing rule configured on the caller's team for the given business type. Caller must have MANAGE_LEAD_DISTRIBUTION or MANAGE_LEAD_DISTRIBUTION_DEPARTMENT permission. # List assign roles Source: https://developer.lofty.com/api-reference/routing/list-assign-roles openapi/openapi.json GET /v1.0/routing/role/list Returns the assignable roles defined on the caller's team. Use the returned IDs when the routing business type is ASSISTANT_LEAD. Caller must have MANAGE_LEAD_DISTRIBUTION or MANAGE_LEAD_DISTRIBUTION_DEPARTMENT permission. # List members available for assignment Source: https://developer.lofty.com/api-reference/routing/list-members-available-for-assignment openapi/openapi.json GET /v1.0/routing/member/list/{type} Returns members that can be assigned when routing leads of the given business type. Caller must have MANAGE_LEAD_DISTRIBUTION or MANAGE_LEAD_DISTRIBUTION_DEPARTMENT permission. # Update a routing rule Source: https://developer.lofty.com/api-reference/routing/update-a-routing-rule openapi/openapi.json PUT /v1.0/routing/rule/{type} Updates a routing rule identified by its ID. Validates activeHours and routingStrategy: - activeDays: list of 1-7; deduplicated and sorted server-side. - hoursStart / hoursEnd: each in [0, 23]. - routingStrategy.strategyType must match the business type. LEAD_POND is only allowed for AGENT_LEAD; NEXT_UP and BLAST_ALERT are not allowed for ASSISTANT_LEAD. - memberWeights must reference valid members in the team. - touchMinutes must be in (0, 1440]; when > 60, must be an integer multiple of 60. Caller must have MANAGE_LEAD_DISTRIBUTION or MANAGE_LEAD_DISTRIBUTION_DEPARTMENT permission. # Update default (supplement) routing rule Source: https://developer.lofty.com/api-reference/routing/update-default-supplement-routing-rule openapi/openapi.json PUT /v1.0/routing/rule/supplement/{type} Updates the default / fallback routing rule for the given business type. Notes: - BLAST_ALERT and Next_Up strategies are not allowed for ASSISTANT_LEAD. - When strategyType = DEFAULT_ASSIGNEE and type = AGENT_LEAD, exactly one of assigneeAgentId or assigneeGroupId must be supplied. - For non-DEFAULT_ASSIGNEE strategies, touchMinutes must be in (0, 1440] and, when > 60, an integer multiple of 60. Caller must have MANAGE_LEAD_DISTRIBUTION or MANAGE_LEAD_DISTRIBUTION_DEPARTMENT permission. # Batch add leads to Sales Agent working pool Source: https://developer.lofty.com/api-reference/sales-agents/batch-add-leads-to-sales-agent-working-pool openapi/openapi.json POST /v2.0/working-leads/add Adds multiple leads to the AI Sales Agent working pool. Optionally creates plan tasks for each successfully added lead. Returns succeeded and failed lead IDs. Max 100 leads per request. # Batch create plan tasks for leads Source: https://developer.lofty.com/api-reference/sales-agents/batch-create-plan-tasks-for-leads openapi/openapi.json POST /v2.0/plan-tasks/create Creates AI plan tasks for multiple leads. Each lead must already be in the Sales Agent working pool. Returns succeeded and failed lead IDs. Max 100 leads per request. # Check if lead is a working lead Source: https://developer.lofty.com/api-reference/sales-agents/check-if-lead-is-a-working-lead openapi/openapi.json GET /v2.0/sales-agents/working-lead/{leadId} Checks whether a lead is currently in the Sales Agent's AI working pool. Returns true if the lead is actively being followed up, false otherwise. # Get current user's Sales Agent Source: https://developer.lofty.com/api-reference/sales-agents/get-current-users-sales-agent openapi/openapi.json GET /v2.0/sales-agents/current Retrieves the Sales Agent (AI Assistant) configuration for the current logged-in user. Returns detailed information including assistant profile, virtual contact info, and quota settings. # Get current user's Sales Agent quota Source: https://developer.lofty.com/api-reference/sales-agents/get-current-users-sales-agent-quota openapi/openapi.json GET /v2.0/sales-agents/quota Retrieves AI quota information for the current logged-in user, including total quota, used quota, remaining quota, and borrow status. # Get plan tasks by lead ID Source: https://developer.lofty.com/api-reference/sales-agents/get-plan-tasks-by-lead-id openapi/openapi.json GET /v2.0/plan-tasks/lead/{leadId} Retrieves AI tasks associated with a lead. Tasks are returned in V2 format with human-friendly status and type names. # Get Sales Agent by lead ID Source: https://developer.lofty.com/api-reference/sales-agents/get-sales-agent-by-lead-id openapi/openapi.json GET /v2.0/sales-agents/by-lead Retrieves the Sales Agent (AI Assistant) configuration associated with the specified lead. # Get Sales Agent lead mute status Source: https://developer.lofty.com/api-reference/sales-agents/get-sales-agent-lead-mute-status openapi/openapi.json GET /v2.0/sales-agent/lead/mute-status Returns the mute status of the AI assistant for the specified lead. # Mute a working lead Source: https://developer.lofty.com/api-reference/sales-agents/mute-a-working-lead openapi/openapi.json PUT /v2.0/sales-agents/working-lead/{leadId}/mute Mutes the AI assistant for the specified lead to stop further automated follow-ups. Verifies success by checking mute status after operation. # Query Sales Agent settings Source: https://developer.lofty.com/api-reference/sales-agents/query-sales-agent-settings openapi/openapi.json GET /v2.0/sales-agent/settings Returns the Sales Agent settings configured for the current caller. # Query working leads (paginated) Source: https://developer.lofty.com/api-reference/sales-agents/query-working-leads-paginated openapi/openapi.json GET /v2.0/working-leads Retrieves a paginated list of leads currently being followed up by AI Sales Agent. Supports filtering by AI stage (HIGH_PRIORITY, AI_PROSPECTING, AI_MONITORING). Returns lead basic info with AI stage, task counts, and timestamps. # Send SMS to agent via AI number Source: https://developer.lofty.com/api-reference/sales-agents/send-sms-to-agent-via-ai-number openapi/openapi.json POST /v2.0/sales-agent/ai-number/send-sms-to-agent Sends an SMS message to the current logged-in agent using the Sales Agent's AI virtual number as the sender. # Update Sales Agent settings Source: https://developer.lofty.com/api-reference/sales-agents/update-sales-agent-settings openapi/openapi.json PUT /v2.0/sales-agent/settings Update the Sales Agent settings for the current user. Sales Agent creation must be completed in CRM. This API only supports updating existing settings. # Overview Source: https://developer.lofty.com/api-reference/tasks/overview Manage tasks, appointments, and calendar events in Lofty. The Task & Calendar API lets you create, update, and track tasks and calendar events associated with leads. Tasks and appointments share a unified namespace in V2 — each endpoint resolves the target by ID regardless of type. All endpoints require a valid Bearer token in the `Authorization` header. See [Authentication](/authentication/overview) for details. ## Endpoints ### Tasks | Method | Path | Description | | -------- | ------------------------------- | -------------------------------------------------------- | | `GET` | `/v2.0/tasks` | List tasks for a lead | | `POST` | `/v2.0/tasks` | Create a task or appointment | | `GET` | `/v2.0/tasks/{taskId}` | Get a task by ID | | `PUT` | `/v2.0/tasks/{taskId}` | Update a task or appointment | | `DELETE` | `/v2.0/tasks/{taskId}` | Delete a task or appointment | | `POST` | `/v2.0/tasks/{taskId}/finish` | Mark a task as completed | | `POST` | `/v2.0/tasks/{taskId}/unfinish` | Reopen a completed task | | `GET` | `/v2.0/tasks/my-tasks` | List tasks and appointments assigned to the current user | ### Calendar | Method | Path | Description | | -------- | -------------------------------------- | ---------------------------- | | `GET` | `/v2.0/calendar` | List calendar events | | `POST` | `/v2.0/calendar` | Create a calendar event | | `PUT` | `/v2.0/calendar/{calendarId}` | Update a calendar event | | `DELETE` | `/v2.0/calendar/{calendarId}` | Delete a calendar event | | `POST` | `/v2.0/calendar/{calendarId}/finish` | Mark event as completed | | `POST` | `/v2.0/calendar/{calendarId}/unfinish` | Reopen a completed event | | `GET` | `/v2.0/calendar/meetings/available` | List available meeting slots | ## Legacy (V1) V1 endpoints are still supported but will not receive new features. **Use V2 for all new integrations.** | Method | Path | Description | | -------- | ---------------------- | ----------------- | | `GET` | `/v1.0/tasks` | List tasks | | `POST` | `/v1.0/tasks` | Create a task | | `GET` | `/v1.0/tasks/{taskId}` | Get a task by ID | | `PUT` | `/v1.0/tasks/{taskId}` | Update a task | | `DELETE` | `/v1.0/tasks/{taskId}` | Delete a task | | `GET` | `/v1.0/appts` | List appointments | In V1, tasks and appointments use separate endpoints. V2 unifies them — a single `POST /v2.0/tasks` can create both types, distinguished by a type field. ## Related resources * [Webhooks — Task events](/concepts/webhooks#10-task) — receive real-time notifications on task changes * [Webhooks — Appointment events](/concepts/webhooks#11-appointment) — receive real-time notifications on appointment changes # Create Task (V1) Source: https://developer.lofty.com/api-reference/tasks/v1/create-task openapi/openapi.json POST /v1.0/tasks Creates a task on the target lead. Notes: - content, leadId, deadline, type and assignedRole are required. - Caller must have manage permission on the lead. - The response contains only the new task's ID under the 'taskId' key. # Delete Task (V1) Source: https://developer.lofty.com/api-reference/tasks/v1/delete-task openapi/openapi.json DELETE /v1.0/tasks/{taskId} Deletes a task by ID. Caller must have manage permission on the task's lead. The response body is empty. # Get Task (V1) Source: https://developer.lofty.com/api-reference/tasks/v1/get-task openapi/openapi.json GET /v1.0/tasks/{taskId} Returns a single task by ID, along with the lead's pipeline context. Caller must have manage permission on the lead that owns the task. # List Appointments Source: https://developer.lofty.com/api-reference/tasks/v1/list-appointments openapi/openapi.json GET /v1.0/appts Returns appointments attached to the given lead. Caller must have manage permission on the lead. # List Tasks (V1) Source: https://developer.lofty.com/api-reference/tasks/v1/list-tasks openapi/openapi.json GET /v1.0/tasks Returns tasks attached to the given lead. Caller must have manage permission on the lead. # Update Task (V1) Source: https://developer.lofty.com/api-reference/tasks/v1/update-task openapi/openapi.json PUT /v1.0/tasks/{taskId} Partially updates an existing task. Fields not supplied are left unchanged. Notes: - When finishFlag is true, the task is marked completed and all other fields are ignored. Completion is rejected for smart-plan tasks (TASK_IS_SMARTPLAN). - When leadId is supplied, it must match the task's existing lead; otherwise 400 INVALID_UPDATE_OPERATION_LEADID. - At least one field must be supplied; an entirely empty payload returns 400 MISSING_PARAMETER. - Caller must have manage permission on the task's lead. - The response body is empty. # Create Task Source: https://developer.lofty.com/api-reference/tasks/v2/create-task openapi/openapi.json POST /v2.0/tasks Creates a new task or appointment on the target lead. Whether an appointment or a regular task is created depends on the payload's type field. Notes: - content, assignedRole, leadId and endAt are required. - Caller must have manage permission on the lead. - The response contains only the new entry's ID under the 'taskId' key. # Delete Task Source: https://developer.lofty.com/api-reference/tasks/v2/delete-task openapi/openapi.json DELETE /v2.0/tasks/{taskId} Deletes a task or appointment by ID. Caller must have manage permission on the entry's lead. # Finish Task Source: https://developer.lofty.com/api-reference/tasks/v2/finish-task openapi/openapi.json POST /v2.0/tasks/{taskId}/finish Marks a task or appointment as completed. Caller must have manage permission on the entry's lead. Smart-plan tasks cannot be finished through this endpoint (TASK_IS_SMARTPLAN). # Get Task Source: https://developer.lofty.com/api-reference/tasks/v2/get-task openapi/openapi.json GET /v2.0/tasks/{taskId} Returns a single task or appointment by ID. Tasks and appointments share the same taskId namespace; the endpoint resolves the target automatically. Caller must have manage permission on the task's lead. # List Tasks Source: https://developer.lofty.com/api-reference/tasks/v2/list-tasks openapi/openapi.json GET /v2.0/tasks Returns all tasks and appointments attached to the given lead. Caller must have manage permission on the lead. # My Tasks Source: https://developer.lofty.com/api-reference/tasks/v2/my-tasks openapi/openapi.json GET /v2.0/tasks/my-tasks Returns tasks assigned to the caller, sorted by startTime descending. Supports cursor-based pagination via currentId (offset) + limit. Notes: - currentId must be >= 0 (INVALID_PARAMETER otherwise). - limit must be in [1, 100] (LIMIT_NOT_VALID otherwise). - timeZoneCode selects how start/end times are formatted; when omitted, the caller's configured timezone is used. # Unfinish Task Source: https://developer.lofty.com/api-reference/tasks/v2/unfinish-task openapi/openapi.json POST /v2.0/tasks/{taskId}/unfinish Reverts a task or appointment back to the not-completed state. Caller must have manage permission on the entry's lead. # Update Task Source: https://developer.lofty.com/api-reference/tasks/v2/update-task openapi/openapi.json PUT /v2.0/tasks/{taskId} Partially updates a task or appointment. Only non-null fields in the request body are applied. Notes: - To mark an entry as completed or not, use the finish / unfinish endpoints instead. - At least one of content, startAt, endAt, address must be supplied; an entirely empty payload returns 400 MISSING_PARAMETER. - Caller must have manage permission on the entry's lead. # Add Custom Field Source: https://developer.lofty.com/api-reference/team-features/add-a-lead-custom-field openapi/openapi.json POST /v1.0/teamFeatures/custom-field Creates a new lead-level custom field on the caller's team. Notes: - attributeName is required and is trimmed; empty names are rejected. - attributeType is required. Supported types: text, number, date, anniversary_date, single_select, multi_select, percentage, currency. - anniversary_date: a date field that recurs annually (e.g. birthdays, work anniversaries). Accepts the same date format as date. - The response body is a plain string message. # Get Lead Pond Source: https://developer.lofty.com/api-reference/team-features/get-a-lead-pond-by-id openapi/openapi.json GET /v1.0/team-features/lead-pond/{id} Returns a single lead pond by ID. The pond must belong to the caller's team and be visible to the caller (office-level lead-pond manage permission). If it is not, the request fails with 400 Permission denied; the same error is returned whether the pond does not exist or simply is not visible, so existence is not disclosed. # List Custom Fields Source: https://developer.lofty.com/api-reference/team-features/list-lead-custom-field-definitions openapi/openapi.json GET /v1.0/teamFeatures/listCustomField Returns the lead-level custom field definitions configured on the caller's team. Use the returned IDs when populating custom field values on leads. # List Lead Pipelines Source: https://developer.lofty.com/api-reference/team-features/list-lead-pipelines openapi/openapi.json GET /v1.0/teamFeatures/lead-pipelines Returns a page of lead pipeline entries defined on the caller's team. Use the returned lead pipeline IDs when assigning or filtering leads. Notes: - currPage is zero-based. - Pass a non-empty name to filter lead pipeline entries by a name substring; omit it to list all. - _metadata.total reflects the full result size before pagination. # List Lead Ponds Source: https://developer.lofty.com/api-reference/team-features/list-lead-ponds-visible-to-the-caller openapi/openapi.json GET /v1.0/team-features/lead-ponds Returns lead ponds on the caller's team that the caller is allowed to see. # List Tags Source: https://developer.lofty.com/api-reference/team-features/list-lead-tags-visible-to-the-caller openapi/openapi.json GET /v1.0/teamFeatures/listTag Returns lead tags visible to the caller within the team. Includes both team-wide tags and tags scoped to the caller. # Get a team member by account (email) Source: https://developer.lofty.com/api-reference/team/get-a-team-member-by-account-email openapi/openapi.json GET /v1.0/members/{account} Returns a team member of the caller's team identified by their login email. The member must have accepted the team invitation. # Get a team member by user ID (deprecated) Source: https://developer.lofty.com/api-reference/team/get-a-team-member-by-user-id-deprecated openapi/openapi.json GET /v1.0/users/{userId} Returns a team member of the caller's team by user ID. Deprecated: prefer GET /v1.0/members/{account} when only the email is available, or GET /v1.0/members with pagination for directory listing. # Get the current user's profile Source: https://developer.lofty.com/api-reference/team/get-the-current-users-profile openapi/openapi.json GET /v1.0/me Returns the team-member profile of the caller, resolved from the authentication token. # Team Members Source: https://developer.lofty.com/api-reference/team/members openapi/openapi.json GET /v1.0/members Returns members of the caller's team, optionally filtered by office / group IDs, with pagination metadata. Notes: - limit must not exceed the configured maximum; oversized values return 400 LIMIT_PARAM_OVERSIZE. - offset < 0 is silently reset to 0. - total in _metadata reflects the full result size before pagination. - When groupIds is omitted, all members visible to the caller are returned. # Lead Routing Rules Source: https://developer.lofty.com/api-reference/team/routing openapi/openapi.json GET /v1.0/routing/rule/list/{type} Returns the routing rules configured on the caller's team for the given business type. Caller must have MANAGE_LEAD_DISTRIBUTION or MANAGE_LEAD_DISTRIBUTION_DEPARTMENT permission. # Brokermint — Sync Transaction Source: https://developer.lofty.com/api-reference/transactions/brokermint-webhook-update-a-synced-transaction openapi/openapi.json PUT /v1.0/brokermint/transaction Callback endpoint for Brokermint integration to sync transaction updates into Lofty. The transaction must be linked to Brokermint via the Lofty UI before this endpoint can update it. See the request body schema for supported fields. # Create Transaction Source: https://developer.lofty.com/api-reference/transactions/create-transaction openapi/openapi.json POST /v1.0/leads/{leadId}/transaction Creates a new transaction under the given lead. # Get Transaction Source: https://developer.lofty.com/api-reference/transactions/get-transaction openapi/openapi.json GET /v1.0/leads/{leadId}/transaction/{transactionId} Fetches a single transaction that belongs to the given lead. Prerequisites: - leadId must be manageable by the caller; otherwise 404 LEAD_NOT_EXIST. - transactionId must exist, not be deleted, AND belong to leadId; otherwise 404 TRANSACTION_NOT_EXIST. For security, cross-lead transactionIds are treated as 'not exist' rather than 'forbidden'. # Get Property Address Source: https://developer.lofty.com/api-reference/transactions/get-transaction-property-address openapi/openapi.json GET /v1.0/leads/{leadId}/transaction/{transactionId}/property/address Returns the property address associated with a transaction. # List Transaction Custom Fields Source: https://developer.lofty.com/api-reference/transactions/list-transaction-custom-field-definitions openapi/openapi.json GET /v1.0/transaction/customfields Returns the team-level custom field definitions applicable to transactions. Use the returned ids when populating LeadTransaction.customFields on create / update. Scope: the caller's team. # List Transaction Pipelines Source: https://developer.lofty.com/api-reference/transactions/list-transaction-pipelines openapi/openapi.json GET /v2.0/transactions/pipelines Returns the team's full transaction pipeline configuration: for each transaction type (Purchase, Listing, Lease, Other), the ordered list of stages with their IDs, names and semantic flags. Use this to discover the valid status values before creating or updating transactions — stage names are customizable per team, so they must not be hardcoded. # List Transactions Source: https://developer.lofty.com/api-reference/transactions/list-transactions openapi/openapi.json GET /v2.0/transactions Search and list transactions with pagination and optional keyword filter. # List by Lead Source: https://developer.lofty.com/api-reference/transactions/list-transactions-for-a-lead openapi/openapi.json GET /v1.0/leads/{leadId}/transactions Returns all transactions attached to the given lead. # Update Transaction Source: https://developer.lofty.com/api-reference/transactions/partially-update-a-transaction openapi/openapi.json PUT /v1.0/leads/{leadId}/transaction/{transactionId} Partially updates an existing transaction. Only supplied fields are applied; omitted fields remain unchanged. `transactionStatus` is writable: supply the stage's display name exactly as it appears in the team's transaction pipeline for the given `transactionType` (case-insensitive, e.g. "Under Contract"). A status name that matches no stage in that pipeline is rejected (200125); omit the field to keep the current status. # Update Property Address Source: https://developer.lofty.com/api-reference/transactions/update-transaction-property-address openapi/openapi.json POST /v1.0/leads/{leadId}/transaction/property/address Updates the property address of a transaction. The `transactionId` is carried in the request body, not the URL. **Full replacement** — omitted fields are set to null. To update a single field, GET the current address first, modify, then resubmit the whole object. # List Vendors Source: https://developer.lofty.com/api-reference/vendor/list-vendors-of-the-callers-team openapi/openapi.json GET /v1.0/vendor/list Returns the vendor directory for the team .Notes: - Scope: the list is scoped to the caller's team (teamId is resolved from the token). - Ordering is not guaranteed; do not rely on a specific sort order. - Only the fields defined in VendorInfo are exposed; internal fields are not returned. - The response is a bare JSON array; empty array when the team has no vendors. # Create Webhook Source: https://developer.lofty.com/api-reference/webhooks/create-webhook openapi/openapi.json POST /v1.0/webhook Subscribes the caller's team to a webhook event type. See the Webhooks tag description for the full list of event types and delivery rules. For detailed payload shapes per event type, see [Webhook Event Payloads](/concepts/webhooks#event-payloads). # Delete Webhook Source: https://developer.lofty.com/api-reference/webhooks/delete-webhook openapi/openapi.json DELETE /v1.0/webhook/{subscribeId} Deletes a webhook subscription by ID. The subscription must belong to the caller's team. The response body is empty. # List Webhooks Source: https://developer.lofty.com/api-reference/webhooks/list-webhooks openapi/openapi.json GET /v1.0/webhooks Returns all webhook subscriptions configured for the caller's team. See the Webhooks tag description for the full list of event types. For detailed payload shapes per event type, see [Webhook Event Payloads](/concepts/webhooks#event-payloads). # API Keys Source: https://developer.lofty.com/authentication/api-keys Authenticate with the Lofty API using a personal access token (API key). API keys (personal access tokens) are user-scoped credentials for authenticating with the Lofty API. They are well suited for personal automation, scripts, CLI usage, and server-side integrations where the developer controls the environment. The Lofty CLI uses API keys as `LOFTY_CUSTOMER_KEY` for the [Client Credentials flow](/cli/authentication). API keys are a fully supported authentication method — not just for testing. ## Obtain your API key Navigate to **Settings → Integrations → API** in your Lofty account to view or generate your key. Each key is bound to the account that generated it. Requests authenticated with that key operate within that account's permissions and carry a `THIRD_PARTY_OPERATION` scope. ## Key properties | Property | Description | | -------------- | -------------------------------------------------------------------------------------------------------------- | | **Scope** | `THIRD_PARTY_OPERATION` — enforced at token verification | | **Expiration** | Configurable. Keys can have an `expires_at` date set at creation. Check your key's expiry in account settings. | | **Revocation** | Can be revoked immediately in account settings | | **Format** | JWS token with metadata (not a static string) | ## Authenticate requests Pass your API key in the `Authorization` header with the `token` prefix: ```bash theme={null} curl https://api.lofty.com/v1.0/leads \ -H "Content-Type: application/json" \ -H "Authorization: token " ``` Or use the Lofty CLI: ```bash theme={null} export LOFTY_CUSTOMER_KEY="" lofty-cli leads list --limit 10 ``` ## Security considerations * **Do not** expose keys in client-side code, public repositories, or logs. * **Do not** share keys with external vendors or third parties. * Keys are not subject to Lofty's security review process — you are responsible for access control. * If a key is compromised, revoke it immediately in your account settings and generate a new one. * Monitor your key's expiration date and rotate before it expires. ## When to use each method | Scenario | Recommended | | -------------------------------------- | -------------------------------------- | | Personal automation or scripts | API Key | | CLI usage (`lofty-cli`) | API Key (as `LOFTY_CUSTOMER_KEY`) | | Server-to-server (no user interaction) | OAuth 2.0 Client Credentials + API Key | | Third-party multi-tenant integrations | OAuth 2.0 Authorization Code | | User-facing applications / SPAs | OAuth 2.0 with PKCE | # Error Codes Source: https://developer.lofty.com/authentication/error-codes Complete reference of error codes returned by the Lofty API. The Lofty API uses standard HTTP status codes combined with a `code` field in the response body for detailed error identification. ## Response format Error responses follow this structure: ```json theme={null} { "code": 200100, "message": "Sorry, the vendor does not have permission for this API." } ``` ## HTTP status codes | Status | Meaning | | ------ | ----------------------------------------------------------- | | `200` | Success | | `400` | Bad Request — invalid parameters or validation failure | | `401` | Unauthorized — missing or invalid credentials | | `403` | Forbidden — valid token but insufficient permissions | | `404` | Not Found — resource does not exist or caller has no access | | `429` | Too Many Requests — rate limit exceeded | | `500` | Internal Server Error | ## Authentication & permission errors | Code | HTTP | Description | | -------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `200100` | 403 | OAuth token lacks the required permission scope. Grant the scope in the [Developer Portal](https://api.lofty.com/vendor/frontend/static/index.html#/login) and re-authorize. | | `BAD_CREDENTIALS` | 401 | Invalid API key or access token. | | `VENDOR_HAS_NO_PERMISSION` | 403 | Vendor application does not have access to this API. | | `PERMISSION_DENIED` | 403 | Caller does not have the required role for this operation (e.g. accessing another agent's tasks). | | `NO_ACCESS_WEBSITE` | 403 | Caller has no access to the specified user's listing data. | | `ACCESS_ALL_TEAM_LEADS` | 403 | Team-wide communication search requires the `ACCESS_ALL_TEAM_LEADS` permission. | | `20017` | 400 or 403 | Permission denied. Meaning depends on the endpoint — e.g. on `GET /v1.0/team-features/lead-pond/{id}` it means the pond does not exist, is disabled, belongs to another team, or is outside the caller's manageable offices (these cases are intentionally indistinguishable). | ## Validation errors (400) | Code | Description | | ------------------------- | ---------------------------------------------------------------- | | `INVALID_PARAMETER` | One or more request parameters are invalid or missing. | | `MISSING_PARAMETER` | A required parameter was not provided. | | `LIMIT_NOT_VALID` | `limit` parameter is out of the allowed range (typically 1–100). | | `LIMIT_PARAM_OVERSIZE` | `limit` exceeds the configured maximum for this endpoint. | | `PARAMETER_TOO_LONG` | A string parameter exceeds the maximum allowed length. | | `LISTING_LIMIT_NOT_VALID` | Listing search `limit` is out of range. | ## Resource not found (404) | Code | Description | | ---------------------------- | ------------------------------------------------------------- | | `LEAD_NOT_EXIST` | The specified lead was not found or the caller has no access. | | `LEAD_NOT_FOUND` | Lead not found (used by some V2 endpoints). | | `TASK_NOT_EXIST` | The specified task or appointment was not found. | | `TRANSACTION_NOT_EXIST` | The specified transaction was not found. | | `USER_NOT_EXIST` | The specified user was not found. | | `RESOURCE_NOT_EXIST` | Generic — the requested resource was not found. | | `ALERT_CONDITION_FEGIN_FAIL` | Listing alert condition lookup failed. | ## Lead errors (400) | Code | Description | | --------------------------------- | ------------------------------------------------- | | `PARAM_FIRST_NAME_FILL` | `firstName` is required when resolving assignees. | | `PARAM_EMAIL_MUST_FILL` | `email` is required when resolving assignees. | | `INVALID_UPDATE_OPERATION_LEADID` | Invalid lead ID in update operation. | ## Transaction errors (400) | Code | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `TRANSACTION_NAME_BLANK` | Transaction name cannot be empty. | | `TRANSACTION_NAME_HTML` | Transaction name cannot contain HTML. | | `200125` | `TRANSACTION_STATUS_INVALID` — on `PUT /v1.0/leads/{leadId}/transaction/{transactionId}`, the supplied `transactionStatus` does not match any stage configured in the team's pipeline for the transaction's `transactionType`. | ## Agent lifecycle errors (200 with error code) `POST /v2.0/agents/{agentId}/deactivate` and `POST /v2.0/agents/{agentId}/reactivate` always return HTTP `200` once authentication passes. Check the response body's `code` field (absent or `0` means success) rather than the status code. | Code | Description | | --------- | ----------------------------------------------------------------------------- | | `990105` | Agent is already deactivated. | | `990106` | Target agent is an office or team owner and cannot be deactivated. | | `990107` | Caller attempted to deactivate their own account. | | `990112` | Agent is already active. | | `990113` | Agent has not completed onboarding / no password set. | | `310111` | `SEAT_INSUFFICIENT` — no free license seat available to reactivate the agent. | | `1900012` | Caller lacks `MANAGE_TEAM_AGENT` / `MANAGE_TEAM_AGENT_DEPARTMENT` permission. | ## Task errors (400) | Code | Description | | ------------------- | -------------------------------------------------- | | `TASK_IS_SMARTPLAN` | Cannot modify a task that belongs to a Smart Plan. | ## Team & custom field errors (400) | Code | Description | | ---------------------- | ------------------------------ | | `ATTRIBUTE_NAME_EMPTY` | Custom field name is required. | | `ATTRIBUTE_TYPE_EMPTY` | Custom field type is required. | ## Routing errors (400) | Code | Description | | ------------------------- | ---------------------------------------------- | | `BUSINESS_TYPE_ERROR` | Invalid routing business type. | | `STRATEGY_TYPE_ERROR` | Invalid routing strategy type. | | `STRATEGY_TYPE_NOT_MATCH` | Routing strategy type does not match the rule. | | `INVAID_ROLE_ID` | Invalid role ID. | | `INVAID_ID` | Invalid routing rule ID. | | `INVAID_GROUP_ID` | Invalid group/office ID. | | `INVAID_ACTIVE_HOURS` | Invalid active hours configuration. | | `INVAID_ACTIVE_DAY` | Invalid active day configuration. | | `INVAID_MEMBER_WEIGHT` | Invalid member weight value. | | `INVAID_TOUCHMINUTES` | Invalid touch minutes value. | ## Webhook errors (400) | Code | Description | | ------------------------------ | ------------------------------------------------ | | `WEBHOOK_CALLBACK_URL_INVALID` | Callback URL is not a valid HTTPS URL. | | `WEBHOOK_EXCEED_LIMIT` | Maximum number of webhook subscriptions reached. | | `WEBHOOK_DELETE_NOT_EXIST` | Subscription ID does not exist. | | `WEBHOOK_DELETE_NO_AUTHORITY` | Caller does not own this subscription. | ## Rate limit (429) When rate limited, the response includes: ```http theme={null} HTTP/1.1 429 X-RateLimit-Limit: 20 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 21000 {"message":"Too Many Requests"} ``` See [Rate Limiting](/authentication/rate-limits) for details. ## Handling errors ```javascript theme={null} const response = await fetch('https://api.lofty.com/v1.0/leads', { headers: { 'Authorization': 'Bearer YOUR_TOKEN' } }); if (!response.ok) { const error = await response.json(); console.error(`HTTP ${response.status}: ${error.code} — ${error.message}`); } ``` Always check both the HTTP status code **and** the `code` field in the response body. Some business errors return specific codes (like `200100`) that provide more detail than the HTTP status alone. # OAuth 2.0 Source: https://developer.lofty.com/authentication/oauth2 Set up OAuth 2.0 authentication for your Lofty API integration. OAuth 2.0 is the recommended authentication method for multi-tenant integrations. Lofty supports two grant types: | Grant Type | Use Case | | ----------------------------- | --------------------------------------------------- | | **Authorization Code** | Web apps where a user authorizes access via browser | | **Authorization Code + PKCE** | SPAs and native apps (no client secret required) | ## Prerequisites You need a **Developer Platform account** and a **registered application**. Sign up at the [Lofty Developer Portal](https://api.lofty.com/vendor/frontend/static/index.html#/login). ## Authorization Code flow Create a new application in the Developer Portal. Provide your app name, description, and redirect URI(s). New applications start in **Development Mode** — you can test the full OAuth flow without review. After registration, you'll receive a **Client ID** and **Client Secret**. Never expose your Client Secret in client-side code, public repositories, or logs. Exchange your credentials for an access token via the OAuth 2.0 authorization code flow. ```bash theme={null} curl -X POST https://crm.lofty.com/api/user-web/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "Authorization: Basic " \ -d "grant_type=authorization_code" \ -d "code=" \ -d "client_id=" \ -d "redirect_uri=" ``` The `Authorization` header uses HTTP Basic authentication: Base64-encode `:` to produce the credential string. For complete token endpoint details, see the [OAuth 2.0 API Reference](https://help.lofty.com/hc/en-us/articles/47801571926811). Pass the access token in the `Authorization` header: ```bash theme={null} curl https://api.lofty.com/v1.0/leads \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " ``` ## PKCE flow For SPAs and native apps that cannot securely store a client secret. Uses a code verifier/challenge instead. ```bash theme={null} curl -X POST https://crm.lofty.com/api/user-web/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "client_id=" \ -d "code=" \ -d "code_verifier=" \ -d "redirect_uri=" ``` No `client_secret` is required. The Lofty CLI uses this flow for interactive browser login (`lofty-cli auth login-browser`). ## Permission scopes Each token is scoped to the endpoints granted to your application in the Developer Portal. Calling an out-of-scope endpoint returns `HTTP 403`: ```json theme={null} { "code": 200100, "message": "Sorry, the vendor does not have permission for this API." } ``` **Adding scopes is a breaking change.** When you add new permission scopes to a production app, all existing tokens are invalidated (`authorized_api_ids` changes). Existing users must re-authorize your app. Always notify users before adding scopes. ## App lifecycle | Stage | Description | | ---------------- | ----------------------------------------------------------------- | | **Development** | Test the full OAuth flow without review. Rate limit: 100/min. | | **Under Review** | Submitted for production access. | | **Production** | Approved. Real users can authorize your app. Rate limit: 500/min. | | **Declined** | Not approved. Edit and resubmit at any time. | ## Updating a production app Once in production, edits require review before taking effect. Your app continues operating with the current approved configuration during review. **Non-breaking changes** — app name, description, redirect URIs, removing scopes. Existing connections are unaffected. **Breaking changes** — adding new permission scopes invalidates existing tokens. Users must re-authorize. Notify users before submitting. No. Edit controls are locked during review. Cancel the pending edit first if you need to make urgent changes. Your app continues operating with its current configuration. You'll receive the decline reason via email and can resubmit after making adjustments. # Overview Source: https://developer.lofty.com/authentication/overview Authenticate with the Lofty API using OAuth 2.0 or API keys. All API requests require authentication via the `Authorization` header. | Method | Header format | Best for | | ------------- | ----------------------- | ------------------------------------------- | | **OAuth 2.0** | `Bearer ` | Multi-tenant apps, third-party integrations | | **API Key** | `token ` | Personal automation, scripts, CLI | ## Which method should I use? | Scenario | Method | Grant Type | | ----------------------------- | --------- | -------------------------------------------------- | | CLI tool (`lofty-cli`) | API Key | Used as `LOFTY_CUSTOMER_KEY` in Client Credentials | | Personal scripts / automation | API Key | Direct `token` header | | Server-to-server (no user) | OAuth 2.0 | Client Credentials + `customer_key` | | Web app (user authorizes) | OAuth 2.0 | Authorization Code | | SPA / mobile app | OAuth 2.0 | Authorization Code + PKCE | | Multi-tenant SaaS platform | OAuth 2.0 | Authorization Code | Three grant types: Authorization Code, PKCE, and Client Credentials. Scoped permissions with security review. User-scoped personal access tokens. Configurable expiration and `THIRD_PARTY_OPERATION` scope. ## Base URL ``` https://api.lofty.com ``` ## Request format ```bash theme={null} curl https://api.lofty.com/v1.0/leads \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " ``` Or via CLI: ```bash theme={null} lofty-cli leads list --limit 10 ``` ## Common errors | HTTP Status | Code | Description | | ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `401` | — | Invalid or missing authentication credentials. | | `403` | `200100` | Token is valid but lacks the required permission scope. Grant the scope in the [Developer Portal](https://api.lofty.com/vendor/frontend/static/index.html#/login) and re-authorize. | | `429` | — | Rate limit exceeded. Retry with backoff. | See [Error Codes](/authentication/error-codes) for the full reference. # Rate Limits Source: https://developer.lofty.com/authentication/rate-limits Two limits apply to every request: one per IP and one per credential. There is no app-wide quota. Two independent limits apply, and a request has to pass both: one on the calling IP, one on the credential it presents. For an OAuth app, the app's review status sets the *value* of that per-credential limit — it is not a separate app-wide quota. | Limit | Counted against | Default | | -------------- | ----------------------------------------------------------------------- | --------------- | | Per IP | The calling IP address | **1,500 / min** | | Per credential | An API key | **500 / min** | | Per credential | An OAuth token from an app in `PRODUCTION` | **500 / min** | | Per credential | An OAuth token from an app in any other status, including `DEVELOPMENT` | **100 / min** | Both windows are **60 seconds**. ## The counter is per credential, not per app The *value* of an OAuth limit comes from the application: `PRODUCTION` gets 500, everything else gets 100. The *counter* does not. Each access token has its own bucket, keyed by the token and the user it belongs to. Whatever that value is, every token gets all of it. Two users who have each authorized a `PRODUCTION` app get **500 requests per minute each**, not 500 between them — and a thousand users get a thousand separate buckets. Issuing a second token for the same user does the same thing again. A busy integration is never slowed down by a quiet one, and there is no ceiling on an app's total throughput. ## Headers Every rate-limited response carries all three headers, whether or not the limit was hit: | Header | Value | | ----------------------- | -------------------------------------------------------------------------------- | | `X-RateLimit-Limit` | Requests allowed in the current window | | `X-RateLimit-Remaining` | Requests left in it (never below `0`) | | `X-RateLimit-Reset` | **Milliseconds remaining** until the window resets — a duration, not a timestamp | ## When a limit is hit ```json theme={null} HTTP/1.1 429 Too Many Requests {"code": 20003, "message": "Too Many Requests"} ``` Read `X-RateLimit-Reset`, wait that many milliseconds, and retry. Nothing is queued on your behalf, and a refused request does not count against the next window. An IP that has been restricted outright answers `429` with code `200077` instead. That is not a quota — it doesn't clear on its own, so contact support. ## Needing more Limits can be raised per application without a deploy, for integrations that genuinely need the headroom. Talk to your Lofty contact before building around a higher number — the default is not a hard ceiling, but it isn't lifted automatically either. One MCP tool call is not always one request. A [bulk write](/mcp/guides/bulk-writes) of ten records issues ten REST calls against the same bucket, and a tool that pages internally issues one per page. Budget for the requests underneath, not the tool calls above. # 2023 Source: https://developer.lofty.com/changelog/2023 ## Groups Renamed to Segments **Breaking change** — `groups` parameter renamed to `segments` across all lead endpoints. The `groups` parameter is still accepted but deprecated. ## Lead Sharing Limits * Added share lead limit controls for team visibility management. ## Multiple Webhook Subscriptions * A single team can now subscribe to the same event type multiple times with different callback URLs. ## Lead Source Permissions * Added permission checks for lead source modifications. ## Webhook Event Payloads * Added Pipeline Change event (listId: 12) for tracking lead stage transitions. ## Lead Fields * Added `note` parameter to `POST /v1.0/leads` — attach a note when creating a lead. * Added `assignGroupId` parameter — specify routing rule scope (-1 for company-level). ## Custom Fields Support * Added `POST /v1.0/teamFeatures/custom-field` — create custom field definitions. * Added custom field type support in transaction responses. ## Agent Management * `POST /v1.0/agent/profile/add` — expanded to support more fields (phone, email, office assignment). * Default phone code set to US (`1`) when not specified. ## Lead Opt-in Flags * Added `cannotCall`, `cannotText`, `cannotEmail` fields to lead model for communication preferences. ## Lead Source Management * Added ability to create and update lead sources via API. * Added source validation with group-level source checking. ## Email Sending * Added `POST /v1.0/message/email/send` endpoint for outbound email delivery. ## Lead Property Fix * Fixed `POST /v1.0/leads/{leadId}/property` — property association now works correctly. # 2024 Source: https://developer.lofty.com/changelog/2024 ## Organization Redesign Restructured the Agent Organization API with new office and company management endpoints. ## Published Listings * Added `GET /v1.0/getPublishedListings` — export team listings as XML for syndication partners. ## Lead Enhancements * **`withTrash`** parameter added to `GET /v1.0/leads/{leadId}` — retrieve trashed leads. * **`isPrivate` renamed to `isHidden`** — lead visibility field updated across all endpoints. * **Lead ownership** — added ownership scope validation for lead access control. * Removed `leadRouting` parameter from lead creation. ## Multi Lead Types Leads now support **multiple types simultaneously** (e.g., a lead can be both Buyer and Seller). The `leadTypes` field accepts an array of type IDs. ## Notes Enhancement * Added notes support with content length validation. ## Send Email API * Added `POST /v1.0/message/email/send` for sending outbound emails to leads. ## Lead Search * Migrated lead search to Elasticsearch for improved performance. * Added `currentId` parameter for cursor-based pagination. ## Transaction Commissions * Added `commissions` field documentation with detailed property descriptions. ## Lead Routing Annotations * Improved lead routing documentation for `POST /v1.0/leads`, clarifying auto-assignment behavior. ## Agent Fields * Added `agentPid` field to agent endpoints. # 2025 Source: https://developer.lofty.com/changelog/2025 ## Call Recording URL * Added `GET /v1.0/call/url/{callId}` — retrieve the recording URL for a specific call. ## Canva Design Center Integration * Integrated Canva design export functionality (internal use, hidden from public API). * Added `teamId` to Canva agent info response. ## Listing Search Enhancements * Added support for CRM my-listing and office-listing search endpoints. ## Lead Type Labels * Added automatic label tag generation based on `leadTypes` — buyer/seller classification is now applied automatically when creating or updating leads. ## Lead Activity * Added default `createTime` for lead activities when not provided. ## Lead Source * Improved lead source validation logic for updates. ## Field Removals * `leadType` (singular) field hidden from API — use `leadTypes` (array) instead. ## Authentication * Deprecated legacy API key token info endpoint — use OAuth 2.0 token exchange instead. ## Bug Fixes * Fixed `cannotEmail` opt-out flag not being applied correctly on lead updates. # 2026 Source: https://developer.lofty.com/changelog/2026 ## MCP Server Released the Lofty MCP server — connect Claude, or any other [Model Context Protocol](https://modelcontextprotocol.io) client, to the Lofty API with nothing to install and no generated SDK: ``` https://mcp.lofty.com/mcp ``` Rather than one tool per endpoint, the server exposes four tools that cover the entire API: **`search_apis`** to find the operation matching an intent, **`describe_api`** to get that operation's parameter schema, **`invoke_api`** to call it, and **`list_custom_fields`** to look up your team's custom lead fields (which aren't in any static schema). The tool list stays the same size as the API grows. Authentication uses your existing credentials — an [API key](/authentication/api-keys) or an [OAuth 2.0](/authentication/oauth2) access token, with nothing to register specifically for MCP. Note that the MCP transport requires **`Authorization: Bearer `** for both kinds, including API keys, which use the `token` prefix on the direct REST API. Calls run as the authenticated caller under the same permissions, scopes, and [rate limits](/authentication/overview#rate-limiting) as a direct REST request. See the [MCP Server documentation](/mcp/overview) to get connected. ## Leads — Saved searches Added **`GET /v1.0/leads/{leadId}/savedSearches`** — returns every saved property search of a lead, including its conditions and alert delivery settings. * **`source`** — comma-separated origins to keep: `AGENT_CREATED`, `LEAD_CREATED`, `SYSTEM_CREATED`. Defaults to all. When migrating to another system, exclude `SYSTEM_CREATED` — the target usually generates its own, and importing ours duplicates the alerts. * **`includeInactive`** (default `false`) — include searches whose alerts are switched off. An `INSTANTLY` cadence counts as active, not off. Returns an empty array when the lead has no saved searches, and `403` when the token's user can't access the lead. ## Fixes * **Tasks V2 / Calendar V2** — `startAt` / `endAt` are now parsed correctly when the time zone is supplied separately as `timeZoneCode`. A bare ISO timestamp such as `2026-03-01T15:00:00` sent alongside a `timeZoneCode` previously failed to parse and returned HTTP `500` (`20005`); timestamps carrying their own offset were unaffected. An offset in the string still takes precedence over `timeZoneCode`. * **Time parameters** — a timestamp that can't be parsed now returns HTTP `400` (`20012 INVALID_PARAMETER`), with a message naming the offending value and the expected format, instead of HTTP `500` (`20005`). ## Agents V2 — Deactivate / Reactivate Added two endpoints for managing an agent's lifecycle without losing their data: * **`POST /v2.0/agents/{agentId}/deactivate`** — Releases the agent's license seat and revokes all sessions and access tokens, while leaving their leads, deals, and data untouched. The caller needs `MANAGE_TEAM_AGENT` (or `MANAGE_TEAM_AGENT_DEPARTMENT`); the target can't be the team owner, an office owner, or the caller. * **`POST /v2.0/agents/{agentId}/reactivate`** — Restores a deactivated agent's access and re-occupies a seat (fails with `SEAT_INSUFFICIENT` if none are free). Both endpoints always return HTTP `200` once authenticated — check the response body's `code` field (absent or `0` means success) rather than the status. See [Error Codes](/authentication/error-codes#agent-lifecycle-errors-200-with-error-code) for the full list of failure codes. ## Agents — Multi-license support Agents can now hold up to 5 real estate license entries instead of one: * **`licenses`** (array of `{label, value}`) is now returned on **`GET /v1.0/me`**, **`GET /v1.0/users/{userId}`**, and **`GET /v1.0/members`** (both the paginated list and single-account lookup). * **`licenseId`** is now deprecated in favor of `licenses`. ## Transactions V2 — Pipeline discovery & type filtering * Added **`GET /v2.0/transactions/pipelines`** — Returns each transaction type's (Purchase, Listing, Lease, Other) configured stages, so you can look up valid status values instead of hardcoding team-specific stage names. * **`GET /v2.0/transactions`** gains a **`transactionType`** query parameter to filter by type, or `All` to return every type in one call. Defaults to `Purchase`; an unrecognized value now returns `400` (`20012 INVALID_PARAMETER`). ## Leads — remove tags on Update Lead **`PUT /v1.0/leads/{leadId}`** gains two request fields for removing tags without resending the full list: * **`tagsRemove`** (string array) — removes the named tags from the lead. Names are matched literally; a name not currently on the lead is ignored. Applied after `tags` / `tagsAdd`. * **`clearAllTags`** (boolean, default `false`) — set to `true` to remove **all** tags from the lead. Takes precedence over `tags` / `tagsAdd` / `tagsRemove`. Previously, removing a tag required replacing the entire tag set via `tags`. ## Communications — Cursor-paged search Added **`GET /v2.0/communication/agent`** — cursor-paginated search of an agent's communications. Supports `type=ALL` to merge calls, texts, and emails; a query window of up to 90 days; and `search_after`-based pagination via an opaque `cursor` (replacing offset paging). The response includes `data`, `nextCursor`, `hasMore`, `limit`, and `totalCount`. ## Webhooks — Ownership-based delivery Added a **`permissionMode`** field on `POST /v1.0/webhook`: * **`0`** (default) — assignment-based delivery; unchanged behavior. * **`1`** — ownership-based delivery; the subscriber also receives callbacks for any lead within their ownership scope (team, office, or personally owned), not only assigned leads. ## Field Changes * **System Logs** (`GET /v1.0/systemLogs`) — each entry now includes **`timelineTypeDesc`**, a human-readable event name paired with the integer `timelineType`, so you no longer need to map the integer yourself. ## Fixes * **Leads** (`GET /v1.0/leads`) — the **`opportunity`** field is now correctly populated (it previously always returned empty). ## Communications — single-record lookups Added v2 endpoints to fetch a single communication by ID: * **`GET /v2.0/communication/call/{communicationId}`** * **`GET /v2.0/communication/text/{communicationId}`** * **`GET /v2.0/communication/email/{communicationId}`** Single-record responses also surface manually-logged calls and their text content. ## Lofty CLI Released [`@loftyai/lofty-cli`](https://www.npmjs.com/package/@loftyai/lofty-cli) — a command-line tool for the Lofty API. Manage leads, agents, listings, tasks, transactions, and more from your terminal. ```bash theme={null} npm install -g @loftyai/lofty-cli lofty-cli auth login lofty-cli leads list --limit 10 ``` See the [Lofty CLI documentation](/cli/install) for setup and command reference. ## Transaction V2 Search Added `GET /v2.0/transactions` — paginated transaction search with keyword, lead, and date filters. ## Lead Query Enhancements New parameters for `GET /v1.0/leads`: * **`returnFields`**: Specify which fields to return (comma-separated), reducing payload size. * **`otherFilters`**: Pass custom LeadFilter overrides for advanced filtering. * **`key`**: Fuzzy search by name, phone, or email in a single parameter. ## Field Changes * **Calendar V2** — `startTime` / `endTime` now accept millisecond timestamps in addition to ISO 8601. * **Manual Log** (`POST /v1.0/logType`) — Added `emailSubject`, `toEmail`, `fromEmail` fields for email log entries. * **Manual Log** (`POST /v1.0/logType`) — The response now returns the created entry's `id`. * **Manual Log** (`GET /v1.0/logType`) — Added `sort` / `order` parameters to control result ordering, and the response now includes the `content` field. ## Sales Agents & AI 8 new endpoints for managing Sales Agent workflows: * **`GET /v2.0/sales-agents/by-lead`** — Get Sales Agent by lead ID * **`GET /v2.0/sales-agents/quota`** — Get current user's quota * **`GET /PUT /v2.0/sales-agent/settings`** — Query and update settings * **`PUT /v2.0/sales-agents/working-lead/{leadId}/mute`** — Mute a working lead * **`POST /v2.0/working-leads/add`** — Batch add leads to working pool * **`POST /v2.0/plan-tasks/create`** — Batch create plan tasks ## Intelligent Features (AI) 6 new AI-powered endpoints: * **`POST /v2.0/ai/call-script`** — Generate call script * **`GET /POST /v2.0/ai/call-summary`** — Get or trigger call summary generation * **`POST /v2.0/ai/prepare-insight`** — Generate prepare insight * **`GET /POST /v2.0/ai/lead-analysis`** — List or create lead analysis tasks ## Calendar V2 Full CRUD + lifecycle management for calendar events: * **`GET /POST /v2.0/calendar`** — List and create events * **`PUT /DELETE /v2.0/calendar/{calendarId}`** — Update and delete events * **`POST finish/unfinish`** — Mark events as completed or reopen * **`GET /v2.0/calendar/meetings/available`** — List available meeting slots ## Tasks V2 Enhancements * **`GET /v2.0/tasks/my-tasks`** — List tasks and appointments assigned to the current user * **`POST finish/unfinish`** — Mark tasks as completed or reopen ## Lead Activity V2 * **`GET /v2.0/leads/{leadId}/activities`** — Unified timeline with calls, texts, and emails in chronological order ## Notifications * **`POST /v2.0/sales-agent/notification/app-push/send-task-reminder`** — Task reminder push * **`POST send-to-agent`** — Send system SMS or email to agent * **`POST /v2.0/sales-agent/ai-number/send-sms-to-agent`** — Send SMS via AI number ## Agent Tags * **`POST /v1.0/agent/{agentId}/tag/add`** — Batch add tags with autoCreate support ## Field Changes * **Listing** — Added `propertyTypePrimary` and `propertyTypeSecondary` to response * **Note** — Added `noteId`, `noteType`, `idOfType` fields * **Calendar V2** — Added `taskId` field to calendar items ## Authentication * Added OAuth 2.0 Client Credentials flow (`/oauth/token`) for server-to-server integrations * Added per-app rate limiting and token scope enforcement # Authentication Source: https://developer.lofty.com/cli/authentication lofty-cli supports four authentication methods. They are evaluated in priority order at every command invocation: the first one that yields a valid access token wins. `lofty-cli` supports four authentication methods. They are evaluated **in priority order** at every command invocation: the first one that yields a valid access token wins. Pick the method that fits how you are running the CLI. | Method | Best for | Configured via | | ------------------------ | --------------------------------- | ---------------------------------------------------------------- | | **Token URL** | CI/CD, automation with session ID | `LOFTY_TOKEN_URL` + `LOFTY_SESSION_ID` | | **Browser OAuth (PKCE)** | Interactive developer use | `lofty-cli auth login-browser` | | **Client Credentials** | Server-to-server | `LOFTY_CLIENT_ID` + `LOFTY_CLIENT_SECRET` + `LOFTY_CUSTOMER_KEY` | | **Direct Token** | One-off testing, CI injection | `LOFTY_ACCESS_TOKEN` | Tokens obtained by Token URL, Browser OAuth, and Client Credentials are cached locally and refreshed automatically when they expire. Use this when an upstream system can mint a session ID and exchange it for tokens through a custom endpoint. ```bash theme={null} export LOFTY_TOKEN_URL="https://your-auth-server.example.com/token" export LOFTY_SESSION_ID="ses_xxx" lofty-cli auth login-session ``` You can also pass the values inline: ```bash theme={null} lofty-cli auth login-session --tokenurl https://... --sessionid ses_xxx ``` Tokens are cached at `~/.config/tokens/lofty-session-cache.json` and refreshed transparently. The simplest method for interactive use. The CLI launches your default browser, you log in once, and the token is stored locally. ```bash theme={null} lofty-cli auth login-browser ``` The flow uses OAuth 2.0 with [PKCE](https://oauth.net/2/pkce/) — no client secret is needed on your machine. Use this for server-to-server scenarios where there is no human to complete a browser flow. Implements the [OAuth 2.0 Client Credentials Grant](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) with an additional resource owner identifier. Sign in to the [Lofty Developer Portal](https://api.lofty.com/vendor/frontend/static/index.html#/login) to create an application and obtain your `client_id` and `client_secret`. ```bash theme={null} export LOFTY_CLIENT_ID="your-client-id" export LOFTY_CLIENT_SECRET="your-client-secret" ``` The customer key identifies which Lofty account's data the CLI will access (the resource owner). Each Lofty user has a unique API key — find it in the Lofty CRM under **Settings > Integrations > API**. ```bash theme={null} export LOFTY_CUSTOMER_KEY="your-customer-key" ``` ```bash theme={null} lofty-cli auth login ``` Unlike a standard Client Credentials Grant where the client acts on its own behalf, Lofty requires `LOFTY_CUSTOMER_KEY` to specify which account to access. The token is scoped to that account's data and permissions. When another system has already obtained an access token (e.g. a CI job that ran `auth login` upstream), inject it directly: ```bash theme={null} export LOFTY_ACCESS_TOKEN="eyJhbGciOi..." export LOFTY_REFRESH_TOKEN="dGhpcyBpcyBh..." # optional ``` No `auth login*` command is required; the token is used as-is. ## Verifying authentication | Command | Purpose | | ----------------------- | --------------------------------------------------------------------- | | `lofty-cli auth status` | Show which method resolved and where credentials came from | | `lofty-cli auth test` | Make a real API call (typically `GET /me`) to confirm the token works | | `lofty-cli auth show` | Print the active customer key (masked by default) | ## Logging out ```bash theme={null} lofty-cli auth logout ``` This deletes the Token URL cache, the PKCE token, the OAuth refresh cache, and the saved customer key. It does **not** revoke server-side sessions — for that, rotate the credential at its source. ## Where credentials live on disk | Path | Created by | Contents | | ------------------------------------------- | -------------------- | ----------------------------------- | | `~/.config/lofty-cli/customer-key` | `auth set` | Plain-text customer key (chmod 600) | | `~/.config/tokens/lofty-session-cache.json` | `auth login-session` | Token URL response | | `~/.config/tokens/lofty-pkce-cache.json` | `auth login-browser` | Browser OAuth tokens | | `~/.config/tokens/lofty-oauth-cache.json` | `auth login` | Client-credentials tokens | Never commit any of these files to source control. Use `auth logout` or remove the directory to wipe state. ## Troubleshooting If `auth test` fails, see **[Troubleshooting](/cli/troubleshooting)** for the common causes (clock skew, expired refresh token, wrong customer key, missing scope). # Configuration Source: https://developer.lofty.com/cli/configuration lofty-cli is configured through environment variables, on-disk config files, and per-invocation flags. Everything has a sensible default -- you only need to override what is relevant to your environment. `lofty-cli` is configured through environment variables, on-disk config files, and per-invocation flags. Everything has a sensible default — you only need to override what is relevant to your environment. ## Environment variables | Variable | Required | Default | Purpose | | --------------------- | ------------------ | ----------------------- | ------------------------------------------------------ | | `LOFTY_BASE_URL` | No | `https://api.lofty.com` | Override the API host (e.g. for a staging environment) | | `LOFTY_TOKEN_URL` | For Token URL auth | — | Endpoint that exchanges a session ID for tokens | | `LOFTY_SESSION_ID` | For Token URL auth | — | Session ID submitted to `LOFTY_TOKEN_URL` | | `LOFTY_ACCESS_TOKEN` | No | — | Direct access token (skips other auth methods) | | `LOFTY_REFRESH_TOKEN` | No | — | Refresh token paired with `LOFTY_ACCESS_TOKEN` | | `LOFTY_CLIENT_ID` | For OAuth | — | Vendor client ID from the Vendor Portal | | `LOFTY_CLIENT_SECRET` | For OAuth | — | Vendor client secret | | `LOFTY_CUSTOMER_KEY` | For OAuth | — | Customer key for the Lofty user being represented | | `NO_COLOR` | No | unset | Disables ANSI colors (also see `--no-color`) | | `DEBUG` | No | unset | Set to any value to mirror `--verbose` | See **[Authentication](/cli/authentication)** for the full priority order between these variables. ## Global flags These flags are accepted by **every** command: | Flag | Effect | | ----------------- | -------------------------------------------------------- | | `--json` | Equivalent to `--format json` | | `--format ` | Output format: `text` (default), `json`, `csv`, `yaml` | | `--verbose` | Enable debug logging to stderr (does not pollute stdout) | | `--no-color` | Disable ANSI color in output | | `--no-header` | Omit table / CSV headers — useful when piping | | `-V`, `--version` | Print the CLI version | | `-h`, `--help` | Show help for any command at any depth | See **[Output formats](/cli/output-formats)** for examples of each format. ## On-disk files | Path | Managed by | Purpose | | ---------------------------------- | -------------------------- | ------------------------------------------------------------- | | `~/.config/lofty-cli/customer-key` | `auth set` / `auth remove` | Persistent customer key | | `~/.config/tokens/*.json` | `auth login*` commands | Cached access / refresh tokens | | `aliases.yaml` (in the package) | Maintainers | Maps short option names to long ones (e.g. `-q` to `--query`) | If you want a fully ephemeral run (e.g. inside a container), set every credential via environment variables and never invoke `auth set` — no files will be written. ## Aliases Many commands accept short option aliases for ergonomics. The full mapping ships inside the package; run a command with `--help` to see what is available, for example: ```bash theme={null} lofty-cli leads list --help ``` Aliases never conflict with long options — you can mix them in the same invocation. ## Output character encoding The CLI emits UTF-8 unconditionally. On Windows terminals, set the code page once per session if your output looks garbled: ```cmd theme={null} chcp 65001 ``` ## Locking the CLI version For reproducible automation, install a specific version rather than `@latest`: ```bash theme={null} npm install -g @loftyai/lofty-cli@0.3.30 ``` Pinning is especially recommended for CI: a minor version bump can introduce new flags or change defaults. # Install the Lofty CLI Source: https://developer.lofty.com/cli/install Install lofty-cli and verify the installation. ## Prerequisites | Requirement | Minimum version | Notes | | ---------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | | **Bun** or **Node.js** | Bun 1.0+ / Node 18.0+ | Bun is recommended (the CLI bundle uses Bun internally). Node.js works via npm install but may require additional setup. | | npm (or pnpm / yarn) | any recent | For installing the package | | A Lofty account | — | For API key or OAuth credentials | The CLI is distributed as an npm package but internally uses Bun. If you only have Node.js, `npm install -g` will work, but some edge cases may require [Bun](https://bun.sh) installed as the runtime. ## Install ```bash theme={null} npm install -g @loftyai/lofty-cli ``` ## Verify installation ```bash theme={null} # Check version lofty-cli --version # Check available commands lofty-cli --help # Verify auth is working (after login) lofty-cli auth status ``` If `lofty-cli: command not found` appears: * **npm**: Run `npm config get prefix` and add `/bin` to your shell `PATH` * **nvm**: Make sure your current Node version is active (`nvm use default`) * **Windows/PowerShell**: Use `npx lofty-cli` instead, or add the npm global bin to your system PATH ## Upgrade ```bash theme={null} npm install -g @loftyai/lofty-cli@latest ``` The CLI also performs a lightweight background check at startup and prints a notice when a newer version is available. ## Uninstall ```bash theme={null} npm uninstall -g @loftyai/lofty-cli ``` Credential files are **not** removed automatically. Delete them manually for a clean slate: ```bash theme={null} rm -f ~/.config/tokens/lofty-*.txt rm -f ~/.config/tokens/lofty-*.json ``` | Path | Created by | Contents | | ------------------------------------------- | -------------------- | ------------------------- | | `~/.config/tokens/lofty-session-cache.json` | `auth login-session` | Token URL response | | `~/.config/tokens/lofty-pkce-cache.json` | `auth login-browser` | Browser OAuth tokens | | `~/.config/tokens/lofty-oauth-cache.json` | `auth login` | Client credentials tokens | ## Alternative: per-project install ```bash theme={null} npm install --save-dev @loftyai/lofty-cli npx lofty-cli --help ``` Useful when different projects need different CLI versions, or in CI without a global install. ## Next steps Required before any API call. Common end-to-end examples. # Output Formats Source: https://developer.lofty.com/cli/output-formats lofty-cli supports four output formats so you can pipe results into any downstream tool. The default is human-readable text; everything else is selected with --format or the --json shortcut. `lofty-cli` supports four output formats so you can pipe results into any downstream tool. The default is human-readable text; everything else is selected with `--format` (or the `--json` shortcut). ## `text` (default) Designed for terminals. Tables are aligned and colorized; long fields are truncated. ```bash theme={null} lofty-cli leads list ``` ```text theme={null} ID FIRSTNAME LASTNAME STAGE CREATED 12345 John Smith New 2026-04-01 12346 Jane Doe Contacted 2026-04-02 ``` Use `--no-color` to drop ANSI sequences and `--no-header` to omit the column row — useful when piping to `awk` or `cut`: ```bash theme={null} lofty-cli leads list --no-header --no-color | awk '{print $1}' ``` ## `json` Stable, machine-readable, and the only format that preserves nested fields and full precision for 64-bit IDs. ```bash theme={null} lofty-cli leads list --json # or equivalently: lofty-cli leads list --format json ``` ```json theme={null} [ { "leadId": "1234567890123", "firstName": "John", "lastName": "Smith", "stage": "New" } ] ``` Lofty IDs are 64-bit integers. Always use `--json` (not `text`) when piping IDs to another script — see [JavaScript / TypeScript Integration](/javascript-integration) for why precision matters. Combine with [`jq`](https://jqlang.github.io/jq/) for ad-hoc filtering: ```bash theme={null} lofty-cli leads list --json | jq '.[] | select(.stage == "New") | .leadId' ``` ## `csv` Round-trips cleanly into spreadsheets and `csvkit` / `pandas`. ```bash theme={null} lofty-cli leads list --format csv ``` ```csv theme={null} leadId,firstName,lastName,stage,createdAt 1234567890123,John,Smith,New,2026-04-01T12:00:00Z ``` Add `--no-header` to drop the header row when appending to an existing file. ## `yaml` Useful for human review of nested API responses. ```bash theme={null} lofty-cli leads get --id 1234567890123 --format yaml ``` ```yaml theme={null} leadId: '1234567890123' firstName: John lastName: Smith stage: New addresses: - line1: 123 Main St city: San Francisco ``` ## Choosing fields Most `list` and `get` commands accept `--return-fields` to reduce payload size and speed up downstream parsing: ```bash theme={null} lofty-cli leads list --return-fields leadId,firstName,lastName,stage ``` The server applies the projection — the wire response itself is smaller, not just the rendered output. ## Exit codes | Code | Meaning | | ---- | ------------------------------------------------------------------ | | `0` | Success — output is valid for the requested format | | `1` | Generic CLI error (validation, network, parsing) | | `2` | Authentication failure — see [Authentication](/cli/authentication) | | `>2` | API error — the response body is printed in the chosen format | Scripts should always check the exit code rather than inspect stderr text. # Quickstart Source: https://developer.lofty.com/cli/quickstart Common lofty-cli recipes you can copy and run today. Each example assumes you have already completed Install and Authentication. Common `lofty-cli` recipes you can copy and run today. Each example assumes you have already completed **[Install](/cli/install)** and **[Authentication](/cli/authentication)**. ## 1. List leads ```bash theme={null} lofty-cli leads list ``` Limit to fields you actually need: ```bash theme={null} lofty-cli leads list --return-fields leadId,firstName,lastName,stage ``` Fuzzy-search by name, phone, or email — the server matches all three: ```bash theme={null} lofty-cli leads list --query "John Smith" ``` ## 2. Get one record by ID ```bash theme={null} lofty-cli leads get --id 1234567890123 --format yaml ``` ## 3. Create a record from JSON Pass the request body as inline JSON: ```bash theme={null} lofty-cli leads create --body '{"firstName":"Jane","lastName":"Doe","email":"jane@example.com"}' ``` For larger payloads, source the body from a file: ```bash theme={null} lofty-cli leads create --body @lead-payload.json ``` ## 4. Pipe to `jq` for ad-hoc analytics ```bash theme={null} # Count leads by stage lofty-cli leads list --json \ | jq -r '.[].stage' \ | sort | uniq -c | sort -rn ``` ## 5. Bulk export to CSV ```bash theme={null} lofty-cli leads list --format csv > leads-$(date +%Y%m%d).csv ``` Append more pages without repeating the header: ```bash theme={null} lofty-cli leads list --offset 1000 --format csv --no-header >> leads-$(date +%Y%m%d).csv ``` ## 6. Use the CLI from a script with strict error handling ```bash theme={null} #!/usr/bin/env bash set -euo pipefail LEAD_ID=$(lofty-cli leads list --json | jq -r '.[0].leadId') echo "First lead ID: $LEAD_ID" lofty-cli leads get --id "$LEAD_ID" --format yaml ``` ## 7. Run from CI/CD Configure environment variables once on the runner: ```bash theme={null} export LOFTY_CLIENT_ID="your-client-id" export LOFTY_CLIENT_SECRET="your-client-secret" export LOFTY_CUSTOMER_KEY="your-customer-key" ``` Then any command works without an interactive `auth login`: ```bash theme={null} lofty-cli auth test lofty-cli leads list --json ``` ## 8. Discover commands you do not know The CLI is fully self-describing. Walk the help tree until you find what you need: ```bash theme={null} lofty-cli --help # all top-level resources lofty-cli leads --help # subcommands of `leads` lofty-cli leads list --help # options for `leads list` ``` Or browse the **[Command Reference](/cli/reference)** on this site. ## Next steps Cross-reference commands with the live API spec. Piping patterns for JSON, CSV, YAML, and text. Common fixes when something fails unexpectedly. # agent-org Source: https://developer.lofty.com/cli/reference/agent-org Reference for the lofty-cli agent-org command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli agent-org [options] [command] Manage agent-org Options: -h, --help display help for command Commands: update-office [options] Update Agent Office add-office [options] Add Agent Office update-company [options] Update Agent Company get [options] Get User Organization permission-profiles [options] Get Permission Profiles help [command] display help for command ``` ## `lofty-cli agent-org update-office` ```bash theme={null} Usage: lofty-cli agent-org update-office [options] Update Agent Office Options: --id id (number) --parent-id parentId (number) --website-owner-id websiteOwnerId (number) --name name (string) --phone phone (string) --phone-code phoneCode (string) --phone-country phoneCountry (string) --email email (string) --city city (string) --state state (string) --zipcode zipcode (string) --street-address streetAddress (string) --allow-manage-sub allowManageSub (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli agent-org add-office` ```bash theme={null} Usage: lofty-cli agent-org add-office [options] Add Agent Office Options: --id id (number) --parent-id parentId (number) --website-owner-id websiteOwnerId (number) --name name (string) --phone phone (string) --phone-code phoneCode (string) --phone-country phoneCountry (string) --email email (string) --city city (string) --state state (string) --zipcode zipcode (string) --street-address streetAddress (string) --allow-manage-sub allowManageSub (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli agent-org update-company` ```bash theme={null} Usage: lofty-cli agent-org update-company [options] Update Agent Company Options: --team-id teamId (number) --name name (string) --phone phone (string) --phone-code phoneCode (string) --phone-country phoneCountry (string) --email email (string) --city city (string) --state state (string) --zipcode zipcode (string) --street-address streetAddress (string) --header header (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli agent-org get` ```bash theme={null} Usage: lofty-cli agent-org get [options] Get User Organization Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli agent-org permission-profiles` ```bash theme={null} Usage: lofty-cli agent-org permission-profiles [options] Get Permission Profiles Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # agent-user Source: https://developer.lofty.com/cli/reference/agent-user Reference for the lofty-cli agent-user command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli agent-user [options] [command] Manage agent-user Options: -h, --help display help for command Commands: add [options] Add tags for agent create [options] Create a new agent with profile message help [command] display help for command ``` ## `lofty-cli agent-user add` ```bash theme={null} Usage: lofty-cli agent-user add [options] Add tags for agent Options: --tag-names [REQUIRED] List of tag names to add, max 20 at a time (array, e.g. VIP,Premium) --auto-create Whether to auto-create tags that do not exist (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli agent-user create` ```bash theme={null} Usage: lofty-cli agent-user create [options] Create a new agent with profile message Options: --account-type [REQUIRED] The account type of the agent (number) --account-info-last-name [REQUIRED] last name of the agent (string, e.g. Bob) --account-info-first-name [REQUIRED] first name of the agent (string, e.g. Bob) --account-info-email-address [REQUIRED] email address of the agent (string, e.g. aaaa@google.com) --account-info-head-url The head url of the agent (string, e.g. http://xxx) --account-info-phone-number phone number of the agent (string, e.g. 83834748) --account-info-phone-code phone code of the agent (string, e.g. 000001) --account-info-phone-country phone country of the agent (string, e.g. 000001) --account-info-tag-ids tags of the agent (array, e.g. 1,2,3) --account-info-sending-email sendingEmail (string) --account-info-domain domain (string) --account-info-allow-change-domain allowChangeDomain (true|false) --account-info-role-name roleName (string, e.g. role of the agent) --account-info-group-id groupId (number) --account-info-role-id roleId (number) --account-info-seat-type-name the seat type code of the agent (string, e.g. Full Time Agent) --work-info-street-address Street Address (string, e.g. 123 Main St) --work-info-city City (string, e.g. New York) --work-info-country Country (string, e.g. US) --work-info-state State (string, e.g. NY) --work-info-zipcode Zipcode (string, e.g. 10001) --work-info-license-id licenseId of the agent (string, e.g. xxx) --work-info-company-name Company Name (string, e.g. xxx) --work-info-company-address Company Address (string, e.g. xxx) --work-info-position position (string, e.g. xxx) --work-info-personal-website Personal Website (string, e.g. xxx) --work-info-personal-attachments personalAttachments (JSON array) --work-info-personal-disclaimers personalDisclaimers (JSON array) --work-info-personal-introduction Personal Introduction (string, e.g. xxx) --social-medias-items items (JSON array) --personal-photos-urls urls (array) --has-back-office hasBackOffice (true|false) --agent-pid agentPid (string) --full-sync fullSync (true|false) --should-sync shouldSync (true|false) --check-roster-num checkRosterNum (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # alerts Source: https://developer.lofty.com/cli/reference/alerts Reference for the lofty-cli alerts command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli alerts [options] [command] Manage alerts Options: -h, --help display help for command Commands: get [options] Get Alert Detail ids [options] Get Alert Id List emails [options] Get Mail Detail email-ids [options] Get Mail Id List help [command] display help for command ``` ## `lofty-cli alerts get` ```bash theme={null} Usage: lofty-cli alerts get [options] Get Alert Detail Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli alerts ids` ```bash theme={null} Usage: lofty-cli alerts ids [options] Get Alert Id List Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli alerts emails` ```bash theme={null} Usage: lofty-cli alerts emails [options] Get Mail Detail Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli alerts email-ids` ```bash theme={null} Usage: lofty-cli alerts email-ids [options] Get Mail Id List Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # auth Source: https://developer.lofty.com/cli/reference/auth Reference for the lofty-cli auth command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli auth [options] [command] Manage API authentication Options: -h, --help display help for command Commands: set Save your customer key (API key) show [options] Display current customer key (masked by default) remove Delete the saved customer key and OAuth cache login Obtain an OAuth access_token using customer key + vendor credentials status Show current authentication status login-session [options] Authenticate via Token URL using session ID (Level 0) login-browser Authorize via browser using OAuth PKCE flow (like Claude CLI) logout Clear all locally stored authentication credentials (Token URL cache, PKCE token, OAuth cache, customer key) test Verify authentication by making a test API call help [command] display help for command ``` ## `lofty-cli auth set` ```bash theme={null} Usage: lofty-cli auth set [options] Save your customer key (API key) Arguments: customer-key Your Lofty customer key Options: -h, --help display help for command Example: lofty-cli auth set eyJhbGciOiJIUzI1NiJ9... ``` ## `lofty-cli auth show` ```bash theme={null} Usage: lofty-cli auth show [options] Display current customer key (masked by default) Options: --raw Show the full unmasked key -h, --help display help for command Example: lofty-cli auth show lofty-cli auth show --raw ``` ## `lofty-cli auth remove` ```bash theme={null} Usage: lofty-cli auth remove [options] Delete the saved customer key and OAuth cache Options: -h, --help display help for command Example: lofty-cli auth remove ``` ## `lofty-cli auth login` ```bash theme={null} Usage: lofty-cli auth login [options] Obtain an OAuth access_token using customer key + vendor credentials Options: -h, --help display help for command Requires environment variables: LOFTY_CLIENT_ID Vendor client ID LOFTY_CLIENT_SECRET Vendor client secret Example: lofty-cli auth login ``` ## `lofty-cli auth status` ```bash theme={null} Usage: lofty-cli auth status [options] Show current authentication status Options: -h, --help display help for command Example: lofty-cli auth status ``` ## `lofty-cli auth login-session` ```bash theme={null} Usage: lofty-cli auth login-session [options] Authenticate via Token URL using session ID (Level 0) Options: --tokenurl Token URL (overrides LOFTY_TOKEN_URL env var) --sessionid Session ID (overrides LOFTY_SESSION_ID env var) -h, --help display help for command Requires environment variables (or CLI flags): LOFTY_TOKEN_URL URL to obtain tokens from LOFTY_SESSION_ID Session ID to authenticate with Example: lofty-cli auth login-session lofty-cli auth login-session --tokenurl https://example.com/token --sessionid abc123 ``` ## `lofty-cli auth login-browser` ```bash theme={null} Usage: lofty-cli auth login-browser [options] Authorize via browser using OAuth PKCE flow (like Claude CLI) Options: -h, --help display help for command This opens a browser for authorization. If browser cannot be opened, a URL is printed for manual copy-paste. Example: lofty-cli auth login-browser ``` ## `lofty-cli auth logout` ```bash theme={null} Usage: lofty-cli auth logout [options] Clear all locally stored authentication credentials (Token URL cache, PKCE token, OAuth cache, customer key) Options: -h, --help display help for command Removes all locally stored authentication credentials. This includes PKCE token, OAuth cache, and customer key. Environment variables already exported in your current shell are not removed. Example: lofty-cli auth logout ``` ## `lofty-cli auth test` ```bash theme={null} Usage: lofty-cli auth test [options] Verify authentication by making a test API call Options: -h, --help display help for command Example: lofty-cli auth test ``` # calendar Source: https://developer.lofty.com/cli/reference/calendar Reference for the lofty-cli calendar command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli calendar [options] [command] Manage calendar Options: -h, --help display help for command Commands: update [options] Update a calendar entry delete [options] Delete a calendar entry list [options] Query calendar entries create [options] Create a calendar entry unfinish [options] Mark a calendar entry as not completed finish [options] Mark a calendar entry as completed available [options] List available meeting slots help [command] display help for command ``` ## `lofty-cli calendar update` ```bash theme={null} Usage: lofty-cli calendar update [options] Update a calendar entry Options: --content Updated content/description (string) --start-at Updated start time in ISO8601 format. Preferred over startAtMs when both are provided. (string) --end-at Updated end time in ISO8601 format. Preferred over endAtMs when both are provided. (string) --start-at-ms Updated start time as Unix timestamp in milliseconds. Used when startAt is not provided. (number) --end-at-ms Updated end time as Unix timestamp in milliseconds. Used when endAt is not provided. (number) --time-zone-code Updated timezone code (string) --reminder-type Updated reminder type (string) --reminder-time Updated reminder time (string) --lead-id Updated lead ID (number) --address Updated address (for APPOINTMENT) (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli calendar delete` ```bash theme={null} Usage: lofty-cli calendar delete [options] Delete a calendar entry Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli calendar list` ```bash theme={null} Usage: lofty-cli calendar list [options] Query calendar entries Options: --lead-id Lead ID (optional, filter by specific lead) (string, e.g. 123456) --start-time Start time, ISO 8601 with offset. At least one of startTime or endTime is required. (string, e.g. 2026-01-01T00:00:00+08:00) --end-time End time, ISO 8601 with offset. At least one of startTime or endTime is required. (string, e.g. 2026-12-31T23:59:59+08:00) --start-time-ms Start time as Unix timestamp in milliseconds. Used when startTime is not provided. (string, e.g. 1704038400000) --end-time-ms End time as Unix timestamp in milliseconds. Used when endTime is not provided. (string, e.g. 1735660799000) --time-zone-code Timezone code (IANA format, e.g., America/Los_Angeles) (string, e.g. America/Los_Angeles) --include-finished Include finished calendars (string, e.g. false) --page Page number (starts from 0) (string, e.g. 0) --page-size Page size (max 500) (string, e.g. 20) --sort Sort field (e.g., startTime) (string, e.g. startTime) --desc Sort in descending order (string, e.g. true) --source-type Filter by source type codes. Multiple values allowed. (string, e.g. 1,2) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli calendar create` ```bash theme={null} Usage: lofty-cli calendar create [options] Create a calendar entry Options: --type [REQUIRED] Calendar type: TASK or APPOINTMENT (string, e.g. TASK) --content [REQUIRED] Content/Description of the calendar event (string, e.g. Follow up with client about...) --lead-id [REQUIRED] Lead ID associated with this calendar event (number, e.g. 563172647619608) --time-zone-code [REQUIRED] Timezone code (string, e.g. America/Los_Angeles) --start-at Start time in ISO8601 format with timezone. Preferred over startAtMs when both are provided. (string, e.g. 2026-03-01T14:00:00-08:00) --end-at End time in ISO8601 format. Preferred over endAtMs when both are provided. (string, e.g. 2026-03-01T15:00:00-08:00) --start-at-ms Start time as Unix timestamp in milliseconds. Used when startAt is not provided. (number, e.g. 1740866400000) --end-at-ms End time as Unix timestamp in milliseconds. Used when endAt is not provided. (number, e.g. 1740870000000) --task-way Task way type: Call, Email, Text, Other (only for TASK) (string, e.g. Call) --assigned-role Assigned role: Agent or Assistant (only for TASK) (string, e.g. Agent) --address Location/Address (only for APPOINTMENT) (string, e.g. 123 Main St, Los Angeles, CA) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli calendar unfinish` ```bash theme={null} Usage: lofty-cli calendar unfinish [options] Mark a calendar entry as not completed Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli calendar finish` ```bash theme={null} Usage: lofty-cli calendar finish [options] Mark a calendar entry as completed Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli calendar available` ```bash theme={null} Usage: lofty-cli calendar available [options] List available meeting slots Options: --start-time Start time in ISO 8601 format with offset. Preferred over startTimeMs when both are provided. (string, e.g. 2026-01-15T09:00:00-08:00) --end-time End time in ISO 8601 format with offset. Preferred over endTimeMs when both are provided. (string, e.g. 2026-03-15T09:00:00-08:00) --start-time-ms Start time as Unix timestamp in milliseconds. Used when startTime is not provided. (number, e.g. 1768496400000) --end-time-ms End time as Unix timestamp in milliseconds. Used when endTime is not provided. (number, e.g. 1773680400000) --time-zone-code IANA timezone identifier. When omitted, the caller's configured timezone is used. (string, e.g. America/Los_Angeles) --limit Maximum number of slots to return. (number, e.g. 10) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # calls Source: https://developer.lofty.com/cli/reference/calls Reference for the lofty-cli calls command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli calls [options] [command] Manage calls Options: -h, --help display help for command Commands: list [options] Search for calls get [options] Get a call by Id recording-url [options] View your call recording help [command] display help for command ``` ## `lofty-cli calls list` ```bash theme={null} Usage: lofty-cli calls list [options] Search for calls Options: --lead-id [REQUIRED] Finds calls related to a lead ID (number) --limit Number of results to return (number) --offset Specifies the number of rows to skip, before starting to return results (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli calls get` ```bash theme={null} Usage: lofty-cli calls get [options] Get a call by Id Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli calls recording-url` ```bash theme={null} Usage: lofty-cli calls recording-url [options] View your call recording Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # communication Source: https://developer.lofty.com/cli/reference/communication Reference for the lofty-cli communication command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli communication [options] [command] Manage communication Options: -h, --help display help for command Commands: send-sms [options] Send SMS message send-email [options] Send Email message agent-communication [options] Search for communication by agent texts [options] Search for text history emails [options] Search for email history calls [options] Search for call history calls-v2 [options] Search for call history v2 help [command] display help for command ``` ## `lofty-cli communication send-sms` ```bash theme={null} Usage: lofty-cli communication send-sms [options] Send SMS message Options: --content [REQUIRED] SMS content (string, e.g. Hello, this is a test message) --lead-id [REQUIRED] Lead ID. When phoneNumber and phoneCode are not provided or no matching phone is found, the lead's default phone is used. (number, e.g. 563172647619608) --phone-number Recipient phone number (string, e.g. 5865865860) --phone-code Recipient phone code (string, e.g. 1) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli communication send-email` ```bash theme={null} Usage: lofty-cli communication send-email [options] Send Email message Options: --subject [REQUIRED] Email subject (string, e.g. Welcome to our service) --content [REQUIRED] Email content (string, e.g. Hello, this is a test email) --lead-id [REQUIRED] Lead ID. When toEmail is not provided or no matching email is found for the lead, the lead's default email is used. (number, e.g. 563172647619608) --to-email Recipient email address. (string, e.g. recipient@example.com) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli communication agent-communication` ```bash theme={null} Usage: lofty-cli communication agent-communication [options] Search for communication by agent Options: --type [REQUIRED] CALL, EMAIL OR TEXT (string: CALL | TEXT | EMAIL, e.g. CALL) --start-time [REQUIRED] startTimestamp (number, e.g. 946710000000) --end-time [REQUIRED] endTimestamp (The maximum time interval for a single query is 24 hours) (number, e.g. 946710000000) --offset offset default Value 0 (number, e.g. 0) --limit limit default Value 10,max value 1000 (number, e.g. 50) --team-view search team lead's communication, default false (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli communication texts` ```bash theme={null} Usage: lofty-cli communication texts [options] Search for text history Options: --lead-id [REQUIRED] ID of Lead (number) --offset offset default Value 0 (number) --limit limit default Value 10, max value 1000 (number) --current-id This parameter is used for page turning. When searching by ID is invoked, the interface starts from the given ID. (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli communication emails` ```bash theme={null} Usage: lofty-cli communication emails [options] Search for email history Options: --lead-id [REQUIRED] ID of Lead (number) --offset offset default Value 0 (number) --limit limit default Value 10, max value 1000 (number) --current-id This parameter is used for page turning. When searching by ID is invoked, the interface starts from the given ID. (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli communication calls` ```bash theme={null} Usage: lofty-cli communication calls [options] Search for call history Options: --lead-id [REQUIRED] ID of Lead (number) --offset offset default Value 0 (number) --limit limit default Value 10, max value 1000 (number) --current-id This parameter is used for page turning. When searching by ID is invoked, the interface starts from the given ID. (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli communication calls-v2` ```bash theme={null} Usage: lofty-cli communication calls-v2 [options] Search for call history v2 Options: --lead-id [REQUIRED] ID of Lead (number) --offset offset default Value 0 (number) --limit limit default Value 10, max value 1000 (number) --current-id This parameter is used for page turning. When searching by ID is invoked, the interface starts from the given ID. (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # intelligent-features Source: https://developer.lofty.com/cli/reference/intelligent-features Reference for the lofty-cli intelligent-features command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli intelligent-features [options] [command] Manage intelligent-features Options: -h, --help display help for command Commands: prepare-insight [options] Generate Prepare Insight lead-analysis [options] Query LeadAnalysis task list post-lead-analysis [options] Create LeadAnalysis task generate [options] Trigger Call Summary generation call-script [options] Generate Call Script call-summary [options] Get Call Summary help [command] display help for command ``` ## `lofty-cli intelligent-features prepare-insight` ```bash theme={null} Usage: lofty-cli intelligent-features prepare-insight [options] Generate Prepare Insight Options: --lead-id [REQUIRED] Lead ID (number, e.g. 100001) --appointment-id [REQUIRED] Appointment ID (number, e.g. 200001) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli intelligent-features lead-analysis` ```bash theme={null} Usage: lofty-cli intelligent-features lead-analysis [options] Query LeadAnalysis task list Options: --page-num pageNum (number) --page-size pageSize (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli intelligent-features post-lead-analysis` ```bash theme={null} Usage: lofty-cli intelligent-features post-lead-analysis [options] Create LeadAnalysis task Options: --lead-ids [REQUIRED] Lead IDs to analyze (required, cannot be empty) (array, e.g. 100001,100002) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli intelligent-features generate` ```bash theme={null} Usage: lofty-cli intelligent-features generate [options] Trigger Call Summary generation Options: --call-record-id [REQUIRED] Call record ID (number, e.g. 123456) --lead-id [REQUIRED] Lead ID (number, e.g. 100001) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli intelligent-features call-script` ```bash theme={null} Usage: lofty-cli intelligent-features call-script [options] Generate Call Script Options: --lead-id [REQUIRED] Lead ID (number, e.g. 100001) --task-id [REQUIRED] Task ID (number, e.g. 300001) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli intelligent-features call-summary` ```bash theme={null} Usage: lofty-cli intelligent-features call-summary [options] Get Call Summary Options: --call-record-id [REQUIRED] callRecordId (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # lead-activity Source: https://developer.lofty.com/cli/reference/lead-activity Reference for the lofty-cli lead-activity command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli lead-activity [options] [command] Manage lead-activity Options: -h, --help display help for command Commands: activities [options] List lead activities help [command] display help for command ``` ## `lofty-cli lead-activity activities` ```bash theme={null} Usage: lofty-cli lead-activity activities [options] List lead activities Options: --current-id Cursor-based pagination anchor. Pass the last returned item's id to fetch the next page. Default 0 (start from beginning). (number) --offset Offset for offset-based pagination. Ignored when currentId > 0. Default 0. (number) --limit Page size, 1-1000. Default 10. (number) --time-zone-code Timezone in IANA format (e.g. America/Los_Angeles). Affects activityTime formatting. Defaults to user config. (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # lead-routing Source: https://developer.lofty.com/cli/reference/lead-routing Reference for the lofty-cli lead-routing command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli lead-routing [options] [command] Manage lead-routing Options: -h, --help display help for command Commands: update-rule [options] Update routing rule get-default [options] Get default rule update-default [options] Update default routing rule rules [options] Get Routing List roles [options] Get Assign Role List members [options] Get Assign Member List help [command] display help for command ``` ## `lofty-cli lead-routing update-rule` ```bash theme={null} Usage: lofty-cli lead-routing update-rule [options] Update routing rule Options: --id [REQUIRED] Rule id (number) --active-hours-active-days Active day list, between 1 and 7 (array, e.g. 1,2) --active-hours-hours-start Active hours start, between 0 and 23 (number) --active-hours-hours-end Active hours end, between 0 and 23 (number) --routing-strategy-strategy-type Strategy type (number) --routing-strategy-group-id Only lead pond are affected (number) --routing-strategy-group-name Edit method don't need this params (string) --routing-strategy-touch-minutes Only next up and blast alert are affected (number) --routing-strategy-member-weights Except for the lead pond type (JSON string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli lead-routing get-default` ```bash theme={null} Usage: lofty-cli lead-routing get-default [options] Get default rule Options: --role-id Role id, This parameter is required when type == 4 (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli lead-routing update-default` ```bash theme={null} Usage: lofty-cli lead-routing update-default [options] Update default routing rule Options: --role-id Role id, This parameter is required when type == 4 (number) --strategy-type Strategy type (number) --touch-minutes Only next up and blast alert are affected (number) --assignee-agent-id Only default assignee are affected (number) --assignee-group-id Only default assignee and routing type is AGENT_LEAD are affected (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli lead-routing rules` ```bash theme={null} Usage: lofty-cli lead-routing rules [options] Get Routing List Options: --role-id Role id, This parameter is required when type == 4 (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli lead-routing roles` ```bash theme={null} Usage: lofty-cli lead-routing roles [options] Get Assign Role List Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli lead-routing members` ```bash theme={null} Usage: lofty-cli lead-routing members [options] Get Assign Member List Options: --role-id roleId (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # leads Source: https://developer.lofty.com/cli/reference/leads Reference for the lofty-cli leads command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli leads [options] [command] Manage leads Options: -h, --help display help for command Commands: get [options] Get a lead by Id update [options] Update a lead delete [options] Delete a lead brokermint-lead [options] Update a lead from brokermint list|search [options] Search for leads create [options] Create a new lead property [options] Place a property inquiry [options] Place an inquiry assign [options] Assign a lead add-activity [options] Add activity search-assignee [options] Search for assignee by lead info activities [options] Search for activities help [command] display help for command ``` ## `lofty-cli leads get` ```bash theme={null} Usage: lofty-cli leads get [options] Get a lead by Id Options: --with-trash Whether to query deleted lead. (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli leads update` Run `lofty-cli leads update --help` for full usage details. ## `lofty-cli leads delete` ```bash theme={null} Usage: lofty-cli leads delete [options] Delete a lead Options: --reason [REQUIRED] The reason to trash this lead (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli leads brokermint-lead` ```bash theme={null} Usage: lofty-cli leads brokermint-lead [options] Update a lead from brokermint Options: --email email (string) --first-name firstName (string) --last-name lastName (string) --contact-type contactType (string) --external-id externalId (string) --address address (string) --city city (string) --state state (string) --zip zip (string) --phone phone (string) --bm-private bmPrivate (true|false) --lead-source leadSource (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli leads list|search` ```bash theme={null} Usage: lofty-cli leads list|search [options] Search for leads Options: -h, --help display help for command ``` Run `lofty-cli leads list --help` for full option details. ## `lofty-cli leads create` Run `lofty-cli leads create --help` for full usage details. ## `lofty-cli leads property` ```bash theme={null} Usage: lofty-cli leads property [options] Place a property Options: --price The price Applies to lead routing only if the leadType field is Seller(1) (number, e.g. 100000) --state The state Applies to lead routing only if the leadType field is Seller(1) (string, e.g. California) --city The city Applies to lead routing only if the leadType field is Seller(1) (string, e.g. New York) --street-address The street address Applies to lead routing only if the leadType field is Seller(1) (string, e.g. 22348 Regnart RD) --zip-code The zip code Applies to lead routing only if the leadType field is Seller(1) (string, e.g. 25401) --county The county Applies to lead routing only if the leadType field is Seller(1) (string) --property-type Property type: Single Family Home, Multi-Family, Condo, Townhouse, Mobile ... (string, e.g. Single Family Home) --bedrooms The bed rooms. (number, e.g. 3) --bathrooms The bath rooms. (number, e.g. 2) --square-feet The square feet. (number, e.g. 100) --lot-size The lot size (acreage). (number, e.g. 26.33) --parking-space The parking space. (number, e.g. 1) --floors The floors. (number, e.g. 1) --mail-address Mailing address (true|false) --id id (number) --lead-user-id leadUserId (number) --listing-id listingId (string) --auto-listing-id autoListingId (number) --label label (string) --note note (string) --listing-status listingStatus (string) --label-list labelList (string) --picture-url pictureUrl (string) --site-listing-url siteListingUrl (string) --label-type labelType (string) --price-max The max price Applies to lead routing only if the leadType field is Seller(1) (number, e.g. 10000000) --price-min The min price Applies to lead routing only if the leadType field is Seller(1) (number, e.g. 100000) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli leads inquiry` Run `lofty-cli leads inquiry --help` for full usage details. ## `lofty-cli leads assign` ```bash theme={null} Usage: lofty-cli leads assign [options] Assign a lead Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli leads add-activity` ```bash theme={null} Usage: lofty-cli leads add-activity [options] Add activity Options: --type [REQUIRED] Specifies the type of activity being logged for the lead. Accepted Values: Search, Browse, Favorite (string, e.g. Search) --text Search Text. Required when type is 'Search' (string, e.g. Search) --link Url for web page. Required when type is 'Browse' (string, e.g. https://lofty.com) --picture Preview picture url. Required when type is 'Browse' or 'Favorite' (string, e.g. https://lofty.com/default.jpg) --listing-price The price Applies to lead routing only if the leadType field is Seller(1) (number, e.g. 100000) --listing-state The state Applies to lead routing only if the leadType field is Seller(1) (string, e.g. California) --listing-city The city Applies to lead routing only if the leadType field is Seller(1) (string, e.g. New York) --listing-street-address The street address Applies to lead routing only if the leadType field is Seller(1) (string, e.g. 22348 Regnart RD) --listing-zip-code The zip code Applies to lead routing only if the leadType field is Seller(1) (string, e.g. 25401) --listing-county The county Applies to lead routing only if the leadType field is Seller(1) (string) --listing-property-type Property type: Single Family Home, Multi-Family, Condo, Townhouse, Mobile ... (string, e.g. Single Family Home) --listing-bedrooms The bed rooms. (number, e.g. 3) --listing-bathrooms The bath rooms. (number, e.g. 2) --listing-square-feet The square feet. (number, e.g. 100) --listing-lot-size The lot size (acreage). (number, e.g. 26.33) --listing-parking-space The parking space. (number, e.g. 1) --listing-floors The floors. (number, e.g. 1) --listing-mail-address Mailing address (true|false) --listing-id id (number) --listing-lead-user-id leadUserId (number) --listing-listing-id listingId (string) --listing-auto-listing-id autoListingId (number) --listing-label label (string) --listing-note note (string) --listing-listing-status listingStatus (string) --listing-label-list labelList (string) --listing-picture-url pictureUrl (string) --listing-site-listing-url siteListingUrl (string) --listing-label-type labelType (string) --listing-price-max The max price Applies to lead routing only if the leadType field is Seller(1) (number, e.g. 10000000) --listing-price-min The min price Applies to lead routing only if the leadType field is Seller(1) (number, e.g. 100000) --created created time in milliseconds. Recommended to always provide a valid timestamp; defaults ... (number, e.g. 1747272300000) --scheduled-date Scheduled date about this showing request (string) --page-name The page name of the lead's submission(re-inquiry) page (string, e.g. Blog Registration) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli leads search-assignee` ```bash theme={null} Usage: lofty-cli leads search-assignee [options] Search for assignee by lead info Options: --first-name [REQUIRED] The first name of the lead (string, e.g. Bob) --email [REQUIRED] The email address associated with the lead (string, e.g. 123456789@gmail.com) --last-name The last name of the lead (string, e.g. Li) --phone The phone number associated with the lead. (No more than 20 characters per phone) (string, e.g. 1234567890) --birthday The birthday of the lead(pattern:MMM d, yyyy) (string, e.g. Jun 5, 2001) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli leads activities` ```bash theme={null} Usage: lofty-cli leads activities [options] Search for activities Options: --cur-page The page number of the returning data. One page has 100 data. Default value is 0. (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # listing Source: https://developer.lofty.com/cli/reference/listing Reference for the lofty-cli listing command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli listing [options] [command] Manage listing Options: -h, --help display help for command Commands: list [options] Get Listings published [options] Get published listings search [options] Search listings help [command] display help for command ``` ## `lofty-cli listing list` ```bash theme={null} Usage: lofty-cli listing list [options] Get Listings Options: --user-id userId (number) --listing-id listingId (string) --next-page-key nextPageKey (string) --sold-next-page-key soldNextPageKey (string) --mls-listing-ids mlsListingIds (string) --street street (string) --limit limit (number) --sold-listing-id soldListingId (string) --office-listings officeListings (true|false) --agent-listings agentListings (true|false) --building-name buildingName (string) --building-address buildingAddress (string) --show-condition showCondition (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli listing published` ```bash theme={null} Usage: lofty-cli listing published [options] Get published listings Options: --type [REQUIRED] LuxVT|AptCom|AllOnMarket (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli listing search` Run `lofty-cli listing search --help` for full usage details. # log-type Source: https://developer.lofty.com/cli/reference/log-type Reference for the lofty-cli log-type command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli log-type [options] [command] Manage log-type Options: -h, --help display help for command Commands: list [options] Search for log call/email/text create [options] Add a log call/email/text get [options] Get log call/email/text by Id delete [options] Delete a log type help [command] display help for command ``` ## `lofty-cli log-type list` ```bash theme={null} Usage: lofty-cli log-type list [options] Search for log call/email/text Options: --lead-id [REQUIRED] ID of Lead (number) --log-type [REQUIRED] query type value: logCall,logEmail,logText (string) --offset offset default Value 0 (number) --limit limit default Value 10, max value 1000 (number) --current-id This parameter is used for page turning. When searching by ID is invoked, the interface starts from the given ID. (number) --sort sort field: createTime (default) or id (string) --order sort order: asc (default) or desc (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli log-type create` ```bash theme={null} Usage: lofty-cli log-type create [options] Add a log call/email/text Options: --lead-id [REQUIRED] The ID of the lead this log type is related to (number, e.g. 563172647619608) --log-type [REQUIRED] values:logCall,logEmail,logText (string, e.g. logCall) --content [REQUIRED] log type content (string, e.g. example log content) --lead-phone-number The value of the lead phone number (string, e.g. 586****860) --calling-outcome Talked,VoiceMessage,NoAnswer,BadNumber,DNCNumber,DNCContact (string, e.g. VoiceMessage) --outbound-or-inbound Outbound or inbound communication (string, e.g. outbound,inbound) --is-pin Pin this activity or not (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli log-type get` ```bash theme={null} Usage: lofty-cli log-type get [options] Get log call/email/text by Id Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli log-type delete` ```bash theme={null} Usage: lofty-cli log-type delete [options] Delete a log type Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # members Source: https://developer.lofty.com/cli/reference/members Reference for the lofty-cli members command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli members [options] [command] Manage members Options: -h, --help display help for command Commands: get-user [options] Get user by Id list [options] Get team members get [options] Get member by account me [options] Get current user's info help [command] display help for command ``` ## `lofty-cli members get-user` ```bash theme={null} Usage: lofty-cli members get-user [options] Get user by Id Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli members list` ```bash theme={null} Usage: lofty-cli members list [options] Get team members Options: --group-ids Group Ids of responded members (array) --offset Start index of the responded members (number) --limit Quantitative limitation of responded members (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli members get` ```bash theme={null} Usage: lofty-cli members get [options] Get member by account Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli members me` ```bash theme={null} Usage: lofty-cli members me [options] Get current user's info Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # notes Source: https://developer.lofty.com/cli/reference/notes Reference for the lofty-cli notes command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli notes [options] [command] Manage notes Options: -h, --help display help for command Commands: get [options] Get note by Id update [options] Update a note delete [options] Delete a note list [options] Search for notes create [options] Add a note help [command] display help for command ``` ## `lofty-cli notes get` ```bash theme={null} Usage: lofty-cli notes get [options] Get note by Id Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli notes update` ```bash theme={null} Usage: lofty-cli notes update [options] Update a note Options: --content [REQUIRED] Note content (string, e.g. example_note) --lead-id [REQUIRED] The ID of the lead this note is related to (number, e.g. 563172647619608) --is-pin Pin this note or not (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli notes delete` ```bash theme={null} Usage: lofty-cli notes delete [options] Delete a note Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli notes list` ```bash theme={null} Usage: lofty-cli notes list [options] Search for notes Options: --lead-id [REQUIRED] ID of Lead (number) --include-system-note Include System note or not (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli notes create` ```bash theme={null} Usage: lofty-cli notes create [options] Add a note Options: --content [REQUIRED] Note content (string, e.g. example_note) --lead-id [REQUIRED] The ID of the lead this note is related to (number, e.g. 563172647619608) --is-pin Pin this note or not (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # notifications Source: https://developer.lofty.com/cli/reference/notifications Reference for the lofty-cli notifications command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli notifications [options] [command] Manage notifications Options: -h, --help display help for command Commands: send-task-reminder [options] Send app push for task/appointment reminder send-to-agent [options] Send system SMS to agent post-send-to-agent [options] Send system email to agent help [command] display help for command ``` ## `lofty-cli notifications send-task-reminder` ```bash theme={null} Usage: lofty-cli notifications send-task-reminder [options] Send app push for task/appointment reminder Options: --task-id [REQUIRED] Task ID or Appointment ID (number, e.g. 123456) --type [REQUIRED] Task type. APPOINTMENT or TASK. (string: TASK | APPOINTMENT, e.g. TASK) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli notifications send-to-agent` ```bash theme={null} Usage: lofty-cli notifications send-to-agent [options] Send system SMS to agent Options: --content [REQUIRED] SMS content to send to the agent's phone number (string, e.g. You have a new lead assigned.) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli notifications post-send-to-agent` ```bash theme={null} Usage: lofty-cli notifications post-send-to-agent [options] Send system email to agent Options: --subject [REQUIRED] Email subject (string, e.g. You have a new lead assigned.) --content [REQUIRED] Email body content (HTML supported) (string, e.g.

Hello, you have a new lead...

) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # opportunity Source: https://developer.lofty.com/cli/reference/opportunity Reference for the lofty-cli opportunity command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli opportunity [options] [command] Manage opportunity Options: -h, --help display help for command Commands: send-notification [options] POST send-notification help [command] display help for command ``` ## `lofty-cli opportunity send-notification` ```bash theme={null} Usage: lofty-cli opportunity send-notification [options] POST send-notification Options: --lead-id [REQUIRED] the id of lead (number, e.g. 1) --notification-type [REQUIRED] the notificationType contains flow: 48-Viewed a listing 3 times; 50-Searched; 52-Saved a listing; 4... (number, e.g. 48) --description description and link is a combo, only if you fill both of them, they will show on the notification (string, e.g. San Francisco, CA 94102) --link description and link is a combo, only if you fill both of them, they will show on the notification (string) --message the message show on notification (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # Command Reference Source: https://developer.lofty.com/cli/reference/overview Every lofty-cli resource, command, and subcommand. This section is generated from the live output of `lofty-cli --help`. Every top-level resource gets its own page; click through for the full list of subcommands and options. ## Global flags All commands accept the standard `--help` (`-h`) flag to display usage information. ## Resources | Command | Page | | -------------------------------- | ----------------------------------------------------------- | | `lofty-cli auth` | [auth](/cli/reference/auth) | | `lofty-cli agent-org` | [agent-org](/cli/reference/agent-org) | | `lofty-cli agent-user` | [agent-user](/cli/reference/agent-user) | | `lofty-cli alerts` | [alerts](/cli/reference/alerts) | | `lofty-cli calendar` | [calendar](/cli/reference/calendar) | | `lofty-cli calls` | [calls](/cli/reference/calls) | | `lofty-cli communication` | [communication](/cli/reference/communication) | | `lofty-cli intelligent-features` | [intelligent-features](/cli/reference/intelligent-features) | | `lofty-cli lead-activity` | [lead-activity](/cli/reference/lead-activity) | | `lofty-cli lead-routing` | [lead-routing](/cli/reference/lead-routing) | | `lofty-cli leads` | [leads](/cli/reference/leads) | | `lofty-cli listing` | [listing](/cli/reference/listing) | | `lofty-cli log-type` | [log-type](/cli/reference/log-type) | | `lofty-cli members` | [members](/cli/reference/members) | | `lofty-cli notes` | [notes](/cli/reference/notes) | | `lofty-cli notifications` | [notifications](/cli/reference/notifications) | | `lofty-cli opportunity` | [opportunity](/cli/reference/opportunity) | | `lofty-cli sales-agents` | [sales-agents](/cli/reference/sales-agents) | | `lofty-cli system-logs` | [system-logs](/cli/reference/system-logs) | | `lofty-cli tasks-appointments` | [tasks-appointments](/cli/reference/tasks-appointments) | | `lofty-cli team-features` | [team-features](/cli/reference/team-features) | | `lofty-cli transactions` | [transactions](/cli/reference/transactions) | | `lofty-cli vendor` | [vendor](/cli/reference/vendor) | | `lofty-cli webhooks` | [webhooks](/cli/reference/webhooks) | See **[Quickstart](/cli/quickstart)** for end-to-end examples or **[Output formats](/cli/output-formats)** for piping patterns. # sales-agents Source: https://developer.lofty.com/cli/reference/sales-agents Reference for the lofty-cli sales-agents command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli sales-agents [options] [command] Manage sales-agents Options: -h, --help display help for command Commands: mute [options] Mute a working lead settings [options] Query Sales Agent settings put-settings [options] Update Sales Agent settings add [options] Batch add leads to Sales Agent working pool send-sms-to-agent [options] Send SMS to agent via AI number create [options] Batch create plan tasks for leads list [options] Query working leads (paginated) working-lead [options] Check if lead is a working lead quota [options] Get current user's Sales Agent quota current [options] Get current user's Sales Agent by-lead [options] Get Sales Agent by lead ID mute-status [options] Get Sales Agent lead mute status lead [options] Get plan tasks by lead ID help [command] display help for command ``` ## `lofty-cli sales-agents mute` ```bash theme={null} Usage: lofty-cli sales-agents mute [options] Mute a working lead Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents settings` ```bash theme={null} Usage: lofty-cli sales-agents settings [options] Query Sales Agent settings Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents put-settings` ```bash theme={null} Usage: lofty-cli sales-agents put-settings [options] Update Sales Agent settings Options: --id [REQUIRED] Sales Agent ID (required, must be greater than 0) (number, e.g. 12345) --assistant-name Assistant name (string, e.g. Anna) --virtual-number Virtual phone number (string, e.g. +1234567890) --virtual-number-country Virtual number country (string, e.g. US) --call-transfer-type Call transfer type: Disabled, ToAssignedAgent (string, e.g. Disabled) --fixed-number Fixed phone number (string, e.g. +1234567890) --agent-experience Agent experience description (string, e.g. 10 years in real estate) --rental-business Whether agent handles rental business (true|false) --website-channel Whether website channel is enabled (true|false) --text-channel Whether text channel is enabled (true|false) --email-channel Whether email channel is enabled (true|false) --start-active-hour Active start hour (0-23) (number, e.g. 8) --end-active-hour Active end hour (0-23) (number, e.g. 20) --active-days Active days of week (0=Sun, 6=Sat), empty means all days (array, e.g. 1,2) --lead-types Lead types filter, empty means all (array, e.g. 1,2) --lead-sources Lead source IDs filter, empty means all (array, e.g. 1001,1002) --lead-pipelines Lead pipeline IDs filter, empty means all (array, e.g. 2001,2002) --agent-ids Agent IDs with access, contains -1 means all (array, e.g. 101,102) --office-ids Office IDs with access, contains -1 means all (array, e.g. 201,202) --lead-pond-ids Lead pond IDs with access, contains -1 means all (array, e.g. 301,302) --new-lead-enable Whether new lead follow-up is enabled (true|false) --visitors-enable Whether visitor follow-up is enabled (true|false) --follow-up-role Follow up role: AI or Agent (string, e.g. AI) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents add` ```bash theme={null} Usage: lofty-cli sales-agents add [options] Batch add leads to Sales Agent working pool Options: --lead-ids [REQUIRED] Lead IDs to add to Sales Agent working pool (max 100) (array, e.g. 563172647619608,563172647619609) --create-plan-task Whether to create plan tasks for each lead after adding to working pool (true|false) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents send-sms-to-agent` ```bash theme={null} Usage: lofty-cli sales-agents send-sms-to-agent [options] Send SMS to agent via AI number Options: --content [REQUIRED] SMS content to send to the agent's phone number (string, e.g. You have a new lead assigned.) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents create` ```bash theme={null} Usage: lofty-cli sales-agents create [options] Batch create plan tasks for leads Options: --lead-ids [REQUIRED] Lead IDs to create plan tasks for (max 100) (array, e.g. 563172647619608,563172647619609) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents list` ```bash theme={null} Usage: lofty-cli sales-agents list [options] Query working leads (paginated) Options: --ai-stage AI follow-up stage filter (HIGH_PRIORITY, AI_PROSPECTING, AI_MONITORING) (string, e.g. AI_PROSPECTING) --limit Page size (max 100) (string, e.g. 20) --offset Start index of the responded leads (string, e.g. 0) --sort Sort field (Default, CreateTime, UpdateTime) (string, e.g. UpdateTime) --desc Descending sort (true) or ascending sort (false) (string, e.g. true) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents working-lead` ```bash theme={null} Usage: lofty-cli sales-agents working-lead [options] Check if lead is a working lead Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents quota` ```bash theme={null} Usage: lofty-cli sales-agents quota [options] Get current user's Sales Agent quota Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents current` ```bash theme={null} Usage: lofty-cli sales-agents current [options] Get current user's Sales Agent Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents by-lead` ```bash theme={null} Usage: lofty-cli sales-agents by-lead [options] Get Sales Agent by lead ID Options: --lead-id [REQUIRED] Lead ID (number, e.g. 563172647619608) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents mute-status` ```bash theme={null} Usage: lofty-cli sales-agents mute-status [options] Get Sales Agent lead mute status Options: --lead-id [REQUIRED] Lead ID (number, e.g. 563172647619608) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli sales-agents lead` ```bash theme={null} Usage: lofty-cli sales-agents lead [options] Get plan tasks by lead ID Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # system-logs Source: https://developer.lofty.com/cli/reference/system-logs Reference for the lofty-cli system-logs command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli system-logs [options] [command] Manage system-logs Options: -h, --help display help for command Commands: list [options] Search for system logs help [command] display help for command ``` ## `lofty-cli system-logs list` ```bash theme={null} Usage: lofty-cli system-logs list [options] Search for system logs Options: --lead-id [REQUIRED] ID of Lead (number) --start-time The timestamp of start (number) --end-time The timestamp of end (number) --page-number Page number, first is 0 (number) --page-size Page size (number) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # tasks-appointments Source: https://developer.lofty.com/cli/reference/tasks-appointments Reference for the lofty-cli tasks-appointments command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli tasks-appointments [options] [command] Manage tasks-appointments Options: -h, --help display help for command Commands: get [options] Get a task by ID update [options] Update a task or appointment delete [options] Delete a task or appointment list [options] List tasks of a lead create [options] Add a task or appointment unfinish [options] Mark a task as not completed finish [options] Mark a task as completed my-tasks [options] List tasks assigned to the caller help [command] display help for command ``` ## `lofty-cli tasks-appointments get` ```bash theme={null} Usage: lofty-cli tasks-appointments get [options] Get a task by ID Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli tasks-appointments update` ```bash theme={null} Usage: lofty-cli tasks-appointments update [options] Update a task or appointment Options: --content Description of the task (string, e.g. Send an email to my lead) --start-at When this task/appointment should start. ISO8601 format in specified timezone. (string, e.g. 2026-03-01T15:00:00-08:00) --end-at When this task/appointment should end. ISO8601 format in specified timezone. (string, e.g. 2026-03-01T16:00:00-08:00) --time-zone-code Timezone code for this task/appointment (string, e.g. America/Los_Angeles) --address Address/location for appointment type tasks (string, e.g. 123 Main St, Los Angeles, CA) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli tasks-appointments delete` ```bash theme={null} Usage: lofty-cli tasks-appointments delete [options] Delete a task or appointment Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli tasks-appointments list` ```bash theme={null} Usage: lofty-cli tasks-appointments list [options] List tasks of a lead Options: --lead-id [REQUIRED] ID of the lead whose tasks and appointments to return. (number, e.g. 100001) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli tasks-appointments create` ```bash theme={null} Usage: lofty-cli tasks-appointments create [options] Add a task or appointment Options: --content [REQUIRED] Description of the task (string, e.g. Send an email to my lead) --lead-id [REQUIRED] The ID of the lead this task is related to (number, e.g. 563172647619608) --type [REQUIRED] The type of the task. Could be Other, Call, Email, Text, Appointment (string, e.g. Call) --assigned-role [REQUIRED] Role the task should be assigned to (Agent or Assistant) (string, e.g. Agent) --end-at [REQUIRED] When this task/appointment should end. ISO8601 format in specified timezone. (string, e.g. 2026-03-01T16:00:00-08:00) --start-at When this task/appointment should start. ISO8601 format in specified timezone. (string, e.g. 2026-03-01T15:00:00-08:00) --time-zone-code Timezone code for this task/appointment (string, e.g. America/Los_Angeles) --address Address/location for appointment type tasks (string, e.g. 123 Main St, Los Angeles, CA) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli tasks-appointments unfinish` ```bash theme={null} Usage: lofty-cli tasks-appointments unfinish [options] Mark a task as not completed Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli tasks-appointments finish` ```bash theme={null} Usage: lofty-cli tasks-appointments finish [options] Mark a task as completed Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli tasks-appointments my-tasks` ```bash theme={null} Usage: lofty-cli tasks-appointments my-tasks [options] List tasks assigned to the caller Options: --user-id User ID to query tasks for. Defaults to the current authenticated user if not provided. (number) --current-id Cursor-based pagination anchor. Pass the last returned item's id to fetch the next page. Default 0 (start from beginning). (number) --limit Page size, 1-100. Default 10. (number) --time-zone-code IANA timezone code (e.g. 'America/New_York'). If not provided, falls back to the user's configured timezone. (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # team-features Source: https://developer.lofty.com/cli/reference/team-features Reference for the lofty-cli team-features command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli team-features [options] [command] Manage team-features Options: -h, --help display help for command Commands: create-custom-field [options] Add new custom field tags [options] Search existing tag custom-fields [options] Search custom field lead-ponds [options] List lead pond lead-pond [options] Search lead pond help [command] display help for command ``` ## `lofty-cli team-features create-custom-field` Run `lofty-cli team-features create-custom-field --help` for full usage details. ## `lofty-cli team-features tags` ```bash theme={null} Usage: lofty-cli team-features tags [options] Search existing tag Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli team-features custom-fields` ```bash theme={null} Usage: lofty-cli team-features custom-fields [options] Search custom field Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli team-features lead-ponds` ```bash theme={null} Usage: lofty-cli team-features lead-ponds [options] List lead pond Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli team-features lead-pond` ```bash theme={null} Usage: lofty-cli team-features lead-pond [options] Search lead pond Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # transactions Source: https://developer.lofty.com/cli/reference/transactions Reference for the lofty-cli transactions command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli transactions [options] [command] Manage transactions Options: -h, --help display help for command Commands: get [options] Get transaction by leadId AND transactionId update [options] Update a transaction brokermint-transaction [options] Update a transaction from brokermint create [options] Add a transaction update-address [options] Update address of transaction's property custom-fields [options] Get transaction all custom fields list [options] Get transactions by leadId get-address [options] Get address of transaction's property by transactionId search [options] Search transactions help [command] display help for command ``` ## `lofty-cli transactions get` ```bash theme={null} Usage: lofty-cli transactions get [options] Get transaction by leadId AND transactionId Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli transactions update` ```bash theme={null} Usage: lofty-cli transactions update [options] Update a transaction Options: --created [REQUIRED] created time in milliseconds (number, e.g. 1518078026000) --updated [REQUIRED] updated time in milliseconds (not support updating) (number, e.g. 1518078026000) --transaction-name [REQUIRED] Property Address/Transaction Name (string, e.g. 123 Main St) --transaction-id transactionId (number) --lead-name Lead's name whom the transaction is assigned to (not support updating) (string, e.g. Bob) --assigned-agent Lead's assigned agent (not support updating) (number) --transaction-type The type of Transaction:Purchase, Listing (string, e.g. [Purchase,Listing]) --home-price The home price of transaction (number, e.g. 1000000.0) --transaction-status The status of transaction (string, e.g. Pre-contract) --expected-close-date The expected close date of transaction (number, e.g. 1508580010000) --close-date The closed date of transaction (number, e.g. 1508580010000) --commission-rate The commission rate of transaction (number, e.g. 3) --gci The GCI of transaction (number, e.g. 5000.0) --team-revenue The team revenue of transaction (number, e.g. 1000.0) --agent-revenue The agent revenue of transaction (number, e.g. 4000.0) --appointment-date appointmentDate (number) --agreement-signed-date agreementSignedDate (number) --offer-date offerDate (number) --contract-date contractDate (number) --appraisal-date appraisalDate (number) --home-inspection-date homeInspectionDate (number) --escrow-date escrowDate (number) --expiration expiration (number) --custom-fields Custom field full coverage update or insertion (JSON array) --commissions-pre-split preSplit (JSON array) --commissions-split split (JSON array) --commissions-post-split postSplit (JSON array) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli transactions brokermint-transaction` ```bash theme={null} Usage: lofty-cli transactions brokermint-transaction [options] Update a transaction from brokermint Options: --id id (number) --agent-id agentId (number) --agent-name agentName (string) --address address (string) --city city (string) --state state (string) --zip zip (string) --status status (string) --transaction-type transactionType (string) --price price (number) --representing representing (string) --acceptance-date acceptanceDate (string) --expiration-date expirationDate (string) --closed-at closedAt (string) --closing-date closingDate (string) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli transactions create` ```bash theme={null} Usage: lofty-cli transactions create [options] Add a transaction Options: --created [REQUIRED] created time in milliseconds (number, e.g. 1518078026000) --updated [REQUIRED] updated time in milliseconds (not support updating) (number, e.g. 1518078026000) --transaction-name [REQUIRED] Property Address/Transaction Name (string, e.g. 123 Main St) --transaction-id transactionId (number) --lead-name Lead's name whom the transaction is assigned to (not support updating) (string, e.g. Bob) --assigned-agent Lead's assigned agent (not support updating) (number) --transaction-type The type of Transaction:Purchase, Listing (string, e.g. [Purchase,Listing]) --home-price The home price of transaction (number, e.g. 1000000.0) --transaction-status The status of transaction (string, e.g. Pre-contract) --expected-close-date The expected close date of transaction (number, e.g. 1508580010000) --close-date The closed date of transaction (number, e.g. 1508580010000) --commission-rate The commission rate of transaction (number, e.g. 3) --gci The GCI of transaction (number, e.g. 5000.0) --team-revenue The team revenue of transaction (number, e.g. 1000.0) --agent-revenue The agent revenue of transaction (number, e.g. 4000.0) --appointment-date appointmentDate (number) --agreement-signed-date agreementSignedDate (number) --offer-date offerDate (number) --contract-date contractDate (number) --appraisal-date appraisalDate (number) --home-inspection-date homeInspectionDate (number) --escrow-date escrowDate (number) --expiration expiration (number) --custom-fields Custom field full coverage update or insertion (JSON array) --commissions-pre-split preSplit (JSON array) --commissions-split split (JSON array) --commissions-post-split postSplit (JSON array) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli transactions update-address` ```bash theme={null} Usage: lofty-cli transactions update-address [options] Update address of transaction's property Options: --transaction-id [REQUIRED] transactionId (number) --unit The unit (string, e.g. 1) --label The label (string, e.g. Investment) --state The state (string, e.g. CA) --city The city (string, e.g. Los Angeles) --zip-code The zip code (string, e.g. 90012) --street-address The street address (string, e.g. 100 W 1st St) --county The county (string, e.g. Log Angeles) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli transactions custom-fields` ```bash theme={null} Usage: lofty-cli transactions custom-fields [options] Get transaction all custom fields Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli transactions list` ```bash theme={null} Usage: lofty-cli transactions list [options] Get transactions by leadId Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli transactions get-address` ```bash theme={null} Usage: lofty-cli transactions get-address [options] Get address of transaction's property by transactionId Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli transactions search` ```bash theme={null} Usage: lofty-cli transactions search [options] Search transactions Options: --page-size Page size; must be between 1 and 100. (number, e.g. 20) --page-num Page number (0-based); must be >= 0. (number, e.g. 0) --query-key Search keyword. Performs a contains-style fuzzy match (first 15 characters only) against transaction ... (string, e.g. 123) --lead-id Optional. Restrict results to transactions belonging to this lead. (number, e.g. 100001) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # vendor Source: https://developer.lofty.com/cli/reference/vendor Reference for the lofty-cli vendor command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli vendor [options] [command] Manage vendor Options: -h, --help display help for command Commands: list [options] Get vendor info help [command] display help for command ``` ## `lofty-cli vendor list` ```bash theme={null} Usage: lofty-cli vendor list [options] Get vendor info Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # webhooks Source: https://developer.lofty.com/cli/reference/webhooks Reference for the lofty-cli webhooks command and its subcommands. ## Overview ```bash theme={null} Usage: lofty-cli webhooks [options] [command] Manage webhooks Options: -h, --help display help for command Commands: create [options] Add a new webhook list [options] Get all webhook from current team delete [options] Delete a webhook help [command] display help for command ``` ## `lofty-cli webhooks create` ```bash theme={null} Usage: lofty-cli webhooks create [options] Add a new webhook Options: --list-id [REQUIRED] The list that you want to subscribe. ListId: agent info(1), lead info(2), lead activity(3), alert(4), transaction... (number) --callback-url [REQUIRED] The url link that you receive information (string, e.g. http://xxx.test.com) --limit limitation of webhook notifications in 30 mins for each, max is 5000 (number, e.g. 100) --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli webhooks list` ```bash theme={null} Usage: lofty-cli webhooks list [options] Get all webhook from current team Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` ## `lofty-cli webhooks delete` ```bash theme={null} Usage: lofty-cli webhooks delete [options] Delete a webhook Options: --fields Comma-separated columns to display --json Output as JSON --format Output format: text, json, csv, yaml -h, --help display help for command ``` # Troubleshooting Source: https://developer.lofty.com/cli/troubleshooting If something does not work as expected, run the failing command with --verbose first. It prints the resolved auth method, the outgoing URL, and any error details to stderr. If something does not work as expected, run the failing command with `--verbose` first — it prints the resolved auth method, the outgoing URL, and any error details to stderr. Most issues fall into one of the buckets below. ## Authentication ### `401 Unauthorized` from every command Check which credential path the CLI is using: ```bash theme={null} lofty-cli auth status --verbose ``` Common causes: | Symptom | Likely cause | Fix | | -------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | No credentials found | None of the four auth methods are configured | Pick one method from **[Authentication](/cli/authentication)** | | Token expired and refresh failed | Refresh token was revoked or stale | `lofty-cli auth logout` then re-authenticate | | `LOFTY_CUSTOMER_KEY` is set but wrong | Key copied with surrounding whitespace | Re-copy from **Settings > Integrations > API** in the Lofty CRM | | OAuth flow fails with `invalid_client` | `LOFTY_CLIENT_ID` / `LOFTY_CLIENT_SECRET` mismatch | Reissue the credential in the [Vendor Portal](https://api.lofty.com/vendor/frontend/static/index.html#/login) | ### Browser flow opens but never completes `lofty-cli auth login-browser` listens on a free local port. Anti-virus, VPNs, or terminal multiplexers that block loopback may interfere. Try one of: * Run the command in a plain terminal (outside `tmux` or remote SSH) * Use **Method 3 — OAuth client credentials** instead, which does not require a browser * On a remote machine, run `lofty-cli auth login` (client credentials) or set `LOFTY_ACCESS_TOKEN` directly ## Rate limiting A `429 Too Many Requests` response means you have exceeded the API rate budget. The CLI does **not** retry automatically. Wrap your invocations in a backoff loop if you are running large bulk jobs: ```bash theme={null} #!/usr/bin/env bash set -euo pipefail OFFSET=0 LIMIT=100 while true; do if ! lofty-cli leads list --offset "$OFFSET" --limit "$LIMIT" --json; then echo "Request failed, waiting 30s before retry..." >&2 sleep 30 continue fi OFFSET=$((OFFSET + LIMIT)) done ``` ## Pagination cut-offs If you only see the first page of results, you are hitting the default page size. Use `--offset` and `--limit` (where supported) or stream pages until empty: ```bash theme={null} #!/usr/bin/env bash set -euo pipefail OFFSET=0 LIMIT=100 while true; do PAGE=$(lofty-cli leads list --offset "$OFFSET" --limit "$LIMIT" --json) COUNT=$(echo "$PAGE" | jq 'length') echo "$PAGE" [ "$COUNT" -lt "$LIMIT" ] && break OFFSET=$((OFFSET + LIMIT)) done ``` ## ID precision in shell pipelines Lofty IDs are 64-bit integers. `jq` and most shells preserve precision for `--json` output, but `bc`, `awk`, and JavaScript (when piped through `node -e`) do **not**. When you must hand an ID to JavaScript, treat it as a string from the start: ```bash theme={null} ID=$(lofty-cli leads list --json | jq -r '.[0].leadId') # ID is a string node -e "console.log('Lead:', process.argv[1])" "$ID" ``` See [JavaScript / TypeScript Integration](/javascript-integration) for full guidance on handling 64-bit IDs. ## Network and TLS ### `getaddrinfo ENOTFOUND` or `EAI_AGAIN` DNS cannot resolve `api.lofty.com`. Confirm: ```bash theme={null} curl -sSf https://api.lofty.com/health ``` If `curl` also fails, the issue is your network (corporate proxy, VPN, DNS). ### TLS certificate errors If your environment intercepts TLS (corporate proxy with a custom CA), set: ```bash theme={null} export NODE_EXTRA_CA_CERTS=/path/to/your/ca-bundle.pem ``` Do **not** disable TLS verification for production traffic. ## Debug output `--verbose` is the universal escape hatch: ```bash theme={null} lofty-cli leads list --verbose 2> debug.log ``` `stderr` carries debug; `stdout` stays clean for piping. Attach the log to any support request along with the failing command and CLI version (`lofty-cli --version`). ## Still stuck? * Re-check **[Authentication](/cli/authentication)** for the credential precedence rules * Reproduce the request directly with `curl` against the endpoint shown in `--verbose` * Ask in the internal `#openapi` channel and include `lofty-cli --version`, the failing command, and the `--verbose` log # Leads Source: https://developer.lofty.com/concepts/leads Understand the lead data model, lifecycle, and routing in Lofty. A lead represents a contact or prospect in the Lofty CRM. Nearly every other resource — tasks, transactions, activities, communications — is associated with a lead. ## Data model | Field | Type | Description | | ------------------------ | --------- | ------------------------------------------------------------------------------------------------------------- | | `leadId` | int64 | Unique identifier | | `firstName` / `lastName` | string | Name (max 30 chars each) | | `emails` | string\[] | Email addresses | | `phones` | string\[] | Phone numbers | | `assignedUserId` | int64 | Assigned agent's user ID | | `stage` | string | Pipeline stage name | | `source` | string | Lead source label | | `score` | int32 | Lead score | | `tags` | object\[] | Applied tags | | `leadTypes` | int32\[] | Type IDs: Buyer (2), Seller (1), Renter (5), Investor (6), Agent (7), Homeowner (8), Landlord (9), Other (-1) | | `teamId` | int64 | Team the lead belongs to | All lead IDs are 64-bit integers. JavaScript clients must handle these carefully — see [JS / TS Integration](/javascript-integration). ## Lifecycle Leads move through four stages: 1. **Capture** — A lead is created via API, form submission, or third-party import. Lofty creates a lead record with contact info and source. 2. **Routing** — If no `assignedUserId` is provided, Lofty applies the team's routing rules to automatically assign the lead to an agent. 3. **Working** — The assigned agent works the lead. Activities (calls, emails, texts) are tracked in the lead's timeline. Agents can add leads to the working pool for AI assistant follow-up. 4. **Conversion** — The lead progresses through pipeline stages toward a closed deal. Transactions are created and associated with the lead. ## Pipeline stages Each lead has a `stage` that reflects where it is in the sales process. Stages are configurable per team. Common stages include New Lead, Contacted, Qualified, Under Contract, and Closed. Stage changes trigger [Pipeline Change webhooks](/concepts/webhooks#12-pipeline-change) (listId: 12). ## Hidden leads When a lead has visibility restrictions, only the assigned agent receives webhook notifications and can access the lead. Other team members — including admins — are excluded. ## Activities The activity timeline captures all interactions with a lead: * **V1** (`GET /v1.0/leads/{leadId}/activities`) — Site activities: property views, favorites, searches. * **V2** (`GET /v2.0/leads/{leadId}/activities`) — Unified timeline: calls, texts, emails, and agent-logged communications in chronological order. Use the V2 activities endpoint for new integrations — it provides a complete picture in a single response. ## Related resources Step-by-step: create, update, assign, and delete leads. Full endpoint documentation with parameters and responses. # Listings Source: https://developer.lofty.com/concepts/listings Search active and sold MLS listings, retrieve published listings for a site feed, and associate properties with leads using the Lofty REST API. Listings in Lofty represent MLS properties that agents and their clients can browse, search, and save. The API gives you two ways to access listing data — an agent/office listing search and a published listings feed — and lets you associate specific properties directly with a lead record. ## What listings represent A listing in Lofty corresponds to a property available on the MLS. Each listing carries the property's address, price, physical attributes (beds, baths, square footage), MLS identifiers, and listing metadata such as days on site and listing type. Lofty stores both **active** and **sold** listings. The listing search endpoint lets you query across both. The published listings endpoint returns the set of listings your team has published to a site feed in XML format, typically used to power public-facing property search pages. ## Two ways to access listings ### Published listings (V1) `GET /v1.0/getPublishedListings` returns listings your team has published to a public site feed in XML format. This endpoint is intended for powering site search pages and content syndication rather than programmatic data processing. ```bash theme={null} GET https://api.lofty.com/v1.0/getPublishedListings ``` ### Listing search (V1) For searching active and sold listings by agent, office, or MLS ID, use: ```bash theme={null} GET https://api.lofty.com/v1.0/listing ``` Use `GET /v1.0/listing` for integrations that need to look up listings by agent, office, or MLS ID. Use `GET /v1.0/getPublishedListings` only when you specifically need the XML site feed format. ## Key listing fields | Field | Type | Description | | ------------------- | --------- | ----------------------------------- | | `listingId` | string | Lofty listing ID | | `mlsListingId` | string | MLS listing ID (e.g., `ML82040728`) | | `listingStreetName` | string | Street address | | `listingCity` | string | City | | `listingState` | string | State abbreviation | | `listingZipcode` | string\[] | ZIP code(s) | | `price` | string | Listing price | | `beds` | float | Number of bedrooms | | `baths` | float | Number of bathrooms | | `sqft` | float | Square footage | | `builtYear` | int32 | Year built | | `county` | string | County name | | `area` | string | Area/submarket name | ## Associating a property with a lead When a lead expresses interest in a specific property, you can attach that property to their record. This drives Lofty's listing alert and inquiry workflows. ```bash theme={null} POST https://api.lofty.com/v1.0/leads/{leadId}/property ``` Use this endpoint to record a property a lead has viewed, inquired about, or favorited. You can also place a formal inquiry on a lead using `POST /v1.0/leads/{leadId}/inquiry`, which triggers the inquiry workflow within Lofty. ## Endpoint overview | Method | Endpoint | Description | | ------ | ------------------------------- | ------------------------------------------- | | `GET` | `/v1.0/getPublishedListings` | Get published listings as XML (site feed) | | `GET` | `/v1.0/listing` | Search listings by agent, office, or MLS ID | | `POST` | `/v1.0/leads/{leadId}/property` | Associate a property with a lead | | `POST` | `/v1.0/leads/{leadId}/inquiry` | Place an inquiry on a lead | # Tasks & Appointments Source: https://developer.lofty.com/concepts/tasks-appointments Tasks are to-do items for agents; appointments are scheduled meetings. Both share a unified taskId namespace and are managed through the V2 API. Tasks and appointments help agents stay organized and follow up with leads at the right time. Tasks are action items — a call to make, an email to send, a follow-up to complete. Appointments are scheduled meetings with a specific start time, end time, and location. Both are associated with a lead and tracked through the same API surface. ## Shared taskId namespace Tasks and appointments in Lofty share the same `taskId` namespace. This means a task and an appointment can never have the same numeric ID, and when you reference an item by ID on any V2 endpoint, the API resolves it correctly regardless of whether it is a task or an appointment. The V2 endpoints (`/v2.0/tasks/...` and `/v2.0/calendar/...`) operate on the same underlying ID space. A `taskId` returned from a task creation endpoint is the same identifier you would use to fetch or update that record through the calendar endpoints. ## Tasks A task is a to-do item assigned to an agent or assistant, linked to a specific lead. Use tasks to represent the action an agent needs to take next — scheduling a call, sending a listing, or following up after a showing. ### Key task fields | Field | Type | Description | | -------------- | ------- | ---------------------------------------------------- | | `id` | int64 | Task ID (shared namespace with appointments) | | `leadId` | int64 | Lead associated with this task | | `content` | string | Task description | | `type` | string | Task type: `Call`, `Email`, `Text`, `Other` | | `assignedRole` | string | Assigned role: `Agent` or `Assistant` | | `startAt` | string | Start time in ISO 8601 format with timezone | | `endAt` | string | End time / deadline in ISO 8601 format | | `timeZoneCode` | string | IANA timezone code (e.g., `America/Los_Angeles`) | | `finishFlag` | boolean | Whether the task is completed | | `overdueFlag` | boolean | Whether the task is past its deadline and still open | ### Creating a task (V2) ```bash theme={null} POST https://api.lofty.com/v2.0/tasks ``` ```json theme={null} { "content": "Follow up about the Maple Street listing", "leadId": 563172647619608, "type": "Call", "assignedRole": "Agent", "startAt": "2026-06-01T10:00:00-07:00", "endAt": "2026-06-01T10:30:00-07:00", "timeZoneCode": "America/Los_Angeles" } ``` ## Appointments An appointment is a scheduled event with a defined start time, end time, and optional location. Unlike tasks, appointments represent a meeting or showing rather than a generic action item. ### Key appointment fields | Field | Type | Description | | ------------- | ------- | -------------------------------------------- | | `id` | int64 | Appointment ID (shared namespace with tasks) | | `leadId` | int64 | Lead associated with this appointment | | `descr` | string | Appointment description | | `address` | string | Location of the appointment | | `deadline` | int64 | Start time as Unix timestamp in milliseconds | | `endTime` | int64 | End time as Unix timestamp in milliseconds | | `allDay` | boolean | Whether the appointment spans the full day | | `finishFlag` | boolean | Whether the appointment is completed | | `assignToUid` | int64 | User ID the appointment is assigned to | To list appointments for a lead, use `GET /v1.0/appts`. ## V1 vs V2 API Lofty exposes tasks through both V1 and V2 endpoints. The V2 API adds support for `Appointment` as a task type, uses ISO 8601 timestamps with timezone, and is the recommended path for new integrations. | Feature | V1 (`/v1.0/tasks`) | V2 (`/v2.0/tasks`) | | ------------------------ | -------------------------------- | --------------------------------------------- | | Task types | `Call`, `Email`, `Text`, `Other` | All V1 types + `Appointment` | | Timestamp format | `yyyy-MM-dd HH:mm:ss` UTC | ISO 8601 with timezone | | Finish/unfinish actions | Via `finishFlag` on update | Dedicated `/finish` and `/unfinish` endpoints | | Preferred for new builds | No | Yes | Use the V2 endpoints for all new integrations. The V1 task endpoints remain available but do not support appointments as a task type. ## Finish and unfinish workflow When an agent completes a task or appointment, you mark it finished. If needed, you can revert that status. The V2 API provides dedicated endpoints for this rather than requiring a full update: ```bash theme={null} # Mark a task or appointment as completed POST https://api.lofty.com/v2.0/tasks/{taskId}/finish # Revert a completed task or appointment to open POST https://api.lofty.com/v2.0/tasks/{taskId}/unfinish ``` The same pattern is available through the calendar API: ```bash theme={null} POST https://api.lofty.com/v2.0/calendar/{calendarId}/finish POST https://api.lofty.com/v2.0/calendar/{calendarId}/unfinish ``` ## Calendar API vs tasks API The V2 Calendar API (`/v2.0/calendar`) is an alternative surface for managing the same tasks and appointments, with additional capabilities: * **Available meeting slots** — `GET /v2.0/calendar/meetings/available` returns open time slots for scheduling. * **Composite calendar IDs** — Calendar entries have IDs of the form `-task` or `-appointment`. You must pass this full composite string to per-entry calendar endpoints. Calendar IDs returned by `POST /v2.0/calendar` are composite strings like `12345-task` or `12345-appointment`. Always pass the full string — including the type suffix — when calling `PUT /v2.0/calendar/{calendarId}`, `DELETE /v2.0/calendar/{calendarId}`, or the finish/unfinish endpoints. ## Plan tasks Plan tasks are auto-generated action items created by Lofty's Sales Agent (AI Assistant) feature as part of automated follow-up plans. You can fetch plan tasks for a specific lead and batch-create them programmatically: ```bash theme={null} # Get plan tasks for a lead GET https://api.lofty.com/v2.0/plan-tasks/lead/{leadId} # Batch create plan tasks POST https://api.lofty.com/v2.0/plan-tasks/create ``` Plan tasks are associated with the Sales Agent workflow and are separate from agent-created tasks, though they share the same underlying data model. ## Endpoint overview | Method | Endpoint | Description | | -------- | -------------------------------------- | --------------------------------------- | | `GET` | `/v2.0/tasks` | List tasks for a lead | | `POST` | `/v2.0/tasks` | Create a task or appointment | | `GET` | `/v2.0/tasks/{taskId}` | Get a task or appointment by ID | | `PUT` | `/v2.0/tasks/{taskId}` | Update a task or appointment | | `DELETE` | `/v2.0/tasks/{taskId}` | Delete a task or appointment | | `POST` | `/v2.0/tasks/{taskId}/finish` | Mark as completed | | `POST` | `/v2.0/tasks/{taskId}/unfinish` | Revert to open | | `GET` | `/v2.0/tasks/my-tasks` | List tasks assigned to the current user | | `GET` | `/v1.0/tasks` | List tasks for a lead (V1) | | `POST` | `/v1.0/tasks` | Create a task (V1) | | `GET` | `/v1.0/tasks/{taskId}` | Get a task by ID (V1) | | `PUT` | `/v1.0/tasks/{taskId}` | Update a task (V1) | | `DELETE` | `/v1.0/tasks/{taskId}` | Delete a task (V1) | | `GET` | `/v1.0/appts` | List appointments for a lead | | `GET` | `/v2.0/calendar` | Query calendar entries | | `POST` | `/v2.0/calendar` | Create a calendar entry | | `GET` | `/v2.0/calendar/{calendarId}` | Get a calendar entry | | `PUT` | `/v2.0/calendar/{calendarId}` | Update a calendar entry | | `DELETE` | `/v2.0/calendar/{calendarId}` | Delete a calendar entry | | `POST` | `/v2.0/calendar/{calendarId}/finish` | Mark calendar entry as completed | | `POST` | `/v2.0/calendar/{calendarId}/unfinish` | Revert calendar entry to open | | `GET` | `/v2.0/plan-tasks/lead/{leadId}` | Get plan tasks for a lead | | `POST` | `/v2.0/plan-tasks/create` | Batch create plan tasks | # Transactions Source: https://developer.lofty.com/concepts/transactions Transactions represent real estate deals linked to a lead. Create, update, and track deals through your pipeline with custom fields and property addresses. A transaction in Lofty represents a real estate deal — a purchase, listing, lease, or other arrangement — associated with a specific lead. Transactions track deal financials, pipeline status, close dates, commission, and the property address, and they move through a configurable pipeline as the deal progresses. ## What is a transaction? Each transaction belongs to exactly one lead and is managed by an assigned agent. When you create a transaction, you give it a name (typically the property address or deal identifier), a type, and an initial status. From there, you update it as the deal moves through your team's configured pipeline stages. Transactions support custom fields, letting teams capture deal-specific data beyond the standard schema. You can retrieve your team's custom field definitions before creating or updating transactions. ## Key transaction fields | Field | Type | Description | | ------------------- | ------ | ------------------------------------------------------------- | | `transactionId` | int64 | Unique transaction ID (read-only) | | `leadId` | int64 | Lead this transaction belongs to | | `transactionName` | string | Property address or deal name (required on create) | | `transactionType` | string | `Purchase`, `Listing`, `Lease`, or `Other` | | `transactionStatus` | string | Pipeline status name (must match your team's pipeline config) | | `homePrice` | number | Deal price | | `expectedCloseDate` | int64 | Expected close date (Unix ms) | | `closeDate` | int64 | Actual close date (Unix ms) | | `commissionRate` | number | Commission rate as a percentage (e.g., `3` for 3%) | | `gci` | number | Gross Commission Income | | `teamRevenue` | number | Portion of GCI attributed to the team | | `agentRevenue` | number | Portion of GCI attributed to the agent | | `assignedAgent` | int64 | User ID of the assigned agent | | `created` | int64 | Creation timestamp (Unix ms, read-only) | | `updated` | int64 | Last-modified timestamp (Unix ms, read-only) | A transaction record's `transactionType` is always one of the four concrete types above. When listing transactions with [`GET /v2.0/transactions`](/api-reference/transactions/list-transactions), the `transactionType` query parameter additionally accepts `All` to return every type in a single call — `All` is a filter value, never a field value. ## Transaction lifecycle Transactions move through a pipeline you configure in Lofty. The typical progression is: 1. **Created** — You create the transaction with `POST /v1.0/leads/{leadId}/transaction`. Supply a `transactionName`, `transactionType`, and initial `transactionStatus`. 2. **In-progress** — As the deal advances, you update the status with `PUT /v1.0/leads/{leadId}/transaction/{transactionId}`. The status value must match one of the statuses configured in your team's pipeline for the given `transactionType`. 3. **Closed** — Set `closeDate` and update the status to reflect the closed or cancelled state. Both `transactionType` and `transactionStatus` are case-sensitive and must match your team's pipeline configuration exactly. Unmatched values silently fall back to the first pipeline or first status — they do not produce an error. Always validate these values against your pipeline configuration before submitting. ## Creating a transaction Transactions are created under a specific lead. The `transactionName` field is required and must not be blank or contain `<` or `>` characters. ```bash theme={null} POST https://api.lofty.com/v1.0/leads/{leadId}/transaction ``` ```json theme={null} { "transactionName": "456 Oak Avenue", "transactionType": "Purchase", "transactionStatus": "Pre-contract", "homePrice": 875000, "expectedCloseDate": 1780000000000, "commissionRate": 3 } ``` ## Custom fields Your team can define custom fields on transactions to capture deal-specific data. Before creating or updating transactions with custom fields, retrieve the field definitions: ```bash theme={null} GET https://api.lofty.com/v1.0/transaction/customfields ``` This returns the list of custom field definitions for your team, including field names, types, and IDs. Use these definitions to populate custom field values correctly when creating or updating a transaction. ## Property address Each transaction can have a property address attached to it separately from the `transactionName`. This is useful when you need to store a structured address alongside the deal name. ```bash theme={null} # Update the property address on a transaction POST https://api.lofty.com/v1.0/leads/{leadId}/transaction/property/address # Retrieve the current property address GET https://api.lofty.com/v1.0/leads/{leadId}/transaction/{transactionId}/property/address ``` ## Searching transactions (V2) The V2 transactions endpoint lets you search across transactions without filtering by a specific lead. This is useful for building pipeline dashboards or reports that span multiple leads. ```bash theme={null} GET https://api.lofty.com/v2.0/transactions ``` Use `GET /v2.0/transactions` when building team-level reporting or pipeline views. Use `GET /v1.0/leads/{leadId}/transactions` when you need all transactions for a specific lead. ## Endpoint overview | Method | Endpoint | Description | | ------ | ------------------------------------------------------------------- | ------------------------------------------ | | `GET` | `/v1.0/leads/{leadId}/transactions` | List all transactions for a lead | | `POST` | `/v1.0/leads/{leadId}/transaction` | Create a transaction on a lead | | `GET` | `/v1.0/leads/{leadId}/transaction/{transactionId}` | Get a transaction by ID | | `PUT` | `/v1.0/leads/{leadId}/transaction/{transactionId}` | Partially update a transaction | | `GET` | `/v2.0/transactions` | Search transactions across leads | | `GET` | `/v1.0/transaction/customfields` | List transaction custom field definitions | | `POST` | `/v1.0/leads/{leadId}/transaction/property/address` | Set the property address on a transaction | | `GET` | `/v1.0/leads/{leadId}/transaction/{transactionId}/property/address` | Get the property address for a transaction | # Webhooks Source: https://developer.lofty.com/concepts/webhooks Subscribe to real-time event notifications from Lofty via HTTP callbacks. Webhooks deliver real-time HTTP POST notifications to your endpoint when data changes in a Lofty account. Instead of polling the API, register an HTTPS callback URL and Lofty pushes a JSON payload on each subscribed event. ## Subscribe ```bash theme={null} POST https://api.lofty.com/v1.0/webhook ``` ```json theme={null} { "listId": 2, "callbackUrl": "https://your-app.example.com/webhooks/lofty", "limit": 100 } ``` | Field | Type | Required | Description | | ------------- | ------- | -------- | -------------------------------------------- | | `listId` | integer | Yes | Event type (1–12, see table below) | | `callbackUrl` | string | Yes | HTTPS endpoint to receive payloads | | `limit` | integer | No | Max events per batch (default 100, max 5000) | The `callbackUrl` must use HTTPS. HTTP endpoints are rejected. ## Manage subscriptions | Method | Endpoint | Description | | -------- | ----------------------------- | ------------------------- | | `POST` | `/v1.0/webhook` | Create a subscription | | `GET` | `/v1.0/webhooks` | List active subscriptions | | `DELETE` | `/v1.0/webhook/{subscribeId}` | Delete a subscription | ## Delivery timing Events to a healthy endpoint are typically delivered within **1 minute** of the triggering change. Actual delivery speed depends on your endpoint. Lofty delivers to each endpoint over a limited number of concurrent connections, so a slow handler queues its own events: an endpoint that responds in under a second keeps up with any volume, while one that takes several seconds per request can fall minutes behind at peak — see the throughput note under [Best practices](#best-practices). Failed deliveries follow the [retry schedule](#retry-schedule) below. Deliveries to a disabled subscription pause and resume automatically after recovery — see [Health probes](#health-probes-and-automatic-recovery). ## Retries and failure handling A delivery counts as successful when your endpoint returns any **2xx** status code. Any other response — an error status, a timeout, or a connection failure — is handled by the rules in this section. ### Retry schedule Failed deliveries are retried up to **9 total attempts within 48 hours**, with exponential backoff between attempts: | Attempt | Delay after previous failure | | ------- | ---------------------------- | | 2 | 30 seconds | | 3 | 2 minutes | | 4 | 10 minutes | | 5 | 30 minutes | | 6 | 2 hours | | 7 | 6 hours | | 8 | 12 hours | | 9 | 24 hours | Each delay includes ±20% random jitter so retries from many events don't arrive in synchronized bursts. An event that still cannot be delivered **48 hours** after it was created is discarded. ### Response handling | Your response | What Lofty does | | ---------------------------------------------- | ------------------------------------------------------------------------------------------- | | `2xx` | Delivered — no further attempts | | `408`, `429`, `5xx`, timeout, connection error | Retried on the schedule above | | `429` with a `Retry-After` header | Retried after the delay you request (minimum 60 seconds, capped at 1 hour) | | `410 Gone` | The subscription is **disabled immediately** — return 410 when you decommission an endpoint | | Any other `4xx` | Not retried — the event is discarded | ### Idempotent delivery Every delivery request carries a `Lofty-Webhook-Id` header. Its value is unique per event and **stable across retries**. If a timeout caused your response to be lost, the retry arrives with the same `Lofty-Webhook-Id` — use it to deduplicate instead of processing the event twice. ### Endpoint health and automatic disabling Lofty tracks delivery health per subscription and disables a subscription automatically when: * your endpoint returns **410 Gone** — immediately, or * deliveries fail **continuously with no successes in between** — within hours for high-volume endpoints, or after roughly a day of sustained failures otherwise. While a subscription is disabled, Lofty stops attempting deliveries to it. Pending events wait for recovery but still expire 48 hours after creation. ### Health probes and automatic recovery Disabled subscriptions receive a **ping probe** approximately every 6 hours: ```json theme={null} { "eventType": "ping", "webhookId": 123456 } ``` The first probe that gets a 2xx response re-enables the subscription, and all pending unexpired events are delivered immediately. No manual action is needed on either side. **Your receiver must return 2xx to ping probes.** If your endpoint rejects unrecognized event types with an error status, a disabled subscription can never recover automatically. Acknowledge first, then ignore event types you don't handle. ## Notification recipients Which subscribed users receive a callback depends on the subscription's delivery mode (`permissionMode`). In both modes, only users in the lead's team who have subscribed to the event type are considered. ### Assignment-based delivery — `permissionMode: 0` (default) A subscriber receives the callback only for leads assigned to them or that they administer: * **Hidden leads** — only the assigned agent receives the notification. * **Other leads** — the subscriber receives it if they are the assigned agent, or a Company Owner / Company Admin. All other subscribers are excluded. ### Ownership-based delivery — `permissionMode: 1` A subscriber receives the callback for any lead they manage, regardless of who it is assigned to: * Every assignment-based case above still applies (assigned agent, Company Owner / Company Admin). * In addition, the subscriber receives the callback for any lead within their **ownership scope** — owned by their team or office, or personally owned by them — even if it is assigned to someone else. * Hidden leads are still delivered only to users who manage them. Ownership-based mode is intended for team/account-level integrations that need to cover every lead in their scope, not only assigned leads. Set `permissionMode` when creating the subscription; it defaults to `0`, so existing subscriptions are unaffected. ## Common fields All payloads include: | Field | Type | Description | | -------- | ------- | -------------------------------- | | `teamId` | integer | Team ID where the event occurred | | `listId` | integer | Event type ID (1–12) | ## Event types | listId | Event | Triggers | | ------ | --------------- | ---------------------------------------------------------- | | 1 | Agent Info | Agent created or updated | | 2 | Lead Info | Lead created, updated, or deleted | | 3 | Lead Activity | Lead site activity (browse, favorite, search) | | 4 | Listing Alert | Listing alert changed | | 5 | Transaction | Transaction created, updated, or deleted | | 6 | Call | Call event (MANUAL and LOGGED only, excludes AUTO) | | 7 | Email | Email event (MANUAL and LOGGED only, excludes AUTO) | | 8 | Text | Text message event (MANUAL and LOGGED only, excludes AUTO) | | 9 | Note | Note created, updated, or deleted | | 10 | Task | Task created, updated, finished, or deleted | | 11 | Appointment | Appointment created, updated, finished, or deleted | | 12 | Pipeline Change | Lead pipeline stage changed | **Call, Email, and Text webhooks do not fire for AUTO communications.** Only agent-initiated (`MANUAL`) and after-the-fact logged (`LOGGED`) events trigger webhooks. Automated messages sent by Lofty (drip campaigns, auto-responders, Smart Plans, etc.) are excluded. If your integration depends on tracking all communications, you must also poll the Communication API for `AUTO` type events. *** ## Event payloads ### 1. Agent Info Triggered when an agent is created or updated. ```json theme={null} { "teamId": 123456, "listId": 1, "updatedAgent": [ { "agentUserId": 789, "updateTime": 1736935800000 } ] } ``` | Field | Type | Description | | ---------------------------- | ------- | -------------------------------- | | `updatedAgent` | array | List of updated agent records | | `updatedAgent[].agentUserId` | integer | User ID of the updated agent | | `updatedAgent[].updateTime` | integer | Epoch milliseconds of the update | ### 2. Lead Info Triggered when a lead is created, updated, or deleted. Only the relevant action fields are present. ```json theme={null} { "teamId": 123456, "listId": 2, "createdLead": [ { "leadId": 456, "updateTime": 1736935800000 } ], "updatedLead": [ { "leadId": 457, "updateTime": 1736935860000 } ], "deletedLead": [ { "leadId": 458, "updateTime": 1736935920000 } ] } ``` | Field | Type | Description | | --------------- | ------- | --------------------------------- | | `createdLead` | array | Created leads (present on create) | | `updatedLead` | array | Updated leads (present on update) | | `deletedLead` | array | Deleted leads (present on delete) | | `[].leadId` | integer | Lead ID | | `[].updateTime` | integer | Epoch milliseconds of the change | ### 3. Lead Activity Triggered when a lead performs site activity (SiteBrowse, SiteFavorite, SiteSearch, etc.). ```json theme={null} { "teamId": 123456, "listId": 3, "updatedLead": [ { "leadId": 456, "updateTime": 1736935800000 } ] } ``` | Field | Type | Description | | -------------------------- | ------- | ---------------------------------- | | `updatedLead` | array | Leads with new activity | | `updatedLead[].leadId` | integer | Lead ID | | `updatedLead[].updateTime` | integer | Epoch milliseconds of the activity | ### 4. Listing Alert Triggered when a listing alert changes. ```json theme={null} { "teamId": 123456, "listId": 4, "alertId": 789 } ``` | Field | Type | Description | | --------- | ------- | ----------------------- | | `alertId` | integer | ID of the changed alert | ### 5. Transaction Triggered when a transaction is created, updated, or deleted. Only the relevant action fields are present. ```json theme={null} { "teamId": 123456, "listId": 5, "createdTransaction": [ { "leadId": 456, "transactionId": 789, "updateTime": 1736935800000 } ], "updatedTransaction": [ { "leadId": 457, "transactionId": 790, "updateTime": 1736935860000, "updatedField": { "fieldName": "newValue" }, "updatedFields": [{ "fieldName": "newValue" }] } ], "deletedTransaction": [ { "leadId": 458, "transactionId": 791, "updateTime": 1736935920000 } ] } ``` | Field | Type | Description | | -------------------- | ------- | ------------------------------------------------ | | `createdTransaction` | array | Created transactions (present on create) | | `updatedTransaction` | array | Updated transactions (present on update) | | `deletedTransaction` | array | Deleted transactions (present on delete) | | `[].leadId` | integer | Associated lead ID | | `[].transactionId` | integer | Transaction ID | | `[].updateTime` | integer | Epoch milliseconds of the change | | `[].updatedField` | object | Changed field key-value pair (nullable) | | `[].updatedFields` | array | List of changed field key-value pairs (nullable) | ### 6. Call Triggered on a call event. ```json theme={null} { "teamId": 123456, "listId": 6, "createdCall": [ { "callId": 101, "timelineId": 9876, "leadId": 456, "updateTime": 1736935800000, "agentId": 789, "communicationType": "MANUAL" } ] } ``` | Field | Type | Description | | ---------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `createdCall` | array | Call event records | | `[].callId` | integer | Dialer call record ID (`MANUAL`) or timeline ID (`LOGGED`). Kept for backward compatibility — prefer `timelineId` for new integrations. | | `[].timelineId` | integer | Lead timeline row ID for this call event. Always `lead_timeline.id` regardless of `communicationType`. Pass as `communicationId` to `GET /v2.0/communication/call/{communicationId}` to fetch the full record. | | `[].leadId` | integer | Associated lead ID | | `[].updateTime` | integer | Epoch milliseconds of the call | | `[].agentId` | integer | Agent user ID | | `[].communicationType` | string | `MANUAL` or `LOGGED` | ### 7. Email Triggered on an email event. ```json theme={null} { "teamId": 123456, "listId": 7, "createdEmail": [ { "emailId": 101, "timelineId": 9876, "leadId": 456, "updateTime": 1736935800000, "agentId": 789, "communicationType": "MANUAL" } ] } ``` | Field | Type | Description | | ---------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `createdEmail` | array | Email event records | | `[].emailId` | integer | Email record ID (`MANUAL`) or timeline ID (`LOGGED`). Kept for backward compatibility — prefer `timelineId` for new integrations. | | `[].timelineId` | integer | Lead timeline row ID for this email event. Always `lead_timeline.id` regardless of `communicationType`. Pass as `communicationId` to `GET /v2.0/communication/email/{communicationId}` to fetch the full record. | | `[].leadId` | integer | Associated lead ID | | `[].updateTime` | integer | Epoch milliseconds of the event | | `[].agentId` | integer | Agent user ID | | `[].communicationType` | string | `MANUAL` or `LOGGED` | ### 8. Text Triggered on a text/SMS event. ```json theme={null} { "teamId": 123456, "listId": 8, "createdText": [ { "textId": 101, "timelineId": 9876, "leadId": 456, "updateTime": 1736935800000, "agentId": 789, "communicationType": "MANUAL" } ] } ``` | Field | Type | Description | | ---------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `createdText` | array | Text message event records | | `[].textId` | integer | SMS record ID (`MANUAL`) or timeline ID (`LOGGED`). Kept for backward compatibility — prefer `timelineId` for new integrations. | | `[].timelineId` | integer | Lead timeline row ID for this text event. Always `lead_timeline.id` regardless of `communicationType`. Pass as `communicationId` to `GET /v2.0/communication/text/{communicationId}` to fetch the full record. | | `[].leadId` | integer | Associated lead ID | | `[].updateTime` | integer | Epoch milliseconds of the event | | `[].agentId` | integer | Agent user ID | | `[].communicationType` | string | `MANUAL` or `LOGGED` | **communicationType values** — `MANUAL`: agent-initiated communication. `LOGGED`: communication recorded after the fact. `AUTO` events are excluded from webhooks. ### 9. Note Triggered when a note is created, updated, or deleted. Only the relevant action fields are present. ```json theme={null} { "teamId": 123456, "listId": 9, "createdNote": [ { "noteId": 101, "leadId": 456, "updateTime": 1736935800000, "noteType": "Manual", "agentId": 789 } ], "updatedNote": { "noteId": 102, "leadId": 457, "updateTime": 1736935860000, "noteType": "Manual", "agentId": 789 }, "deletedNote": { "noteId": 103, "leadId": 458, "updateTime": 1736935920000, "noteType": "Manual", "agentId": 789 } } ``` | Field | Type | Description | | --------------- | ------- | --------------------------------- | | `createdNote` | array | Created notes (present on create) | | `updatedNote` | object | Updated note (present on update) | | `deletedNote` | object | Deleted note (present on delete) | | `[].noteId` | integer | Note ID | | `[].leadId` | integer | Associated lead ID | | `[].updateTime` | integer | Epoch milliseconds of the change | | `[].noteType` | string | `Manual`, `System`, or `AI` | | `[].agentId` | integer | Agent user ID | ### 10. Task Triggered when a task is created, updated, finished, or deleted. Multiple action fields may appear in a single payload. ```json theme={null} { "teamId": 123456, "listId": 10, "createdTask": [ { "taskId": 101, "leadId": 456, "updateTime": 1736935800000 } ], "updatedTask": [ { "taskId": 102, "leadId": 457, "updateTime": 1736935860000 } ], "finishedTask": [ { "taskId": 103, "leadId": 458, "updateTime": 1736935920000 } ], "deletedTask": [ { "taskId": 104, "leadId": 459, "updateTime": 1736935980000 } ] } ``` | Field | Type | Description | | --------------- | ------- | ---------------------------------- | | `createdTask` | array | Created tasks (present on create) | | `updatedTask` | array | Updated tasks (present on update) | | `finishedTask` | array | Finished tasks (present on finish) | | `deletedTask` | array | Deleted tasks (present on delete) | | `[].taskId` | integer | Task ID | | `[].leadId` | integer | Associated lead ID | | `[].updateTime` | integer | Epoch milliseconds of the change | ### 11. Appointment Triggered when an appointment is created, updated, finished, or deleted. Multiple action fields may appear in a single payload. ```json theme={null} { "teamId": 123456, "listId": 11, "createdAppt": [ { "apptId": 101, "leadId": 456, "updateTime": 1736935800000 } ], "updatedAppt": [ { "apptId": 102, "leadId": 457, "updateTime": 1736935860000 } ], "finishedAppt": [ { "apptId": 103, "leadId": 458, "updateTime": 1736935920000 } ], "deletedAppt": [ { "apptId": 104, "leadId": 459, "updateTime": 1736935980000 } ] } ``` | Field | Type | Description | | --------------- | ------- | ----------------------------------------- | | `createdAppt` | array | Created appointments (present on create) | | `updatedAppt` | array | Updated appointments (present on update) | | `finishedAppt` | array | Finished appointments (present on finish) | | `deletedAppt` | array | Deleted appointments (present on delete) | | `[].apptId` | integer | Appointment ID | | `[].leadId` | integer | Associated lead ID | | `[].updateTime` | integer | Epoch milliseconds of the change | ### 12. Pipeline Change Triggered when a lead's pipeline stage changes. ```json theme={null} { "teamId": 123456, "listId": 12, "updatedLeadstage": [ { "leadId": 456, "updateTime": 1736935800000, "changeInfo": { "oldstage": "New Lead", "newstage": "Contacted" } } ] } ``` | Field | Type | Description | | ------------------------ | ------- | -------------------------------------- | | `updatedLeadstage` | array | Pipeline stage change records | | `[].leadId` | integer | Lead ID | | `[].updateTime` | integer | Epoch milliseconds of the stage change | | `[].changeInfo.oldstage` | string | Previous pipeline stage name | | `[].changeInfo.newstage` | string | New pipeline stage name | *** ## Best practices **Respond immediately.** Return a `2xx` before processing and move your logic to a background job. Lofty limits concurrent deliveries per endpoint, so a slow handler directly reduces your delivery throughput — at high event volume, synchronous processing is the most common cause of delayed webhooks. **Handle duplicates.** The same event may be delivered more than once (for example, when a timeout swallowed your response). Deduplicate with the `Lofty-Webhook-Id` header — it is stable across retries of the same event. **Don't assume ordering.** Retries mean an older event can arrive after a newer one. Order by the `updateTime` field inside the payload, not by arrival time. **Acknowledge pings.** Return `2xx` to `{"eventType": "ping"}` probes — they are how a disabled subscription recovers automatically. **Decommission cleanly.** When you shut an endpoint down for good, return `410 Gone` so Lofty stops delivering immediately instead of retrying against a dead URL. **Validate payloads.** Check that `listId` matches your expected event type before processing. **Use HTTPS.** Plain HTTP endpoints are rejected. Use [ngrok](https://ngrok.com) during development to tunnel webhooks to your local machine. ## Example receiver ```javascript theme={null} const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhooks/lofty', (req, res) => { res.sendStatus(200); // ack first — this also covers ping probes if (req.body.eventType === 'ping') return; const { listId, events } = req.body; processEvents(listId, events).catch(console.error); }); async function processEvents(listId, events) { for (const event of events) { console.log(`Event type ${listId}:`, event); } } app.listen(3000); ``` ## Resolving communication events to full records After receiving a Call / Email / Text webhook, fetch the full record by passing the event's `timelineId` as the `communicationId` path parameter of the v2 by-ID endpoint: | Webhook event | Lookup endpoint | Response | | ------------- | ------------------------------------------------- | --------------- | | Call | `GET /v2.0/communication/call/{communicationId}` | `CallResponse` | | Email | `GET /v2.0/communication/email/{communicationId}` | `EmailResponse` | | Text | `GET /v2.0/communication/text/{communicationId}` | `TextResponse` | `timelineId` is the recommended round-trip key — it always points to the same `lead_timeline` row regardless of whether the event is `MANUAL` or `LOGGED`. The legacy `callId` / `emailId` / `textId` fields still ship for backward compatibility, but their underlying record changes between the two `communicationType` values, so they are not safe to pass to the v2 by-ID endpoints. All IDs in webhook payloads are 64-bit integers. JavaScript clients must handle them carefully — see [JS / TS Integration](/javascript-integration). # AI Features Source: https://developer.lofty.com/guides/ai-features Use Lofty's AI endpoints to analyze leads, summarize calls, generate call scripts, and prepare insights for more effective outreach. Lofty's AI endpoints give you programmatic access to intelligent features built on top of lead and call data. You can retrieve AI-generated analysis of lead behavior, get summaries of recorded calls, generate personalized call scripts, and pre-compute insights to speed up downstream AI operations. This guide walks through each AI feature and shows you how to integrate them into your workflow. All AI endpoints require appropriate OAuth permission scopes granted in the Developer Portal. Ensure your application has the necessary AI scopes before calling these endpoints. Requests made without the required scopes will return a `403 Forbidden` response. Generate and retrieve AI summaries of a lead's behavior, engagement patterns, and likely intent. Automatically extract key points and action items from recorded calls with a lead. Generate a personalized outreach script tailored to a lead's profile and interaction history. Pre-compute insights for a lead to accelerate subsequent AI operations like lead analysis. ## Lead analysis Lead analysis returns an AI-generated summary of a lead's behavior and engagement with your team. Use the `GET` endpoint to retrieve an existing analysis, or the `POST` endpoint to generate a new one on demand. **Retrieve an existing analysis** ```bash theme={null} curl -X GET "https://api.lofty.com/v2.0/ai/lead-analysis?leadId=789012345678" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` **Generate a new analysis** ```bash theme={null} curl -X POST https://api.lofty.com/v2.0/ai/lead-analysis \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "leadId": 789012345678 }' ``` The response includes an AI-generated summary covering the lead's engagement patterns, communication preferences, and recommended next steps based on their history in Lofty. Call [Prepare insight](#prepare-insight) before generating lead analysis. Pre-computing insights improves the quality and relevance of the analysis output. ## Call summary Call summaries extract structured information from recorded calls, including key discussion points and recommended action items. Use the `GET` endpoint to fetch an existing summary, or trigger generation with `POST`. **Retrieve an existing summary** ```bash theme={null} curl -X GET "https://api.lofty.com/v2.0/ai/call-summary?callId=call_abc123" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` **Generate a summary** ```bash theme={null} curl -X POST https://api.lofty.com/v2.0/ai/call-summary/generate \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "callId": "call_abc123" }' ``` The response includes a structured summary with key points discussed during the call and a list of follow-up action items identified by the AI. ## Call script The call script endpoint generates a personalized script for an upcoming call with a lead. The script is tailored to the lead's profile, communication history, and current pipeline stage. ```bash theme={null} curl -X POST https://api.lofty.com/v2.0/ai/call-script \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "leadId": 789012345678 }' ``` The response returns a structured script with an opening, suggested talking points, objection handling guidance, and a recommended call-to-action based on the lead's history. ## Prepare insight The prepare insight endpoint pre-computes AI insights for a lead and stores them for use by other AI endpoints. Calling this endpoint before lead analysis or before generating a call script gives the AI more context to work with, resulting in higher-quality output. ```bash theme={null} curl -X POST https://api.lofty.com/v2.0/ai/prepare-insight \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "leadId": 789012345678 }' ``` Insight preparation runs asynchronously. The response confirms the request was accepted; the computed insights will be available to downstream AI endpoints once processing is complete. # Communication Source: https://developer.lofty.com/guides/communication Send emails and SMS, retrieve call and message history, add notes, and log external communications for Lofty leads using the REST API. Lofty's communication APIs let you send emails and SMS messages directly to leads, retrieve their full interaction history, manage notes, and log calls or messages that took place outside the CRM. This guide covers each communication channel and explains how to keep your lead timeline accurate and up to date. ## Unified activity timeline The activity timeline endpoint returns all interactions for a lead—calls, texts, and emails—sorted chronologically. This gives you a single view of every touchpoint with a lead. ```bash theme={null} curl -X GET "https://api.lofty.com/v2.0/leads/789012345678/activities" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` **Example response** ```json theme={null} { "activities": [ { "activityId": "act_001", "type": "call", "direction": "outbound", "timestamp": "2026-05-06T14:32:00Z", "duration": 180, "summary": "Discussed property at 123 Main St" }, { "activityId": "act_002", "type": "sms", "direction": "inbound", "timestamp": "2026-05-06T16:10:00Z", "content": "Thanks for calling! I have a few questions." }, { "activityId": "act_003", "type": "email", "direction": "outbound", "timestamp": "2026-05-07T09:00:00Z", "subject": "Follow-up on 123 Main St" } ], "page": 1, "pageSize": 20, "total": 3 } ``` ## Send an email or SMS Use the email and SMS endpoints to send messages to a lead directly from your integration. ```bash Send email theme={null} curl -X POST https://api.lofty.com/v1.0/message/email/send \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "leadId": 789012345678, "subject": "Follow-up on your home search", "body": "Hi Jane, I wanted to follow up on the properties we discussed. Let me know if you have any questions." }' ``` ```bash Send SMS theme={null} curl -X POST https://api.lofty.com/v1.0/message/sms/send \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "leadId": 789012345678, "content": "Hi Jane! Just checking in. Feel free to reach out if you have questions about any listings." }' ``` ## Call history Retrieve the call history for a lead using the call communication endpoint. Use the V2 endpoint when you need richer detail such as recordings, transcripts, and AI-generated summaries. ```bash theme={null} # Standard call history curl -X GET "https://api.lofty.com/v1.0/communication/call?leadId=789012345678" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` ```bash theme={null} # Detailed call history (V2) curl -X GET "https://api.lofty.com/v1.0/communication/call/v2?leadId=789012345678" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` ## Resolve a webhook event to a full record Call, email, and text [webhook payloads](/concepts/webhooks#event-payloads) include a `timelineId` that uniquely identifies the timeline entry for the event. Pass it as the `communicationId` path parameter of the matching V2 by-ID endpoint to fetch the full record without scanning the lead's history. | Webhook event | Lookup endpoint | Response | | ------------- | ------------------------------------------------- | --------------- | | Call | `GET /v2.0/communication/call/{communicationId}` | `CallResponse` | | Email | `GET /v2.0/communication/email/{communicationId}` | `EmailResponse` | | Text | `GET /v2.0/communication/text/{communicationId}` | `TextResponse` | ```bash theme={null} curl -X GET "https://api.lofty.com/v2.0/communication/call/9876" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` Always round-trip with `timelineId` — the legacy `callId` / `emailId` / `textId` fields are retained for backward compatibility, but their underlying record changes between `MANUAL` and `LOGGED` events and is not safe to pass to the V2 by-ID endpoints. ## Notes Notes let you attach free-form text to a lead's record. You can list, create, update, and delete notes using the `/v1.0/notes` endpoints. **List notes for a lead** ```bash theme={null} curl -X GET "https://api.lofty.com/v1.0/notes?leadId=789012345678" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` **Create a note** ```bash theme={null} curl -X POST https://api.lofty.com/v1.0/notes \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "leadId": 789012345678, "content": "Lead is interested in 3-bedroom homes in the Riverside area. Budget up to $650,000." }' ``` **Update a note** ```bash theme={null} curl -X PUT https://api.lofty.com/v1.0/notes/note_abc123 \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "content": "Lead is interested in 3-4 bedroom homes in the Riverside area. Budget up to $700,000 after pre-approval." }' ``` **Delete a note** ```bash theme={null} curl -X DELETE https://api.lofty.com/v1.0/notes/note_abc123 \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` ## Log external communications If a call, email, or text happened outside of Lofty—for example, from a personal phone or a third-party email client—you can log it manually to keep the lead's activity timeline accurate. Send a `POST` request to `/v1.0/leads/{leadId}/activity` with the details of the interaction. ```bash theme={null} curl -X POST https://api.lofty.com/v1.0/leads/789012345678/activity \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "type": "call", "direction": "outbound", "duration": 240, "timestamp": "2026-05-07T10:15:00Z", "notes": "Called to confirm showing appointment for tomorrow at 2pm." }' ``` The `direction` field indicates who initiated the interaction: * `"outbound"` — the agent reached out to the lead * `"inbound"` — the lead reached out to the agent Activity logging is processed asynchronously. After creating a manual activity entry, allow a short delay before querying the timeline—the entry may not appear immediately in subsequent GET requests. # Lead Management Source: https://developer.lofty.com/guides/lead-management Create, retrieve, update, assign, and delete leads using the Lofty API. This guide shows how to perform common lead operations. All examples assume you have a valid access token — see [Authentication](/authentication/overview). ## Create a lead ```bash theme={null} curl -X POST https://api.lofty.com/v1.0/leads \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "firstName": "Jane", "lastName": "Smith", "emails": ["jane.smith@example.com"], "phones": ["5551234567"], "leadTypes": [2], "source": "Website" }' ``` Response: ```json theme={null} { "leadId": 651095960136641 } ``` The `leadId` is returned as a JSON number. In JavaScript, parse the response as text and handle the ID as a string or `BigInt` to avoid precision loss. Omit `assignedUserId` to let Lofty's routing rules automatically assign the lead. Include it to skip routing and assign directly. ## Retrieve a lead ```bash theme={null} curl https://api.lofty.com/v1.0/leads/563172647619608 \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` ## List and filter leads Use query parameters to paginate and filter: ```bash theme={null} curl "https://api.lofty.com/v1.0/leads?limit=20&offset=0&stage=New+Lead&sort=LastActivity&desc=true" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` Key filters: `assignedUserId`, `stage`, `source`, `email`, `phone`, `key` (fuzzy search), `anyTags`, `allTags`, `contacted`. Use `scrollId` from the response for efficient cursor-based pagination over large result sets. ## Update a lead Send only the fields you want to change: ```bash theme={null} curl -X PUT https://api.lofty.com/v1.0/leads/563172647619608 \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "stage": "Contacted", "tags": [{"tagName": "follow-up"}] }' ``` Note that `tags` replaces the lead's entire tag set. To add tags without touching existing ones use `tagsAdd`; to remove specific tags by name use `tagsRemove`; to remove every tag set `clearAllTags: true` (takes precedence over the other tag fields). ## Assign a lead ```bash theme={null} curl -X POST https://api.lofty.com/v1.0/leads/563172647619608/assignment \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "assignees": [{"userId": 10000000514}] }' ``` To preview which agent routing would select without creating a lead, use the [Preview Routing](/api-reference/leads/resolve-assignees-by-lead-info) endpoint. ## Delete a lead `DELETE /v1.0/leads/{leadId}` moves the lead to the trash — it is **not permanently deleted**. A `reason` parameter is required and stored for audit purposes. ```bash theme={null} curl -X DELETE "https://api.lofty.com/v1.0/leads/563172647619608?reason=Duplicate+record" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` Trashed leads may be recoverable from the Lofty CRM interface. The API does not provide a restore endpoint. ## Monitor lead changes Subscribe to [Lead Info webhooks](/concepts/webhooks#2-lead-info) (listId: 2) to receive real-time notifications when leads are created, updated, or deleted. ## Next steps * [Leads concept](/concepts/leads) — understand the data model and lifecycle * [Leads API reference](/api-reference/leads/overview) — full endpoint documentation * [Communication guide](/guides/communication) — log calls, emails, and texts for leads # Working Leads Source: https://developer.lofty.com/guides/working-leads Add leads to an agent's active working pipeline, check who is working a lead, manage mute status, and query quota limits using the Lofty V2 API. In Lofty, a *working lead* is a lead that an agent is actively pursuing. The working leads set represents the agent's current pipeline—the subset of all assigned leads that require immediate attention and outreach. This guide covers how to list, add, and manage working leads, check quota limits, identify which agents are working a specific lead, and control mute status for leads in the queue. ## List working leads Send a `GET` request to `/v2.0/working-leads` to retrieve the paginated list of leads currently in the authenticated agent's working set. ```bash theme={null} curl -X GET "https://api.lofty.com/v2.0/working-leads?page=1&pageSize=25" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` The response returns a paginated array of lead objects, each including the lead's ID, name, stage, and assignment details. ## Add a lead to working leads Send a `POST` request to `/v2.0/working-leads/add` to add a lead to the authenticated agent's working set. ```bash theme={null} curl -X POST https://api.lofty.com/v2.0/working-leads/add \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "leadId": 789012345678 }' ``` ## Get the current agent's profile Send a `GET` request to `/v2.0/sales-agents/current` to retrieve the profile of the agent associated with the authenticated access token. ```bash theme={null} curl -X GET https://api.lofty.com/v2.0/sales-agents/current \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` The response includes the agent's ID, name, email, and role within the Lofty organization. ## Check working lead quota Each agent has a maximum number of leads they can hold in their working set at one time. Send a `GET` request to `/v2.0/sales-agents/quota` to see the current agent's limit and how many slots are in use. ```bash theme={null} curl -X GET https://api.lofty.com/v2.0/sales-agents/quota \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` **Example response** ```json theme={null} { "maxWorkingLeads": 50, "currentWorkingLeads": 34 } ``` Before adding leads to the working set, check this endpoint to confirm the agent has available capacity. ## Find which agents are working a lead Send a `GET` request to `/v2.0/sales-agents/by-lead` with a `leadId` query parameter to retrieve the list of agents currently working that lead. ```bash theme={null} curl -X GET "https://api.lofty.com/v2.0/sales-agents/by-lead?leadId=789012345678" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` The response returns an array of agent objects, each including the agent's ID, name, and contact details. ## Mute a working lead Muting a working lead deprioritizes it in the agent's queue without removing it from the working set. This is useful when a lead has gone temporarily unresponsive or the agent is waiting for a callback before following up. Send a `PUT` request to `/v2.0/sales-agents/working-lead/{leadId}/mute` with the desired mute state. ```bash theme={null} # Mute a lead curl -X PUT https://api.lofty.com/v2.0/sales-agents/working-lead/789012345678/mute \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "muted": true }' ``` ```bash theme={null} # Unmute a lead curl -X PUT https://api.lofty.com/v2.0/sales-agents/working-lead/789012345678/mute \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "muted": false }' ``` ## Check mute status Send a `GET` request to `/v2.0/sales-agent/lead/mute-status` with a `leadId` query parameter to check whether a lead is currently muted for the authenticated agent. ```bash theme={null} curl -X GET "https://api.lofty.com/v2.0/sales-agent/lead/mute-status?leadId=789012345678" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` **Example response** ```json theme={null} { "leadId": 789012345678, "muted": true } ``` # Introduction Source: https://developer.lofty.com/introduction The Lofty Developer API provides REST access to leads, listings, transactions, communications, tasks, and webhooks in Lofty's real estate CRM platform. The Lofty Developer API is a RESTful API that gives you programmatic access to the Lofty real estate CRM platform. If you're building a third-party integration, automating lead workflows, syncing CRM data, or embedding real estate functionality into your own application, this API is your entry point. ## Base URL and versioning All API requests are made to the following base URL: ```text theme={null} https://api.lofty.com ``` The API exposes two versioned path prefixes: | Version | Path prefix | Notes | | ------- | ----------- | ------------------------------------------------------------------------ | | v1.0 | `/v1.0/` | Current stable version for most endpoints | | v2.0 | `/v2.0/` | Available for select endpoints; check the API reference for availability | ## Authentication Lofty supports **OAuth 2.0** and **API Keys** (personal access tokens). | Method | Use case | Header | | --------- | ------------------------------------------- | ----------------------- | | OAuth 2.0 | Multi-tenant apps, third-party integrations | `Bearer ` | | API Key | Personal automation, scripts, CLI | `token ` | **OAuth 2.0** — Three grant types: Authorization Code, PKCE (for SPAs), and Client Credentials (for server-to-server). Register your app in the [Developer Portal](https://api.lofty.com/vendor/frontend/static/index.html#/login). **API Key** — User-scoped personal access tokens with configurable expiration and `THIRD_PARTY_OPERATION` scope. Used by the Lofty CLI as `LOFTY_CUSTOMER_KEY`. Generate yours under **Settings → Integrations → API**. Get credentials and make your first API call in minutes. Install the CLI for fast terminal-based API access. Connect Claude or any MCP client to the API — no SDK required. 115 endpoints across leads, tasks, listings, transactions, and more. Subscribe to real-time events when data changes. # JS / TS Integration Source: https://developer.lofty.com/javascript-integration Safely handle 64-bit integer IDs from the Lofty API in JavaScript and TypeScript to prevent silent precision loss in your application. ## int64 ID Precision Lofty entity IDs are 64-bit integers (Java `Long`), but JavaScript's `Number` only supports up to **2^53 - 1**. Larger IDs are **silently corrupted** by `JSON.parse()`: ```javascript theme={null} // ❌ Unsafe — ID is silently corrupted const data = JSON.parse('{"leadId": 1738234566814937089}'); console.log(data.leadId); // 1738234566814937088 ← wrong! ``` ## Solutions ```javascript Using BigInt theme={null} // Parse the response as text first, then replace large integers before parsing // This example uses a regex to wrap large numeric IDs in quotes as strings const response = await fetch('https://api.lofty.com/v1.0/leads', { headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN' } }); const text = await response.text(); // Convert to BigInt after extracting the raw string value // Use json-bigint (see next tab) for a more robust solution const raw = JSON.parse(text.replace(/"id"\s*:\s*(\d+)/g, '"id":"$1"')); const leadId = BigInt(raw.data.list[0].id); console.log(leadId); // 563172647619623n — exact, no precision loss // Only use BigInt arithmetic when you need to manipulate the value const nextId = leadId + 1n; ``` ```javascript Using json-bigint theme={null} // Install: npm install json-bigint // json-bigint parses large integers as BigInt automatically import JSONbig from 'json-bigint'; const response = await fetch('https://api.lofty.com/v1.0/leads', { headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN' } }); const text = await response.text(); const data = JSONbig({ useNativeBigInt: true }).parse(text); const lead = data.data.list[0]; console.log(lead.id); // 563172647619623n — BigInt, exact value console.log(typeof lead.id); // "bigint" ``` ```typescript Treating IDs as strings (TypeScript) theme={null} // Define your types with IDs as strings to force safe handling throughout your app interface Lead { id: string; // Store as string — never number name: string; email: string; phone: string; } // Use a reviver function with JSON.parse to convert IDs to strings at parse time function parseWithStringIds(text: string): unknown { return JSON.parse(text, (key, value, context) => { // context.source is the raw string from the JSON input (Node 18+, modern browsers) if (typeof value === 'number' && context?.source) { return context.source; // Return the raw string instead of the parsed number } return value; }); } const response = await fetch('https://api.lofty.com/v1.0/leads', { headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN' as string } }); const text = await response.text(); const result = parseWithStringIds(text) as { data: { list: Lead[] } }; const lead = result.data.list[0]; console.log(lead.id); // "563172647619623" — exact string, no precision loss console.log(typeof lead.id); // "string" ``` ## Best practices **Store and compare IDs as strings.** Once you've safely parsed an ID out of the response, keep it as a string or `BigInt` for its entire lifetime in your application. When you need to compare two IDs, compare them as strings: ```javascript theme={null} // Safe comparison if (lead.id === selectedId) { ... } // Also safe with BigInt if (lead.id === BigInt(selectedId)) { ... } // UNSAFE — converting to Number for comparison defeats the purpose if (Number(lead.id) === Number(selectedId)) { ... } ``` **Only use BigInt when doing arithmetic.** If you need to perform arithmetic on an ID (uncommon, but possible in pagination or range queries), parse it to `BigInt`. Convert back to a string before serializing or sending the value back to the API. **Never use `parseInt()` on IDs.** `parseInt()` converts its argument through a `Number` first, which means precision loss happens before you even try to parse it. `parseInt()` and `Number()` both use JavaScript's `Number` type internally. Neither is safe for Lofty IDs. Always treat IDs as opaque strings unless you specifically need to do integer arithmetic — and if you do, use `BigInt`. **When sending IDs back to the API** (in request bodies or query parameters), serialize them as their original string or `BigInt.toString()` value. The API accepts numeric strings in places where IDs are expected. ```javascript theme={null} // Sending an ID in a request body — safe const body = JSON.stringify({ leadId: lead.id.toString(), // works for both string and BigInt }); ``` # Authentication Source: https://developer.lofty.com/mcp/authentication Every request carries its credential the same way, whichever kind it is: ``` Authorization: Bearer ``` This differs from the direct REST API, where an API key normally uses the `token` prefix and only an OAuth token uses `Bearer`. Over MCP, always use `Bearer` — the server inspects the credential and routes it to the verification path that recognizes it. ## API key For headless and CI use, or clients that cannot run a browser flow. An API key from **Settings → Integrations → API** is user-scoped, carries the `THIRD_PARTY_OPERATION` scope, and is the same credential the Lofty CLI reads from `LOFTY_CUSTOMER_KEY`. Send it as `Authorization: Bearer ` using any of the [client configurations](/mcp/connect-client). ```bash theme={null} curl https://mcp.lofty.com/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "Authorization: Bearer " \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_team_config","arguments":{"kind":"members.me"}}}' ``` ## OAuth 2.0 authorization code + PKCE The recommended path. A client discovers the flow on its own: an unauthenticated call returns `401` with a `WWW-Authenticate` header naming the resource metadata, and the client follows that to the authorization server. Nothing is configured by hand. ```json theme={null} // GET https://mcp.lofty.com/.well-known/oauth-protected-resource { "resource": "https://mcp.lofty.com/mcp", "authorization_servers": ["https://lofty.com"], "scopes_supported": ["openApi"], "bearer_methods_supported": ["header"], "resource_name": "Lofty CRM" } ``` PKCE with `S256` is mandatory and enforced at both ends. Public clients authenticate at the token endpoint with method `none` — no client secret. Code exchange returns an access token and a rotating refresh token. ## Standards | Standard | Detail | | ---------------------------------------- | ----------------------------------------------------------------------------------------- | | Protected Resource Metadata (RFC 9728) | `https://mcp.lofty.com/.well-known/oauth-protected-resource` | | Authorization Server Metadata (RFC 8414) | `https://lofty.com/.well-known/oauth-authorization-server` | | Discovery challenge | An unauthenticated call returns `401` with `WWW-Authenticate: Bearer resource_metadata=…` | | PKCE (RFC 7636) | Required. `S256` only — `plain` is rejected | | Grant types | `authorization_code`, `refresh_token` | | Token endpoint auth | `none` for public clients, `client_secret_basic` for confidential ones | | Dynamic Client Registration (RFC 7591) | **Not supported.** Register in the vendor portal — see [below](#registering-a-client) | ## Scopes The authorization server issues a single scope: `openApi`, which grants access to the Open API surface and therefore to every MCP tool. **There's no read/write split.** `openApi` is a single grant covering the whole surface, so a connection that can read can also write — see [the one invariant](/mcp/invariant) for what the tool boundary does and doesn't enforce. What a scope never does is widen what a person can do. Authorization runs unchanged underneath every call: a token cannot touch a record its user could not touch in the CRM itself, and the denial surfaces as the same `403` a direct REST request would return. ## Registering a client Applications are registered by hand in the Lofty vendor portal, which issues a `clientId`. Public clients get no secret — PKCE replaces the client secret, but **not** the client id, which is still required on every authorize request. | Constraint | Detail | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Redirect URIs | HTTPS, or plain `http://` on a loopback host only (`localhost`, `127.0.0.1`, `[::1]`, any port — RFC 8252 §7.3, for native clients such as Claude Code) · at most 5 · compared for exact equality (case-insensitive). A trailing slash or an extra parameter is a mismatch. | | URI ordering | The first URI is also written to a legacy single-value field — put your primary one first. | | New app status | A newly created app is in `DEVELOPMENT`: rate limited to 100/min, and **only the designated test account can authorize it**. Anyone else attempting to grant access is rejected. | ## Permissions and rate limits A tool call is authenticated and authorized exactly like a direct call to the endpoint it resolves to: * It runs as the caller identified by the token, with that caller's existing permissions. An operation the caller couldn't perform through the CRM directly (or through the REST API) is refused here too, with the same error. * It's subject to the same [rate limits](/authentication/rate-limits) as a direct REST call to that endpoint — using MCP doesn't grant a separate quota. ## Invalid or missing credentials If the credential is missing, expired, or doesn't resolve to a real user or application, a tool call surfaces the underlying API's own error rather than a generic MCP failure — for example: ```json theme={null} {"content":[{"type":"text","text":"The Lofty API returned HTTP 401: {\"message\":\"...\"}"}],"isError":true} ``` See [Error Codes](/authentication/error-codes) for what each code means. # Connect your client Source: https://developer.lofty.com/mcp/connect-client Connect Lofty to Claude.ai and Claude Desktop, Claude Code, Cursor, VS Code, Codex CLI, and any other Streamable HTTP MCP client. Any Streamable HTTP MCP client works. Two ways to authenticate: **OAuth 2.0 + PKCE**, where the client signs the user in through the browser and discovers the flow from the endpoint's own `401` challenge, or an **API key** passed as a header, which every client supports. Lofty does not support Dynamic Client Registration, so an OAuth client needs a `client_id` registered in the vendor portal; see [Authentication](/mcp/authentication). One connector covers claude.ai web, the Claude desktop and mobile apps, Cowork, and Claude Code on the web: remote MCP servers are brokered through your Claude account, so a connector added once shows up everywhere you are signed in. (`claude_desktop_config.json` is for local stdio servers only and cannot point at a remote server.) Lofty publishes a public OAuth client, so you don't register anything and there is no secret to paste. 1. **Settings → Connectors → Add custom connector** 2. Remote MCP server URL: `https://mcp.lofty.com/mcp` 3. Under **OAuth client** choose **Use your own OAuth client** (in some versions this is under *Advanced settings*). Client ID: `1wzhz7fnrr8qo`. Leave Client Secret empty — Claude then acts as a public client with PKCE. 4. **Add**, then **Connect**. Sign in to Lofty in the browser and click **Authorize**. The other two OAuth options — *Register automatically* (Dynamic Client Registration) and *Use Claude's published identity* — are not supported by Lofty's authorization server and fail; see [Authentication](/mcp/authentication). On Team and Enterprise plans only an organization Owner can add the connector; members then each sign in with their own Lofty account. On Free, Pro and Max plans you add it yourself. Headless alternative: an admin can instead set a fixed request header `Authorization: Bearer ` on the connector. Everyone in the organization then acts as the one Lofty user who owns that key, so prefer OAuth for anything beyond a demo. **OAuth 2.0 + PKCE (recommended).** Lofty publishes a public OAuth client for Claude Code, so you don't register anything. Use the client id and callback port exactly as shown — the port is part of the registered redirect URI, so a different port fails with a redirect mismatch. No secret is needed; Claude Code is a public client. ```bash theme={null} claude mcp add --transport http \ --client-id 1wzhz7fnrr8qo --callback-port 8765 \ lofty https://mcp.lofty.com/mcp ``` Run `/mcp` inside a session, pick `lofty`, sign in to Lofty in the browser and click **Authorize**. Or from the shell: `claude mcp login lofty` (`--no-browser` over SSH prints the URL and lets you paste the redirect back). Tokens are stored in the keychain and refreshed automatically. Without `--client-id`, Claude Code tries Dynamic Client Registration and fails with *"does not support dynamic client registration"*. **API key (headless / CI).** Skips the browser entirely. Note that once any `Authorization` header is set the server never sends the `401` discovery challenge, so OAuth is not attempted. ```bash theme={null} claude mcp add --transport http lofty https://mcp.lofty.com/mcp \ --header "Authorization: Bearer $LOFTY_CUSTOMER_KEY" ``` Verify with `claude mcp list`, then `/mcp` inside a session. `~/.cursor/mcp.json` — or `.cursor/mcp.json` for one project ```json theme={null} { "mcpServers": { "lofty": { "url": "https://mcp.lofty.com/mcp", "headers": { "Authorization": "Bearer YOUR_KEY" } } } } ``` `.vscode/mcp.json` — prompts for the key ```json theme={null} { "inputs": [ { "id": "lofty-key", "type": "promptString", "description": "Lofty API key", "password": true } ], "servers": { "lofty": { "type": "http", "url": "https://mcp.lofty.com/mcp", "headers": { "Authorization": "Bearer ${input:lofty-key}" } } } } ``` The `inputs` block makes VS Code prompt for the key and store it in its secret store, so the token never lands in a file you might commit. `~/.codex/config.toml` ```toml theme={null} [mcp_servers.lofty] url = "https://mcp.lofty.com/mcp" bearer_token_env_var = "LOFTY_CUSTOMER_KEY" ``` Reads the key from the environment rather than the config file — the same variable the Lofty CLI already uses. Follow your client's MCP setup instructions with: * Transport: **Streamable HTTP** (not SSE, not stdio) * URL: `https://mcp.lofty.com/mcp` * Header: `Authorization: Bearer ` To test with no client at all, the endpoint is plain JSON-RPC over HTTP: ```bash theme={null} curl https://mcp.lofty.com/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "Authorization: Bearer $LOFTY_CUSTOMER_KEY" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` # Bulk writes Source: https://developer.lofty.com/mcp/guides/bulk-writes Array-shaped write arguments, capped at 10 enumerated records per call, with a per-record result. Write tools take array-shaped arguments (`ids`, `updates`), **capped at 10 records** per call. Records are processed in order and each one reports its own outcome: ```json theme={null} { "results": [ { "id": "100001", "ok": true }, { "id": "100002", "ok": false, "error": "lead not found" } ] } ``` Nothing is retried automatically — a partial batch has no transaction behind it, so re-running would reapply the writes that already succeeded. Past the cap the call is refused rather than executed in pieces: a set split across repeated calls has no single confirmation covering it, and a failure halfway through leaves the data half-changed. **No tool accepts a filter to decide what to change.** The risk boundary isn't *how many* records but *who chose the set*. Enumerated ids are visible in a confirmation dialog; a filter-defined set could match five records or five thousand, and nobody knows which at call time. "Change every Zillow-sourced lead to Nurture" is therefore **not expressible** in this tool surface — not refused, but unwritable. A filter-driven bulk tool, when it exists, gets its own scope and a confirmation that runs a count first. [`manage_sales_agent`](/mcp/tools/manage-sales-agent)'s `add_to_pool` and `create_plan_tasks` are the two exceptions — both have a real batch endpoint underneath, so they accept a large array outright. They still confirm past 10 (the [threshold tier](/mcp/guides/confirmation-tiers)), just without a hard rejection. # Confirmation tiers Source: https://developer.lofty.com/mcp/guides/confirmation-tiers The confirm_token protocol, and the threshold set per action rather than per tool. Confirmation is one protocol, learned once: a write past its risk threshold returns `needsConfirmation: true` with a single-use `confirm_token`, valid five minutes and bound to a hash of the arguments. A token issued for two recipients cannot be replayed for two hundred. ```json Response when confirmation is required theme={null} { "needsConfirmation": true, "confirm_token": "3f9c1e2a-...", "summary": "Permanently delete 3 lead(s): 100001, 100002, 100003.", "expires_in_seconds": 300 } ``` If your client supports MCP **elicitation**, the prompt is raised in your own interface and you answer there. If it doesn't, replay the *identical* call with `confirm_token` added within five minutes. ## Tiers The threshold is set **per action, not per tool** — [`manage_leads`](/mcp/tools/manage-leads) alone spans "add a note" and "delete a lead", three orders of magnitude apart in consequence. Confirming everything trains people to click through; confirming nothing eventually loses data. | Tier | Actions | Rule | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Always** | `trash`, `delete_note`, `delete_manual_log`, `manage_tasks.delete`, all of `send_message` / `notify_agent` / `manage_webhooks`, `deactivate_agent`, `add_agent`, `sync_brokermint` | Irreversible, externally visible, or affects other people. A `send_message` confirmation must show the recipient count and the full rendered body. | | **Over threshold** | `create`, `update`, `add_to_pool`, `create_plan_tasks`, all of `manage_transactions` | Confirms past 10 affected records, showing a count and a sample by name. | | **Never** | `add_note`, `add_manual_log`, `add_activity`, `add_inquiry`, `update_property`, `manage_tasks.create`/`update`/`complete`, `mute`, `unmute`, all of `generate_lead_insight` | Additive, reversible, and scoped to a single lead — `mute` reverses with `unmute` ([`manage_sales_agent`](/mcp/tools/manage-sales-agent)), a note or manual log can be deleted, a task un-completed. | | **Its own case** | `assign` | Changes ownership and is visible to the new owner, but is a high-frequency daily action. Confirms by default, configurable per connection; `preview: true` never confirms. | # Troubleshooting Source: https://developer.lofty.com/mcp/guides/troubleshooting Common issues when calling the Lofty MCP server, and how to resolve them. ## My client wants to "sign in with Lofty" and can't find anything Discovery only fires on a request that carries **no** `Authorization` header at all — the `401` + `WWW-Authenticate` challenge is what points the client at `/.well-known/oauth-protected-resource`. If the client is configured with a key, however wrong, it never sees the challenge. Clear the header and let it retry. ## The client rejected the granted scope Almost always casing. The issued scope is `openApi`, camelCase — a client comparing it against `openapi` concludes it was granted something unsupported and stops. Match the exact string. ## "redirect uri is not the same as the registered" Redirect URIs are compared for exact equality. A trailing slash, an extra query parameter, a different port, or `127.0.0.1` where `localhost` was registered all fail. Copy the value your client actually sends, character for character, into the vendor portal — don't retype it from documentation. Plain `http://` is accepted only for loopback hosts; anything else must be `https://`. ## Authorization is rejected for everyone except one account A newly registered app sits in `DEVELOPMENT` status, where only the designated test account may complete the consent flow, and requests are capped at 100/min. Other users being refused is the status working as intended, not a misconfiguration. ## "40101 The client not found", or authorize returns 400 PKCE replaces the client *secret*, not the client *id*. `client_id` is still required on every authorize request; omitting it returns `400`, and sending an unregistered value returns `40101`. The id isn't a secret — Lofty's own CLI ships one in plain source. ## A call returned needsConfirmation and nothing happened That's the write path working. If your client supports MCP **elicitation**, the prompt is raised in your own interface and you answer there. If it doesn't, the result carries a `confirm_token` — replay the *identical* call with that token added within five minutes. The token is bound to a hash of the arguments. Changing even one argument invalidates it, deliberately: a token issued for two recipients cannot be reused for two hundred. See [Confirmation](/mcp/guides/confirmation-tiers) for the full protocol. ## I asked for notes and can't find a notes tool There isn't one — reads and writes are split by design. Read notes with `search_lead_activities({ channel: "note" })`; create, edit or delete them with `manage_leads({ action: "add_note" | "update_note" | "delete_note" })`. Manual logs follow the same pattern with `channel: "manual_log"`. ## A manual log I just wrote isn't readable yet Manual log writes are asynchronous upstream: the POST returns an id once the write is queued, and the entry becomes readable a moment later. Don't treat an empty read-back as a failed write, and don't retry the write — you'll create a duplicate. ## A batch write was refused, or I'm tempted to loop Enumerated batches are capped at 10 records. **Do not split the work into repeated calls.** No single confirmation would cover the whole set, a failure partway leaves the data half-changed with no rollback, and the call volume can trip rate limits. Narrow the operation, or use the CRM's own bulk tools. "Change every Zillow-sourced lead to Nurture" cannot be expressed here at all — no tool accepts a filter to decide what to modify. See [Batch writes](/mcp/guides/bulk-writes). ## A tool call returned a 401 or 403 | Status | Likely cause | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `401` | Missing, malformed or expired credential. Confirm you're sending `Authorization: Bearer ` — an API key uses the `token` prefix on the direct REST API, but **always** `Bearer` over MCP. | | `403` (code `200100`) | An OAuth app calling an operation it was never granted. Add the scope in the Developer Portal and re-authorize. | | `403` (other) | The authenticated user genuinely lacks permission for that record or action. The same restriction applies in the CRM itself — this isn't an MCP problem. | ## 404 for a record I'm sure exists Usually a correct answer rather than a bug. A lead lookup returns `404` when that id doesn't exist *for the authenticated caller's team* — which includes leads that exist but belong to another account. Check the id, and check which credential you're using. ## A field I expect isn't in the schema It's probably a team-defined custom field, which can't appear in any static schema. Call `search_team_config({ kind: "settings.custom_fields" })` before concluding the field doesn't exist or guessing an id for it. ## An id changed value, or a write hit the wrong record Lofty ids are 64-bit and exceed `2^53 − 1`. Every id is returned as a **string** for exactly this reason — if your client re-parses tool output with `JSON.parse()` and coerces it to a number, the value is silently rewritten into a different, valid-looking id. Keep ids as strings end to end. ## Should I use v1 or v2? Neither — that choice no longer exists at this layer. Four resource families ship non-equivalent v1 and v2 surfaces, and the tool picks per field coverage. If a field you need seems unavailable, report it rather than reaching for a version. ## Still stuck? Check [Error Codes](/authentication/error-codes) for the full list of API error codes, or see the [API Reference](/api-reference/leads/overview) for the same operations documented as plain REST calls. # What the layer absorbs Source: https://developer.lofty.com/mcp/guides/what-the-layer-absorbs Quirks found while mapping all 123 operations — none of which reach the model. Quirks found while mapping all 123 operations. None of them reach the model. | Quirk | Handled by | | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | `PUT /v1.0/org/office` **creates** and `POST` **updates** — reversed from REST convention | Distinct `action` values; HTTP verbs are never exposed | | Manual log writes are asynchronous — a POST returns an id before the entry is readable | No immediate read-back; an empty read reports "visible shortly" rather than an error | | v2 tasks and calendar events share one `taskId` namespace | Resolved inside `search_tasks` / `manage_tasks` | | `GET /v1.0/users/{userId}` is deprecated | Not exposed; only `/v1.0/members/{account}` is | | Four resource families ship non-equivalent v1 and v2 surfaces | Version chosen per field coverage, inside the tool | | Lead ids are 64-bit and exceed `2^53−1`, so `JSON.parse()` silently rewrites them | Every id is returned as a string | # The one invariant Source: https://developer.lofty.com/mcp/invariant search_* never changes data. Every other tool might. **Learn this and nothing else:** `search_*` never changes data. Every other tool might. **For the model:** a `search_` prefix means the call is safe, needs no confirmation, and can be retried freely after a failure. Everything else is treated as having side effects. This is a property of the tool surface itself and holds today. **Why the split is drawn at the tool boundary:** a read-only connection can then be enforced by name prefix alone — serve the nine `search_*` tools and nothing else, and the write tools are **absent from `tools/list`** rather than present and failing. A model cannot call what it cannot see. This is what the naming buys; enforcing it needs a read/write scope split, which doesn't exist yet — today the single `openApi` scope means any connection that can read can also write. # Lofty MCP Server Source: https://developer.lofty.com/mcp/overview Connect AI agents to the Lofty CRM over the Model Context Protocol — 18 intent-shaped tools covering all 123 REST operations, with one rule the model only has to learn once. The MCP server exposes the same [Lofty Developer API](/introduction) documented on this site to any [Model Context Protocol](https://modelcontextprotocol.io) client — Claude, or any other MCP-compatible agent — so it can find a lead, log a call, book an appointment, or move a deal forward with no integration code. There is no generated SDK and nothing to install; you point your client at one URL. ``` POST https://mcp.lofty.com/mcp ``` The server speaks Streamable HTTP (JSON-RPC 2.0) and is stateless by design — no session affinity between requests, so it's safe to call from behind a load balancer or a serverless client. ## Start here Eighteen tools, paired read and write, grouped by the part of the CRM they touch. `search_*` never changes data. Every other tool might. A credential and three calls. Config snippets for Claude Code, Claude Desktop, Cursor, VS Code and Codex CLI. API keys, OAuth 2.0 + PKCE, and the discovery endpoints. Common errors and how to resolve them. # Quickstart Source: https://developer.lofty.com/mcp/quickstart Get a credential, connect your client, and make your first three MCP calls. Get a credential first — an API key from **Settings → Integrations → API**, or an OAuth 2.0 access token (see [Authentication](/mcp/authentication) for both). Then [connect your client](/mcp/connect-client) and make three calls. ```js theme={null} // 1. find the lead (no id needed — filters or a name both work) search_leads({ filters: { query: "Ada Chen", status: "active" } }) // 2. read its timeline across every channel search_lead_activities({ scope: "lead", lead_id: "100001", channel: "all" }) // 3. act — this one asks for confirmation before it sends send_message({ lead_id: "100001", channel: "sms", body: "..." }) ``` **Did it work?** Ask your client something that should route to a tool — *"how many leads did I get this week?"* — and confirm it invokes `search_leads` rather than answering from memory. If the tool list is empty or the call 401s, see [Troubleshooting](/mcp/guides/troubleshooting). ## Next steps Config snippets for every major MCP client, plus raw curl. API keys, OAuth 2.0 + PKCE, discovery and scopes. Nine tools, 65 operations, none of which writes. The confirm\_token protocol and which actions ask first. # Session context Source: https://developer.lofty.com/mcp/session-context Who the caller is, which team they belong to, and what timezone they're in — delivered without a tool call. The current user's identity — user, team, office, role, granted scopes and **timezone** — is not a tool. It arrives two ways, both free: * in the `instructions` field of the `initialize` response; * as the MCP resource `lofty://me`. Every session needs this before it can resolve "my tasks" or "last week", so charging a round trip for it is waste. The same data is also reachable explicitly through [`search_team_config({ kind: "members.me" })`](/mcp/tools/search-team-config) for clients that don't support resources. # Tool overview Source: https://developer.lofty.com/mcp/tool-overview Eighteen tools, paired read and write, grouped by the part of the CRM they touch. Eighteen tools, paired read and write, grouped by the part of the CRM they touch. Lofty is lead-centric — tasks, calls, texts, emails, notes and most transactions all hang off a lead — so the surface is shaped the same way, with listings, webhooks and account configuration standing on their own. | What you're working with | Read | Write | | ----------------------------------------------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | Leads | [`search_leads`](/mcp/tools/search-leads) | [`manage_leads`](/mcp/tools/manage-leads) | | Activity & communication
calls, texts, emails, notes, logs | [`search_lead_activities`](/mcp/tools/search-lead-activities) | [`send_message`](/mcp/tools/send-message)
[`manage_leads`](/mcp/tools/manage-leads) *(notes, logs)* | | Tasks & calendar | [`search_tasks`](/mcp/tools/search-tasks) | [`manage_tasks`](/mcp/tools/manage-tasks) | | Transactions | [`search_transactions`](/mcp/tools/search-transactions) | [`manage_transactions`](/mcp/tools/manage-transactions) | | Listings | [`search_listings`](/mcp/tools/search-listings) | — read-only surface | | AI Sales Agent
working pool, quota, plan tasks | [`search_sales_agent`](/mcp/tools/search-sales-agent) | [`manage_sales_agent`](/mcp/tools/manage-sales-agent) | | Notifying your own agents | — | [`notify_agent`](/mcp/tools/notify-agent) | | Team & organization
members, tags, pipelines, routing, org | [`search_team_config`](/mcp/tools/search-team-config) | [`manage_team_config`](/mcp/tools/manage-team-config) | | Webhooks | [`search_webhooks`](/mcp/tools/search-webhooks) | [`manage_webhooks`](/mcp/tools/manage-webhooks) | | AI insights
call scripts, summaries, analysis | [`search_lead_insights`](/mcp/tools/search-lead-insights) | [`generate_lead_insight`](/mcp/tools/generate-lead-insight) | Between them these cover all 123 operations of the Lofty API — anything the REST API can do is reachable here. Three rows are asymmetric on purpose: listings are read-only at this layer, `notify_agent` only sends, and notes and manual logs are read from the activity timeline but written through `manage_leads`. Every tool call is a standard MCP `tools/call` request and returns a standard `CallToolResult`. Optional arguments are marked with `?` throughout the reference. Write tools past a risk threshold return `{"needsConfirmation": true, "confirm_token": "..."}` instead of executing — see [Confirmation tiers](/mcp/guides/confirmation-tiers). # generate_lead_insight Source: https://developer.lofty.com/mcp/tools/generate-lead-insight Produce a call script, a prepare-insight, a call summary, or queue a lead analysis. **WRITE** · 4 operations · may change data Produce a call script, a prepare-insight, a call summary, or queue a lead analysis. Generating is a write — reading a cached result is [`search_lead_insights`](/mcp/tools/search-lead-insights). Each `kind` needs its own id — there is no single shared `lead_id` shape: ```js theme={null} // kind=call_script: lead_id and task_id are both required. generate_lead_insight({ kind: "call_script", lead_id, task_id }) // kind=insight: lead_id and appointment_id are both required. generate_lead_insight({ kind: "insight", lead_id, appointment_id }) // kind=call_summary: lead_id and call_record_id are both required. generate_lead_insight({ kind: "call_summary", lead_id, call_record_id }) // kind=analysis: lead_ids (up to 10), not lead_id. generate_lead_insight({ kind: "analysis", lead_ids: [lead_id, ...] }) ``` # manage_leads Source: https://developer.lofty.com/mcp/tools/manage-leads Everything that writes to a lead or to the records hanging off it — the lead, its notes, its manual logs, and backfilled activity. **WRITE** · 13 operations · may change data Everything that writes to a lead or to the records hanging off it — the lead itself, its notes, its manual logs, and backfilled activity entries. ```js theme={null} manage_leads({ action, ids?, lead_id?, fields?, owner?, confirm_token? }) ``` `action`: `create` · `update` · `assign` · `update_property` · `add_inquiry` · `trash` · `add_note` · `update_note` · `delete_note` · `add_manual_log` · `delete_manual_log` · `add_activity` · `sync_brokermint` `owner` (`assign` only) accepts an account **email**, or a team member's name resolved against the team's member list — not a raw numeric id. An ambiguous or unmatched name is a clear error, never a guess. `assign` with `fields.preview: true` reports who *would* receive the lead without committing. `add_manual_log`'s `fields` requires `logType` (one of `logCall`/`logEmail`/`logText`) and `content`; `leadPhoneNumber` is additionally required when `logType` is `logCall`. `add_note`'s `fields` takes `content` (required) and `isPin`. `create`'s `fields` is the new lead body — one lead per call, not an array. `trash`'s `fields.reason` is required. # manage_sales_agent Source: https://developer.lofty.com/mcp/tools/manage-sales-agent Add leads to the AI working pool, mute or unmute a lead, write settings, send an SMS through the AI number, or batch-create plan tasks. **WRITE** · 6 operations · may change data Add leads to the AI working pool, mute or unmute a lead, write settings, send an SMS through the AI number, or batch-create plan tasks. ```js theme={null} manage_sales_agent({ action: "add_to_pool" | "mute" | "unmute" | "update_settings" | "ai_number_sms" | "create_plan_tasks", lead_ids?, lead_id?, settings?, ai_assistant_id?, confirm_token? }) ``` `lead_id` (singular) is required for `mute` and `unmute`. `lead_ids` (plural) is for `add_to_pool`/`create_plan_tasks`; both confirm past 10 leads (see below) — replay with the returned `confirm_token` to proceed. `ai_assistant_id` (`add_to_pool` only) targets a specific Sales Agent instead of the hierarchy-resolved one for every added lead — seat and quota bill to its owner. `update_settings`'s `settings` requires `id` (the Sales Agent id, greater than 0) — `assistantName`/`virtualNumber`/`callTransferType`/`agentExperience`/channel toggles/active hours/lead filters are all optional partial updates. `ai_number_sms`'s `settings` requires `content` (the message text; the recipient is implicit in the AI number's own configuration, not a `lead_id`). `add_to_pool` and `create_plan_tasks` are backed by genuine batch endpoints, so they accept large arrays — unlike the [10-record cap](/mcp/guides/bulk-writes) that applies elsewhere. `unmute` (CHIME-70595) reverses a `mute` — the response's `success` field reports whether follow-up actually resumed. If `success` is `false` and `shouldBuyAi` is greater than 0, the team has no available AI seat for this lead rather than a generic failure. # manage_tasks Source: https://developer.lofty.com/mcp/tools/manage-tasks Create, update, delete, complete and un-complete tasks and calendar events. **WRITE** · 13 operations · may change data Create, update, delete, complete and un-complete tasks and calendar events. The v1/v2 choice is resolved internally — writes always use the current (v2) endpoints. ```js theme={null} manage_tasks({ action: "create" | "update" | "delete" | "complete" | "uncomplete", type: "task" | "event", ids?, fields?, confirm_token? }) ``` `delete` is an [Always-confirm](/mcp/guides/confirmation-tiers) action: the first call returns `needsConfirmation` with a `confirm_token`; replay the identical call with that token to actually delete. `fields` (`create`, or the patch for `update`) has its own required shape per top-level `type`, distinct from the `type` *inside* `fields` for a task record itself (`Other`/`Call`/`Email`/`Text`/`Appointment`) — don't confuse the two: | `type` | Required `fields` | Notes | | ------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | | `task` | `content`, `leadId`, `type` (`Other`/`Call`/`Email`/`Text`/`Appointment`), `assignedRole` (`Agent`/`Assistant`), `endAt` | `endAt` is the deadline, not a range end. Optional: `startAt`, `timeZoneCode`. | | `event` | `content`, `leadId`, `startAt`, `endAt` | Both ISO 8601 with a UTC offset, e.g. `2026-03-01T15:00:00-08:00`. Optional: `timeZoneCode`, `address`. | There is no `title`/`subject`/`dueDate` field — `content` and `endAt` are it. A missing required field is rejected before the call reaches the backend, naming exactly which one. # manage_team_config Source: https://developer.lofty.com/mcp/tools/manage-team-config Team and organization administration: custom fields, routing rules, offices, company details and agent accounts. **WRITE** · 10 operations · may change data Team and organization administration: create a custom field, edit routing rules, update office or company details, add an agent, deactivate or reactivate one. ```js theme={null} manage_team_config({ action, routing_type?, agent_id?, fields?, confirm_token? }) ``` `action`: `add_custom_field` · `update_routing_rule` · `update_default_routing_rule` · `add_office` · `update_office` · `update_company` · `add_agent` · `add_agent_tag` · `deactivate_agent` · `reactivate_agent` `add_custom_field`'s `fields` requires `attributeName` and `attributeType` (one of `text`/`number`/`date`/`anniversary_date`/`single_select`/`multi_select`/`percentage`/`currency`); the two select types additionally require `fields.params`, e.g. `{"option": ["item1", "item2"]}`. `update_routing_rule`/`update_default_routing_rule` require `routing_type`. `add_agent_tag`/`deactivate_agent`/`reactivate_agent` require `agent_id`. `deactivate_agent` frees a license seat and revokes that agent's sessions and tokens. It's materially riskier than anything else in this tool and always requires confirmation. The one HTTP-verb inversion in the whole API: `PUT /v1.0/org/office` **creates** an office, `POST` to the same path **updates** one — hidden behind `add_office`/`update_office` so you never need to know it. # manage_transactions Source: https://developer.lofty.com/mcp/tools/manage-transactions Create or update a transaction, write its property address, or sync one from Brokermint. **WRITE** · 4 operations · may change data Create or update a transaction, write its property address, or sync one from Brokermint. ```js theme={null} manage_transactions({ action, lead_id?, transaction_id?, fields?, confirm_token? }) ``` There's no delete action — no such endpoint exists in the real API. # manage_webhooks Source: https://developer.lofty.com/mcp/tools/manage-webhooks Subscribe or unsubscribe an event callback. **WRITE** · 2 operations · may change data Subscribe or unsubscribe an event callback. ```js theme={null} manage_webhooks({ action: "subscribe" | "unsubscribe", event?, target_url?, fields?, subscribe_id?, confirm_token? }) ``` `subscribe` requires `target_url` (the callback URL); `event` (the event/list id — the real field is `listId`) is optional. `fields` holds any extra webhook fields beyond `event`/`target_url`: `limit`, `permissionMode`. `unsubscribe` requires `subscribe_id`. # notify_agent Source: https://developer.lofty.com/mcp/tools/notify-agent Push an app notification, system SMS or system email to an internal agent, or fire an opportunity alert. **WRITE** · 4 operations · may change data Push an app notification, system SMS or system email **to an internal agent**, or fire an opportunity alert. ```js theme={null} notify_agent({ channel: "app_push" | "sms" | "email" | "opportunity_alert", agent_id, body, confirm_token }) ``` Recipients are always internal agents, never leads. `agent_id` targets a specific teammate for `channel: "sms"` and `channel: "email"` only — the teammate must be on the caller's own team. `app_push` and `opportunity_alert` never accept a redirect: `app_push` always notifies the assignee of the given `task_id`, and `opportunity_alert` always resolves its recipient from the named `lead_id`. This is by design, not a gap — both of those exist to notify the person actually responsible for a task or a lead, and letting a caller redirect that to an unrelated third party would defeat the point of the channel rather than extend it. Each channel also needs its own extra parameter: ```js theme={null} // app_push: task_id required, no body, agent_id has no effect. notify_agent({ channel: "app_push", task_id }) // sms: body required. agent_id optional - defaults to the caller. notify_agent({ channel: "sms", body, agent_id }) // email: subject and body required. agent_id optional - defaults to the caller. notify_agent({ channel: "email", subject, body, agent_id }) // opportunity_alert: lead_id required, body is the alert description, agent_id has no effect. notify_agent({ channel: "opportunity_alert", lead_id, body }) ``` # search_lead_activities Source: https://developer.lofty.com/mcp/tools/search-lead-activities Calls, texts, emails, notes, manual logs and the unified activity timeline for a lead or for every lead an agent owns. **READ** · 18 operations · never changes data **Everything that has happened on a lead** — calls, texts, emails, notes, manual logs and the unified activity timeline. Fetch one entry by id, or a call's recording URL. Set `scope: "agent"` to search across every lead an agent owns. ```js theme={null} search_lead_activities({ scope: "lead" | "agent", lead_id?, channel: "all" | "call" | "sms" | "email" | "note" | "manual_log", id?, log_type?, include_recording_url?, limit?, cursor?, filters? }) ``` Channel is a filter, not a separate tool — one call can span every channel or narrow to one. `log_type` is required when searching `channel: "manual_log"` (not set when fetching by `id`). `filters` holds whatever extra query parameters the resolved endpoint accepts: `includeSystemNote` for `channel: "note"`, `startTime`/`endTime` for `scope: "agent"` (required there). Writing to these channels lives in [`manage_leads`](/mcp/tools/manage-leads) (notes, manual logs) or [`send_message`](/mcp/tools/send-message) (SMS, email). # search_lead_insights Source: https://developer.lofty.com/mcp/tools/search-lead-insights Read an existing call summary, or list lead-analysis tasks. **READ** · 2 operations · never changes data Read an existing call summary, or list lead-analysis tasks. Reading a cached AI result is a read; producing a new one is [`generate_lead_insight`](/mcp/tools/generate-lead-insight). Each `kind` needs its own id, not a shared `lead_id`: ```js theme={null} // kind=call_summary: call_record_id is required. lead_id is accepted but not used by the // real endpoint, which is keyed by call_record_id. search_lead_insights({ kind: "call_summary", call_record_id }) // kind=analysis_tasks: no id required; optionally page through results. search_lead_insights({ kind: "analysis_tasks", filters: { pageNum, pageSize } }) ``` # search_leads Source: https://developer.lofty.com/mcp/tools/search-leads Filter leads by status, source, tag, owner or custom field; fetch by id; read saved property searches. **READ** · 3 operations · never changes data Filter leads by status, source, tag, owner or custom field; fetch one or many by id; read the property searches a lead has saved. ```js theme={null} search_leads({ ids?, filters?, sort?, limit?, cursor?, group_by?, metric?, lead_id? }) ``` `ids` (up to 100) returns full records and ignores every other parameter. `filters` returns a paged summary — `stage`, `source`, `phone`, `email`, `assignedUserId`, `contacted`, `allTags`, `anyTags`, `groupIds`, `key` (free text), and more. `lead_id` set alone (no `ids`/`filters`) instead lists that lead's saved property searches. `sort` is an exact, case-sensitive enum name from the real API — not a display label, and not camelCase or a `-`-prefixed field. Common values: `CreateTime` (the default), `LastContact`, `LastActivity`, `AssignTime`, `A2Z`, `Score`. Anything else is rejected. Set `group_by` (a field name, e.g. `"stage"`) — optionally with `metric`: `"count"` (default), `"sum:"`, or `"avg:"` — for grouped counts/sums instead of a record list, rather than paging through everything yourself. It refuses outright past a fixed scan limit rather than ever returning a silently-truncated number. Notes, manual logs, calls, texts, emails and the activity timeline live in [`search_lead_activities`](/mcp/tools/search-lead-activities); writes live in [`manage_leads`](/mcp/tools/manage-leads). # search_listings Source: https://developer.lofty.com/mcp/tools/search-listings Published listings by agent or office. Read-only at this layer. **READ** · 2 operations · never changes data Published listings by agent or office. Read-only at this layer — listing creation and maintenance isn't part of this API surface. ```js theme={null} // scope=agent/office: JSON response. search_listings({ scope: "agent" | "office", filters?: { userId, listingId, mlsListingIds, street, limit, agentListings, officeListings, buildingName, buildingAddress, showCondition } }) // scope=published: type is required, one of LuxVT / AptCom / AllOnMarket. search_listings({ scope: "published", type }) ``` `scope=published` returns that feed's own XML verbatim, not JSON — unlike `scope=agent`/ `scope=office`, both of which return JSON. This is intentional: the underlying feed is an XML-only syndication export (`teamId`-scoped, not a global dump), and there's no plan to convert it to JSON — parse it as XML. # search_sales_agent Source: https://developer.lofty.com/mcp/tools/search-sales-agent The AI Sales Agent working pool: membership, mute status, quota, settings and generated plan tasks. **READ** · 8 operations · never changes data The AI Sales Agent working pool: which leads are in it, whether a given lead is muted, the account's quota, current settings, and generated plan tasks. ```js theme={null} search_sales_agent({ kind: "pool" | "is_in_pool" | "mute_status" | "quota" | "current" | "by_lead" | "settings" | "plan_tasks", lead_id?, filters? }) ``` `lead_id` is required for `is_in_pool`, `mute_status`, `by_lead` and `plan_tasks`. `filters` (`kind: "pool"` only) holds `aiStage`/`limit`/`offset`/`sort`/`desc`. `current` returns the caller's own current AI assistant profile and takes no other parameter. Boundary — this manages *which leads the AI follows up on*, not the lead data itself. # search_tasks Source: https://developer.lofty.com/mcp/tools/search-tasks Tasks and calendar events — list, fetch by id, "my tasks" across every lead, and available meeting slots. **READ** · 8 operations · never changes data Tasks and calendar events — list, fetch by id, "my tasks" across every lead, and available meeting slots. Tasks and appointments share one id namespace in v2; the tool resolves which is which, and which API version to use. ```js theme={null} search_tasks({ type?, lead_id?, id?, my_tasks?, available_slots?, filters?, limit?, cursor? }) ``` `type` (default `"task"`) selects the endpoint: `"task"` (current, v2), `"event"` (v2 calendar), or the legacy `"task_v1"`/`"appointment_v1"` for data with no v2 equivalent. `lead_id` is required for a plain search of `type=task`/`task_v1`/`appointment_v1` (optional filter for `type=event`); it's not used at all for `my_tasks`/`available_slots`. `id` fetches a single record instead of searching — valid for `type=task`/`task_v1` only; there's no single-item endpoint for events. `filters` holds whatever extra query parameters the resolved endpoint accepts: `startTime`/`endTime`/`includeFinished`/`page`/`pageSize`/`sort` for `type=event`, `startTime`/`endTime` for `available_slots`, `userId` for `my_tasks`. `assignee`, `completed` and `due_between` are **not** real parameters, despite looking like reasonable filters — neither the v1 nor v2 task-list endpoint accepts them. Passing any of them returns a clear error rather than being silently ignored. Fetch by `lead_id` (or `my_tasks`) and filter client-side instead. Writes (create/update/complete/delete) live in [`manage_tasks`](/mcp/tools/manage-tasks). # search_team_config Source: https://developer.lofty.com/mcp/tools/search-team-config Members, tags, custom fields, pipelines, ponds, vendors, organization info, lead routing and system logs. **READ** · 17 operations · never changes data Everything the team and organization are configured with. The `kind` values mirror the five sections of the Lofty back office, so a kind name tells you which screen it corresponds to. ```js theme={null} search_team_config({ kind, lead_id?, account?, pond_id?, routing_type?, filters? }) ``` `filters` holds whatever extra query parameters the resolved `kind` accepts: `name`/`currPage`/`pageSize` for `settings.lead_pipelines`, `groupIds`/`offset`/`limit` for `members.list`, `roleId` for the `lead_routing.*` kinds, `startTime`/`endTime`/`pageNumber`/`pageSize` for `system_logs`. | Section | `kind` values | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Members | `members.me` · `members.list` · `members.find` | | Settings | `settings.tags` · `settings.custom_fields` · `settings.lead_pipelines` · `settings.transaction_pipelines` · `settings.ponds` · `settings.pond` · `settings.vendors` | | Organization | `organization.info` · `organization.permission_profiles` | | Lead routing | `lead_routing.rules` · `lead_routing.default_rule` · `lead_routing.roles` · `lead_routing.assignable_members` | | Other | `system_logs` | `GET /v1.0/users/{userId}` is deprecated upstream and isn't exposed under any kind — use `members.find` (by account email) instead. # search_transactions Source: https://developer.lofty.com/mcp/tools/search-transactions Search transactions, fetch by id, list by lead, read property address details and transaction custom field definitions. **READ** · 5 operations · never changes data Search transactions, fetch one by id, list by lead, read property address details and the team's transaction custom field definitions. ```js theme={null} search_transactions({ lead_id?, filters?, ids?, group_by?, metric?, custom_fields?, address_for? }) ``` `ids` requires `lead_id` (a transaction is addressed by its own id together with its lead's id) and accepts up to 100. `address_for` (a transaction id) also requires `lead_id`. `custom_fields: true` returns the team's transaction custom field definitions instead of searching, and takes no other parameter. Set `group_by` (a field name, e.g. `"stage"`) — optionally with `metric`: `"count"` (default), `"sum:"` e.g. `"sum:price"`, or `"avg:"` — for grouped counts/sums instead of a record list, rather than paging through everything yourself. It refuses outright past a fixed scan limit rather than ever returning a silently-truncated number. `metric` without `group_by` is ignored. # search_webhooks Source: https://developer.lofty.com/mcp/tools/search-webhooks List the event subscriptions this account holds. **READ** · 1 operation · never changes data List the event subscriptions this account holds. One operation, but a read-only connection still needs to answer "is my integration subscribed?" without being handed write access. ```js theme={null} search_webhooks({}) ``` # send_message Source: https://developer.lofty.com/mcp/tools/send-message Send an SMS or email to a lead, as the authenticated agent. **WRITE** · 2 operations · may change data Send an SMS or email **to a lead**, as the authenticated agent. ```js theme={null} send_message({ channel: "sms" | "email", lead_id, body, subject?, confirm_token }) ``` Recipients are always leads. To reach a teammate, use [`notify_agent`](/mcp/tools/notify-agent) — the two take near-identical arguments and point in opposite directions, which is exactly why they're separate tools. # Quickstart Source: https://developer.lofty.com/quickstart Create a Lofty developer account, register your application, obtain OAuth 2.0 credentials or an API key, and make your first authenticated API call in minutes. This guide walks you through everything you need to make your first API call to Lofty. By the end, you'll have a developer account, valid credentials, and a working request that returns lead data from the API. Go to the [Lofty Developer Platform](https://api.lofty.com/vendor/frontend/static/index.html#/login) and complete the signup flow: 1. Click **Sign Up**. 2. Enter your full name, organization name, email address, phone number, and a password. 3. Check your email for a verification code — it's valid for 10 minutes. 4. Enter the verification code to complete signup. After signing up, you'll land on the Developer Platform dashboard where you can register and manage your applications. If you're building an integration for production use, you'll need to submit your application for review. Lofty's review process typically takes 2–5 business days. You can continue developing against the API in Development Mode while your app is under review. Choose the authentication method that fits your use case: **OAuth 2.0 (recommended for third-party integrations)** Once your application is registered in the Developer Portal, you'll receive a **Client ID** and **Client Secret**. Use these to obtain a short-lived `access_token` by following the [OAuth 2.0 flow](/authentication/oauth2). Each token is scoped to the API permissions you've granted to your application. If you call an endpoint your application hasn't been granted access to, the API returns `HTTP 403` with error code `200100` ("Sorry, the vendor does not have permission for this API.") — even if your token is otherwise valid. Grant the necessary scopes in the Developer Portal before making those calls. **API Key (for personal use, scripts, CLI)** In the Lofty CRM, go to **Settings > Integrations > API** to generate an API key. Each key is a user-scoped personal access token with configurable expiration. API keys are also used as `LOFTY_CUSTOMER_KEY` in the [Lofty CLI](/cli/install) and the [Client Credentials flow](/authentication/oauth2#client-credentials-flow). Fetch a list of leads from your Lofty account. The endpoint requires your credentials in the `Authorization` header. ```bash CLI theme={null} lofty-cli leads list --limit 10 ``` ```bash OAuth 2.0 theme={null} curl --request GET \ --url https://api.lofty.com/v1.0/leads \ --header 'Content-type: application/json' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' ``` ```bash API Key theme={null} curl --request GET \ --url https://api.lofty.com/v1.0/leads \ --header 'Content-type: application/json' \ --header 'Authorization: token YOUR_API_KEY' ``` Replace `YOUR_ACCESS_TOKEN` or `YOUR_API_KEY` with your actual credentials. A successful response returns a JSON object containing your leads data. Check the `code` field to confirm success — Lofty uses `0` to indicate a successful operation. ```json theme={null} { "code": 0, "msg": "success", "data": { "total": 42, "list": [ { "id": 563172647619608, "name": "Alex Johnson", "email": "alex@example.com", "phone": "+15551234567" } ] } } ``` Notice that `id` is a 64-bit integer. **Do not parse this with `JSON.parse()` in JavaScript** — values this large can exceed JavaScript's safe integer range (2^53-1), causing silent precision loss where the ID is silently rounded to a different number. Read the [JavaScript and TypeScript Integration Guide](/javascript-integration) before building a JS or TS client. If the request fails, the response includes a non-zero `code` and a human-readable `msg` field explaining the error. Common error codes include `200100` (missing API permission scope, returns `HTTP 403`) and `401` (invalid or expired credentials).