Download OpenAPI specification:
API for integration with Homebase360 customer and job management system
Simple endpoint to verify API key authentication is working correctly. Returns basic information about the authenticated business account.
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/ping" \ -H "x-api-key: your-api-key-here" \ -H "Content-Type: application/json"
{- "success": true,
- "data": {
- "message": "API authentication successful",
- "email": "your-business-email@example.com",
- "timestamp": "2023-11-07T19:43:00.000Z"
}
}Retrieves customer information based on search criteria. Multiple search parameters can be combined and will return customers matching ANY of the provided criteria (logical OR). If no criteria is provided, returns empty array.
| customerId | string Example: customerId=Ekk9AvDeDihAEU8CMT0W Specific customer ID to look for |
| phone | string Example: phone=+13031234567 Customer's mobile phone number (E.164 format, e.g. +13038284444) |
string Example: email=john.smith@example.com Customer's email address | |
| name | string Example: name=deri Fuzzy, typo-tolerant search by customer name (first, last, or display name). E.g. "deri" matches "Derick". Returns up to |
| identifiers | string Example: identifiers=abc123,+13038284444,john@example.com Comma-separated list of customer IDs, phone numbers, or email addresses (max 100 identifiers) |
| limit | integer <= 100 Default: 25 Example: limit=50 Maximum number of results to return (max 100, default 25) |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/customers?customerId=Ekk9AvDeDihAEU8CMT0W" \ -H "x-api-key: your-api-key-here" \ -H "Content-Type: application/json"
{- "success": true,
- "data": [
- {
- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "businessId": "xsUrbqrcTQSyFjJOtpEZyC0BUUK2",
- "firstName": "John",
- "lastName": "Smith",
- "displayName": "John Smith",
- "phone": {
- "mobile": "+13038284444",
- "home": "+13035551234",
- "work": "+13037778888",
- "additional": "+13039990000",
- "property1": "string",
- "property2": "string"
}, - "email": [
- "john.smith@example.com"
], - "address": [
- "123 Main St, Denver, CO 80202"
], - "notifications": true,
- "notes": "Prefers afternoon appointments",
- "tags": [
- "VIP",
- "Referral"
], - "attachments": [ ],
- "dateAdded": "2023-04-15T10:30:00Z",
- "lastUpdated": "2023-04-20T15:45:00Z",
- "lastJob": "2023-04-18T09:00:00Z"
}
]
}Creates a new customer with the provided information. Requires at least firstName or lastName, and at least one contact method (phone or email).
| firstName | string Customer's first name |
| lastName | string Customer's last name |
object (Phone) All phone numbers must be in E.164 format (e.g. +13038284444) | |
Array of strings List of email addresses (max 2). Can also provide as comma or semicolon-separated string. | |
| address | Array of strings List of physical addresses (max 2). Can also provide as semicolon or asterisk-separated string. |
| notes | string Default: "" Additional notes about the customer |
| tags | Array of strings Tags for categorization. Can also provide as comma, semicolon, or asterisk-separated string. |
| notifications | boolean Default: true Whether the customer should receive notifications |
{- "firstName": "John",
- "lastName": "Smith",
- "phone": {
- "mobile": "+13038284444",
- "home": "+13035551234"
}, - "email": [
- "john.smith@example.com"
], - "address": [
- "123 Main St, Denver, CO 80202"
], - "notes": "Prefers afternoon appointments",
- "tags": [
- "VIP",
- "Referral"
], - "notifications": true
}{- "success": true,
- "data": {
- "customerId": "Ekk9AvDeDihAEU8CMT0W"
}
}Updates an existing customer's information
| customerId required | string ID of the customer to update |
| firstName | string Customer's first name |
| lastName | string Customer's last name |
object (Phone) All phone numbers must be in E.164 format (e.g. +13038284444) | |
Array of strings List of email addresses | |
| address | Array of strings List of physical addresses |
| notes | string Additional notes about the customer |
| tags | Array of strings Tags for categorization |
| notifications | boolean Whether the customer should receive notifications |
{- "firstName": "John",
- "lastName": "Smith Updated",
- "phone": {
- "mobile": "+13038284444"
}, - "notes": "Updated notes",
- "notifications": false
}{- "success": true,
- "data": {
- "message": "Customer updated"
}
}Retrieves jobs based on search criteria.
customerId alone → all jobs for that customer, ordered newest-first by dateAdded (paginate with cursor).sort=dateAdded → all jobs ordered by creation date (defaults to newest-first).sort=lastUpdated → all jobs ordered by last modification time (defaults to newest-first). Useful for change-feed / sync integrations that need to surface recent edits and reschedules.startDate/endDate → jobs whose start falls in the range, ordered by start time.When sort is provided, the default today-window filter is NOT applied — the caller almost always wants results across all time, not just today.
| jobId | string Specific job ID to look for |
| customerId | string Filter jobs by customer ID. When used alone, returns all jobs for that customer ordered newest-first by |
| startDate | string <date-time> Start date in ISO 8601 format (e.g. "2023-05-15T00:00:00Z") - jobs starting on/after this date |
| endDate | string <date-time> End date in ISO 8601 format (e.g. "2023-05-15T23:59:59Z") - jobs starting on/before this date |
| sort | string Enum: "dateAdded" "lastUpdated" Sort field.
|
| orderDirection | string Enum: "asc" "desc" Sort direction. Defaults to |
| limit | integer <= 100 Default: 50 Maximum number of results to return (max 100) |
| cursor | string Cursor token for pagination (returned as |
{- "success": true,
- "data": [
- {
- "jobId": "jKv8AvFrRihBTU7CMQ9Z",
- "businessId": "xsUrbqrcTQSyFjJOtpEZyC0BUUK2",
- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "customer": {
- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "businessId": "xsUrbqrcTQSyFjJOtpEZyC0BUUK2",
- "firstName": "John",
- "lastName": "Smith",
- "displayName": "John Smith",
- "phone": {
- "mobile": "+13038284444",
- "home": "+13035551234",
- "work": "+13037778888",
- "additional": "+13039990000",
- "property1": "string",
- "property2": "string"
}, - "email": [
- "john.smith@example.com"
], - "address": [
- "123 Main St, Denver, CO 80202"
], - "notifications": true,
- "notes": "Prefers afternoon appointments",
- "tags": [
- "VIP",
- "Referral"
], - "attachments": [ ],
- "dateAdded": "2023-04-15T10:30:00Z",
- "lastUpdated": "2023-04-20T15:45:00Z",
- "lastJob": "2023-04-18T09:00:00Z"
}, - "lineItems": [
- {
- "name": "Window Cleaning",
- "description": "Cleaning all windows",
- "quantity": 1,
- "unitPrice": "150.00",
- "taxRate": {
- "name": "Standard",
- "rate": "7.00"
}, - "saveHidden": true
}
], - "start": "2023-05-15T14:30:00Z",
- "end": "2023-05-15T16:30:00Z",
- "jobTotal": 160.5,
- "note": "Please call customer before arrival",
- "timezone": "America/New_York",
- "isRecurring": false,
- "dateAdded": "2023-04-15T10:30:00Z",
- "lastUpdated": "2023-04-20T15:45:00Z"
}
], - "nextPageToken": "string"
}Creates a new job associated with a customer
| customerId required | string Customer ID (must exist in your account) |
| start required | string <date-time> Start date and time in ISO 8601 format |
| end required | string <date-time> End date and time in ISO 8601 format (must be after start) |
required | Array of objects (LineItem) non-empty List of items/services for the job (minimum 1 item required) |
| note | string Default: "" Note for the job (optional) |
LeadSource (string) or object Lead source information (optional). Accepts string format or legacy object format for backwards compatibility | |
| salespersonEmail | string Email of an existing employee to set as the salesperson on the lead source (used with string leadSource). The salesperson gains view access to the job. If no employee matches, the job is still created without a salesperson. |
{- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "start": "2023-05-15T14:30:00Z",
- "end": "2023-05-15T16:30:00Z",
- "lineItems": [
- {
- "name": "Window Cleaning",
- "description": "Cleaning all windows",
- "quantity": 1,
- "unitPrice": "150.00",
- "taxRate": {
- "name": "Standard",
- "rate": "7.00"
}
}
], - "note": "Please call customer before arrival"
}{- "success": true,
- "data": {
- "jobId": "jKv8AvFrRihBTU7CMQ9Z"
}
}Updates an existing job's information. Passing lineItems replaces the
whole list and recomputes the job total — to change one item, use
POST /v1/jobs/{jobId}/line-items. Line-item changes are rejected once
the job has been paid (409); other fields stay editable.
| jobId required | string ID of the job to update |
| start | string <date-time> New start date and time in ISO 8601 format |
| end | string <date-time> New end date and time in ISO 8601 format |
| note | string New note for the job |
Array of strings or string Replacement assignment — array of employee emails, or a comma-separated string. | |
Array of objects (LineItem) Replacement array of line items (recomputes the job total). To change one item, use the line-items endpoint instead. | |
| leadSource | string (LeadSource) Source of the lead. Use "Online" for online sources, "Direct" for direct contact/referrals, or provide a custom source name (e.g., "Facebook Ad", "Google Ads", "Referral Partner"). Custom sources will be automatically created and associated with your business. |
| salespersonEmail | string Email of an existing employee to set as the salesperson on the lead source (grants them view access to the job). Send "" or null to clear the salesperson. Returns 400 if no employee matches the email. |
{- "start": "2023-05-15T15:00:00Z",
- "end": "2023-05-15T17:00:00Z",
- "note": "Customer requested a later time"
}{- "success": true,
- "data": {
- "message": "Job updated"
}
}Edits a job's line items with surgical operations instead of replacing the whole list — so you can change, add, or remove a single item without re-sending (and risking the loss of) the others.
Operations are applied in order against the current list:
add — append a new itemupdate — change only the provided fields of a matched item (the rest are kept)remove — delete a matched itemreplace_all — replace the whole list (must be the only operation)Items are matched by name (case-insensitive); when several items share
a name, pass a 0-based index to disambiguate. The job total is
recomputed automatically. Removing the last remaining item is rejected.
Once the job has been paid, line-item edits are rejected (409).
| jobId required | string ID of the job to edit |
required | Array of objects (LineItemOperation) non-empty Ordered list of line-item operations to apply |
{- "operations": [
- {
- "op": "update",
- "match": {
- "name": "Window Cleaning"
}, - "fields": {
- "unitPrice": "175.00"
}
}
]
}{- "success": true,
- "data": {
- "message": "Job updated"
}
}Retrieves tasks (to-dos / reminders, optionally attached to a customer), with cursor-based pagination.
start. Unscheduled tasks (no start) are excluded — pass sort=createdAt to list all tasks.taskId → that single task.startDate/endDate → tasks whose start falls in the range, ordered by start (combinable with status/employeeId; not combinable with customerId).status, employeeId, or customerId → equality filters, paginated by document id.sort=createdAt / sort=updatedAt → all tasks (including unscheduled), newest-first. Applies to the otherwise-unfiltered listing.| taskId | string Specific task ID to look for |
| customerId | string Filter tasks for a specific customer |
| status | string Enum: "pending" "completed" Filter by completion status |
| employeeId | string Filter by assigned employee's user ID |
| startDate | string <date-time> ISO 8601 — tasks starting on/after this time |
| endDate | string <date-time> ISO 8601 — tasks starting on/before this time |
| sort | string Enum: "createdAt" "updatedAt" Order all tasks by creation or last-update time (newest-first by default). Applies to the otherwise-unfiltered listing; any other value returns 400. |
| orderDirection | string Enum: "asc" "desc" Sort direction. Defaults to |
| limit | integer <= 100 Default: 50 Maximum number of results to return (max 100) |
| cursor | string Cursor token for pagination (returned as |
{- "success": true,
- "data": [
- {
- "taskId": "tKv8AvFrRihBTU7CMQ9Z",
- "title": "Call customer to confirm",
- "instructions": "Confirm the Tuesday window cleaning appointment.",
- "customer": {
- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "businessId": "xsUrbqrcTQSyFjJOtpEZyC0BUUK2",
- "firstName": "John",
- "lastName": "Smith",
- "displayName": "John Smith",
- "phone": {
- "mobile": "+13038284444",
- "home": "+13035551234",
- "work": "+13037778888",
- "additional": "+13039990000",
- "property1": "string",
- "property2": "string"
}, - "email": [
- "john.smith@example.com"
], - "address": [
- "123 Main St, Denver, CO 80202"
], - "notifications": true,
- "notes": "Prefers afternoon appointments",
- "tags": [
- "VIP",
- "Referral"
], - "attachments": [ ],
- "dateAdded": "2023-04-15T10:30:00Z",
- "lastUpdated": "2023-04-20T15:45:00Z",
- "lastJob": "2023-04-18T09:00:00Z"
}, - "status": "pending",
- "createdBy": "string",
- "completedAt": "2019-08-24T14:15:22Z",
- "completedBy": "string",
- "assignedTo": [
- {
- "id": "string",
- "firstName": "string",
- "lastName": "string",
- "email": "string",
- "displayName": "string"
}
], - "notificationMethods": {
- "email": true,
- "push": true
}, - "reminderSettings": {
- "start": true,
- "thirtyMin": true,
- "oneHour": true,
- "twoHour": true,
- "fiveHour": true,
- "twentyFourHour": true
}, - "reminderStatus": { },
- "timezone": "America/New_York",
- "start": "2019-08-24T14:15:22Z",
- "end": "2019-08-24T14:15:22Z",
- "noteHistory": [
- {
- "noteId": "string",
- "userId": "string",
- "name": "string",
- "note": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "recurrenceData": {
- "isRecurring": false
}, - "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "nextPageToken": "string"
}Creates a task (to-do / reminder), optionally attached to a customer and assigned to employees. title and instructions are required.
When assignees and notification methods are set, assigned employees are notified automatically; when a reminder is set, it is delivered automatically before the task's start time.
Notes:
start and end must BOTH be provided or BOTH omitted (a task is either scheduled or unscheduled).reminder requires start/end (it fires relative to start).createdBy is null. An unassigned task has no viewers and will not appear in any technician's in-app task list — assign at least one employee for it to be visible in the app.| title required | string Task title (required; may be empty string but not null) |
| instructions required | string Task details/instructions (required; may be empty string but not null) |
| customerId | string Existing customer ID to attach to the task (optional) |
| start | string <date-time> Start date/time, ISO 8601. Must be paired with |
| end | string <date-time> End date/time, ISO 8601. Must be paired with |
| assignedTo | Array of strings Array of employee emails to assign the task to (optional) |
| reminder | string Default: "none" Enum: "none" "start" "thirtyMin" "oneHour" "twoHour" "fiveHour" "twentyFourHour" Single reminder before |
object (NotificationMethods) Notification preferences applied to both assignment and reminder notifications. | |
| note | string Initial note added to the task's note history (optional) |
{- "title": "Call customer to confirm",
- "instructions": "Confirm the Tuesday window cleaning appointment.",
- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "start": "2026-06-16T09:00:00-06:00",
- "end": "2026-06-16T09:30:00-06:00",
- "assignedTo": [
- "john@company.com"
], - "reminder": "oneHour",
- "notificationMethods": {
- "email": true,
- "push": true
}, - "note": "High-priority customer"
}{- "success": true,
- "data": {
- "taskId": "tKv8AvFrRihBTU7CMQ9Z"
}
}Updates a task. Only the provided fields change. Set status to completed to mark a task done (or pending to reopen it); completedBy is null for API-driven completions. A provided note is appended to the task's note history. Reassigning employees (adding new ones) notifies the newly-assigned employees automatically.
Notes:
start and end must be updated together. Pass both as null to clear the schedule.reminder resets its sent-state so the new reminder can fire; a non-none reminder requires the task to have a start (existing or set in the same call).assignedTo as [] to unassign everyone. Any email that doesn't match an employee returns 400.| taskId required | string ID of the task to update |
| status | string Enum: "pending" "completed"
|
| title | string |
| instructions | string |
| start | string or null <date-time> New start, ISO 8601. Pass together with |
| end | string or null <date-time> New end, ISO 8601. Pass together with |
| assignedTo | Array of strings Full replacement list of employee emails. |
| reminder | string Enum: "none" "start" "thirtyMin" "oneHour" "twoHour" "fiveHour" "twentyFourHour" Single reminder before |
object (NotificationMethods) Notification preferences applied to both assignment and reminder notifications. | |
| note | string Note appended to the task's note history |
{- "status": "completed",
- "note": "Confirmed with customer."
}{- "success": true,
- "data": {
- "message": "Task updated"
}
}Retrieves the business's employees (team members), with cursor-based pagination. Read-only.
Use this to discover employee names, emails, and ids — pass an employee's email to dispatchedTo
(jobs) or assignedTo (tasks/leads) when assigning work, and their employeeId to the employeeId
filter on GET /v1/tasks (and similar).
employeeId → that single employee (scoped to your business).email → exact (case-insensitive) match.search → case-insensitive substring match on name (first + last + display) and email.| employeeId | string Fetch a specific employee by ID |
string Exact email address (case-insensitive) | |
| search | string Substring to match against employee name or email |
| limit | integer <= 100 Default: 50 Maximum number of results to return (max 100) |
| cursor | string Cursor token for pagination (returned as |
{- "success": true,
- "data": [
- {
- "employeeId": "wxNogbkRPYnGcGgDjK39",
- "firstName": "Jane",
- "lastName": "Tech",
- "displayName": "Jane Tech",
- "email": "jane@company.com",
- "phone": "+13035551111",
- "userType": "Employee",
- "isAdmin": false
}
], - "nextPageToken": "string"
}Retrieves leads based on search criteria.
Non-archived leads are returned by default. Pass includeArchived=true
to include archived leads alongside, or includeArchived=only to
return archived leads only.
Ordering (all served by a single archived + dateAdded composite
index):
dateAdded.startDate / endDate filter applied → ordered by dateAdded
ascending. Filters operate on dateAdded.customerId, status, or employeeId filter applied → results
paginated in document-ID order.| leadId | string Fetch a specific lead by ID |
| customerId | string Fetch all leads for a specific customer |
| status | string Enum: "new" "first_contact" "second_contact" "third_contact" "on_hold" Filter by lead status |
| employeeId | string Filter by assigned employee ID |
| startDate | string <date-time> ISO-8601 date — only return leads with |
| endDate | string <date-time> ISO-8601 date — only return leads with |
| includeArchived | string Enum: "true" "only" Archive filter. |
| cursor | string Cursor token from a previous response ( |
| limit | integer <= 100 Default: 25 Maximum number of results to return |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/leads?status=new&limit=10" \ -H "x-api-key: your-api-key-here" \ -H "Content-Type: application/json"
{- "success": true,
- "data": [
- {
- "leadId": "abc123",
- "leadNumber": 42,
- "customer": {
- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "businessId": "xsUrbqrcTQSyFjJOtpEZyC0BUUK2",
- "firstName": "John",
- "lastName": "Smith",
- "displayName": "John Smith",
- "phone": {
- "mobile": "+13038284444",
- "home": "+13035551234",
- "work": "+13037778888",
- "additional": "+13039990000",
- "property1": "string",
- "property2": "string"
}, - "email": [
- "john.smith@example.com"
], - "address": [
- "123 Main St, Denver, CO 80202"
], - "notifications": true,
- "notes": "Prefers afternoon appointments",
- "tags": [
- "VIP",
- "Referral"
], - "attachments": [ ],
- "dateAdded": "2023-04-15T10:30:00Z",
- "lastUpdated": "2023-04-20T15:45:00Z",
- "lastJob": "2023-04-18T09:00:00Z"
}, - "status": "new",
- "lineItems": [
- {
- "name": "Window Cleaning",
- "description": "Cleaning all windows",
- "quantity": 1,
- "unitPrice": "150.00",
- "taxRate": {
- "name": "Standard",
- "rate": "7.00"
}, - "saveHidden": true
}
], - "note": "Customer interested in monthly service",
- "leadSource": "Online",
- "dateAdded": "2023-05-15T14:30:00Z",
- "lastUpdated": "2023-05-15T14:30:00Z"
}
]
}Creates a new lead with smart customer lookup/creation. This intelligent endpoint handles three scenarios:
Scenario 1: Existing Customer by ID
Provide the customerId field to create a lead for an existing customer. Returns 404 if customer not found.
Scenario 2: Phone-based Lookup/Merge
Provide customer details including phone.mobile. The API will:
Scenario 3: New Customer
Provide customer details without customerId or phone.mobile. The API will:
You can either provide a customerId, or a name and a contact method: firstName/lastName and any-phone/email.
| customerId | string Method 1: Existing customer ID |
| firstName | string Method 2: Customer's first name (at least firstName or lastName required if using customer details) |
| lastName | string Method 2: Customer's last name (at least firstName or lastName required if using customer details) |
object (Phone) All phone numbers must be in E.164 format (e.g. +13038284444) | |
Array of strings Method 2: List of email addresses | |
| address | Array of strings Method 2: List of physical addresses |
| notes | string Method 2: Additional notes about the customer |
| tags | Array of strings Method 2: Tags for categorization |
| notifications | boolean Method 2: Whether customer should receive notifications |
Array of objects (LineItem) Array of line items (optional for leads) | |
| note | string Note about the lead |
| leadSource | string (LeadSource) Source of the lead. Use "Online" for online sources, "Direct" for direct contact/referrals, or provide a custom source name (e.g., "Facebook Ad", "Google Ads", "Referral Partner"). Custom sources will be automatically created and associated with your business. |
| status | string Default: "new" Enum: "new" "first_contact" "second_contact" "third_contact" "on_hold" Initial status of the lead |
| assignedTo | Array of strings Array of employee IDs to assign the lead to |
{- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "lineItems": [
- {
- "name": "Window Cleaning",
- "description": "All windows",
- "quantity": 1,
- "unitPrice": "150.00"
}
], - "note": "Customer interested in monthly service",
- "leadSource": "Online",
- "status": "new"
}{- "success": true,
- "data": {
- "leadId": "lead789",
- "customerId": "cust456",
- "leadNumber": 43
}
}Updates an existing lead's information. Only non-archived leads can be updated.
| leadId required | string ID of the lead to update |
| status | string Enum: "new" "first_contact" "second_contact" "third_contact" "on_hold" Updated lead status |
| note | string Updated note for the lead |
Array of objects (LineItem) Replacement array of line items | |
| assignedTo | Array of strings Replacement array of employee IDs |
| leadSource | string (LeadSource) Source of the lead. Use "Online" for online sources, "Direct" for direct contact/referrals, or provide a custom source name (e.g., "Facebook Ad", "Google Ads", "Referral Partner"). Custom sources will be automatically created and associated with your business. |
{- "status": "first_contact",
- "note": "Called customer, discussed options. Following up next week."
}{- "success": true,
- "data": {
- "message": "Lead updated"
}
}Edits a lead's line items with surgical operations instead of replacing the whole list — so you can change, add, or remove a single item without re-sending (and risking the loss of) the others.
Operations are applied in order against the current list:
add — append a new itemupdate — change only the provided fields of a matched item (the rest are kept)remove — delete a matched itemreplace_all — replace the whole list (must be the only operation)Items are matched by name (case-insensitive); when several items share
a name, pass a 0-based index to disambiguate. Removing the last
remaining item is rejected. Converted leads cannot be edited.
| leadId required | string ID of the lead to edit |
required | Array of objects (LineItemOperation) non-empty Ordered list of line-item operations to apply |
{- "operations": [
- {
- "op": "update",
- "match": {
- "name": "Window Cleaning"
}, - "fields": {
- "unitPrice": "200.00"
}
}, - {
- "op": "add",
- "item": {
- "name": "Screen Repair",
- "quantity": 1,
- "unitPrice": "40.00"
}
}
]
}{- "success": true,
- "data": {
- "message": "Lead updated"
}
}Retrieves estimates (quotes) based on search criteria.
Ordering:
dateAdded.sort=lastUpdated → most-recently-modified first.startDate / endDate → ordered by dateAdded ascending (filters operate on dateAdded).customerId alone → newest-created first.| estimateId | string Fetch a specific estimate by ID (short-circuits all other filters) |
| customerId | string Fetch all estimates for a specific customer |
| startDate | string <date-time> ISO-8601 date — only return estimates with |
| endDate | string <date-time> ISO-8601 date — only return estimates with |
| sort | string Enum: "dateAdded" "lastUpdated" Sort field — |
| orderDirection | string Enum: "asc" "desc" Sort direction. Defaults to |
| cursor | string Cursor token from a previous response ( |
| limit | integer <= 100 Default: 25 Maximum number of results to return |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/estimates?limit=10" \ -H "x-api-key: your-api-key-here" \ -H "Content-Type: application/json"
{- "success": true,
- "data": [
- {
- "estimateId": "est789",
- "estimateNumber": 103,
- "customer": {
- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "businessId": "xsUrbqrcTQSyFjJOtpEZyC0BUUK2",
- "firstName": "John",
- "lastName": "Smith",
- "displayName": "John Smith",
- "phone": {
- "mobile": "+13038284444",
- "home": "+13035551234",
- "work": "+13037778888",
- "additional": "+13039990000",
- "property1": "string",
- "property2": "string"
}, - "email": [
- "john.smith@example.com"
], - "address": [
- "123 Main St, Denver, CO 80202"
], - "notifications": true,
- "notes": "Prefers afternoon appointments",
- "tags": [
- "VIP",
- "Referral"
], - "attachments": [ ],
- "dateAdded": "2023-04-15T10:30:00Z",
- "lastUpdated": "2023-04-20T15:45:00Z",
- "lastJob": "2023-04-18T09:00:00Z"
}, - "lineItems": [
- {
- "name": "Window Cleaning",
- "description": "Cleaning all windows",
- "quantity": 1,
- "unitPrice": "150.00",
- "taxRate": {
- "name": "Standard",
- "rate": "7.00"
}, - "saveHidden": true
}
], - "estimateTotal": 337.87,
- "note": "Quote for spring cleaning",
- "leadSource": "Online",
- "publicStatus": {
- "status": "pending",
- "timestamp": "2019-08-24T14:15:22Z"
}, - "statusTimestamps": {
- "sent": "2019-08-24T14:15:22Z",
- "approved": "2019-08-24T14:15:22Z",
- "copied": "2019-08-24T14:15:22Z"
}, - "dateAdded": "2023-05-15T14:30:00Z",
- "lastUpdated": "2023-05-15T14:30:00Z"
}
], - "nextPageToken": "string"
}Creates an estimate (an unsent draft quote). Provide either an existing
customerId or new-contact details (a name plus phone or email), in
which case a customer is found-or-created automatically — the same
contract as creating a lead.
The estimate is created in the pending public status with no sent
timestamp (a draft). Delivering the estimate to the customer (PDF +
email/SMS) remains an in-app action; this endpoint does not send anything.
| customerId | string Method 1: Existing customer ID |
| firstName | string Method 2: Customer's first name (at least firstName or lastName required if using customer details) |
| lastName | string Method 2: Customer's last name (at least firstName or lastName required if using customer details) |
object (Phone) All phone numbers must be in E.164 format (e.g. +13038284444) | |
Array of strings Method 2: List of email addresses | |
| address | Array of strings Method 2: List of physical addresses |
Array of objects (LineItem) Services/products being quoted. Defaults to a single placeholder item if omitted. | |
| note | string Note shown on the estimate |
| leadSource | string (LeadSource) Source of the lead. Use "Online" for online sources, "Direct" for direct contact/referrals, or provide a custom source name (e.g., "Facebook Ad", "Google Ads", "Referral Partner"). Custom sources will be automatically created and associated with your business. |
| salespersonEmail | string Email of an existing employee to set as the salesperson on the lead source |
{- "customerId": "cust456",
- "lineItems": [
- {
- "name": "Exterior Window Cleaning",
- "description": "All exterior windows",
- "quantity": 1,
- "unitPrice": "299.00",
- "taxRate": {
- "name": "Sales Tax",
- "rate": "8.25"
}
}
], - "note": "Quote for spring cleaning"
}{- "success": true,
- "data": {
- "estimateId": "est789",
- "customerId": "cust456",
- "estimateNumber": 103
}
}Updates an existing estimate. Only the provided fields change. Updatable
fields are lineItems, note, and leadSource. Passing lineItems
replaces the whole array and recomputes the total — to change a single
item, prefer POST /estimates/{estimateId}/line-items.
Approved estimates (the customer accepted the quote) and estimates already converted to a job are immutable and return 409.
| estimateId required | string ID of the estimate to update |
Array of objects (LineItem) Replacement array of line items (recomputes the total). To change one item, use the line-items endpoint instead. | |
| note | string Updated note shown on the estimate |
| leadSource | string (LeadSource) Source of the lead. Use "Online" for online sources, "Direct" for direct contact/referrals, or provide a custom source name (e.g., "Facebook Ad", "Google Ads", "Referral Partner"). Custom sources will be automatically created and associated with your business. |
| salespersonEmail | string Email of an existing employee to set as the salesperson (used with leadSource) |
{- "note": "Updated quote after site visit",
- "lineItems": [
- {
- "name": "Window Cleaning",
- "quantity": 1,
- "unitPrice": "325.00"
}
]
}{- "success": true,
- "data": {
- "message": "Estimate updated"
}
}Edits an estimate's line items with surgical operations instead of replacing the whole list — so you can change, add, or remove a single item without re-sending (and risking the loss of) the others.
Operations are applied in order against the current list:
add — append a new itemupdate — change only the provided fields of a matched item (the rest are kept)remove — delete a matched itemreplace_all — replace the whole list (must be the only operation)Items are matched by name (case-insensitive); when several items share
a name, pass a 0-based index to disambiguate. The estimate total is
recomputed automatically. Removing the last remaining item is rejected.
Approved or converted-to-job estimates are immutable (409).
| estimateId required | string ID of the estimate to edit |
required | Array of objects (LineItemOperation) non-empty Ordered list of line-item operations to apply |
{- "operations": [
- {
- "op": "update",
- "match": {
- "name": "Window Cleaning"
}, - "fields": {
- "unitPrice": "325.00"
}
}, - {
- "op": "remove",
- "match": {
- "name": "Gutter Cleaning"
}
}
]
}{- "success": true,
- "data": {
- "message": "Estimate updated"
}
}Sends an SMS or MMS message to a customer's mobile phone. Requires an active Enterprise subscription. Subject to daily rate limits (500 SMS/day).
Provide either mobilePhone or customerId (or both):
phone.mobile of an existing customerphone.mobile on filemobilePhone must match the customer's phone.mobile| mobilePhone | string Customer's mobile phone in E.164 format. Must belong to an existing customer. |
| customerId | string Customer ID. Customer must have a mobile phone on file. |
| message | string <= 200 characters Message text. Max 200 characters. Required if no mediaUrl. |
| mediaUrl | string <uri> HTTPS URL of media to send as MMS. Required if no message. |
{- "mobilePhone": "+13031234444",
- "message": "Your appointment is confirmed for tomorrow at 2pm"
}{- "success": true,
- "data": {
- "mobilePhone": "+13031234444",
- "customerId": "abc123",
- "timestamp": "2024-01-15T14:30:00.000Z",
- "rateLimits": {
- "dailyUsed": 5,
- "dailyLimit": 500
}
}
}Lists messages across all of the business's conversations, newest-first by
createdAt. Returns both text messages and calls (calls are stored as
message documents). Use this to pull "messages from a given time period"
and to see what came in and whether it was answered.
Data availability (forward-only): text messages are returned from the
date this endpoint was deployed onward — historical texts are not surfaced
by this business-wide query. Calls are available for full history. To read
an entire single thread including older messages, use
GET /conversations/{phone}/messages.
| direction | string Enum: "inbound" "outbound" Filter by message direction |
| type | string Enum: "text" "call" Filter by message type |
| startDate | string <date-time> Only messages at/after this ISO 8601 time |
| endDate | string <date-time> Only messages at/before this ISO 8601 time |
| automated | string Enum: "true" "false" "exclude" Filter automated (system) messages. "exclude" is an alias for "false" (human-only) — useful to ignore appointment-reminder noise. Applies to texts; calls have no automated flag. |
| phone | string Scope to one customer's thread (E.164) |
| customerId | string Scope to one customer's thread by customer ID |
| limit | integer <= 100 Default: 50 Maximum number of results to return (max 100) |
| cursor | string Cursor token for pagination (returned as |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/messages?direction=inbound&limit=25" \ -H "x-api-key: your-api-key-here"
{- "success": true,
- "data": [
- {
- "messageId": "msg_abc123",
- "conversationId": "+13031234444",
- "phone": "+13031234444",
- "customerName": "John Smith",
- "direction": "inbound",
- "type": "text",
- "createdAt": "2026-06-15T14:02:00.000Z",
- "text": "Need to reschedule Tuesday",
- "mediaUrl": "string",
- "automated": true,
- "delivered": true,
- "deliveredAt": "2019-08-24T14:15:22Z",
- "error": "string",
- "status": "ringing",
- "duration": 0,
- "connectedAt": "2019-08-24T14:15:22Z",
- "endedAt": "2019-08-24T14:15:22Z",
- "recordingUrl": "string",
- "voicemailUrl": "string",
- "answeredBy": "string"
}
], - "nextPageToken": "string"
}Returns the current messaging usage stats and daily rate limits for the business.
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/messages/stats" \ -H "x-api-key: your-api-key-here" \ -H "Content-Type: application/json"
{- "success": true,
- "data": {
- "limits": {
- "perDay": 500
}, - "usage": {
- "dailyUsed": 50,
- "dailyRemaining": 450
}
}
}Lists conversations (one per customer phone number), newest-first by
lastMessageTime. Built to answer "which conversations are unanswered?".
Two ways to find unanswered threads:
awaitingReply=true — the precise signal: a customer messaged or called
in and no human has replied since (automated reminders do not count
as a reply). Reflects activity from when this feature was deployed onward.unread=true — the broad signal: the thread is marked unread. Works
across all history, including pre-feature conversations.unansweredForMinutes=N returns awaiting-reply threads whose last inbound
was more than N minutes ago — ideal for "alert me to anything unanswered
too long".
| awaitingReply | string Value: "true" Only threads awaiting a human reply |
| unread | string Value: "true" Only threads marked unread (legacy-safe; works across all history) |
| unansweredForMinutes | integer Only awaiting-reply threads whose last inbound is older than N minutes |
| phone | string Fetch a single conversation by customer phone (E.164) |
| customerId | string Fetch a single conversation by customer ID |
| limit | integer <= 100 Default: 50 Maximum number of results to return (max 100) |
| cursor | string Cursor token for pagination (returned as |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/conversations?awaitingReply=true&limit=25" \ -H "x-api-key: your-api-key-here"
{- "success": true,
- "data": [
- {
- "conversationId": "+13031234444",
- "phone": "+13031234444",
- "customerName": "John Smith",
- "lastMessage": "Need to reschedule Tuesday",
- "lastMessageTime": "2026-06-15T14:02:00.000Z",
- "read": true,
- "lastInboundAt": "2019-08-24T14:15:22Z",
- "lastRespondedAt": "2019-08-24T14:15:22Z",
- "awaitingReply": true
}
], - "nextPageToken": "string"
}Returns the full message thread (texts and calls) for a single customer
phone number, newest-first. Unlike GET /messages, this reads the thread
directly and therefore includes older, pre-feature messages too
(direction/type are inferred for those legacy messages).
| phone required | string Customer phone number in E.164 (e.g. +13031234444) |
| direction | string Enum: "inbound" "outbound" Filter by message direction |
| type | string Enum: "text" "call" Filter by message type |
| startDate | string <date-time> Only messages at/after this ISO 8601 time |
| endDate | string <date-time> Only messages at/before this ISO 8601 time |
| limit | integer <= 100 Default: 50 Maximum number of results to return (max 100) |
| cursor | string Cursor token for pagination (returned as |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/conversations/+13031234444/messages?limit=25" \ -H "x-api-key: your-api-key-here"
{- "success": true,
- "data": [
- {
- "messageId": "msg_abc123",
- "conversationId": "+13031234444",
- "phone": "+13031234444",
- "customerName": "John Smith",
- "direction": "inbound",
- "type": "text",
- "createdAt": "2026-06-15T14:02:00.000Z",
- "text": "Need to reschedule Tuesday",
- "mediaUrl": "string",
- "automated": true,
- "delivered": true,
- "deliveredAt": "2019-08-24T14:15:22Z",
- "error": "string",
- "status": "ringing",
- "duration": 0,
- "connectedAt": "2019-08-24T14:15:22Z",
- "endedAt": "2019-08-24T14:15:22Z",
- "recordingUrl": "string",
- "voicemailUrl": "string",
- "answeredBy": "string"
}
], - "nextPageToken": "string"
}Retrieves recurring service subscriptions (plans sold to your customers). Results are sanitized — internal Firestore plumbing and Stripe connect-account metadata are stripped before being returned.
Legacy subscriptions bill via Stripe and keep the original
Stripe-backed shape. Subscriptions with billingVersion: "v3" are
billed by the internal engine (card or offline collection) and
additionally expose their billing intent (paymentCollection),
cadence (billingConfig), visit schedule, term, and billing-schedule
working set, plus a compact v3Billing summary block with their
price, cadence, and next processing time.
Results are always returned newest-first by createdAt.
| subscriptionId | string Fetch a specific subscription by ID |
| customerId | string Filter subscriptions by customer ID |
| status | string Enum: "draft" "sent" "pending" "active" "paused" "completed" "canceled" "cancelled" "incomplete" "past_due" Filter by subscription lifecycle status (pending/paused/completed are v3-engine statuses) |
| limit | integer <= 100 Default: 25 Maximum number of results to return (max 100) |
| cursor | string Cursor token for pagination (returned as |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/subscriptions?status=active&limit=25" \ -H "x-api-key: your-api-key-here" \ -H "Content-Type: application/json"
{- "success": true,
- "data": [
- {
- "subscriptionId": "sub_abc123",
- "businessId": "biz_xyz",
- "status": "active",
- "customer": {
- "customerId": "Ekk9AvDeDihAEU8CMT0W",
- "businessId": "xsUrbqrcTQSyFjJOtpEZyC0BUUK2",
- "firstName": "John",
- "lastName": "Smith",
- "displayName": "John Smith",
- "phone": {
- "mobile": "+13038284444",
- "home": "+13035551234",
- "work": "+13037778888",
- "additional": "+13039990000",
- "property1": "string",
- "property2": "string"
}, - "email": [
- "john.smith@example.com"
], - "address": [
- "123 Main St, Denver, CO 80202"
], - "notifications": true,
- "notes": "Prefers afternoon appointments",
- "tags": [
- "VIP",
- "Referral"
], - "attachments": [ ],
- "dateAdded": "2023-04-15T10:30:00Z",
- "lastUpdated": "2023-04-20T15:45:00Z",
- "lastJob": "2023-04-18T09:00:00Z"
}, - "template": {
- "title": "Monthly Window Cleaning",
- "description": "Exterior windows on the first floor",
- "amount": "150.00",
- "paymentFrequency": "monthly",
- "type": "indefinite",
- "duration": 1,
- "visits": 12,
- "attachments": [
- { }
]
}, - "tax": {
- "name": "Standard",
- "rate": "7.00"
}, - "startDate": "2024-01-15T00:00:00Z",
- "startDateOriginal": "2019-08-24T14:15:22Z",
- "createdAt": "2024-01-10T14:30:00Z",
- "sentAt": "2019-08-24T14:15:22Z",
- "cancelledAt": "2019-08-24T14:15:22Z",
- "companyName": "Acme Cleaning Co.",
- "accept": {
- "type": "link",
- "message": "string",
- "emailToSend": "string",
- "phoneToSend": "string",
- "signature": "string",
- "dateAccepted": "2019-08-24T14:15:22Z",
- "acceptedBy": "string",
- "signedAt": "2019-08-24T14:15:22Z"
}, - "source": { },
- "stripeSubscription": {
- "id": "sub_1NstripeId",
- "status": "active",
- "start_date": 1705320000,
- "current_period_start": 0,
- "current_period_end": 0,
- "cancel_at": 0,
- "cancel_at_period_end": false,
- "canceled_at": 0,
- "ended_at": 0,
- "trial_start": 0,
- "trial_end": 0,
- "billing_cycle_anchor": 0,
- "plan": {
- "id": "price_xxx",
- "amount": 16050,
- "currency": "usd",
- "interval": "month",
- "interval_count": 1,
- "product": "prod_xxx"
}
}, - "stripePriceId": "price_xxx",
- "stripeProductId": "prod_xxx",
- "billingVersion": "v3",
- "customerId": "string",
- "lineItems": [
- {
- "id": "string",
- "name": "string",
- "description": "string",
- "quantity": 0,
- "unitPrice": "120.00",
- "taxRate": {
- "name": "string",
- "rate": 0
}, - "taxable": true
}
], - "discount": {
- "type": "fixed",
- "value": 0,
- "appliesTo": "initial_and_recurring"
}, - "paymentCollection": {
- "trigger": "billing_cycle",
- "method": "auto_card",
- "emailInvoiceToCustomer": true,
- "billInitialVisitOnCompletion": true,
- "hasStoredPaymentMethod": true
}, - "billingConfig": {
- "frequency": "weekly",
- "intervalWeeks": 0,
- "intervalMonths": 0,
- "customMonths": [
- 0
], - "anchorDate": "2019-08-24T14:15:22Z",
- "anchorYmd": {
- "year": 0,
- "month": 0,
- "day": 0
}, - "dayOfMonth": 0
}, - "visits": [
- {
- "id": "string",
- "name": "Visit 1",
- "number": 0,
- "month": 0,
- "year": 0,
- "status": "unscheduled",
- "jobId": "string",
- "scheduledDate": "2019-08-24T14:15:22Z"
}
], - "visitScheduleMode": "auto",
- "serviceDateRule": { },
- "jobCreation": "now",
- "autoScheduleVisits": true,
- "term": {
- "type": "fixed",
- "durationMonths": 0,
- "startDate": "2019-08-24T14:15:22Z",
- "endDate": "2019-08-24T14:15:22Z",
- "autoRenew": true
}, - "agreement": {
- "agreementTemplateId": "string",
- "agreementTemplateName": "string",
- "status": "draft",
- "signedAt": "2019-08-24T14:15:22Z"
}, - "note": "string",
- "soldBy": { },
- "leadSource": { },
- "activatedAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z",
- "sourceEstimate": { },
- "billingSchedule": [
- {
- "id": "occ_20260801",
- "dueDate": "2019-08-24T14:15:22Z",
- "visitId": "string",
- "status": "scheduled",
- "isInitialPrice": true,
- "pricing": {
- "subtotalCents": 0,
- "discountCents": 0,
- "taxCents": 0,
- "totalCents": 0
}, - "invoiceId": "string",
- "tipCents": 0,
- "attempts": 0,
- "nextRetryAt": "2019-08-24T14:15:22Z"
}
]
}
], - "nextPageToken": "string"
}Creates a v3 service subscription as a DRAFT built from one of the business's subscription templates, optionally sending the customer the review-and-sign link (email/SMS). The API never activates or charges: the customer reviews, signs, and (for auto_card) enters payment through the normal accept-link flow, exactly as if the draft was created in the dashboard.
Visits are seeded automatically on the template's cadence (evenly spread from the start month); the visit grid can be fine-tuned in the dashboard while the subscription is a draft.
With send, the response status is "sent" when at least one
channel (email/SMS) succeeded, otherwise the doc stays "draft" and
can be re-sent from the dashboard.
| customerId required | string Existing customer ID |
| templateId required | string Subscription template to build from |
| requestId | string^[A-Za-z0-9_-]{6,64}$ Optional idempotency token, bound to the request payload (first write wins). Retrying with the same requestId and the IDENTICAL body returns the subscription's current state without modifying it — nothing is overwritten and an already-sent subscription is never re-sent (a failed send is retried). The same requestId with a different body is a 409. Recommended for any integration that retries on timeouts. |
| startDate | string^\d{4}-\d{2}-\d{2}$ Service/billing anchor date, strictly YYYY-MM-DD (default today). Invalid calendar dates (e.g. 2026-02-30) and ISO datetimes are rejected — a timezone offset could shift the anchor a calendar day. |
object | |
| dayOfMonth | integer [ 1 .. 28 ] billing_cycle only — calendar billing day. Defaults to the start date's day (clamped to 28); note the dashboard's creation form defaults to the 1st instead. |
Array of objects Recurring price line items; defaults to the template's line items | |
object | |
| note | string Internal note on the subscription |
boolean or object true sends the review link to the customer's own contact info (email and/or mobile). An object sends to EXACTLY the channels provided — { email } never also texts the customer's phone on file, and an object without email or phone is a 400. Strictly validated — any value other than true, false, or an object is rejected with a 400 (a string like "false" will never trigger a send). |
{- "customerId": "string",
- "templateId": "string",
- "requestId": "string",
- "startDate": "2026-08-01",
- "paymentCollection": {
- "trigger": "billing_cycle",
- "method": "auto_card",
- "emailInvoiceToCustomer": false,
- "billInitialVisitOnCompletion": false
}, - "dayOfMonth": 1,
- "lineItems": [
- {
- "name": "string",
- "unitPrice": "120.00",
- "quantity": 1,
- "description": "string",
- "taxRate": {
- "name": "string",
- "rate": 100
}
}
], - "discount": {
- "type": "fixed",
- "value": 0,
- "appliesTo": "initial_and_recurring"
}, - "note": "string",
- "send": true
}{- "success": true,
- "data": {
- "subscriptionId": "string",
- "customerId": "string",
- "status": "draft",
- "emailSent": true,
- "smsSent": true,
- "alreadySent": true,
- "deduplicated": true,
- "sendInProgress": true
}
}Retrieves the permanent payment ledger of one subscription, newest
first. Invoice kind distinguishes scheduled bills ("bill"),
standalone technician tips ("tip"), and post-payment job balances
("balance"). Money fields ending in Cents are integer cents.
| subscriptionId required | string |
| limit | integer <= 100 Default: 25 |
| cursor | string Cursor token for pagination |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/subscriptions/sub_abc123/invoices?limit=25" \ -H "x-api-key: your-api-key-here" \ -H "Content-Type: application/json"
{- "success": true,
- "data": [
- {
- "invoiceId": "string",
- "subscriptionId": "string",
- "businessId": "string",
- "kind": "bill",
- "status": "open",
- "date": "2019-08-24T14:15:22Z",
- "amountCents": 0,
- "tipCents": 0,
- "refundAmountCents": 0,
- "refundTipAmountCents": 0,
- "currency": "usd",
- "taxCents": 0,
- "pricing": { },
- "customerId": "string",
- "planName": "string",
- "planDescription": "string",
- "paymentFrequency": "string",
- "occurrenceId": "string",
- "visitId": "string",
- "jobId": "string",
- "trigger": "billing_cycle",
- "method": "auto_card",
- "payment": {
- "billingType": "card",
- "otherOption": "string",
- "paymentNote": "string",
- "paidAt": "2019-08-24T14:15:22Z",
- "recordedBy": {
- "name": "string"
}
}, - "paymentIntentId": "string",
- "refunds": [
- {
- "refundId": "string",
- "status": "succeeded",
- "amountCents": 0,
- "tipCents": 0,
- "date": "2019-08-24T14:15:22Z"
}
], - "billingVersion": "v3",
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "nextPageToken": "string"
}Retrieves the business's subscription templates — the recurring-plan blueprints (cadence, term, pricing line items, optional linked agreement) that subscriptions are built from.
| templateId | string Fetch a specific template by ID |
| limit | integer <= 100 Default: 25 |
| cursor | string |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/subscription-templates" \ -H "x-api-key: your-api-key-here" \ -H "Content-Type: application/json"
{- "success": true,
- "data": [
- {
- "templateId": "string",
- "title": "Quarterly Pest Control",
- "description": "string",
- "type": "fixed",
- "duration": 0,
- "visits": 0,
- "paymentFrequency": "weekly",
- "amount": "120.00",
- "lineItems": [
- {
- "id": "string",
- "name": "string",
- "description": "string",
- "quantity": 0,
- "unitPrice": "120.00",
- "taxRate": { },
- "taxable": true
}
], - "initialPriceEnabled": true,
- "initialPrice": "string",
- "initialPriceTaxRate": { },
- "allowOtherPayments": true,
- "requireSignature": true,
- "terms": { },
- "agreementTemplateId": "string",
- "agreementTemplateName": "string",
- "attachments": [
- {
- "url": "string",
- "name": "string"
}
], - "createdAt": "2019-08-24T14:15:22Z",
- "lastUpdated": "2019-08-24T14:15:22Z"
}
], - "nextPageToken": "string"
}Creates a subscription template. When lineItems are provided the
display amount is derived from their subtotal (dashboard behavior);
linking an agreement via agreementTemplateId forces
requireSignature: true and clears inline terms.
| title required | string |
| description | string |
| type required | string Enum: "fixed" "indefinite" |
| duration | integer Term length in YEARS (required when type is "fixed") |
| visits required | integer [ 1 .. 12 ] Visits per year |
| paymentFrequency required | string Enum: "weekly" "bi-weekly" "monthly" "quarterly" "tri-annual" "6 months" "yearly" |
Array of objects | |
| amount | string Legacy flat amount — only used when no lineItems |
| initialPriceEnabled | boolean |
| initialPrice | string Required when initialPriceEnabled is true |
| initialPriceTaxRate | object or null The initial price's own tax — both name and a numeric 0-100 rate required; malformed values are rejected, never coerced to 0%. |
| allowOtherPayments | boolean Offer alternate payment methods at checkout on subscriptions built from this template |
| requireSignature | boolean Only takes effect when there is something to sign (inline terms or a linked agreement); normalized to false otherwise |
object Inline terms (ignored when an agreement is linked) | |
| agreementTemplateId | string Link an existing agreement (must be active and enabled for service subscriptions) |
{- "title": "string",
- "description": "string",
- "type": "fixed",
- "duration": 0,
- "visits": 1,
- "paymentFrequency": "weekly",
- "lineItems": [
- {
- "name": "string",
- "unitPrice": "string",
- "quantity": 0,
- "description": "string",
- "taxRate": { }
}
], - "amount": "string",
- "initialPriceEnabled": true,
- "initialPrice": "string",
- "initialPriceTaxRate": { },
- "allowOtherPayments": true,
- "requireSignature": true,
- "terms": {
- "title": "string",
- "text": "string"
}, - "agreementTemplateId": "string"
}{- "success": true,
- "data": {
- "templateId": "string",
- "title": "Quarterly Pest Control",
- "description": "string",
- "type": "fixed",
- "duration": 0,
- "visits": 0,
- "paymentFrequency": "weekly",
- "amount": "120.00",
- "lineItems": [
- {
- "id": "string",
- "name": "string",
- "description": "string",
- "quantity": 0,
- "unitPrice": "120.00",
- "taxRate": { },
- "taxable": true
}
], - "initialPriceEnabled": true,
- "initialPrice": "string",
- "initialPriceTaxRate": { },
- "allowOtherPayments": true,
- "requireSignature": true,
- "terms": { },
- "agreementTemplateId": "string",
- "agreementTemplateName": "string",
- "attachments": [
- {
- "url": "string",
- "name": "string"
}
], - "createdAt": "2019-08-24T14:15:22Z",
- "lastUpdated": "2019-08-24T14:15:22Z"
}
}Updates a subscription template. Only provided fields change;
amount re-derives when lineItems change. Existing subscriptions
keep their embedded template snapshot — edits only affect future
subscriptions built from the template.
| templateId required | string |
Any subset of the create-template fields
{ }{- "success": true,
- "data": {
- "templateId": "string",
- "title": "Quarterly Pest Control",
- "description": "string",
- "type": "fixed",
- "duration": 0,
- "visits": 0,
- "paymentFrequency": "weekly",
- "amount": "120.00",
- "lineItems": [
- {
- "id": "string",
- "name": "string",
- "description": "string",
- "quantity": 0,
- "unitPrice": "120.00",
- "taxRate": { },
- "taxable": true
}
], - "initialPriceEnabled": true,
- "initialPrice": "string",
- "initialPriceTaxRate": { },
- "allowOtherPayments": true,
- "requireSignature": true,
- "terms": { },
- "agreementTemplateId": "string",
- "agreementTemplateName": "string",
- "attachments": [
- {
- "url": "string",
- "name": "string"
}
], - "createdAt": "2019-08-24T14:15:22Z",
- "lastUpdated": "2019-08-24T14:15:22Z"
}
}Retrieves the business's agreements — the structured agreement
documents (sections, content bodies, branding) that subscription
templates link via agreementTemplateId. Read-only: agreements are
created and edited in the dashboard. Subscriptions embed a frozen
snapshot at creation, so agreement edits only affect future
subscriptions.
| agreementId | string |
| limit | integer <= 100 Default: 25 |
| cursor | string |
curl -X GET "https://us-central1-homebase-360.cloudfunctions.net/api/v1/agreements" \ -H "x-api-key: your-api-key-here" \ -H "Content-Type: application/json"
{- "success": true,
- "data": [
- {
- "agreementId": "string",
- "name": "Service Agreement",
- "active": true,
- "associatedRecordTypes": {
- "serviceSubscriptions": true,
- "jobs": true
}, - "includeDescriptionsInIncludedServices": true,
- "branding": {
- "logoUrl": "string",
- "logoSizePercent": 0,
- "accentColor": "#111827"
}, - "businessInfo": {
- "name": "string",
- "address": "string",
- "city": "string",
- "state": "string",
- "zipCode": "string",
- "country": "string",
- "phone": "string",
- "email": "string"
}, - "sectionOrder": [
- "string"
], - "sections": { },
- "content": {
- "termsConditions": "string",
- "paymentConsent": "string",
- "agreementConsent": "string",
- "additionalServices": "string",
- "guarantees": "string",
- "notes": "string"
}, - "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "nextPageToken": "string"
}Registers a URL to receive HTTP POST callbacks whenever the specified events occur on the authenticated business. Used by REST-Hook integrations such as Zapier.
Each subscription is scoped to the API key that created it: deactivating the API key auto-deletes its subscriptions. Subscriptions created through this endpoint do NOT appear in the Homebase360 dashboard webhook list (which is reserved for webhooks the user adds manually).
Common event names: customer.created, customer.details_changed,
job.created, job.details.line_items_changed, job.status_changed,
lead.created, lead.details_changed, lead.status_updated.
See the full list in webhooks/webhookEvents.js.
| url required | string <uri> Fully-qualified http(s) URL Homebase360 will POST to |
| events required | Array of strings One or more event names from the registry |
{- "events": [
- "customer.created",
- "customer.details_changed"
]
}{- "success": true,
- "data": {
- "endpointId": "ep_a1b2c3d4e5f6a1b2c3d4e5f6",
- "events": [
- "string"
], - "status": "active"
}
}Returns every webhook endpoint created via this API key, newest first. Endpoints created from the Homebase360 dashboard are NOT included. Signing secrets are never returned in this response.
{- "success": true,
- "data": [
- {
- "endpointId": "string",
- "url": "string",
- "events": [
- "string"
], - "status": "active",
- "createdAt": "2019-08-24T14:15:22Z",
- "lastSuccessAt": "2019-08-24T14:15:22Z",
- "lastFailedAt": "2019-08-24T14:15:22Z",
- "consecutiveFailures": 0
}
]
}Removes a subscription previously created with POST /webhooks.
Only subscriptions owned by the calling API key can be deleted;
attempting to delete an endpoint owned by another key (or one
created from the dashboard) returns 404.
| endpointId required | string Example: ep_a1b2c3d4e5f6a1b2c3d4e5f6 Opaque ID returned from the create call |
{- "success": true,
- "data": {
- "endpointId": "string",
- "deleted": true
}
}