Developers
InvoiceTools API reference
A JSON REST API for your invoices, quotes, payments and clients. Create documents, send them to clients, record payments and read your data from your own code, then use webhooks to hear about changes as they happen. Base URL: https://invoicetools.com/api/v1. Available on the Business plan. Prefer no code? The Zapier integration and the integrations directory connect InvoiceTools to your other apps.
Jump to: authentication, pagination, rate limits, errors, endpoints, webhook events.
Authentication
The API is on the Business plan. Create a key in Account, Apps and API. A key is shown once, can be revoked at any time, and only ever sees its own account. Send it as a bearer token:
curl https://invoicetools.com/api/v1/me \
-H "Authorization: Bearer itk_live_your_key"You can send X-API-Key: itk_live_your_key instead. Keep keys on your server. Never put one in a web page or an app other people can open. Revoking a key also removes the Zapier subscriptions made with it.
Base URL, requests and responses
Base URL: https://invoicetools.com/api/v1. All responses are JSON over HTTPS. Send Content-Type: application/json with a JSON object body on POST requests. Money amounts are numbers in the document currency (for example 220 or 49.5), dates are YYYY-MM-DD, and timestamps are ISO 8601. Every response carries an X-Request-Id header. Quote it when you write to support.
Because a key must stay private, call the API from a server and not from a browser page.
Pagination
List endpoints return newest first in this shape:
{
"data": [
"..."
],
"has_more": true,
"next_cursor": "2026-10-01T09:30:00+00:00"
}limit is 1 to 100 (default 25). When has_more is true, pass next_cursor back as before to get the next page. When it is false, next_cursor is null.
Rate limits
120 requests a minute per key. Authenticated responses include these headers:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per minute (120). |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Seconds until the window resets. |
Retry-After | Only on 429: seconds to wait before retrying. |
Past the limit you get 429 rate_limited. Back off until Retry-After has passed. Requests rejected with 401 or 403 are not counted and carry no rate limit headers.
Idempotency and retries
Send an optional external_id in the body of POST /invoices, POST /quotes, POST /clients and POST /invoices/:id/payments, or send the same value as an Idempotency-Key header. Use an id from your own system, such as a spreadsheet row id, a form response id or a bank transaction id. It must be unique per endpoint and account, up to 200 characters.
- First request: the record is created and the response is
201, as usual. - Exact retry (same
external_id, same details): no second record is made. You get the record that already exists, with status200and"idempotent_replay": true. - Same
external_idwith different details:409 idempotency_conflict. Use a newexternal_idfor a new record. - A retry that arrives while the first request is still running:
409 request_in_progress. Wait a few seconds (seeRetry-After) and send it again. - A request that fails with an error does not keep its
external_id, so you can fix it and send it again.
Without external_id or Idempotency-Key, a retried POST can create a second record. You can still send your own number on invoices and quotes: a number already used returns 409 duplicate_number. GET and DELETE requests can be repeated freely. The Zapier app exposes this as the optional External ID field on its create actions.
Errors
Errors are JSON with a stable error code and a readable message:
{
"error": "bad_line",
"message": "Line 1: unit_price must be a number, 0 or more."
}| Status | Meaning | Codes |
|---|---|---|
| 400 | The request is wrong. | bad_json, bad_limit, bad_cursor, bad_status, bad_number, bad_event, bad_url, missing_fields, name_required, bad_email, client_required, client_name_required, bad_client_id, line_items_required, too_many_lines, bad_line, bad_date, bad_currency, bad_amount, amount_exceeds_balance, bad_method, bad_document_id, bad_reference, nothing_to_update, bad_external_id, idempotency_key_mismatch, invoice_void, already_paid, already_void, no_recipient, message_too_long |
| 401 | Missing, wrong or revoked key. | invalid_key |
| 403 | The key is valid but the plan or business does not allow it. | business_plan_required, business_readonly |
| 404 | No such route or record. | not_found, client_not_found |
| 405 | Wrong method for that route. | method_not_allowed |
| 409 | Conflict. | duplicate_number, duplicate_email, document_locked, idempotency_conflict, request_in_progress, record_gone |
| 422 | The request is valid but the address cannot be used. | recipient_suppressed |
| 429 | Too many requests, too many hook subscriptions, or the daily email limit. | rate_limited, too_many_subscriptions, daily_limit |
| 502 | Something failed on our side. Safe to retry after a moment, except send_failed, which means no email went out. | upstream_error, send_failed |
Endpoint reference
In the examples, itk_live_your_key stands for your own key. Sample data is made up.
Account
GET /me
The account the key belongs to. A cheap way to test a key.
Example request
curl https://invoicetools.com/api/v1/me \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"id": "5c8e1a3d-7f20-4b69-a1d4-0e93b6c2f758",
"email": "owner@example.invalid",
"business_name": "Harbor Studio"
}GET /events
The event names you can subscribe to with webhooks or Zapier hooks.
Example request
curl https://invoicetools.com/api/v1/events \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"data": [
"invoice.sent",
"invoice.viewed",
"invoice.paid",
"invoice.partially_paid",
"invoice.overdue",
"quote.sent",
"quote.viewed",
"quote.accepted",
"quote.declined",
"payment.received",
"payment.refunded"
]
}Invoices
Document fields: id, kind, number, status (draft, sent, paid, overdue, accepted, declined, void), client_id, client_name, currency, total, amount_due (invoices only, otherwise null), issue_date, due_date, valid_until (quotes only), share_url, created_at, updated_at, sent_at, viewed_at, line_items (each with description, quantity, unit_price, tax_percent, discount_percent and the worked-out amount), notes, terms.
GET /invoices
Your invoices, newest first. Archived invoices are left out.
Query parameters
| Name | Type | Notes |
|---|---|---|
status | string | A status word such as draft, sent, paid or overdue. |
number | string | Exact match on the printed number, up to 40 characters. |
limit | integer | 1 to 100, default 25. |
before | string | Cursor. Pass the previous next_cursor to get the next page. |
Example request
curl "https://invoicetools.com/api/v1/invoices?status=sent&limit=10" \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"data": [
{
"id": "7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10",
"kind": "invoice",
"number": "INV-1042",
"status": "sent",
"client_id": "3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"client_name": "Northwind Supply",
"currency": "USD",
"total": 220,
"amount_due": 220,
"issue_date": "2026-10-16",
"due_date": "2026-11-15",
"share_url": "https://invoicetools.com/q/?t=Zk3p9QxExample",
"created_at": "2026-10-16T09:12:44+00:00",
"updated_at": "2026-10-16T09:20:05+00:00",
"sent_at": "2026-10-16T09:20:05+00:00",
"viewed_at": null
}
],
"has_more": false,
"next_cursor": null
}GET /invoices/:id
One invoice by its id (a UUID). Returns 404 not_found if it is not yours.
Example request
curl https://invoicetools.com/api/v1/invoices/7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10 \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"id": "7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10",
"kind": "invoice",
"number": "INV-1042",
"status": "sent",
"client_id": "3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"client_name": "Northwind Supply",
"currency": "USD",
"total": 220,
"amount_due": 220,
"issue_date": "2026-10-16",
"due_date": "2026-11-15",
"share_url": "https://invoicetools.com/q/?t=Zk3p9QxExample",
"created_at": "2026-10-16T09:12:44+00:00",
"updated_at": "2026-10-16T09:20:05+00:00",
"sent_at": "2026-10-16T09:20:05+00:00",
"viewed_at": null
}POST /invoices
Creates a draft invoice, saved the way the editor saves it, so it opens in the editor and the dashboard. New documents are drafts and are never emailed. Sending is done from InvoiceTools.
Body fields
| Field | Type | Notes |
|---|---|---|
client_id | string (UUID) | The id of one of your clients. Or send client_name and client_email (below). A client object with the same keys also works. |
client_name | string | Needed when the client is not found by id or email. Without an email, a client with exactly this name is reused. |
client_email | string | A client with this email is reused, otherwise a new client is added. Ignores capitals. |
client_phone, client_address | string | Saved on a newly added client. |
line_items | array | Required. 1 to 100 rows, each an object: description (string, required), unit_price (number, 0 or more, required), quantity (number above 0, default 1), tax_percent (0 to 100, default 0), discount_percent (0 to 100, default 0). Each line is worked out less its discount, plus its tax, then summed and rounded to 2 places. |
issue_date | string | YYYY-MM-DD, default today. |
due_date | string | YYYY-MM-DD, not before issue_date. When left out, it comes from terms or your saved payment terms if they name a number of days (for example Net 30) or say due on receipt. |
terms | string | Up to 200 characters. |
currency | string | Three letter code such as USD. Defaults to your account setting. |
notes | string | Up to 4000 characters. Defaults to your saved default notes. |
number | string | Letters, digits and - _ . / #, up to 40 characters. Leave it out to take the next number in your series. A number already used returns 409 duplicate_number. |
external_id | string | Optional. Your own id for this record, such as a spreadsheet row id. A retry with the same id returns the existing record instead of making a second one. Up to 200 characters. See idempotency and retries. |
Example request
curl -X POST https://invoicetools.com/api/v1/invoices \
-H "Authorization: Bearer itk_live_your_key" -H "Content-Type: application/json" \
-d '{"client_name":"Northwind Supply","client_email":"billing@northwind.example.invalid",
"line_items":[{"description":"Site visit","quantity":2,"unit_price":100,"tax_percent":10}],
"due_date":"2026-11-15","notes":"Thank you."}'Example response, 201
{
"id": "7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10",
"kind": "invoice",
"number": "INV-1042",
"status": "draft",
"client_id": "3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"client_name": "Northwind Supply",
"currency": "USD",
"total": 220,
"amount_due": 220,
"issue_date": "2026-10-16",
"due_date": "2026-11-15",
"share_url": null,
"created_at": "2026-10-16T09:12:44+00:00",
"updated_at": "2026-10-16T09:20:05+00:00",
"sent_at": null,
"viewed_at": null,
"client_created": true,
"edit_url": "https://invoicetools.com/invoice-generator/?doc=7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10"
}The response is the document plus client_id, client_created and edit_url.
PATCH /invoices/:id
Changes a draft invoice. Only the fields you send change. A draft is a document that was never sent, is still in draft status and has no payment. Anything else is refused with 409 document_locked and a message that says what to do next (void it or issue a credit note in InvoiceTools, then create a new document). The change is saved only if the document is still a draft at that moment, so a send that happens at the same time always wins. This route never sends an email and never touches payments.
Body fields
| Field | Type | Notes |
|---|---|---|
line_items | array | Same rows as when creating. When you send it, it replaces ALL current lines and the total is worked out again. Leave it out to keep the lines. |
client_id, client_name, client_email | string | Moves the document to another client, found or added the same way as when creating. |
issue_date | string | YYYY-MM-DD. |
currency | string | Three letter code. |
notes | string | Up to 4000 characters. An empty string clears it. |
number | string | A number already used by another invoice returns 409 duplicate_number. |
due_date | string or null | YYYY-MM-DD, not before issue_date. null clears it. |
terms | string | Up to 200 characters. |
Example request
curl -X PATCH https://invoicetools.com/api/v1/invoices/7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10 \
-H "Authorization: Bearer itk_live_your_key" -H "Content-Type: application/json" \
-d '{"notes":"Thanks for your business.","line_items":[{"description":"Site visit","quantity":2,"unit_price":90}]}'The response is the updated invoice with line_items, client_id and edit_url. A sent, paid or void invoice returns 409 document_locked. Sending no changeable field returns 400 nothing_to_update.
POST /invoices/:id/payments
Records a payment received outside InvoiceTools. The balance and status update as they do in the dashboard, and the payment.received and invoice.paid or invoice.partially_paid events fire. Void and fully paid invoices are refused.
Body fields
| Field | Type | Notes |
|---|---|---|
amount | number | Required. Above 0 and no more than the balance still owed. |
date | string | YYYY-MM-DD, default today. paid_on is accepted as an alias. |
method | string | One of bank, card, cash, cheque, paypal, other. Default other. |
reference | string | Up to 200 characters. |
note | string | Up to 1000 characters. |
external_id | string | Optional. Your own id for this payment, such as a bank transaction id. A retry with the same id does not record the payment twice. Up to 200 characters. See idempotency and retries. |
Example request
curl -X POST https://invoicetools.com/api/v1/invoices/7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10/payments \
-H "Authorization: Bearer itk_live_your_key" -H "Content-Type: application/json" \
-d '{"amount":100,"method":"bank","reference":"TRF-2201"}'Example response, 201
{
"id": "d29e5b7a-1c4f-4a86-b3d0-58a6e1f90c27",
"document_id": "7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10",
"amount": 100,
"paid_on": "2026-10-18",
"method": "bank",
"reference": "TRF-2201",
"note": null,
"invoice": {
"id": "7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10",
"kind": "invoice",
"number": "INV-1042",
"status": "sent",
"client_id": "3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"client_name": "Northwind Supply",
"currency": "USD",
"total": 220,
"amount_due": 120,
"issue_date": "2026-10-16",
"due_date": "2026-11-15",
"share_url": "https://invoicetools.com/q/?t=Zk3p9QxExample",
"created_at": "2026-10-16T09:12:44+00:00",
"updated_at": "2026-10-16T09:20:05+00:00",
"sent_at": "2026-10-16T09:20:05+00:00",
"viewed_at": null
}
}POST /invoices/:id/send
Emails an invoice to the client with a secure link to view it online. A draft becomes sent, the share link is created if it does not exist yet, and the send shows in the email log. The email comes from the sender InvoiceTools uses for document delivery, with your business name as the display name and your business email as the reply address.
Body fields
| Field | Type | Notes |
|---|---|---|
to | string | Optional. The address to send to. Defaults to the client email on the invoice. |
message | string | Optional. A short plain text note shown above the details. Up to 1000 characters. |
Refused with no_recipient when there is no address, already_void or already_paid when the invoice cannot be sent, recipient_suppressed when the address earlier bounced or marked an email as spam, and message_too_long past 1000 characters. At most 100 document emails a day per account.
Example request
curl -X POST https://invoicetools.com/api/v1/invoices/7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10/send \n -H "Authorization: Bearer itk_live_your_key" -H "Content-Type: application/json" \n -d '{"message":"Thanks for the call today."}'Example response, 200
{
"sent": true,
"to": "accounts@northwind.example",
"subject": "Invoice INV-1042 from Acme Plumbing, due Nov 15",
"email_id": "re_9f2c1a7e",
"invoice": {
"id": "7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10",
"kind": "invoice",
"number": "INV-1042",
"status": "sent",
"client_name": "Northwind Supply",
"currency": "USD",
"total": 220,
"share_url": "https://invoicetools.com/q/?t=Zk3p9QxExample",
"sent_at": "2026-10-16T09:20:05+00:00"
}
}Quotes
GET /quotes
Your quotes, newest first. Same parameters as GET /invoices.
Query parameters
| Name | Type | Notes |
|---|---|---|
status | string | A status word such as draft, sent, paid or overdue. |
number | string | Exact match on the printed number, up to 40 characters. |
limit | integer | 1 to 100, default 25. |
before | string | Cursor. Pass the previous next_cursor to get the next page. |
Example request
curl "https://invoicetools.com/api/v1/quotes?status=sent" \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"data": [
{
"id": "a41d7c90-2b6e-4f58-8c13-6e0b95d2c7a4",
"kind": "quote",
"number": "QUO-0217",
"status": "sent",
"client_id": "3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"client_name": "Northwind Supply",
"currency": "USD",
"total": 220,
"amount_due": null,
"issue_date": "2026-10-16",
"due_date": null,
"share_url": "https://invoicetools.com/q/?t=Hn7d2VbExample",
"created_at": "2026-10-16T09:12:44+00:00",
"updated_at": "2026-10-16T09:20:05+00:00",
"sent_at": "2026-10-16T09:20:05+00:00",
"viewed_at": null,
"valid_until": "2026-11-30"
}
],
"has_more": false,
"next_cursor": null
}GET /quotes/:id
One quote by id.
Example request
curl https://invoicetools.com/api/v1/quotes/a41d7c90-2b6e-4f58-8c13-6e0b95d2c7a4 \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"id": "a41d7c90-2b6e-4f58-8c13-6e0b95d2c7a4",
"kind": "quote",
"number": "QUO-0217",
"status": "sent",
"client_id": "3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"client_name": "Northwind Supply",
"currency": "USD",
"total": 220,
"amount_due": null,
"issue_date": "2026-10-16",
"due_date": null,
"share_url": "https://invoicetools.com/q/?t=Hn7d2VbExample",
"created_at": "2026-10-16T09:12:44+00:00",
"updated_at": "2026-10-16T09:20:05+00:00",
"sent_at": "2026-10-16T09:20:05+00:00",
"viewed_at": null,
"valid_until": "2026-11-30"
}POST /quotes
Creates a draft quote. Same body as POST /invoices, with valid_until in place of due_date and terms.
Body fields
| Field | Type | Notes |
|---|---|---|
client_id | string (UUID) | The id of one of your clients. Or send client_name and client_email (below). A client object with the same keys also works. |
client_name | string | Needed when the client is not found by id or email. Without an email, a client with exactly this name is reused. |
client_email | string | A client with this email is reused, otherwise a new client is added. Ignores capitals. |
client_phone, client_address | string | Saved on a newly added client. |
line_items | array | Required. 1 to 100 rows, each an object: description (string, required), unit_price (number, 0 or more, required), quantity (number above 0, default 1), tax_percent (0 to 100, default 0), discount_percent (0 to 100, default 0). Each line is worked out less its discount, plus its tax, then summed and rounded to 2 places. |
issue_date | string | YYYY-MM-DD, default today. |
valid_until | string | YYYY-MM-DD, the last day the quote can be accepted. |
currency | string | Three letter code such as USD. Defaults to your account setting. |
notes | string | Up to 4000 characters. Defaults to your saved default notes. |
number | string | Letters, digits and - _ . / #, up to 40 characters. Leave it out to take the next number in your series. A number already used returns 409 duplicate_number. |
external_id | string | Optional. Your own id for this record, such as a spreadsheet row id. A retry with the same id returns the existing record instead of making a second one. Up to 200 characters. See idempotency and retries. |
Example request
curl -X POST https://invoicetools.com/api/v1/quotes \
-H "Authorization: Bearer itk_live_your_key" -H "Content-Type: application/json" \
-d '{"client_id":"3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"line_items":[{"description":"Kitchen refit","unit_price":4800}],
"valid_until":"2026-11-30"}'Example response, 201
{
"id": "a41d7c90-2b6e-4f58-8c13-6e0b95d2c7a4",
"kind": "quote",
"number": "QUO-0217",
"status": "draft",
"client_id": "3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"client_name": "Northwind Supply",
"currency": "USD",
"total": 220,
"amount_due": null,
"issue_date": "2026-10-16",
"due_date": null,
"share_url": null,
"created_at": "2026-10-16T09:12:44+00:00",
"updated_at": "2026-10-16T09:20:05+00:00",
"sent_at": null,
"viewed_at": null,
"valid_until": "2026-11-30",
"client_created": false,
"edit_url": "https://invoicetools.com/quotegenerator/?doc=a41d7c90-2b6e-4f58-8c13-6e0b95d2c7a4"
}PATCH /quotes/:id
Changes a draft quote. Same rules and fields as PATCH /invoices/:id, with valid_until in place of due_date and terms. A quote that was sent, accepted or declined returns 409 document_locked.
Body fields
| Field | Type | Notes |
|---|---|---|
line_items | array | Same rows as when creating. When you send it, it replaces ALL current lines and the total is worked out again. Leave it out to keep the lines. |
client_id, client_name, client_email | string | Moves the document to another client, found or added the same way as when creating. |
issue_date | string | YYYY-MM-DD. |
currency | string | Three letter code. |
notes | string | Up to 4000 characters. An empty string clears it. |
number | string | A number already used by another quote returns 409 duplicate_number. |
valid_until | string or null | YYYY-MM-DD. null clears it. |
POST /quotes/:id/send
Emails a quote to the client with a secure link to view it online. A draft becomes sent, the share link is created if it does not exist yet, and the send shows in the email log. The email comes from the sender InvoiceTools uses for document delivery, with your business name as the display name and your business email as the reply address.
Body fields
| Field | Type | Notes |
|---|---|---|
to | string | Optional. The address to send to. Defaults to the client email on the quote. |
message | string | Optional. A short plain text note shown above the details. Up to 1000 characters. |
Refused with no_recipient when there is no address, already_void when the quote cannot be sent, recipient_suppressed when the address earlier bounced or marked an email as spam, and message_too_long past 1000 characters. At most 100 document emails a day per account.
Example request
curl -X POST https://invoicetools.com/api/v1/quotes/a41d7c90-2b6e-4f58-8c13-6e0b95d2c7a4/send \n -H "Authorization: Bearer itk_live_your_key" -H "Content-Type: application/json" \n -d '{"message":"Thanks for the call today."}'Example response, 200
{
"sent": true,
"to": "accounts@northwind.example",
"subject": "Quote QUO-0217 from Acme Plumbing, valid until Nov 30",
"email_id": "re_9f2c1a7e",
"quote": {
"id": "a41d7c90-2b6e-4f58-8c13-6e0b95d2c7a4",
"kind": "quote",
"number": "QUO-0217",
"status": "sent",
"client_name": "Northwind Supply",
"currency": "USD",
"total": 220,
"share_url": "https://invoicetools.com/q/?t=Zk3p9QxExample",
"sent_at": "2026-10-16T09:20:05+00:00"
}
}Payments
GET /payments
Recorded payments, newest first.
Query parameters
| Name | Type | Notes |
|---|---|---|
document_id | string (UUID) | Only payments on this invoice. |
reference | string | Exact match on the reference saved with the payment, up to 200 characters. |
limit | integer | 1 to 100, default 25. |
before | string | Cursor. Pass the previous next_cursor to get the next page. |
Example request
curl "https://invoicetools.com/api/v1/payments?document_id=7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10" \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"data": [
{
"id": "d29e5b7a-1c4f-4a86-b3d0-58a6e1f90c27",
"document_id": "7b0c2f4e-5d1a-4c0e-9a57-2f1d3e8a9b10",
"amount": 100,
"paid_on": "2026-10-18",
"method": "bank",
"reference": "TRF-2201",
"created_at": "2026-10-18T14:03:10+00:00"
}
],
"has_more": false,
"next_cursor": null
}GET /payments/:id
One payment by id, with note and provider. To find a payment by the reference saved with it, use GET /payments?reference=TRF-2201 (exact match, can be combined with document_id). A payment on another account returns 404 not_found.
Clients
GET /clients
Your clients, newest first. Archived clients are left out.
Query parameters
| Name | Type | Notes |
|---|---|---|
email | string | Matches the email exactly, ignoring capitals. |
name | string | Matches names that contain this text. |
limit | integer | 1 to 100, default 25. |
before | string | Cursor. Pass the previous next_cursor to get the next page. |
Example request
curl "https://invoicetools.com/api/v1/clients?email=billing@northwind.example.invalid" \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"data": [
{
"id": "3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"name": "Northwind Supply",
"email": "billing@northwind.example.invalid",
"phone": "+1 555 0100",
"created_at": "2026-09-02T16:41:09+00:00"
}
],
"has_more": false,
"next_cursor": null
}POST /clients
Adds a client. If a client with that email already exists it is returned instead of duplicated, with status 200 rather than 201.
Body fields
| Field | Type | Notes |
|---|---|---|
name | string | Required. |
email | string | Must look like an email address. |
phone, address, notes | string | Optional. |
external_id | string | Optional. Your own id for this client. A retry with the same id returns the same client. Up to 200 characters. See idempotency and retries. |
Example request
curl -X POST https://invoicetools.com/api/v1/clients \
-H "Authorization: Bearer itk_live_your_key" -H "Content-Type: application/json" \
-d '{"name":"Northwind Supply","email":"billing@northwind.example.invalid"}'Example response, 201
{
"id": "3f6a9c1e-84b2-4d7e-b0c5-91e2a7d4f836",
"name": "Northwind Supply",
"email": "billing@northwind.example.invalid",
"created": true
}PATCH /clients/:id
Changes a client. Only the fields you send change; the others keep their value. Documents already sent keep the details they were sent with, and drafts are not rewritten.
Body fields
| Field | Type | Notes |
|---|---|---|
name | string | Cannot be blank. |
email | string or null | Must look like an email address and cannot belong to another client (409 duplicate_email). null or an empty string clears it. |
phone, address, notes | string or null | null or an empty string clears the field. |
Example request
curl -X PATCH https://invoicetools.com/api/v1/clients/0a1b2c3d-1111-4222-8333-444455556666 \
-H "Authorization: Bearer itk_live_your_key" -H "Content-Type: application/json" \
-d '{"phone":"555 0100"}'The response is the client with updated_at. Another account's client, or an archived one, returns 404 not_found.
GET /clients/:id
One client by id, with address, notes and updated_at.
Hooks and samples
These three routes exist for the Zapier app. They subscribe a Zapier hook address to one event. To receive signed events at your own server, use webhooks instead.
GET /hooks
The hook subscriptions made with this key.
Example request
curl https://invoicetools.com/api/v1/hooks \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"data": [
{
"id": "e5a1c7d3-92b0-4f46-8d1a-3b7c60f2e914",
"event": "invoice.paid",
"active": true,
"created_at": "2026-10-12T08:00:00+00:00"
}
]
}POST /hooks
Subscribes a hook to one event.
Body fields
| Field | Type | Notes |
|---|---|---|
target_url | string | Required. Must be an https://hooks.zapier.com/ address. |
event | string | Required. One of the names from GET /events. |
Example request
curl -X POST https://invoicetools.com/api/v1/hooks \
-H "Authorization: Bearer itk_live_your_key" -H "Content-Type: application/json" \
-d '{"target_url":"https://hooks.zapier.com/hooks/standard/000/example/","event":"invoice.paid"}'Example response, 201
{
"id": "e5a1c7d3-92b0-4f46-8d1a-3b7c60f2e914"
}A key can hold up to 50 subscriptions. More returns 429 too_many_subscriptions.
DELETE /hooks/:id
Removes a subscription.
Example request
curl -X DELETE https://invoicetools.com/api/v1/hooks/e5a1c7d3-92b0-4f46-8d1a-3b7c60f2e914 \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
{
"ok": true
}GET /samples
The newest real record for an event, as an array with one item, or an empty array when you have none. Useful for building a field mapping.
Query parameters
| Name | Type | Notes |
|---|---|---|
event | string | Required. One of the names from GET /events. |
Example request
curl "https://invoicetools.com/api/v1/samples?event=invoice.paid" \
-H "Authorization: Bearer itk_live_your_key"Example response, 200
[ { "...": "the same object a webhook for that event carries" } ]Webhook events
Subscribe to any of these with webhooks to be told the moment they happen, instead of polling the list endpoints. The webhooks page has the envelope, example payloads, the signature check and the retry schedule.
| Event | When it is sent |
|---|---|
invoice.sent | An invoice was sent or marked as sent. |
invoice.viewed | The client opened the invoice for the first time. |
invoice.paid | The invoice is paid in full. |
invoice.partially_paid | A payment arrived and a balance remains. |
invoice.overdue | The invoice passed its due date unpaid. |
quote.sent | A quote was sent. |
quote.viewed | The client opened the quote for the first time. |
quote.accepted | The client accepted the quote. |
quote.declined | The client declined the quote. |
invoice.due_in_1, invoice.due_in_3, invoice.due_in_7, invoice.due_in_14 | Once per invoice, that many days before its due date, if it is still sent and has a balance. Checked once a day at 02:45 UTC against the due date as written (it has no time zone), so it is not real time. Carries days_before_due. |
invoice.reminder_sent | A payment reminder email for an invoice was actually sent. One event per reminder; id is the reminder, invoice_id the invoice, with reminder_number, sent_to and sent_at. |
quote.expired | A sent quote that was never accepted or declined passed its valid_until date. Once per quote, checked daily at 02:45 UTC. |
client.updated | The name, email, phone, address or notes of a client really changed. Carries changed_fields. An update that changes nothing sends nothing. |
payment.received | A payment was recorded or paid online. |
payment.refunded | An online payment was refunded. |
Questions
Manage keys in your account. For help, email contact@invoicetools.com and include the X-Request-Id of the request.