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
Joist 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.

POST 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

Parameters: list_estimates
NameTypeDescription
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
curl -X POST https://cotlet.dev/api/v1/joist/list_estimates \
  -H "Authorization: Bearer $COTLET_API_KEY"
Python
result = client.call("joist", "list_estimates")
Node.js
const result = await cotlet.call('joist', 'list_estimates');
CLI
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
}

POST 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

Parameters: list_invoices
NameTypeDescription
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
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
result = client.call("joist", "list_invoices", {"view":"active","limit":100})
Node.js
const result = await cotlet.call('joist', 'list_invoices', {"view":"active","limit":100});
CLI
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

Parameters: get_document
NameTypeDescription
document_id required string Estimate or invoice ID from list_estimates or list_invoices.

Request

cURL
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
result = client.call("joist", "get_document", {"document_id":"document_8c41d2"})
Node.js
const result = await cotlet.call('joist', 'get_document', {"document_id":"document_8c41d2"});
CLI
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"
    }
  ]
}

POST 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

Parameters: list_clients
NameTypeDescription
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
curl -X POST https://cotlet.dev/api/v1/joist/list_clients \
  -H "Authorization: Bearer $COTLET_API_KEY"
Python
result = client.call("joist", "list_clients")
Node.js
const result = await cotlet.call('joist', 'list_clients');
CLI
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

read

POST 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

Parameters: list_items
NameTypeDescription
search string Part of the item name.

Request

cURL
curl -X POST https://cotlet.dev/api/v1/joist/list_items \
  -H "Authorization: Bearer $COTLET_API_KEY"
Python
result = client.call("joist", "list_items")
Node.js
const result = await cotlet.call('joist', 'list_items');
CLI
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

read

POST 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

Parameters: get_report
NameTypeDescription
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
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
result = client.call("joist", "get_report", {"report":"revenue","from":"2026-10-01","to":"2026-10-01"})
Node.js
const result = await cotlet.call('joist', 'get_report', {"report":"revenue","from":"2026-10-01","to":"2026-10-01"});
CLI
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
}

POST 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

Parameters: export_documents
NameTypeDescription
type required enum estimates or invoices.
month required string Month as YYYY-MM.

Request

cURL
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
result = client.call("joist", "export_documents", {"type":"estimates","month":"month"})
Node.js
const result = await cotlet.call('joist', 'export_documents', {"type":"estimates","month":"month"});
CLI
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

write

POST 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

Parameters: add_client
NameTypeDescription
name required string Client or company name.
email string Used when you send documents.
phone string Phone number.
address string Job or billing address.

Request

cURL
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
result = client.call("joist", "add_client", {"name":"Jane Doe"})
Node.js
const result = await cotlet.call('joist', 'add_client', {"name":"Jane Doe"});
CLI
cotlet call joist add_client --data '{"name":"Jane Doe"}'

Example response

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

Add item

write

POST 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

Parameters: add_item
NameTypeDescription
name required string Item name.
rate required string Price per unit, e.g. "350.00".
description string Shown on the line item.

Request

cURL
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
result = client.call("joist", "add_item", {"name":"Jane Doe","rate":"350.00"})
Node.js
const result = await cotlet.call('joist', 'add_item', {"name":"Jane Doe","rate":"350.00"});
CLI
cotlet call joist add_item --data '{"name":"Jane Doe","rate":"350.00"}'

Example response

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

POST 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

Parameters: create_estimate
NameTypeDescription
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
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
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
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
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

Parameters: convert_estimate_to_invoice
NameTypeDescription
estimate_id required string Estimate ID from list_estimates.

Request

cURL
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
result = client.call("joist", "convert_estimate_to_invoice", {"estimate_id":"est_20418"})
Node.js
const result = await cotlet.call('joist', 'convert_estimate_to_invoice', {"estimate_id":"est_20418"});
CLI
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"
}

Record payment

write Confirm

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

Parameters: record_payment
NameTypeDescription
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
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
result = client.call("joist", "record_payment", {"invoice_id":"inv_58213","amount":"1200.00","method":"check","paid_on":"2026-10-09"})
Node.js
const result = await cotlet.call('joist', 'record_payment', {"invoice_id":"inv_58213","amount":"1200.00","method":"check","paid_on":"2026-10-09"});
CLI
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"
    }
  ]
}

Send estimate or invoice

write Confirm

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

Parameters: send_document
NameTypeDescription
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
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
result = client.call("joist", "send_document", {"document_id":"document_8c41d2"})
Node.js
const result = await cotlet.call('joist', 'send_document', {"document_id":"document_8c41d2"});
CLI
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"
}