REST API reference
Joist API
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
- Joist
| 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
readPOST https://cotlet.dev/api/v1/joist/list_estimates
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 | Description |
|---|---|---|
| search | string | Client name or estimate number. |
| limit | integer | 1 to 100. Default: 25. |
| cursor | string | Next page cursor from the previous call. |
Request
curl -X POST https://cotlet.dev/api/v1/joist/list_estimates \
-H "Authorization: Bearer $COTLET_API_KEY" result = client.call("joist", "list_estimates") const result = await cotlet.call('joist', 'list_estimates'); cotlet call joist list_estimates Example response
{
"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
readPOST https://cotlet.dev/api/v1/joist/list_invoices
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 | Description |
|---|---|---|
| view | enum | active (not paid in full) or paid. Default: active. |
| search | string | Client name or invoice number. |
| limit | integer | 1 to 100. Default: 25. |
| cursor | string | Next page cursor from the previous call. |
Request
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}' result = client.call("joist", "list_invoices", {"view":"active","limit":100}) const result = await cotlet.call('joist', 'list_invoices', {"view":"active","limit":100}); cotlet call joist list_invoices --data '{"view":"active","limit":100}' Example response
{
"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
} POST https://cotlet.dev/api/v1/joist/get_document
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 | Description |
|---|---|---|
| document_id required | string | Estimate or invoice ID from list_estimates or list_invoices. |
Request
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"}' result = client.call("joist", "get_document", {"document_id":"document_8c41d2"}) const result = await cotlet.call('joist', 'get_document', {"document_id":"document_8c41d2"}); cotlet call joist get_document --data '{"document_id":"document_8c41d2"}' Example response
{
"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
readPOST https://cotlet.dev/api/v1/joist/list_clients
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 | Description |
|---|---|---|
| search | string | Part of a name, email or phone. |
| limit | integer | 1 to 200. Default: 50. |
| cursor | string | Next page cursor from the previous call. |
Request
curl -X POST https://cotlet.dev/api/v1/joist/list_clients \
-H "Authorization: Bearer $COTLET_API_KEY" result = client.call("joist", "list_clients") const result = await cotlet.call('joist', 'list_clients'); cotlet call joist list_clients Example response
{
"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
readPOST https://cotlet.dev/api/v1/joist/list_items
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 | Description |
|---|---|---|
| search | string | Part of the item name. |
Request
curl -X POST https://cotlet.dev/api/v1/joist/list_items \
-H "Authorization: Bearer $COTLET_API_KEY" result = client.call("joist", "list_items") const result = await cotlet.call('joist', 'list_items'); cotlet call joist list_items Example response
{
"items": [
{
"item_id": "it_104",
"name": "Interior door, installed",
"description": "Supply and hang a pre-hung interior door",
"rate": 350
}
]
} Get report
readPOST https://cotlet.dev/api/v1/joist/get_report
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 | Description |
|---|---|---|
| report required | enum | revenue, invoices, estimates or sales_tax. |
| from required | date | Start of the range. |
| to required | date | End of the range. |
| client | string | Only this client, by name. |
Request
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"}' result = client.call("joist", "get_report", {"report":"revenue","from":"2026-10-01","to":"2026-10-01"}) const result = await cotlet.call('joist', 'get_report', {"report":"revenue","from":"2026-10-01","to":"2026-10-01"}); cotlet call joist get_report --data '{"report":"revenue","from":"2026-10-01","to":"2026-10-01"}' Example response
{
"report": "revenue",
"from": "2026-09-01",
"to": "2026-09-30",
"total_received": 8450,
"invoices_with_payments": 6
} Export documents
readPOST https://cotlet.dev/api/v1/joist/export_documents
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 | Description |
|---|---|---|
| type required | enum | estimates or invoices. |
| month required | string | Month as YYYY-MM. |
Request
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"}' result = client.call("joist", "export_documents", {"type":"estimates","month":"month"}) const result = await cotlet.call('joist', 'export_documents', {"type":"estimates","month":"month"}); cotlet call joist export_documents --data '{"type":"estimates","month":"month"}' Example response
{
"type": "invoices",
"month": "2026-09",
"rows": 14,
"download_url": "https://files.cotlet.example/joist-invoices-2026-09.csv"
} Add client
writePOST https://cotlet.dev/api/v1/joist/add_client
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 | Description |
|---|---|---|
| name required | string | Client or company name. |
| string | Used when you send documents. | |
| phone | string | Phone number. |
| address | string | Job or billing address. |
Request
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"}' result = client.call("joist", "add_client", {"name":"Jane Doe"}) const result = await cotlet.call('joist', 'add_client', {"name":"Jane Doe"}); cotlet call joist add_client --data '{"name":"Jane Doe"}' Example response
{
"client_id": "cl_3312",
"name": "Priya Nair",
"email": "priya@example.com"
} Add item
writePOST https://cotlet.dev/api/v1/joist/add_item
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 | Description |
|---|---|---|
| name required | string | Item name. |
| rate required | string | Price per unit, e.g. "350.00". |
| description | string | Shown on the line item. |
Request
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"}' result = client.call("joist", "add_item", {"name":"Jane Doe","rate":"350.00"}) const result = await cotlet.call('joist', 'add_item', {"name":"Jane Doe","rate":"350.00"}); cotlet call joist add_item --data '{"name":"Jane Doe","rate":"350.00"}' Example response
{
"item_id": "it_105",
"name": "Baseboard, installed",
"rate": 4.5
} Create estimate
writePOST https://cotlet.dev/api/v1/joist/create_estimate
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 | Description |
|---|---|---|
| client_id required | string | Client ID from list_clients or add_client. |
| line_items required | object[] | List of { name, description, quantity, rate }. |
| tax_rate | string | Tax percent applied to the estimate, e.g. "8.25". |
| discount | object | { type: percent or amount, value }. |
| deposit | object | Requested deposit: { type: percent or amount, value }. |
| expires_on | date | Expiry date. Must not be in the past. |
| notes | string | Note shown on the estimate. |
Request
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"}' 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"}) 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"}); 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
{
"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
} POST https://cotlet.dev/api/v1/joist/convert_estimate_to_invoice
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 | Description |
|---|---|---|
| estimate_id required | string | Estimate ID from list_estimates. |
Request
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"}' result = client.call("joist", "convert_estimate_to_invoice", {"estimate_id":"est_20418"}) const result = await cotlet.call('joist', 'convert_estimate_to_invoice', {"estimate_id":"est_20418"}); cotlet call joist convert_estimate_to_invoice --data '{"estimate_id":"est_20418"}' Example response
{
"invoice_id": "inv_58300",
"number": "1051",
"from_estimate": "est_20418",
"total": 700,
"deposit_due": 350,
"sent": false,
"estimate_status": "approved"
} POST https://cotlet.dev/api/v1/joist/record_payment
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 | Description |
|---|---|---|
| invoice_id required | string | Invoice ID from list_invoices. |
| amount required | string | Amount received, e.g. "1200.00". |
| method required | enum | cash, check, credit or bank_transfer. |
| paid_on | date | Payment date. Default: today. |
Request
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"}' result = client.call("joist", "record_payment", {"invoice_id":"inv_58213","amount":"1200.00","method":"check","paid_on":"2026-10-09"}) const result = await cotlet.call('joist', 'record_payment', {"invoice_id":"inv_58213","amount":"1200.00","method":"check","paid_on":"2026-10-09"}); cotlet call joist record_payment --data '{"invoice_id":"inv_58213","amount":"1200.00","method":"check","paid_on":"2026-10-09"}' Example response
{
"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"
}
]
} POST https://cotlet.dev/api/v1/joist/send_document
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 | Description |
|---|---|---|
| document_id required | string | Estimate or invoice ID from list_estimates or list_invoices. |
| to | string | Recipient email. Default: the client email on the document. |
| subject | string | Email subject. Default: the Joist subject. |
| message | string | Email body. Default: the Joist message. |
Request
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"}' result = client.call("joist", "send_document", {"document_id":"document_8c41d2"}) const result = await cotlet.call('joist', 'send_document', {"document_id":"document_8c41d2"}); cotlet call joist send_document --data '{"document_id":"document_8c41d2"}' Example response
{
"document_id": "inv_58300",
"to": [
"dana@example.com"
],
"status": "sent",
"sent_at": "2026-10-11T09:20:00+02:00"
}