REST API reference

ZenMaid API

13 endpoints for ZenMaid. Each one is a POST with a JSON body, called with your API key.

Base URL
https://cotlet.dev/api/v1/zenmaid
Connects with
Your ZenMaid owner or office manager account
Integration page
ZenMaid
ZenMaid Endpoints
Endpoint Access Description
list_appointments read Appointments in a date range with customer, time, cleaners, price and paid flag.
get_appointment read Full appointment: address, service type, frequency, work items, billing, notes and cleaners.
list_contacts read Contacts with email, phone, address and status.
get_contact read One contact with notes, custom fields, and past and upcoming appointments.
list_cleaners read Active cleaners and teams with their IDs.
create_contact write Add a new contact. ZenMaid starts it as a lead.
create_appointment write Schedule a one-time or recurring appointment for a contact.
reschedule_appointment write Move one appointment to a new date or time.
assign_cleaners write Set the cleaners or team on one appointment, replacing the current ones.
add_note write Add a note to an appointment or to a contact.
cancel_appointment write Set an appointment to Cancelled, optionally notifying the client and cleaner.
mark_appointment_paid write Record an offline payment such as cash, Venmo or PayPal. Does not charge a card.
send_message write Send a customer an email, text or both from one of your communication templates.

POST https://cotlet.dev/api/v1/zenmaid/list_appointments

Returns appointments between two dates, in date and time order. An appointment with nobody assigned has an empty cleaners list. Filter by payment status to find unpaid jobs, or search by customer name, cleaner name, street address or a custom field, as ZenMaid's List View does. Cancelled appointments are included only when you ask for them.

Parameters

Parameters: list_appointments
NameTypeDescription
from required date First day of the range.
to required date Last day of the range.
query string Customer name, cleaner name, street address or custom field value.
paid enum all, paid or unpaid. Default: all.
include_cancelled boolean Also return cancelled appointments. Default: false.
limit integer 1 to 100. Default: 50.
cursor string Next page cursor from the previous call.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/list_appointments \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from":"2026-10-01","to":"2026-10-01"}'
Python
result = client.call("zenmaid", "list_appointments", {"from":"2026-10-01","to":"2026-10-01"})
Node.js
const result = await cotlet.call('zenmaid', 'list_appointments', {"from":"2026-10-01","to":"2026-10-01"});
CLI
cotlet call zenmaid list_appointments --data '{"from":"2026-10-01","to":"2026-10-01"}'

Example response

{
  "appointments": [
    {
      "appointment_id": "a_993102",
      "contact": "Sample Customer",
      "date": "2026-10-16",
      "start_time": "09:00",
      "end_time": "12:00",
      "cleaners": [],
      "price": "180.00",
      "paid": false
    },
    {
      "appointment_id": "a_993140",
      "contact": "Sample Household",
      "date": "2026-10-16",
      "start_time": "13:00",
      "end_time": "15:00",
      "cleaners": [
        "Cleaner A"
      ],
      "price": "120.00",
      "paid": false
    }
  ],
  "next_cursor": null
}

POST https://cotlet.dev/api/v1/zenmaid/get_appointment

Returns one appointment with the service address, service type, whether it belongs to a recurring service, work items, billing (price before tax, tip, paid flag) and its appointment and customer notes. Use it before changing or cancelling an appointment.

Parameters

Parameters: get_appointment
NameTypeDescription
appointment_id required string Appointment ID from list_appointments.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/get_appointment \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"appointment_id":"appointment_8c41d2"}'
Python
result = client.call("zenmaid", "get_appointment", {"appointment_id":"appointment_8c41d2"})
Node.js
const result = await cotlet.call('zenmaid', 'get_appointment', {"appointment_id":"appointment_8c41d2"});
CLI
cotlet call zenmaid get_appointment --data '{"appointment_id":"appointment_8c41d2"}'

Example response

{
  "appointment_id": "a_993102",
  "contact": {
    "contact_id": "c_48213",
    "name": "Sample Customer"
  },
  "address": "12 Sample Street, Springfield",
  "date": "2026-10-16",
  "start_time": "09:00",
  "end_time": "12:00",
  "service_type": "Deep Clean",
  "recurring": false,
  "cleaners": [],
  "work_items": [
    "Kitchen",
    "Bathrooms"
  ],
  "billing": {
    "price": "180.00",
    "tip": "0.00",
    "paid": false
  },
  "notes": [
    {
      "visibility": "public",
      "body": "Gate code is on the contact."
    }
  ]
}

POST https://cotlet.dev/api/v1/zenmaid/list_contacts

Searches contacts by name, email, phone or address. Status is one of ZenMaid's four: lead, one_time_customer, recurring_customer or former_customer. ZenMaid sets it from the appointments on the calendar, so a new contact stays a lead until someone books them.

Parameters

Parameters: list_contacts
NameTypeDescription
query string Name, email, phone or address to search.
status enum lead, one_time_customer, recurring_customer or former_customer.
limit integer 1 to 100. Default: 50.
cursor string Next page cursor from the previous call.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/list_contacts \
  -H "Authorization: Bearer $COTLET_API_KEY"
Python
result = client.call("zenmaid", "list_contacts")
Node.js
const result = await cotlet.call('zenmaid', 'list_contacts');
CLI
cotlet call zenmaid list_contacts

Example response

{
  "contacts": [
    {
      "contact_id": "c_48213",
      "name": "Sample Customer",
      "email": "customer@example.com",
      "phone": "+1 555 0100",
      "address": "12 Sample Street, Springfield",
      "status": "lead"
    }
  ],
  "next_cursor": null
}

POST https://cotlet.dev/api/v1/zenmaid/get_contact

Returns one contact profile: addresses, customer notes (public and private), contact custom fields, revenue to date, unpaid balance and a list of their appointments, newest first. Private notes are returned only to owner and office manager accounts.

Parameters

Parameters: get_contact
NameTypeDescription
contact_id required string Contact ID from list_contacts.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/get_contact \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contact_id":"contact_8c41d2"}'
Python
result = client.call("zenmaid", "get_contact", {"contact_id":"contact_8c41d2"})
Node.js
const result = await cotlet.call('zenmaid', 'get_contact', {"contact_id":"contact_8c41d2"});
CLI
cotlet call zenmaid get_contact --data '{"contact_id":"contact_8c41d2"}'

Example response

{
  "contact_id": "c_48213",
  "name": "Sample Customer",
  "email": "customer@example.com",
  "phone": "+1 555 0100",
  "status": "one_time_customer",
  "revenue_to_date": "180.00",
  "unpaid_balance": "0.00",
  "notes": [
    {
      "visibility": "private",
      "body": "Prefers morning visits."
    }
  ],
  "appointments": [
    {
      "appointment_id": "a_993102",
      "date": "2026-10-16",
      "paid": false
    }
  ]
}

POST https://cotlet.dev/api/v1/zenmaid/list_cleaners

Returns the cleaners and teams in your account with their IDs, which assign_cleaners and create_appointment need. Inactive staff are left out unless you ask for them.

Parameters

Parameters: list_cleaners
NameTypeDescription
include_inactive boolean Also list inactive staff. Default: false.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/list_cleaners \
  -H "Authorization: Bearer $COTLET_API_KEY"
Python
result = client.call("zenmaid", "list_cleaners")
Node.js
const result = await cotlet.call('zenmaid', 'list_cleaners');
CLI
cotlet call zenmaid list_cleaners

Example response

{
  "cleaners": [
    {
      "cleaner_id": "cl_1207",
      "name": "Cleaner A",
      "type": "cleaner",
      "active": true
    },
    {
      "cleaner_id": "cl_1344",
      "name": "Cleaner B",
      "type": "cleaner",
      "active": true
    },
    {
      "cleaner_id": "tm_21",
      "name": "Team One",
      "type": "team",
      "active": true
    }
  ]
}

POST https://cotlet.dev/api/v1/zenmaid/create_contact

Adds a contact profile. ZenMaid starts a new contact as a Lead until an appointment is booked. It reaches no one and is undone in one call, so it does not ask for your OK.

Parameters

Parameters: create_contact
NameTypeDescription
first_name required string First name.
last_name string Last name.
email string Email address.
phone string Phone number.
address string Service address.
notes string Customer note, public by default.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/create_contact \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"first_name":"first name"}'
Python
result = client.call("zenmaid", "create_contact", {"first_name":"first name"})
Node.js
const result = await cotlet.call('zenmaid', 'create_contact', {"first_name":"first name"});
CLI
cotlet call zenmaid create_contact --data '{"first_name":"first name"}'

Example response

{
  "contact_id": "c_48290",
  "name": "Sample Lead",
  "email": "lead@example.com",
  "phone": "+1 555 0111",
  "status": "lead"
}

Create appointment

write Confirm

POST https://cotlet.dev/api/v1/zenmaid/create_appointment

Books an appointment for an existing contact with a start and end time. Leave cleaner_ids empty to keep it unassigned, and price empty to let ZenMaid calculate it if your account is set up for that. A recurring frequency creates a recurring service. The contact becomes a customer once the appointment is on the calendar. By default you confirm each call; you can switch that off for this tool.

Parameters

Parameters: create_appointment
NameTypeDescription
contact_id required string Contact ID from list_contacts.
date required date Day of the appointment.
start_time required string Start time, HH:MM.
end_time required string End time, HH:MM.
service_type string Service type as set up in your account.
frequency string one_time or a recurring frequency from your account. Default: one_time.
cleaner_ids string[] Cleaner or team IDs from list_cleaners. Empty leaves it unassigned.
price string Price before tax. Empty lets ZenMaid calculate it if configured.
notes string Appointment note.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/create_appointment \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contact_id":"c_48213","date":"2026-10-16","start_time":"09:00","end_time":"12:00","service_type":"Deep Clean","frequency":"one_time","cleaner_ids":["cl_1207"]}'
Python
result = client.call("zenmaid", "create_appointment", {"contact_id":"c_48213","date":"2026-10-16","start_time":"09:00","end_time":"12:00","service_type":"Deep Clean","frequency":"one_time","cleaner_ids":["cl_1207"]})
Node.js
const result = await cotlet.call('zenmaid', 'create_appointment', {"contact_id":"c_48213","date":"2026-10-16","start_time":"09:00","end_time":"12:00","service_type":"Deep Clean","frequency":"one_time","cleaner_ids":["cl_1207"]});
CLI
cotlet call zenmaid create_appointment --data '{"contact_id":"c_48213","date":"2026-10-16","start_time":"09:00","end_time":"12:00","service_type":"Deep Clean","frequency":"one_time","cleaner_ids":["cl_1207"]}'

Example response

{
  "appointment_id": "a_993102",
  "contact": "Sample Customer",
  "date": "2026-10-16",
  "start_time": "09:00",
  "end_time": "12:00",
  "service_type": "Deep Clean",
  "cleaners": [
    "Cleaner A"
  ],
  "price": "180.00",
  "paid": false
}

Reschedule appointment

write Confirm

POST https://cotlet.dev/api/v1/zenmaid/reschedule_appointment

Moves one appointment, as the Reschedule window does in ZenMaid. It keeps work items, price and status. The length stays the same unless you pass end_time. One-time appointment notes do not carry over, so add them again with add_note. For a recurring appointment, apply_to chooses this one or all future appointments. By default you confirm each call; you can switch that off for this tool.

Parameters

Parameters: reschedule_appointment
NameTypeDescription
appointment_id required string Appointment ID from list_appointments.
date required date New day.
start_time required string New start time, HH:MM.
end_time string New end time, HH:MM. Default: keeps the length.
recalculate_price boolean Recalculate the amount owed for the new length. Default: false.
apply_to enum this_appointment or all_future, for recurring appointments. Default: this_appointment.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/reschedule_appointment \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"appointment_id":"a_993102","date":"2026-10-20","start_time":"09:00","apply_to":"this_appointment"}'
Python
result = client.call("zenmaid", "reschedule_appointment", {"appointment_id":"a_993102","date":"2026-10-20","start_time":"09:00","apply_to":"this_appointment"})
Node.js
const result = await cotlet.call('zenmaid', 'reschedule_appointment', {"appointment_id":"a_993102","date":"2026-10-20","start_time":"09:00","apply_to":"this_appointment"});
CLI
cotlet call zenmaid reschedule_appointment --data '{"appointment_id":"a_993102","date":"2026-10-20","start_time":"09:00","apply_to":"this_appointment"}'

Example response

{
  "appointment_id": "a_993102",
  "date": "2026-10-19",
  "start_time": "10:00",
  "end_time": "13:00",
  "price": "180.00",
  "price_recalculated": false
}

POST https://cotlet.dev/api/v1/zenmaid/assign_cleaners

Replaces the cleaners on one appointment. It does not deactivate a cleaner and does not touch the default cleaner of a recurring service. One more call undoes it, so it does not ask for your OK.

Parameters

Parameters: assign_cleaners
NameTypeDescription
appointment_id required string Appointment ID from list_appointments.
cleaner_ids required string[] Cleaner or team IDs from list_cleaners.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/assign_cleaners \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"appointment_id":"a_993102","cleaner_ids":["cl_1344"]}'
Python
result = client.call("zenmaid", "assign_cleaners", {"appointment_id":"a_993102","cleaner_ids":["cl_1344"]})
Node.js
const result = await cotlet.call('zenmaid', 'assign_cleaners', {"appointment_id":"a_993102","cleaner_ids":["cl_1344"]});
CLI
cotlet call zenmaid assign_cleaners --data '{"appointment_id":"a_993102","cleaner_ids":["cl_1344"]}'

Example response

{
  "appointment_id": "a_993102",
  "cleaners": [
    "Cleaner B"
  ]
}

Add note

write

POST https://cotlet.dev/api/v1/zenmaid/add_note

Adds an Appointment Note or a Customer Note, as in ZenMaid. A public note goes on the work orders sent to cleaners; a private note is visible only to owners and office managers. It reaches no one and is undone by editing the note, so it does not ask for your OK.

Parameters

Parameters: add_note
NameTypeDescription
appointment_id string Appointment to note. Give this or contact_id.
contact_id string Contact to note.
body required string Note text.
visibility enum public or private. Default: public.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/add_note \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body":"body"}'
Python
result = client.call("zenmaid", "add_note", {"body":"body"})
Node.js
const result = await cotlet.call('zenmaid', 'add_note', {"body":"body"});
CLI
cotlet call zenmaid add_note --data '{"body":"body"}'

Example response

{
  "note_id": "n_5521",
  "appointment_id": "a_993102",
  "visibility": "public",
  "body": "Moved one day because of rain."
}

Cancel appointment

write Confirm

POST https://cotlet.dev/api/v1/zenmaid/cancel_appointment

Sets the status to Cancelled and keeps the record; it never deletes. notify takes none, client, cleaner or both, the same choices ZenMaid offers when you cancel. A cancelled appointment may be hidden by the calendar filters. By default you confirm each call; you can switch that off for this tool.

Parameters

Parameters: cancel_appointment
NameTypeDescription
appointment_id required string Appointment ID from list_appointments.
notify enum none, client, cleaner or both. Default: none.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/cancel_appointment \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"appointment_id":"appointment_8c41d2"}'
Python
result = client.call("zenmaid", "cancel_appointment", {"appointment_id":"appointment_8c41d2"})
Node.js
const result = await cotlet.call('zenmaid', 'cancel_appointment', {"appointment_id":"appointment_8c41d2"});
CLI
cotlet call zenmaid cancel_appointment --data '{"appointment_id":"appointment_8c41d2"}'

Example response

{
  "appointment_id": "a_993102",
  "status": "Cancelled",
  "notified": [
    "client",
    "cleaner"
  ]
}

Mark appointment paid

write Confirm

POST https://cotlet.dev/api/v1/zenmaid/mark_appointment_paid

Marks one appointment as paid, like the Mark as paid button in its Billing section. It never charges the card on file. Appointments paid by card cannot be changed this way. The customer's Revenue To Date and Unpaid Balance update. By default you confirm each call; you can switch that off for this tool.

Parameters

Parameters: mark_appointment_paid
NameTypeDescription
appointment_id required string Appointment ID from list_appointments.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/mark_appointment_paid \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"appointment_id":"a_991870"}'
Python
result = client.call("zenmaid", "mark_appointment_paid", {"appointment_id":"a_991870"})
Node.js
const result = await cotlet.call('zenmaid', 'mark_appointment_paid', {"appointment_id":"a_991870"});
CLI
cotlet call zenmaid mark_appointment_paid --data '{"appointment_id":"a_991870"}'

Example response

{
  "appointment_id": "a_991870",
  "paid": true,
  "total": "120.00"
}

Send message

write Confirm

POST https://cotlet.dev/api/v1/zenmaid/send_message

Sends from a template under Type of communication, as the Send Text/Email action does. You can replace the template text with your own. ZenMaid only sends if the contact has an email address or phone number on file, and manual texts count toward your SMS usage. By default you confirm each call; you can switch that off for this tool.

Parameters

Parameters: send_message
NameTypeDescription
appointment_id string Appointment the message is about. Give this or contact_id.
contact_id string Contact to message.
template required string Communication template name.
channels enum email, sms or both. Default: email.
body string Replace the template text with your own.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/zenmaid/send_message \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template":"template"}'
Python
result = client.call("zenmaid", "send_message", {"template":"template"})
Node.js
const result = await cotlet.call('zenmaid', 'send_message', {"template":"template"});
CLI
cotlet call zenmaid send_message --data '{"template":"template"}'

Example response

{
  "appointment_id": "a_993102",
  "template": "Appointment Reminder",
  "channels": [
    "email",
    "sms"
  ],
  "sent": true
}