# Thryv API

> Every Thryv endpoint on Cotlet: inputs, JSON Schema, cURL, Python and Node.js examples and an example response for each call.

Source: https://cotlet.dev/docs/api/thryv

Docs sections: [Get started](https://cotlet.dev/docs.md) · [REST API](https://cotlet.dev/docs/api.md) · [MCP](https://cotlet.dev/docs/mcp.md) · [SDKs & CLI](https://cotlet.dev/docs/sdks.md) · [Integrations](https://cotlet.dev/docs/integrations.md)

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

- Base URL: `https://cotlet.dev/api/v1/thryv`
- Connects with: Your own Thryv Business Center account (primary admin for full access)
- Integration page: https://cotlet.dev/integrations/thryv-api-mcp-server

## Endpoints

| Endpoint | Access | Description |
|---|---|---|
| `list_clients` | read | Clients with contact details and tags, newest first. |
| `get_client` | read | Client card: contact details, tags, notes, client details, appointments, invoices and message history. |
| `list_services` | read | Services from your services menu, with their saved prices. |
| `list_appointments` | read | Calendar appointments in a date range, with client, service, staff and price. |
| `list_invoices` | read | Invoices from the Sales tab with client, total, balance due and dates. |
| `list_conversations` | read | Inbox conversations with the latest messages, across email, text and other connected channels. |
| `list_reviews` | read | Reviews from the Reputation dashboard, from every connected site, with rating and whether you replied. |
| `add_client` | write | New client card. Nothing is sent to the client. |
| `book_appointment` | write | New calendar appointment. Thryv emails the client a booking confirmation. |
| `mark_no_show` | write | Mark an appointment as a no-show, before its end time. May send your missed appointment email. |
| `create_estimate` | write | Itemized estimate on the client card, saved or sent for approval in the client portal. |
| `create_invoice` | write | Invoice on the client card, saved to send later or sent with an online payment link. |
| `send_message` | write | Message one or more clients, the same as Send Message on your dashboard. |
| `reply_to_review` | write | Post a public reply to a review from the Reputation dashboard. |

## List clients

`POST https://cotlet.dev/api/v1/thryv/list_clients`

Access: read.

Lists the clients on your account, newest first. Filter by name, email or tag to find a group such as everyone tagged "spring". Pages hold up to 100 clients; pass next_cursor back as cursor for the next page.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `search` | string | no | Name or email. |
| `tag` | string | no | Only clients with this tag. |
| `limit` | integer | no | 1 to 100. Default: 50. |
| `cursor` | string | no | Next page cursor from the previous call. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/list_clients \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("thryv", "list_clients")
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'list_clients');
```

**CLI**

```bash
cotlet call thryv list_clients
```

### Example response

```json
{
  "clients": [
    {
      "client_id": "cl_8f2a91",
      "name": "Sample Client",
      "email": "client@example.com",
      "phone": "+1 555 0100",
      "tags": [
        "spring"
      ]
    },
    {
      "client_id": "cl_20b4d7",
      "name": "Jordan Hale",
      "email": "jordan@example.com",
      "phone": "+1 555 0101",
      "tags": [
        "spring",
        "lawn"
      ]
    }
  ],
  "next_cursor": null
}
```

## Get client

`POST https://cotlet.dev/api/v1/thryv/get_client`

Access: read.

Returns one client card with contact details, tags, internal notes, appointments, invoices and the message history. Use it before replying or invoicing so your agent sees everything about the client.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `client_id` | string | yes | Client ID from list_clients. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/get_client \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"client_id":"client_8c41d2"}'
```

**Python**

```python
result = client.call("thryv", "get_client", {"client_id":"client_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'get_client', {"client_id":"client_8c41d2"});
```

**CLI**

```bash
cotlet call thryv get_client --data '{"client_id":"client_8c41d2"}'
```

### Example response

```json
{
  "client_id": "cl_8f2a91",
  "name": "Sample Client",
  "email": "client@example.com",
  "tags": [
    "spring"
  ],
  "notes": "Prefers morning visits.",
  "appointments": [
    {
      "appointment_id": "ap_40b2d8",
      "service": "Lawn treatment",
      "start": "2026-09-29T09:00:00-05:00"
    }
  ],
  "invoices": [
    {
      "invoice_id": "inv_2041",
      "total": "85.00",
      "balance_due": "0.00"
    }
  ],
  "messages": 3
}
```

## List services

`POST https://cotlet.dev/api/v1/thryv/list_services`

Access: read.

Returns the services on your services menu with their saved prices. Use the name as the service parameter of book_appointment.

### Parameters

No parameters.

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/list_services \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("thryv", "list_services")
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'list_services');
```

**CLI**

```bash
cotlet call thryv list_services
```

### Example response

```json
{
  "services": [
    {
      "name": "Lawn treatment",
      "price": "85.00"
    },
    {
      "name": "Edging",
      "price": "25.00"
    }
  ]
}
```

## List appointments

`POST https://cotlet.dev/api/v1/thryv/list_appointments`

Access: read.

Lists calendar appointments between two dates, with client, service, staff and price. The range is at most 31 days. Filter by staff member to see one person's calendar.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `from` | date | yes | First day. |
| `to` | date | yes | Last day, at most 31 days after from. |
| `staff` | string | no | Only this staff member. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/list_appointments \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from":"2026-10-01","to":"2026-10-01"}'
```

**Python**

```python
result = client.call("thryv", "list_appointments", {"from":"2026-10-01","to":"2026-10-01"})
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'list_appointments', {"from":"2026-10-01","to":"2026-10-01"});
```

**CLI**

```bash
cotlet call thryv list_appointments --data '{"from":"2026-10-01","to":"2026-10-01"}'
```

### Example response

```json
{
  "appointments": [
    {
      "appointment_id": "ap_40b2d8",
      "client": "Sample Client",
      "client_id": "cl_8f2a91",
      "service": "Lawn treatment",
      "start": "2026-10-13T09:00:00-05:00",
      "end": "2026-10-13T10:00:00-05:00",
      "staff": "Alex",
      "price": "85.00"
    },
    {
      "appointment_id": "ap_40b2d9",
      "client": "Jordan Hale",
      "client_id": "cl_20b4d7",
      "service": "Edging",
      "start": "2026-10-13T11:00:00-05:00",
      "end": "2026-10-13T11:30:00-05:00",
      "staff": "Alex",
      "price": "25.00"
    }
  ]
}
```

## List invoices

`POST https://cotlet.dev/api/v1/thryv/list_invoices`

Access: read.

Lists invoices from the Sales tab with client, total, balance due and dates. Set unpaid_only to skip invoices paid in full. Pages hold up to 100 invoices.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `client_id` | string | no | Only this client. |
| `unpaid_only` | boolean | no | Skip invoices paid in full. |
| `limit` | integer | no | 1 to 100. Default: 50. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/list_invoices \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("thryv", "list_invoices")
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'list_invoices');
```

**CLI**

```bash
cotlet call thryv list_invoices
```

### Example response

```json
{
  "invoices": [
    {
      "invoice_id": "inv_2044",
      "client": "Jordan Hale",
      "client_id": "cl_20b4d7",
      "total": "110.00",
      "balance_due": "110.00",
      "issued": "2026-10-05",
      "due": "2026-10-19"
    }
  ]
}
```

## List conversations

`POST https://cotlet.dev/api/v1/thryv/list_conversations`

Access: read.

Lists inbox conversations with the latest messages, across email, text and the other channels you connected in Thryv. Filter by client to see one thread. Pages hold up to 50 conversations.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `client_id` | string | no | Only this client. |
| `limit` | integer | no | 1 to 50. Default: 20. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/list_conversations \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("thryv", "list_conversations")
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'list_conversations');
```

**CLI**

```bash
cotlet call thryv list_conversations
```

### Example response

```json
{
  "conversations": [
    {
      "conversation_id": "cv_77d2",
      "client": "Sample Client",
      "client_id": "cl_8f2a91",
      "channel": "text",
      "last_message": "Can we move Tuesday to 10:00?",
      "updated_at": "2026-10-10T16:02:00-05:00"
    }
  ]
}
```

## List reviews

`POST https://cotlet.dev/api/v1/thryv/list_reviews`

Access: read.

Lists reviews from the Reputation dashboard, from every site you connected, with rating, text and whether you replied. Use unanswered_only to see what needs a reply. Pass review_id to reply_to_review.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `unanswered_only` | boolean | no | Skip reviews you already answered. |
| `since` | date | no | Only reviews posted on or after this date. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/list_reviews \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("thryv", "list_reviews")
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'list_reviews');
```

**CLI**

```bash
cotlet call thryv list_reviews
```

### Example response

```json
{
  "reviews": [
    {
      "review_id": "rv_3b90c1",
      "site": "Google",
      "rating": 5,
      "author": "Dana K.",
      "text": "The lawn looks great.",
      "posted": "2026-10-08",
      "replied": false
    }
  ]
}
```

## Add client

`POST https://cotlet.dev/api/v1/thryv/add_client`

Access: write.

Creates a client card. Nothing is sent to the client, and the card can be edited or removed in Thryv, so the call skips confirmation. Only the first name is required.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `first_name` | string | yes | First name. |
| `last_name` | string | no | Last name. |
| `email` | string | no | Email address. |
| `phone` | string | no | Phone number. |
| `tags` | string[] | no | Tags to add. |
| `notes` | string | no | Internal notes, not shown to the client. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/add_client \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"first_name":"first name"}'
```

**Python**

```python
result = client.call("thryv", "add_client", {"first_name":"first name"})
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'add_client', {"first_name":"first name"});
```

**CLI**

```bash
cotlet call thryv add_client --data '{"first_name":"first name"}'
```

### Example response

```json
{
  "client_id": "cl_8f2a91",
  "name": "Sam Lee",
  "email": "client@example.com",
  "tags": [
    "spring"
  ],
  "status": "created"
}
```

## Book appointment

`POST https://cotlet.dev/api/v1/thryv/book_appointment`

Access: write. Confirm: waits for the user's OK before it runs.

Books an appointment on your calendar for a client. Thryv emails the client a booking confirmation, plus any extra recipients you add. End time defaults to the service length and staff to the primary admin. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `client_id` | string | yes | Client ID from list_clients. |
| `service` | string | yes | Service name from list_services, or a custom title. |
| `start` | datetime | yes | ISO 8601 with time zone. |
| `end` | datetime | no | End time. Default: the service length. |
| `staff` | string | no | Staff member. Default: the primary admin. |
| `price_type` | enum | no | fixed, custom or no_fee. Default: the service setting. |
| `price` | string | no | Price for a fixed-price appointment. |
| `additional_recipients` | string[] | no | More emails that get the booking email. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/book_appointment \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"client_id":"cl_8f2a91","service":"Lawn treatment","start":"2026-10-13T09:00:00-05:00","staff":"Alex","price_type":"fixed","price":"85.00"}'
```

**Python**

```python
result = client.call("thryv", "book_appointment", {"client_id":"cl_8f2a91","service":"Lawn treatment","start":"2026-10-13T09:00:00-05:00","staff":"Alex","price_type":"fixed","price":"85.00"})
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'book_appointment', {"client_id":"cl_8f2a91","service":"Lawn treatment","start":"2026-10-13T09:00:00-05:00","staff":"Alex","price_type":"fixed","price":"85.00"});
```

**CLI**

```bash
cotlet call thryv book_appointment --data '{"client_id":"cl_8f2a91","service":"Lawn treatment","start":"2026-10-13T09:00:00-05:00","staff":"Alex","price_type":"fixed","price":"85.00"}'
```

### Example response

```json
{
  "appointment_id": "ap_51c7e0",
  "client": "Sample Client",
  "service": "Lawn treatment",
  "start": "2026-10-13T09:00:00-05:00",
  "end": "2026-10-13T10:00:00-05:00",
  "staff": "Alex",
  "price": "85.00",
  "confirmation_sent_to": [
    "client@example.com"
  ]
}
```

## Mark no-show

`POST https://cotlet.dev/api/v1/thryv/mark_no_show`

Access: write. Confirm: waits for the user's OK before it runs.

Marks one appointment as a no-show, before its end time. If you set up a missed appointment email in Thryv, it may go out to the client. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `appointment_id` | string | yes | Appointment ID from list_appointments. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/mark_no_show \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"appointment_id":"appointment_8c41d2"}'
```

**Python**

```python
result = client.call("thryv", "mark_no_show", {"appointment_id":"appointment_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'mark_no_show', {"appointment_id":"appointment_8c41d2"});
```

**CLI**

```bash
cotlet call thryv mark_no_show --data '{"appointment_id":"appointment_8c41d2"}'
```

### Example response

```json
{
  "appointment_id": "ap_40b2d9",
  "status": "no_show"
}
```

## Create estimate

`POST https://cotlet.dev/api/v1/thryv/create_estimate`

Access: write. Confirm: waits for the user's OK before it runs.

Creates an itemized estimate on the client card. With send false it is only saved; with send true it goes to the client for approval in the client portal. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `client_id` | string | yes | Client ID from list_clients. |
| `line_items` | object[] | yes | List of items, each with description, quantity and unit_price. |
| `notes` | string | no | Notes shown on the estimate. |
| `send` | boolean | no | Send it to the client now. Default: false. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/create_estimate \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"client_id":"cl_8f2a91","line_items":[{"description":"Gutter cleaning","quantity":1,"unit_price":"140.00"},{"description":"Tree trimming","quantity":2,"unit_price":"95.00"}],"notes":"Based on your message of 10 Oct. Final price confirmed on site.","send":false}'
```

**Python**

```python
result = client.call("thryv", "create_estimate", {"client_id":"cl_8f2a91","line_items":[{"description":"Gutter cleaning","quantity":1,"unit_price":"140.00"},{"description":"Tree trimming","quantity":2,"unit_price":"95.00"}],"notes":"Based on your message of 10 Oct. Final price confirmed on site.","send":False})
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'create_estimate', {"client_id":"cl_8f2a91","line_items":[{"description":"Gutter cleaning","quantity":1,"unit_price":"140.00"},{"description":"Tree trimming","quantity":2,"unit_price":"95.00"}],"notes":"Based on your message of 10 Oct. Final price confirmed on site.","send":false});
```

**CLI**

```bash
cotlet call thryv create_estimate --data '{"client_id":"cl_8f2a91","line_items":[{"description":"Gutter cleaning","quantity":1,"unit_price":"140.00"},{"description":"Tree trimming","quantity":2,"unit_price":"95.00"}],"notes":"Based on your message of 10 Oct. Final price confirmed on site.","send":false}'
```

### Example response

```json
{
  "estimate_id": "est_1180",
  "client_id": "cl_8f2a91",
  "total": "110.00",
  "status": "saved"
}
```

## Create invoice

`POST https://cotlet.dev/api/v1/thryv/create_invoice`

Access: write. Confirm: waits for the user's OK before it runs.

Creates an invoice on the client card from line items. With send false it is saved to send later; with send true it goes to the client with an online payment link. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `client_id` | string | yes | Client ID from list_clients. |
| `line_items` | object[] | yes | List of items, each with description, quantity and unit_price. |
| `send` | boolean | no | Send it to the client now. Default: false. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/create_invoice \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"client_id":"client_8c41d2","line_items":["line items"]}'
```

**Python**

```python
result = client.call("thryv", "create_invoice", {"client_id":"client_8c41d2","line_items":["line items"]})
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'create_invoice', {"client_id":"client_8c41d2","line_items":["line items"]});
```

**CLI**

```bash
cotlet call thryv create_invoice --data '{"client_id":"client_8c41d2","line_items":["line items"]}'
```

### Example response

```json
{
  "invoice_id": "inv_2051",
  "client_id": "cl_8f2a91",
  "line_items": [
    {
      "description": "Lawn treatment",
      "quantity": 1,
      "unit_price": "85.00"
    },
    {
      "description": "Edging",
      "quantity": 1,
      "unit_price": "25.00"
    }
  ],
  "total": "110.00",
  "balance_due": "110.00",
  "status": "draft"
}
```

## Send message

`POST https://cotlet.dev/api/v1/thryv/send_message`

Access: write. Confirm: waits for the user's OK before it runs.

Sends a message to one or more clients, the same as Send Message on your dashboard. Subject and message text are required. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `client_ids` | string[] | yes | Client IDs from list_clients. |
| `subject` | string | yes | Subject line. |
| `message` | string | yes | Message text. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/send_message \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"client_ids":["client ids"],"subject":"subject","message":"message"}'
```

**Python**

```python
result = client.call("thryv", "send_message", {"client_ids":["client ids"],"subject":"subject","message":"message"})
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'send_message', {"client_ids":["client ids"],"subject":"subject","message":"message"});
```

**CLI**

```bash
cotlet call thryv send_message --data '{"client_ids":["client ids"],"subject":"subject","message":"message"}'
```

### Example response

```json
{
  "sent_to": [
    "cl_8f2a91",
    "cl_20b4d7"
  ],
  "subject": "Open slots next week",
  "status": "sent"
}
```

## Reply to review

`POST https://cotlet.dev/api/v1/thryv/reply_to_review`

Access: write. Confirm: waits for the user's OK before it runs.

Posts a public reply to one review from the Reputation dashboard. The reply is visible to anyone who reads the review. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `review_id` | string | yes | Review ID from list_reviews. |
| `text` | string | yes | Reply text. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/thryv/reply_to_review \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"review_id":"rv_3b90c1","text":"Thank you, Dana! Glad the lawn came back green after the fall treatment. See you in the spring."}'
```

**Python**

```python
result = client.call("thryv", "reply_to_review", {"review_id":"rv_3b90c1","text":"Thank you, Dana! Glad the lawn came back green after the fall treatment. See you in the spring."})
```

**Node.js**

```js
const result = await cotlet.call('thryv', 'reply_to_review', {"review_id":"rv_3b90c1","text":"Thank you, Dana! Glad the lawn came back green after the fall treatment. See you in the spring."});
```

**CLI**

```bash
cotlet call thryv reply_to_review --data '{"review_id":"rv_3b90c1","text":"Thank you, Dana! Glad the lawn came back green after the fall treatment. See you in the spring."}'
```

### Example response

```json
{
  "review_id": "rv_3b90c1",
  "status": "published"
}
```
