InvoiceTools

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:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute (120).
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetSeconds until the window resets.
Retry-AfterOnly 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 status 200 and "idempotent_replay": true.
  • Same external_id with different details: 409 idempotency_conflict. Use a new external_id for a new record.
  • A retry that arrives while the first request is still running: 409 request_in_progress. Wait a few seconds (see Retry-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."
}
StatusMeaningCodes
400The 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
401Missing, wrong or revoked key.invalid_key
403The key is valid but the plan or business does not allow it.business_plan_required, business_readonly
404No such route or record.not_found, client_not_found
405Wrong method for that route.method_not_allowed
409Conflict.duplicate_number, duplicate_email, document_locked, idempotency_conflict, request_in_progress, record_gone
422The request is valid but the address cannot be used.recipient_suppressed
429Too many requests, too many hook subscriptions, or the daily email limit.rate_limited, too_many_subscriptions, daily_limit
502Something 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

NameTypeNotes
statusstringA status word such as draft, sent, paid or overdue.
numberstringExact match on the printed number, up to 40 characters.
limitinteger1 to 100, default 25.
beforestringCursor. 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

FieldTypeNotes
client_idstring (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_namestringNeeded when the client is not found by id or email. Without an email, a client with exactly this name is reused.
client_emailstringA client with this email is reused, otherwise a new client is added. Ignores capitals.
client_phone, client_addressstringSaved on a newly added client.
line_itemsarrayRequired. 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_datestringYYYY-MM-DD, default today.
due_datestringYYYY-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.
termsstringUp to 200 characters.
currencystringThree letter code such as USD. Defaults to your account setting.
notesstringUp to 4000 characters. Defaults to your saved default notes.
numberstringLetters, 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_idstringOptional. 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

FieldTypeNotes
line_itemsarraySame 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_emailstringMoves the document to another client, found or added the same way as when creating.
issue_datestringYYYY-MM-DD.
currencystringThree letter code.
notesstringUp to 4000 characters. An empty string clears it.
numberstringA number already used by another invoice returns 409 duplicate_number.
due_datestring or nullYYYY-MM-DD, not before issue_date. null clears it.
termsstringUp 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

FieldTypeNotes
amountnumberRequired. Above 0 and no more than the balance still owed.
datestringYYYY-MM-DD, default today. paid_on is accepted as an alias.
methodstringOne of bank, card, cash, cheque, paypal, other. Default other.
referencestringUp to 200 characters.
notestringUp to 1000 characters.
external_idstringOptional. 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

FieldTypeNotes
tostringOptional. The address to send to. Defaults to the client email on the invoice.
messagestringOptional. 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

NameTypeNotes
statusstringA status word such as draft, sent, paid or overdue.
numberstringExact match on the printed number, up to 40 characters.
limitinteger1 to 100, default 25.
beforestringCursor. 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

FieldTypeNotes
client_idstring (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_namestringNeeded when the client is not found by id or email. Without an email, a client with exactly this name is reused.
client_emailstringA client with this email is reused, otherwise a new client is added. Ignores capitals.
client_phone, client_addressstringSaved on a newly added client.
line_itemsarrayRequired. 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_datestringYYYY-MM-DD, default today.
valid_untilstringYYYY-MM-DD, the last day the quote can be accepted.
currencystringThree letter code such as USD. Defaults to your account setting.
notesstringUp to 4000 characters. Defaults to your saved default notes.
numberstringLetters, 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_idstringOptional. 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

FieldTypeNotes
line_itemsarraySame 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_emailstringMoves the document to another client, found or added the same way as when creating.
issue_datestringYYYY-MM-DD.
currencystringThree letter code.
notesstringUp to 4000 characters. An empty string clears it.
numberstringA number already used by another quote returns 409 duplicate_number.
valid_untilstring or nullYYYY-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

FieldTypeNotes
tostringOptional. The address to send to. Defaults to the client email on the quote.
messagestringOptional. 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

NameTypeNotes
document_idstring (UUID)Only payments on this invoice.
referencestringExact match on the reference saved with the payment, up to 200 characters.
limitinteger1 to 100, default 25.
beforestringCursor. 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

NameTypeNotes
emailstringMatches the email exactly, ignoring capitals.
namestringMatches names that contain this text.
limitinteger1 to 100, default 25.
beforestringCursor. 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

FieldTypeNotes
namestringRequired.
emailstringMust look like an email address.
phone, address, notesstringOptional.
external_idstringOptional. 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

FieldTypeNotes
namestringCannot be blank.
emailstring or nullMust look like an email address and cannot belong to another client (409 duplicate_email). null or an empty string clears it.
phone, address, notesstring or nullnull 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

FieldTypeNotes
target_urlstringRequired. Must be an https://hooks.zapier.com/ address.
eventstringRequired. 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

NameTypeNotes
eventstringRequired. 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.

EventWhen it is sent
invoice.sentAn invoice was sent or marked as sent.
invoice.viewedThe client opened the invoice for the first time.
invoice.paidThe invoice is paid in full.
invoice.partially_paidA payment arrived and a balance remains.
invoice.overdueThe invoice passed its due date unpaid.
quote.sentA quote was sent.
quote.viewedThe client opened the quote for the first time.
quote.acceptedThe client accepted the quote.
quote.declinedThe client declined the quote.
invoice.due_in_1, invoice.due_in_3, invoice.due_in_7, invoice.due_in_14Once 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_sentA 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.expiredA sent quote that was never accepted or declined passed its valid_until date. Once per quote, checked daily at 02:45 UTC.
client.updatedThe name, email, phone, address or notes of a client really changed. Carries changed_fields. An update that changes nothing sends nothing.
payment.receivedA payment was recorded or paid online.
payment.refundedAn 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.

Send it yourself, or send it and watch it

Everything here is free. Generators, calculators and downloads need no account. Send a document as a link instead and you see when it was opened, which line they questioned, and who signed it, so chasing becomes a decision rather than a guess.

The first four documents you send as a tracked link are free with an account. Everything else on InvoiceTools.com stays free. Generators, calculators and downloads need no account; template designs need a free one.