# Joist API

> Every Joist 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/joist

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)

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

- Base URL: `https://cotlet.dev/api/v1/joist`
- Connects with: Your own Joist account (email and password at pro.joistapp.com; Joist accounts have a single user)
- Integration page: https://cotlet.dev/integrations/joist-api-mcp-server

## Endpoints

| Endpoint | Access | Description |
|---|---|---|
| `list_estimates` | read | Estimates with number, client, total, expiry date, status, and whether the client opened the email or signed. |
| `list_invoices` | read | Invoices with number, client, total, amount paid, balance due and due date. Active or Paid. |
| `get_document` | read | Full estimate or invoice: client, line items, tax, discount, deposit and the payments recorded. |
| `list_clients` | read | Clients in your account with name, email, phone and address. |
| `list_items` | read | Saved items from your Items list, with name, description and rate, to reuse as line items. |
| `get_report` | read | A Joist report for a date range: revenue, invoices, estimates or sales tax. |
| `export_documents` | read | Joist's monthly CSV of estimates or invoices, as a download link. |
| `add_client` | write | New client in your Clients list, ready for estimates and invoices. |
| `add_item` | write | Save a service or material to your Items list. |
| `create_estimate` | write | New saved estimate with line items, optional deposit and expiry. Nothing is sent to the client. |
| `convert_estimate_to_invoice` | write | Generate an invoice with everything from the estimate. Not sent until you call send_document. |
| `record_payment` | write | Record a payment received outside Joist Payments; a fully paid invoice moves to Paid. |
| `send_document` | write | Email an estimate or invoice to the client from Joist. |

## List estimates

`POST https://cotlet.dev/api/v1/joist/list_estimates`

Access: read.

Lists the estimates in your Joist account, newest first. Each item shows the status (pending until approved), the expiry date if one is set, and the two markers Joist shows in its list: client opened the email, and signed. Pass estimate_id to convert_estimate_to_invoice or get_document.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `search` | string | no | Client name or estimate number. |
| `limit` | integer | no | 1 to 100. Default: 25. |
| `cursor` | string | no | Next page cursor from the previous call. |

### Request

**cURL**

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

**Python**

```python
result = client.call("joist", "list_estimates")
```

**Node.js**

```js
const result = await cotlet.call('joist', 'list_estimates');
```

**CLI**

```bash
cotlet call joist list_estimates
```

### Example response

```json
{
  "estimates": [
    {
      "estimate_id": "est_20418",
      "number": "318",
      "client": "Dana Lee",
      "total": 700,
      "status": "pending",
      "expires_on": "2026-10-25",
      "email_opened": true,
      "signed": false
    }
  ],
  "next_cursor": null
}
```

## List invoices

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

Access: read.

Lists invoices from the Active view (not paid in full) or the Paid view, newest first. Balance due is the total less recorded payments, so a partly paid invoice shows what is left. The due date is the creation date plus your Payment Terms, and overdue is true where Joist shows the Overdue badge. Each item shows whether the client opened the email and whether it is signed.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `view` | enum | no | active (not paid in full) or paid. Default: active. |
| `search` | string | no | Client name or invoice number. |
| `limit` | integer | no | 1 to 100. Default: 25. |
| `cursor` | string | no | Next page cursor from the previous call. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/joist/list_invoices \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"view":"active","limit":100}'
```

**Python**

```python
result = client.call("joist", "list_invoices", {"view":"active","limit":100})
```

**Node.js**

```js
const result = await cotlet.call('joist', 'list_invoices', {"view":"active","limit":100});
```

**CLI**

```bash
cotlet call joist list_invoices --data '{"view":"active","limit":100}'
```

### Example response

```json
{
  "invoices": [
    {
      "invoice_id": "inv_58213",
      "number": "1042",
      "client": "Mark Ruiz",
      "total": 2400,
      "amount_paid": 1200,
      "balance_due": 1200,
      "due_on": "2026-10-01",
      "overdue": true,
      "email_opened": true,
      "signed": true
    }
  ],
  "next_cursor": null
}
```

## Get estimate or invoice

`POST https://cotlet.dev/api/v1/joist/get_document`

Access: read.

Returns one estimate or invoice with its client, every line item, tax, discount, requested deposit and, for an invoice, the payments recorded on it. Use it before changing or sending a document so your agent sees what the client will see.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `document_id` | string | yes | Estimate or invoice ID from list_estimates or list_invoices. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/joist/get_document \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"document_id":"document_8c41d2"}'
```

**Python**

```python
result = client.call("joist", "get_document", {"document_id":"document_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('joist', 'get_document', {"document_id":"document_8c41d2"});
```

**CLI**

```bash
cotlet call joist get_document --data '{"document_id":"document_8c41d2"}'
```

### Example response

```json
{
  "document_id": "inv_58213",
  "type": "invoice",
  "number": "1042",
  "client": {
    "client_id": "cl_2210",
    "name": "Mark Ruiz"
  },
  "line_items": [
    {
      "name": "Kitchen cabinet install",
      "quantity": 1,
      "rate": 2400
    }
  ],
  "tax": 0,
  "discount": 0,
  "total": 2400,
  "payments": [
    {
      "amount": 1200,
      "method": "bank_transfer",
      "paid_on": "2026-09-25"
    }
  ]
}
```

## List clients

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

Access: read.

Lists the clients in your Joist account. Use client_id from an item in create_estimate. Search matches part of a name, email or phone.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `search` | string | no | Part of a name, email or phone. |
| `limit` | integer | no | 1 to 200. Default: 50. |
| `cursor` | string | no | Next page cursor from the previous call. |

### Request

**cURL**

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

**Python**

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

**Node.js**

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

**CLI**

```bash
cotlet call joist list_clients
```

### Example response

```json
{
  "clients": [
    {
      "client_id": "cl_3307",
      "name": "Dana Lee",
      "email": "dana@example.com",
      "phone": "+1 555 0100",
      "address": "14 Maple Street"
    }
  ],
  "next_cursor": null
}
```

## List items

`POST https://cotlet.dev/api/v1/joist/list_items`

Access: read.

Lists the items you saved in Joist (Joist does not ship with prices built in). Your agent reuses their names and rates on new estimates instead of guessing prices.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `search` | string | no | Part of the item name. |

### Request

**cURL**

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

**Python**

```python
result = client.call("joist", "list_items")
```

**Node.js**

```js
const result = await cotlet.call('joist', 'list_items');
```

**CLI**

```bash
cotlet call joist list_items
```

### Example response

```json
{
  "items": [
    {
      "item_id": "it_104",
      "name": "Interior door, installed",
      "description": "Supply and hang a pre-hung interior door",
      "rate": 350
    }
  ]
}
```

## Get report

`POST https://cotlet.dev/api/v1/joist/get_report`

Access: read.

Returns one of Joist's Reports for a date range, optionally for one client: revenue received (partial payments included), invoice balances and statuses, estimate volume and statuses, or sales tax collected by jurisdiction. Joist reports only include data entered after 1 September 2022. If your account has no Reports, the call returns the error the app shows.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `report` | enum | yes | revenue, invoices, estimates or sales_tax. |
| `from` | date | yes | Start of the range. |
| `to` | date | yes | End of the range. |
| `client` | string | no | Only this client, by name. |

### Request

**cURL**

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

**Python**

```python
result = client.call("joist", "get_report", {"report":"revenue","from":"2026-10-01","to":"2026-10-01"})
```

**Node.js**

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

**CLI**

```bash
cotlet call joist get_report --data '{"report":"revenue","from":"2026-10-01","to":"2026-10-01"}'
```

### Example response

```json
{
  "report": "revenue",
  "from": "2026-09-01",
  "to": "2026-09-30",
  "total_received": 8450,
  "invoices_with_payments": 6
}
```

## Export documents

`POST https://cotlet.dev/api/v1/joist/export_documents`

Access: read.

Runs the same export as Joist's website, for one month: number, client name, subtotal, tax, total, date issued, date created and payment received less refunds. Line items are not in the export; use get_document for those. Returns a download link and the row count.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `type` | enum | yes | estimates or invoices. |
| `month` | string | yes | Month as YYYY-MM. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/joist/export_documents \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"estimates","month":"month"}'
```

**Python**

```python
result = client.call("joist", "export_documents", {"type":"estimates","month":"month"})
```

**Node.js**

```js
const result = await cotlet.call('joist', 'export_documents', {"type":"estimates","month":"month"});
```

**CLI**

```bash
cotlet call joist export_documents --data '{"type":"estimates","month":"month"}'
```

### Example response

```json
{
  "type": "invoices",
  "month": "2026-09",
  "rows": 14,
  "download_url": "https://files.cotlet.example/joist-invoices-2026-09.csv"
}
```

## Add client

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

Access: write.

Creates a client in your Joist account. It reaches no one and nothing is sent, so it runs without asking; you can delete the client in Joist. Check list_clients first to avoid a duplicate.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Client or company name. |
| `email` | string | no | Used when you send documents. |
| `phone` | string | no | Phone number. |
| `address` | string | no | Job or billing address. |

### Request

**cURL**

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

**Python**

```python
result = client.call("joist", "add_client", {"name":"Jane Doe"})
```

**Node.js**

```js
const result = await cotlet.call('joist', 'add_client', {"name":"Jane Doe"});
```

**CLI**

```bash
cotlet call joist add_client --data '{"name":"Jane Doe"}'
```

### Example response

```json
{
  "client_id": "cl_3312",
  "name": "Priya Nair",
  "email": "priya@example.com"
}
```

## Add item

`POST https://cotlet.dev/api/v1/joist/add_item`

Access: write.

Saves an item with a name, rate and optional description to your Items list, so it can be reused on later estimates. It reaches no one, so it runs without asking.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Item name. |
| `rate` | string | yes | Price per unit, e.g. "350.00". |
| `description` | string | no | Shown on the line item. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/joist/add_item \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Jane Doe","rate":"350.00"}'
```

**Python**

```python
result = client.call("joist", "add_item", {"name":"Jane Doe","rate":"350.00"})
```

**Node.js**

```js
const result = await cotlet.call('joist', 'add_item', {"name":"Jane Doe","rate":"350.00"});
```

**CLI**

```bash
cotlet call joist add_item --data '{"name":"Jane Doe","rate":"350.00"}'
```

### Example response

```json
{
  "item_id": "it_105",
  "name": "Baseboard, installed",
  "rate": 4.5
}
```

## Create estimate

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

Access: write.

Creates and saves an estimate for a client. Nothing is sent: the client sees it only after send_document. A deposit is a percent or an amount and shows under the total as Deposit Due. The expiry date cannot be in the past. It reaches no one, so it runs without asking.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `client_id` | string | yes | Client ID from list_clients or add_client. |
| `line_items` | object[] | yes | List of { name, description, quantity, rate }. |
| `tax_rate` | string | no | Tax percent applied to the estimate, e.g. "8.25". |
| `discount` | object | no | { type: percent or amount, value }. |
| `deposit` | object | no | Requested deposit: { type: percent or amount, value }. |
| `expires_on` | date | no | Expiry date. Must not be in the past. |
| `notes` | string | no | Note shown on the estimate. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/joist/create_estimate \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"client_id":"cl_3307","line_items":[{"name":"Interior door, installed","description":"Supply and hang a pre-hung interior door","quantity":2,"rate":"350.00"}],"tax_rate":"8.25","deposit":{"type":"percent","value":50},"expires_on":"2026-10-26"}'
```

**Python**

```python
result = client.call("joist", "create_estimate", {"client_id":"cl_3307","line_items":[{"name":"Interior door, installed","description":"Supply and hang a pre-hung interior door","quantity":2,"rate":"350.00"}],"tax_rate":"8.25","deposit":{"type":"percent","value":50},"expires_on":"2026-10-26"})
```

**Node.js**

```js
const result = await cotlet.call('joist', 'create_estimate', {"client_id":"cl_3307","line_items":[{"name":"Interior door, installed","description":"Supply and hang a pre-hung interior door","quantity":2,"rate":"350.00"}],"tax_rate":"8.25","deposit":{"type":"percent","value":50},"expires_on":"2026-10-26"});
```

**CLI**

```bash
cotlet call joist create_estimate --data '{"client_id":"cl_3307","line_items":[{"name":"Interior door, installed","description":"Supply and hang a pre-hung interior door","quantity":2,"rate":"350.00"}],"tax_rate":"8.25","deposit":{"type":"percent","value":50},"expires_on":"2026-10-26"}'
```

### Example response

```json
{
  "estimate_id": "est_20418",
  "number": "318",
  "client": "Dana Lee",
  "line_items": [
    {
      "name": "Interior door, installed",
      "quantity": 2,
      "rate": 350,
      "amount": 700
    }
  ],
  "total": 700,
  "deposit_due": 350,
  "expires_on": "2026-10-25",
  "sent": false
}
```

## Convert estimate to invoice

`POST https://cotlet.dev/api/v1/joist/convert_estimate_to_invoice`

Access: write.

Does what Generate Invoice does in Joist: the new invoice carries all the information from the estimate. The estimate stays in your Estimates list and its status changes from pending to approved. Nothing is sent to the client, so it runs without asking.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `estimate_id` | string | yes | Estimate ID from list_estimates. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/joist/convert_estimate_to_invoice \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"estimate_id":"est_20418"}'
```

**Python**

```python
result = client.call("joist", "convert_estimate_to_invoice", {"estimate_id":"est_20418"})
```

**Node.js**

```js
const result = await cotlet.call('joist', 'convert_estimate_to_invoice', {"estimate_id":"est_20418"});
```

**CLI**

```bash
cotlet call joist convert_estimate_to_invoice --data '{"estimate_id":"est_20418"}'
```

### Example response

```json
{
  "invoice_id": "inv_58300",
  "number": "1051",
  "from_estimate": "est_20418",
  "total": 700,
  "deposit_due": 350,
  "sent": false,
  "estimate_status": "approved"
}
```

## Record payment

`POST https://cotlet.dev/api/v1/joist/record_payment`

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

Records a payment on an invoice, as Record Payment does in Joist. Partial payments are allowed, and an invoice can hold several. Once the invoice is paid in full it moves from Active to Paid. Payments made through Joist Payments are recorded by Joist itself. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `invoice_id` | string | yes | Invoice ID from list_invoices. |
| `amount` | string | yes | Amount received, e.g. "1200.00". |
| `method` | enum | yes | cash, check, credit or bank_transfer. |
| `paid_on` | date | no | Payment date. Default: today. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/joist/record_payment \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"invoice_id":"inv_58213","amount":"1200.00","method":"check","paid_on":"2026-10-09"}'
```

**Python**

```python
result = client.call("joist", "record_payment", {"invoice_id":"inv_58213","amount":"1200.00","method":"check","paid_on":"2026-10-09"})
```

**Node.js**

```js
const result = await cotlet.call('joist', 'record_payment', {"invoice_id":"inv_58213","amount":"1200.00","method":"check","paid_on":"2026-10-09"});
```

**CLI**

```bash
cotlet call joist record_payment --data '{"invoice_id":"inv_58213","amount":"1200.00","method":"check","paid_on":"2026-10-09"}'
```

### Example response

```json
{
  "invoice_id": "inv_58213",
  "number": "1042",
  "total": 2400,
  "amount_paid": 2400,
  "balance_due": 0,
  "list": "paid",
  "payments": [
    {
      "amount": 1200,
      "method": "bank_transfer",
      "paid_on": "2026-09-25"
    },
    {
      "amount": 1200,
      "method": "check",
      "paid_on": "2026-10-09"
    }
  ]
}
```

## Send estimate or invoice

`POST https://cotlet.dev/api/v1/joist/send_document`

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

Emails the document from Joist on your behalf, as the Email button does. The recipient is prefilled from the client on the document, and subject and body can be changed. The client sees it, and Joist marks it when they open the email or sign. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `document_id` | string | yes | Estimate or invoice ID from list_estimates or list_invoices. |
| `to` | string | no | Recipient email. Default: the client email on the document. |
| `subject` | string | no | Email subject. Default: the Joist subject. |
| `message` | string | no | Email body. Default: the Joist message. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/joist/send_document \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"document_id":"document_8c41d2"}'
```

**Python**

```python
result = client.call("joist", "send_document", {"document_id":"document_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('joist', 'send_document', {"document_id":"document_8c41d2"});
```

**CLI**

```bash
cotlet call joist send_document --data '{"document_id":"document_8c41d2"}'
```

### Example response

```json
{
  "document_id": "inv_58300",
  "to": [
    "dana@example.com"
  ],
  "status": "sent",
  "sent_at": "2026-10-11T09:20:00+02:00"
}
```
