Introduction
REST API for managing invoices, clients, projects, time tracking, and more.
Authentication
Tidybill uses Bearer token authentication. Create a personal access token from Settings > API in your Tidybill dashboard (requires Pro plan).
Include the token in every request:
Authorization: Bearer YOUR_TOKEN
Multi-tenancy
All resource endpoints require an X-Company-Id header to specify which company you're operating on. You can list your companies via GET /api/companies.
X-Company-Id: 1
Money
All currency amounts are stored in cents (e.g. $10.50 = 1050). Most fields are integers, except unit_price which supports up to 6 decimal places for sub-cent pricing (e.g. 3.5 = $0.035). Line item totals and all other monetary fields are rounded to whole cents. Tax rates are in basis points (e.g. 15% = 1500).
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer {YOUR_AUTH_KEY}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Create a personal access token from Settings > API in your Tidybill dashboard. Requires a Pro plan.
Authentication
Register
Example request:
curl --request POST \
"https://tidybill.app/api/auth/register" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"email\": \"[email protected]\",
\"password\": \"-0pBNvYgxw\"
}"
const url = new URL(
"https://tidybill.app/api/auth/register"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"email": "[email protected]",
"password": "-0pBNvYgxw"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"id": 1,
"name": "John",
"email": "[email protected]"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Login
Example request:
curl --request POST \
"https://tidybill.app/api/auth/login" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"email\": \"[email protected]\",
\"password\": \"|]|{+-\"
}"
const url = new URL(
"https://tidybill.app/api/auth/login"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "[email protected]",
"password": "|]|{+-"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1,
"name": "John",
"email": "[email protected]"
}
}
Example response (401):
{
"message": "Invalid credentials."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Send password reset link
Example request:
curl --request POST \
"https://tidybill.app/api/auth/forgot-password" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"email\": \"[email protected]\"
}"
const url = new URL(
"https://tidybill.app/api/auth/forgot-password"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "[email protected]"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "If an account exists, a reset link has been sent."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Reset password
Example request:
curl --request POST \
"https://tidybill.app/api/auth/reset-password" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"token\": \"architecto\",
\"email\": \"[email protected]\",
\"password\": \"-0pBNvYgxw\"
}"
const url = new URL(
"https://tidybill.app/api/auth/reset-password"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"token": "architecto",
"email": "[email protected]",
"password": "-0pBNvYgxw"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Password has been reset."
}
Example response (422):
{
"message": "This password reset token is invalid."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Redirect to Google OAuth
Example request:
curl --request GET \
--get "https://tidybill.app/api/auth/google" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/auth/google"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (302, Redirect to Google):
Example response (302):
Show headers
cache-control: no-cache, private
location: https://accounts.google.com/o/oauth2/auth?client_id=122074070973-q2t44i2big2vg257o5bkbn5kjhuu1d34.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Ftidybill.app%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=openid+profile+email&response_type=code
content-type: text/html; charset=utf-8
access-control-allow-origin: https://tidybill.app
access-control-allow-credentials: true
set-cookie: XSRF-TOKEN=eyJpdiI6IjBCalBaQVRIeFE2Zi90d0U3MHlOR1E9PSIsInZhbHVlIjoiVnhqWkE3amhmRkw1bmptRjVTY05LTmRsejJkbHlVdGYrOTJxNlBDYndrWjBIN2FWSTdzTjg3Njc2ankzdEFIR2s1RlpBM011VVEySVRIdnVYemlsUVJ6VDR1bWprL2kwbWo3QUNuSHFWdGRwMS9XTDkrQXdYcmI0MzRzTmlWOHoiLCJtYWMiOiI0ZjQzOGU1MWZkNjhhMzdjNzZlZWE1NDY2ZjJhMDQzMWRiYWJhYTA4ZmYyZmI2OWIxZTE3MDM2MjE0YWVmMGRmIiwidGFnIjoiIn0%3D; expires=Sun, 12 Apr 2026 19:38:12 GMT; Max-Age=604799; path=/; domain=tidybill.app; secure; samesite=lax; tidybill_session=eyJpdiI6ImUzNExwdytvUHY2UExCNFFlWW9jbnc9PSIsInZhbHVlIjoiS09BdTh3eEJIODlOSExkR3U3NndQL2FudERDKzF1NDltNFBZY3g1dHdBQ3RySGIxdU9RaWRBV1BNa0xuYTRQSjhWRGYrWVVhSjd4b2pNV0lDaXdmakkxZlNJMVcxOWVveHRvWGxtWmJEdEdwbWRtNVFhSDF3bFYwNzUvUERDaGkiLCJtYWMiOiI0MTI4OGMzZDY0ZTJhMDUzZjc3NDU4ODc1ZDA1Yzc4MzEzODAxMjBmY2NhMzAyZjA0NzMyN2ZjYWNjYzg0YmM3IiwidGFnIjoiIn0%3D; expires=Sun, 12 Apr 2026 19:38:12 GMT; Max-Age=604799; path=/; domain=tidybill.app; secure; httponly; samesite=lax
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url='https://accounts.google.com/o/oauth2/auth?client_id=122074070973-q2t44i2big2vg257o5bkbn5kjhuu1d34.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Ftidybill.app%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=openid+profile+email&response_type=code'" />
<title>Redirecting to https://accounts.google.com/o/oauth2/auth?client_id=122074070973-q2t44i2big2vg257o5bkbn5kjhuu1d34.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Ftidybill.app%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=openid+profile+email&response_type=code</title>
</head>
<body>
Redirecting to <a href="https://accounts.google.com/o/oauth2/auth?client_id=122074070973-q2t44i2big2vg257o5bkbn5kjhuu1d34.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Ftidybill.app%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=openid+profile+email&response_type=code">https://accounts.google.com/o/oauth2/auth?client_id=122074070973-q2t44i2big2vg257o5bkbn5kjhuu1d34.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Ftidybill.app%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=openid+profile+email&response_type=code</a>.
</body>
</html>
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Google OAuth callback
Example request:
curl --request GET \
--get "https://tidybill.app/api/auth/google/callback" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/auth/google/callback"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (302, Redirect to frontend after authentication):
Example response (302):
Show headers
cache-control: no-cache, private
location: https://tidybill.app/login?error=google_auth_failed
content-type: text/html; charset=utf-8
access-control-allow-origin: https://tidybill.app
access-control-allow-credentials: true
set-cookie: XSRF-TOKEN=eyJpdiI6IjVWMERBdEhsWnJ3QUFncU5PNVVxVFE9PSIsInZhbHVlIjoiS0tESUNoeHNmN3pCRjJ1ZE9ESmNvQVhZU2dqWU5ldUcxMnpsTjZwdTkvbTFhVCs4OHFHR1JHUjR4TzNQTEhMNTkvWS9kQTAxYW5nL1owd3dLZGVJQVJZQjlKMHhxODVnMEhvMnh1SFB6cEdUUlY0MHMvaE9oUTIxbUJicmFDdjEiLCJtYWMiOiI2ZmY4ZmJjODgzOGFkYzdjYjQ1ZjRiNzQ1OTA3MThjNmYzYjlkNDMzMDdhYmFlZjczOGMyZjQxNWFiNzM5YzM0IiwidGFnIjoiIn0%3D; expires=Sun, 12 Apr 2026 19:38:13 GMT; Max-Age=604800; path=/; domain=tidybill.app; secure; samesite=lax; tidybill_session=eyJpdiI6IjNuVFFhMVNMVUpvTldKOEFIMmVObXc9PSIsInZhbHVlIjoieWNPTDhXazQxeUZCR3FyV0lTYlpRd1ZzTDZvVHFaNEl3L1JyeG1JbGpLeElYbkt0eU5NS2ZsZVBSV1FmU25LRG15Wk5xL0VBTFJ3aVdnS1lCOWdPRWhBS1c4S1pxVEZvc2pDSnNWeHFwb0xFUmFqSFhCb3ExMGdhbytaSkw0ZjgiLCJtYWMiOiI2OTRiN2NiMzJlMzc5Yjg4YTZhMDExZmNkZjk0M2FmZTE4NjVlMzVjNTgxODg5ZGY3MDIxOWYzYjU1ZjU0MTc3IiwidGFnIjoiIn0%3D; expires=Sun, 12 Apr 2026 19:38:13 GMT; Max-Age=604800; path=/; domain=tidybill.app; secure; httponly; samesite=lax
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url='https://tidybill.app/login?error=google_auth_failed'" />
<title>Redirecting to https://tidybill.app/login?error=google_auth_failed</title>
</head>
<body>
Redirecting to <a href="https://tidybill.app/login?error=google_auth_failed">https://tidybill.app/login?error=google_auth_failed</a>.
</body>
</html>
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Logout
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/auth/logout" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/auth/logout"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"message": "Logged out."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get authenticated user
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/auth/user" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/auth/user"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 8,
"name": "Morgan Hirthe",
"email": "[email protected]",
"avatar_url": null,
"current_company_id": null,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update authenticated user
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/auth/user" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\"
}"
const url = new URL(
"https://tidybill.app/api/auth/user"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 9,
"name": "Ms. Elisabeth Okuneva",
"email": "[email protected]",
"avatar_url": null,
"current_company_id": null,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Companies
List companies
requires authentication
Returns all companies the authenticated user is a member of, regardless of role.
Example request:
curl --request GET \
--get "https://tidybill.app/api/companies" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/companies"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 15,
"uuid": "0db07a5e-1aa6-4165-ba1e-f4c0990f7705",
"name": "Price Ltd",
"legal_name": null,
"email": "[email protected]",
"phone": null,
"address_line_1": null,
"address_line_2": null,
"city": null,
"state": null,
"postal_code": null,
"country": null,
"currency": "USD",
"tax_number": null,
"logo_path": null,
"invoice_prefix": "INV-",
"quote_prefix": "QUO-",
"invoice_layout": "classic",
"default_payment_terms": 30,
"default_hourly_rate": null,
"default_tax_name_1": null,
"default_tax_rate_1": null,
"default_tax_name_2": null,
"default_tax_rate_2": null,
"late_fee_type": null,
"late_fee_value": null,
"late_fee_days": null,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
},
{
"id": 16,
"uuid": "81e00a44-fe6f-47fe-9604-3dc4ac4cae5a",
"name": "Leuschke, Bauch and Fritsch",
"legal_name": null,
"email": "[email protected]",
"phone": null,
"address_line_1": null,
"address_line_2": null,
"city": null,
"state": null,
"postal_code": null,
"country": null,
"currency": "USD",
"tax_number": null,
"logo_path": null,
"invoice_prefix": "INV-",
"quote_prefix": "QUO-",
"invoice_layout": "classic",
"default_payment_terms": 30,
"default_hourly_rate": null,
"default_tax_name_1": null,
"default_tax_rate_1": null,
"default_tax_name_2": null,
"default_tax_rate_2": null,
"late_fee_type": null,
"late_fee_value": null,
"late_fee_days": null,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a company
requires authentication
Creates a company, attaches the authenticated user as owner, and creates a default invoice template. Returns 403 if the user's plan company limit is reached.
Example request:
curl --request POST \
"https://tidybill.app/api/companies" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"legal_name\": \"n\",
\"email\": \"[email protected]\",
\"phone\": \"v\",
\"address_line_1\": \"d\",
\"address_line_2\": \"l\",
\"city\": \"j\",
\"state\": \"n\",
\"postal_code\": \"ikhwaykcmyuwpwlv\",
\"country\": \"qw\",
\"currency\": \"rsi\",
\"tax_number\": \"t\",
\"invoice_prefix\": \"cpscql\",
\"quote_prefix\": \"dzsnrw\",
\"default_payment_terms\": 19,
\"default_hourly_rate\": 33,
\"default_tax_name_1\": \"j\",
\"default_tax_rate_1\": 17,
\"default_tax_name_2\": \"v\",
\"default_tax_rate_2\": 24
}"
const url = new URL(
"https://tidybill.app/api/companies"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"legal_name": "n",
"email": "[email protected]",
"phone": "v",
"address_line_1": "d",
"address_line_2": "l",
"city": "j",
"state": "n",
"postal_code": "ikhwaykcmyuwpwlv",
"country": "qw",
"currency": "rsi",
"tax_number": "t",
"invoice_prefix": "cpscql",
"quote_prefix": "dzsnrw",
"default_payment_terms": 19,
"default_hourly_rate": 33,
"default_tax_name_1": "j",
"default_tax_rate_1": 17,
"default_tax_name_2": "v",
"default_tax_rate_2": 24
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 17,
"uuid": "3e26903d-5044-4d8b-ab0e-e1e541451f00",
"name": "Considine LLC",
"legal_name": null,
"email": "[email protected]",
"phone": null,
"address_line_1": null,
"address_line_2": null,
"city": null,
"state": null,
"postal_code": null,
"country": null,
"currency": "USD",
"tax_number": null,
"logo_path": null,
"invoice_prefix": "INV-",
"quote_prefix": "QUO-",
"invoice_layout": "classic",
"default_payment_terms": 30,
"default_hourly_rate": null,
"default_tax_name_1": null,
"default_tax_rate_1": null,
"default_tax_name_2": null,
"default_tax_rate_2": null,
"late_fee_type": null,
"late_fee_value": null,
"late_fee_days": null,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
}
}
Example response (403):
{
"message": "You've reached your free plan limit of 1 companies. Please upgrade.",
"upgrade_required": true
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a company
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/companies/2" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/companies/2"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 18,
"uuid": "1f26aac2-cf55-4962-b801-73a762a46bee",
"name": "Price Ltd",
"legal_name": null,
"email": "[email protected]",
"phone": null,
"address_line_1": null,
"address_line_2": null,
"city": null,
"state": null,
"postal_code": null,
"country": null,
"currency": "USD",
"tax_number": null,
"logo_path": null,
"invoice_prefix": "INV-",
"quote_prefix": "QUO-",
"invoice_layout": "classic",
"default_payment_terms": 30,
"default_hourly_rate": null,
"default_tax_name_1": null,
"default_tax_rate_1": null,
"default_tax_name_2": null,
"default_tax_rate_2": null,
"late_fee_type": null,
"late_fee_value": null,
"late_fee_days": null,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a company
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/companies/2" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"legal_name\": \"n\",
\"email\": \"[email protected]\",
\"phone\": \"v\",
\"address_line_1\": \"d\",
\"address_line_2\": \"l\",
\"city\": \"j\",
\"state\": \"n\",
\"postal_code\": \"ikhwaykcmyuwpwlv\",
\"country\": \"qw\",
\"currency\": \"rsi\",
\"tax_number\": \"t\",
\"invoice_prefix\": \"cpscql\",
\"quote_prefix\": \"dzsnrw\",
\"default_payment_terms\": 19,
\"default_hourly_rate\": 33,
\"default_tax_name_1\": \"j\",
\"default_tax_rate_1\": 17,
\"default_tax_name_2\": \"v\",
\"default_tax_rate_2\": 24
}"
const url = new URL(
"https://tidybill.app/api/companies/2"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"legal_name": "n",
"email": "[email protected]",
"phone": "v",
"address_line_1": "d",
"address_line_2": "l",
"city": "j",
"state": "n",
"postal_code": "ikhwaykcmyuwpwlv",
"country": "qw",
"currency": "rsi",
"tax_number": "t",
"invoice_prefix": "cpscql",
"quote_prefix": "dzsnrw",
"default_payment_terms": 19,
"default_hourly_rate": 33,
"default_tax_name_1": "j",
"default_tax_rate_1": 17,
"default_tax_name_2": "v",
"default_tax_rate_2": 24
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 19,
"uuid": "426db81d-0fdf-43ee-9022-d67d5e37bcd8",
"name": "Considine LLC",
"legal_name": null,
"email": "[email protected]",
"phone": null,
"address_line_1": null,
"address_line_2": null,
"city": null,
"state": null,
"postal_code": null,
"country": null,
"currency": "USD",
"tax_number": null,
"logo_path": null,
"invoice_prefix": "INV-",
"quote_prefix": "QUO-",
"invoice_layout": "classic",
"default_payment_terms": 30,
"default_hourly_rate": null,
"default_tax_name_1": null,
"default_tax_rate_1": null,
"default_tax_name_2": null,
"default_tax_rate_2": null,
"late_fee_type": null,
"late_fee_value": null,
"late_fee_days": null,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a company
requires authentication
Only the company owner can delete a company. Returns 403 for any other role.
Example request:
curl --request DELETE \
"https://tidybill.app/api/companies/2" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/companies/2"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Example response (403):
{
"message": "Only the owner can delete a company."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Switch active company
requires authentication
Updates the user's current_company_id so subsequent requests default to this company.
Example request:
curl --request POST \
"https://tidybill.app/api/companies/2/switch" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/companies/2/switch"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 20,
"uuid": "f7fb15be-3a49-4a8b-bca9-17805160b129",
"name": "Price Ltd",
"legal_name": null,
"email": "[email protected]",
"phone": null,
"address_line_1": null,
"address_line_2": null,
"city": null,
"state": null,
"postal_code": null,
"country": null,
"currency": "USD",
"tax_number": null,
"logo_path": null,
"invoice_prefix": "INV-",
"quote_prefix": "QUO-",
"invoice_layout": "classic",
"default_payment_terms": 30,
"default_hourly_rate": null,
"default_tax_name_1": null,
"default_tax_rate_1": null,
"default_tax_name_2": null,
"default_tax_rate_2": null,
"late_fee_type": null,
"late_fee_value": null,
"late_fee_days": null,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List company members
requires authentication
Returns all users belonging to the company with their assigned role from the pivot table.
Example request:
curl --request GET \
--get "https://tidybill.app/api/companies/2/members" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/companies/2/members"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"name": "John",
"email": "[email protected]",
"role": "owner"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Invite a member
requires authentication
Creates an invitation record with a 7-day expiry and emails the invite link. Returns 422 if the user is already a member or a pending invitation exists.
Example request:
curl --request POST \
"https://tidybill.app/api/companies/2/members/invite" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"email\": \"[email protected]\",
\"role\": \"architecto\"
}"
const url = new URL(
"https://tidybill.app/api/companies/2/members/invite"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "[email protected]",
"role": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "Invitation sent.",
"data": {
"id": 1,
"email": "[email protected]",
"role": "member",
"expires_at": "2026-04-12T00:00:00.000000Z"
}
}
Example response (422):
{
"message": "User is already a member."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List pending invitations
requires authentication
Returns all non-expired, non-accepted invitations for the company, ordered by most recent.
Example request:
curl --request GET \
--get "https://tidybill.app/api/companies/2/members/invitations" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/companies/2/members/invitations"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"email": "[email protected]",
"role": "member",
"invited_by": "John",
"expires_at": "2026-04-12T00:00:00.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Cancel an invitation
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/companies/2/members/invitations/4" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/companies/2/members/invitations/4"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Resend an invitation
requires authentication
Regenerates the invitation token, extends the expiry by 7 days, and re-sends the email. Returns 422 if the invitation has already been accepted.
Example request:
curl --request POST \
"https://tidybill.app/api/companies/2/members/invitations/4/resend" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/companies/2/members/invitations/4/resend"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"message": "Invitation resent."
}
Example response (422):
{
"message": "Invitation already accepted."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a member's role
requires authentication
Changes the role on the company-user pivot. The owner role cannot be changed; returns 403 if attempted.
Example request:
curl --request PUT \
"https://tidybill.app/api/companies/2/members/2" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"role\": \"architecto\"
}"
const url = new URL(
"https://tidybill.app/api/companies/2/members/2"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"role": "architecto"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Member updated."
}
Example response (403):
{
"message": "Cannot change the owner role."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Remove a member
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/companies/2/members/2" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/companies/2/members/2"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Example response (403):
{
"message": "Cannot remove the company owner."
}
Example response (422):
{
"message": "You cannot remove yourself."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Clients
Bulk action on clients
requires authentication
Supported actions: archive, unarchive, delete. Returns the count of affected records.
Example request:
curl --request POST \
"https://tidybill.app/api/clients/bulk-action" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"ids\": [
16
],
\"action\": \"archive\"
}"
const url = new URL(
"https://tidybill.app/api/clients/bulk-action"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"ids": [
16
],
"action": "archive"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"affected": 3
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List clients
requires authentication
Supports name/email search and archived state filtering. Results are paginated and include active/archived counts in the meta.
Example request:
curl --request GET \
--get "https://tidybill.app/api/clients" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/clients"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 128,
"uuid": "d7179e59-a6fc-4543-ad1f-1a32b4d1bde9",
"name": "Price Ltd",
"email": "[email protected]",
"phone": "+14324666067",
"address_line_1": "26432 Leuschke Throughway Apt. 227",
"address_line_2": null,
"city": "Lake Audreyborough",
"state": "Montana",
"postal_code": "36080-0782",
"country": "VG",
"currency": "USD",
"payment_terms": 30,
"notes": null,
"is_archived": false,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
},
{
"id": 129,
"uuid": "5631d5c1-7b94-445f-b78a-96df0d7280b0",
"name": "Fahey, Cartwright and Balistreri",
"email": "[email protected]",
"phone": "1-346-252-9368",
"address_line_1": "20568 Murl Villages",
"address_line_2": null,
"city": "New Modesta",
"state": "Iowa",
"postal_code": "57582-4237",
"country": "TM",
"currency": "USD",
"payment_terms": 30,
"notes": null,
"is_archived": false,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a client
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/clients" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"email\": \"[email protected]\",
\"phone\": \"i\",
\"address_line_1\": \"y\",
\"address_line_2\": \"v\",
\"city\": \"d\",
\"state\": \"l\",
\"postal_code\": \"jnikhwaykcmyuwpw\",
\"country\": \"lv\",
\"currency\": \"qwr\",
\"payment_terms\": 10,
\"notes\": \"i\",
\"is_archived\": false
}"
const url = new URL(
"https://tidybill.app/api/clients"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"email": "[email protected]",
"phone": "i",
"address_line_1": "y",
"address_line_2": "v",
"city": "d",
"state": "l",
"postal_code": "jnikhwaykcmyuwpw",
"country": "lv",
"currency": "qwr",
"payment_terms": 10,
"notes": "i",
"is_archived": false
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 130,
"uuid": "d0ae1404-48ba-4c80-81e2-18f7d3562e16",
"name": "Fritsch-O'Keefe",
"email": "[email protected]",
"phone": "283.476.7809",
"address_line_1": "67339 Gaylord Meadow Suite 788",
"address_line_2": null,
"city": "Verliebury",
"state": "Colorado",
"postal_code": "61747-3805",
"country": "KP",
"currency": "USD",
"payment_terms": 30,
"notes": null,
"is_archived": false,
"created_at": "2026-04-05T19:38:14.000000Z",
"updated_at": "2026-04-05T19:38:14.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a client
requires authentication
Includes the client's contacts and associated projects.
Example request:
curl --request GET \
--get "https://tidybill.app/api/clients/4" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/clients/4"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 131,
"uuid": "fe59f5ac-a325-4d12-9ea3-af4385b8a9b1",
"name": "Price Ltd",
"email": "[email protected]",
"phone": "+14324666067",
"address_line_1": "26432 Leuschke Throughway Apt. 227",
"address_line_2": null,
"city": "Lake Audreyborough",
"state": "Montana",
"postal_code": "36080-0782",
"country": "VG",
"currency": "USD",
"payment_terms": 30,
"notes": null,
"is_archived": false,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a client
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/clients/4" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"email\": \"[email protected]\",
\"phone\": \"i\",
\"address_line_1\": \"y\",
\"address_line_2\": \"v\",
\"city\": \"d\",
\"state\": \"l\",
\"postal_code\": \"jnikhwaykcmyuwpw\",
\"country\": \"lv\",
\"currency\": \"qwr\",
\"payment_terms\": 10,
\"notes\": \"i\",
\"is_archived\": true
}"
const url = new URL(
"https://tidybill.app/api/clients/4"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"email": "[email protected]",
"phone": "i",
"address_line_1": "y",
"address_line_2": "v",
"city": "d",
"state": "l",
"postal_code": "jnikhwaykcmyuwpw",
"country": "lv",
"currency": "qwr",
"payment_terms": 10,
"notes": "i",
"is_archived": true
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 132,
"uuid": "b185a943-0f68-43f2-93a2-d720c608be3a",
"name": "Fritsch-O'Keefe",
"email": "[email protected]",
"phone": "283.476.7809",
"address_line_1": "67339 Gaylord Meadow Suite 788",
"address_line_2": null,
"city": "Verliebury",
"state": "Colorado",
"postal_code": "61747-3805",
"country": "KP",
"currency": "USD",
"payment_terms": 30,
"notes": null,
"is_archived": false,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a client
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/clients/4" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/clients/4"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Move client to another company
requires authentication
Moves the client and all their associated invoices, quotes, recurring invoices, projects, time entries, and expenses to the target company in a single transaction.
Example request:
curl --request POST \
"https://tidybill.app/api/clients/4/move" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"target_company_id\": 16
}"
const url = new URL(
"https://tidybill.app/api/clients/4/move"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"target_company_id": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 133,
"uuid": "345c2f0a-3b35-4f33-a9d5-80a146687983",
"name": "Bailey Ltd",
"email": "[email protected]",
"phone": "1-973-868-2042",
"address_line_1": "77432 Amber Crossing",
"address_line_2": null,
"city": "Leuschkeland",
"state": "Kentucky",
"postal_code": "25744",
"country": "MD",
"currency": "USD",
"payment_terms": 30,
"notes": null,
"is_archived": false,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Example response (422):
{
"data": {
"message": "Client is already in this company."
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get client stats
requires authentication
Returns total invoiced, total paid, outstanding balance (active invoices only), and total tracked hours for the client.
Example request:
curl --request GET \
--get "https://tidybill.app/api/clients/4/stats" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/clients/4/stats"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"total_invoiced": 10000,
"total_paid": 5000,
"outstanding": 5000,
"total_hours": 12.5
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get client statement data
requires authentication
Returns a list of non-draft, non-voided invoices for the date range with running balance, plus total invoiced, paid, and outstanding amounts.
Example request:
curl --request GET \
--get "https://tidybill.app/api/clients/4/statement?from=2026-01-01&to=2026-03-31" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"from\": \"2026-04-05T19:38:15\",
\"to\": \"2026-04-05T19:38:15\"
}"
const url = new URL(
"https://tidybill.app/api/clients/4/statement"
);
const params = {
"from": "2026-01-01",
"to": "2026-03-31",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"from": "2026-04-05T19:38:15",
"to": "2026-04-05T19:38:15"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"client": {},
"from": "2026-01-01",
"to": "2026-03-31",
"total_invoiced": 10000,
"total_paid": 5000,
"total_outstanding": 5000,
"items": []
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Download client statement as PDF
requires authentication
Generates a PDF statement using the company's default invoice template and streams it as a file download.
Example request:
curl --request GET \
--get "https://tidybill.app/api/clients/4/statement/pdf?from=2026-01-01&to=2026-03-31" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"from\": \"2026-04-05T19:38:15\",
\"to\": \"2026-04-05T19:38:15\"
}"
const url = new URL(
"https://tidybill.app/api/clients/4/statement/pdf"
);
const params = {
"from": "2026-01-01",
"to": "2026-03-31",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"from": "2026-04-05T19:38:15",
"to": "2026-04-05T19:38:15"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, PDF binary):
{
"content-type": "application/pdf"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Send client statement by email
requires authentication
Generates the statement PDF and emails it to the client's primary contact or email address. Accepts an optional note for a custom body and a cc list of additional recipients.
Example request:
curl --request POST \
"https://tidybill.app/api/clients/4/statement/send" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"from\": \"2026-04-05T19:38:15\",
\"to\": \"2026-04-05T19:38:15\",
\"note\": \"b\",
\"cc\": [
\"[email protected]\"
]
}"
const url = new URL(
"https://tidybill.app/api/clients/4/statement/send"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"from": "2026-04-05T19:38:15",
"to": "2026-04-05T19:38:15",
"note": "b",
"cc": [
"[email protected]"
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"message": "Statement sent successfully."
}
Example response (422):
{
"message": "Client has no email address."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List client contacts
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/clients/4/contacts" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/clients/4/contacts"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 124,
"name": "Morgan Hirthe",
"email": "[email protected]",
"phone": "+14324666067",
"is_primary": false,
"created_at": "2026-04-05T19:38:15.000000Z"
},
{
"id": 125,
"name": "Ms. Anais Conroy",
"email": "[email protected]",
"phone": "1-678-926-5062",
"is_primary": false,
"created_at": "2026-04-05T19:38:15.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a client contact
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/clients/4/contacts" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"email\": \"[email protected]\",
\"phone\": \"i\",
\"is_primary\": true
}"
const url = new URL(
"https://tidybill.app/api/clients/4/contacts"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"email": "[email protected]",
"phone": "i",
"is_primary": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 126,
"name": "Mr. Gerhard Dach Jr.",
"email": "[email protected]",
"phone": "+1-626-249-0432",
"is_primary": false,
"created_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a client contact
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/clients/4/contacts/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/clients/4/contacts/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 127,
"name": "Morgan Hirthe",
"email": "[email protected]",
"phone": "+14324666067",
"is_primary": false,
"created_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a client contact
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/clients/4/contacts/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"email\": \"[email protected]\",
\"phone\": \"i\",
\"is_primary\": true
}"
const url = new URL(
"https://tidybill.app/api/clients/4/contacts/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"email": "[email protected]",
"phone": "i",
"is_primary": true
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 128,
"name": "Mr. Gerhard Dach Jr.",
"email": "[email protected]",
"phone": "+1-626-249-0432",
"is_primary": false,
"created_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a client contact
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/clients/4/contacts/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/clients/4/contacts/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Invoices
Bulk action on invoices.
requires authentication
Supported actions: archive, unarchive, mark-sent (draft invoices only), delete (draft invoices only, also unbills linked time entries). Returns the count of affected records.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/bulk-action" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"ids\": [
16
],
\"action\": \"unarchive\"
}"
const url = new URL(
"https://tidybill.app/api/invoices/bulk-action"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"ids": [
16
],
"action": "unarchive"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"affected": 3
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Bulk record payments for multiple invoices.
requires authentication
Records a payment against each invoice in a single transaction. If send_notification is true, a payment received email is sent to each affected client.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/bulk-record-payment" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"payments\": [
{
\"invoice_id\": 16,
\"amount\": 22,
\"payment_date\": \"2026-04-05T19:38:19\",
\"payment_method\": \"g\",
\"reference\": \"z\",
\"notes\": \"m\"
}
],
\"send_notification\": false
}"
const url = new URL(
"https://tidybill.app/api/invoices/bulk-record-payment"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"payments": [
{
"invoice_id": 16,
"amount": 22,
"payment_date": "2026-04-05T19:38:19",
"payment_method": "g",
"reference": "z",
"notes": "m"
}
],
"send_notification": false
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"recorded": 2,
"errors": []
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List invoices.
requires authentication
Supports filtering by status, client_id, invoice_number, and a comma-separated ids list. Results are paginated and include aggregate totals and status counts in the meta.
Example request:
curl --request GET \
--get "https://tidybill.app/api/invoices?filter%5Bstatus%5D=sent&filter%5Bclient_id%5D=1&filter%5Binvoice_number%5D=INV-001&filter%5Bids%5D=1%2C2%2C3&sort=-issue_date" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices"
);
const params = {
"filter[status]": "sent",
"filter[client_id]": "1",
"filter[invoice_number]": "INV-001",
"filter[ids]": "1,2,3",
"sort": "-issue_date",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1599,
"uuid": "a2dbfa6b-cc9d-4b33-96f9-0c2b607910bb",
"client_id": 155,
"recurring_invoice_id": null,
"invoice_number": "INV-79198",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
},
{
"id": 1600,
"uuid": "458979ce-7d33-4578-a55f-d98c55ed88f2",
"client_id": 156,
"recurring_invoice_id": null,
"invoice_number": "INV-21385",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create an invoice.
requires authentication
Creates a draft invoice. Pass a line_items array to create and attach line items in the same request.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"client_id\": \"architecto\",
\"issue_date\": \"2026-04-05T19:38:19\",
\"due_date\": \"2052-04-28\",
\"currency\": \"ngz\",
\"discount_type\": \"percentage\",
\"discount_value\": 16,
\"notes\": \"n\",
\"terms\": \"g\",
\"footer\": \"z\",
\"is_archived\": false,
\"line_items\": [
{
\"description\": \"Velit et fugiat sunt nihil accusantium.\",
\"quantity\": 52,
\"unit_price\": 8,
\"tax_name\": \"k\",
\"tax_rate\": 14,
\"sort_order\": 16
}
]
}"
const url = new URL(
"https://tidybill.app/api/invoices"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"client_id": "architecto",
"issue_date": "2026-04-05T19:38:19",
"due_date": "2052-04-28",
"currency": "ngz",
"discount_type": "percentage",
"discount_value": 16,
"notes": "n",
"terms": "g",
"footer": "z",
"is_archived": false,
"line_items": [
{
"description": "Velit et fugiat sunt nihil accusantium.",
"quantity": 52,
"unit_price": 8,
"tax_name": "k",
"tax_rate": 14,
"sort_order": 16
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1601,
"uuid": "5ce597fd-4196-459a-ac53-a97436b8c7b6",
"client_id": 157,
"recurring_invoice_id": null,
"invoice_number": "INV-70546",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get an invoice.
requires authentication
Includes the client, all line items (including late fee items), and payment records.
Example request:
curl --request GET \
--get "https://tidybill.app/api/invoices/1088" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1602,
"uuid": "245c8ecd-a2e5-4497-85e0-fdad85549e30",
"client_id": 158,
"recurring_invoice_id": null,
"invoice_number": "INV-54634",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update an invoice.
requires authentication
If line_items is provided, the full set of line items is synced (added, updated, and removed). Omit line_items to update invoice fields only without touching line items.
Example request:
curl --request PUT \
"https://tidybill.app/api/invoices/1088" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"issue_date\": \"2026-04-05T19:38:19\",
\"due_date\": \"2052-04-28\",
\"currency\": \"ngz\",
\"discount_type\": \"percentage\",
\"discount_value\": 16,
\"notes\": \"n\",
\"terms\": \"g\",
\"footer\": \"z\",
\"is_archived\": true,
\"line_items\": [
{
\"description\": \"Velit et fugiat sunt nihil accusantium.\",
\"quantity\": 52,
\"unit_price\": 8,
\"tax_name\": \"k\",
\"tax_rate\": 14,
\"sort_order\": 16
}
]
}"
const url = new URL(
"https://tidybill.app/api/invoices/1088"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"issue_date": "2026-04-05T19:38:19",
"due_date": "2052-04-28",
"currency": "ngz",
"discount_type": "percentage",
"discount_value": 16,
"notes": "n",
"terms": "g",
"footer": "z",
"is_archived": true,
"line_items": [
{
"description": "Velit et fugiat sunt nihil accusantium.",
"quantity": 52,
"unit_price": 8,
"tax_name": "k",
"tax_rate": 14,
"sort_order": 16
}
]
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1603,
"uuid": "64c63ff1-396c-4f1f-a073-f1ff3c3a2184",
"client_id": 159,
"recurring_invoice_id": null,
"invoice_number": "INV-63526",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete an invoice.
requires authentication
Soft deletes the invoice and marks any linked time entries as unbilled so they can be re-invoiced.
Example request:
curl --request DELETE \
"https://tidybill.app/api/invoices/1088" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List line items for an invoice.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/invoices/1088/line-items" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/line-items"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 10149,
"service_id": null,
"time_entry_id": null,
"description": "Nostrum qui commodi incidunt iure.",
"quantity": 1.02,
"unit_price": "13053.000000",
"amount": 13314,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
},
{
"id": 10150,
"service_id": null,
"time_entry_id": null,
"description": "Nemo voluptate accusamus ut et.",
"quantity": 8.73,
"unit_price": "36405.000000",
"amount": 317816,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add a line item to an invoice.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/line-items" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"description\": \"Eius et animi quos velit et.\",
\"quantity\": 60,
\"unit_price\": 42,
\"tax_name_1\": \"l\",
\"tax_rate_1\": 19,
\"tax_name_2\": \"n\",
\"tax_rate_2\": 5,
\"sort_order\": 16
}"
const url = new URL(
"https://tidybill.app/api/invoices/1088/line-items"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"description": "Eius et animi quos velit et.",
"quantity": 60,
"unit_price": 42,
"tax_name_1": "l",
"tax_rate_1": 19,
"tax_name_2": "n",
"tax_rate_2": 5,
"sort_order": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 10151,
"service_id": null,
"time_entry_id": null,
"description": "Quos velit et fugiat sunt nihil accusantium harum.",
"quantity": 9.96,
"unit_price": "11278.000000",
"amount": 112329,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a line item on an invoice.
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/invoices/1088/line-items/20" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"description\": \"Eius et animi quos velit et.\",
\"quantity\": 60,
\"unit_price\": 42,
\"tax_name_1\": \"l\",
\"tax_rate_1\": 19,
\"tax_name_2\": \"n\",
\"tax_rate_2\": 5,
\"sort_order\": 16
}"
const url = new URL(
"https://tidybill.app/api/invoices/1088/line-items/20"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"description": "Eius et animi quos velit et.",
"quantity": 60,
"unit_price": 42,
"tax_name_1": "l",
"tax_rate_1": 19,
"tax_name_2": "n",
"tax_rate_2": 5,
"sort_order": 16
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 10152,
"service_id": null,
"time_entry_id": null,
"description": "Quos velit et fugiat sunt nihil accusantium harum.",
"quantity": 9.96,
"unit_price": "11278.000000",
"amount": 112329,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a line item from an invoice.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/invoices/1088/line-items/20" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/line-items/20"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Archive an invoice.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/archive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/archive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Unarchive an invoice.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/unarchive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/unarchive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Move an invoice to another company.
requires authentication
The authenticated user must have access to the target company. Reassigns the invoice, its payments, credits, time entries, and expenses to the target company.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/move" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/move"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1608,
"uuid": "3ea400bd-683a-4261-a610-0afa3c801d65",
"client_id": 164,
"recurring_invoice_id": null,
"invoice_number": "INV-80304",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Import time entries as line items onto an invoice.
requires authentication
Converts the specified unbilled time entries into invoice line items and marks them as billed. Accepts an array of time_entry_ids.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/import-time" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"time_entry_ids\": [
16
]
}"
const url = new URL(
"https://tidybill.app/api/invoices/1088/import-time"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"time_entry_ids": [
16
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1609,
"uuid": "fa2ce905-7b4f-4195-afcc-00ffb631e736",
"client_id": 165,
"recurring_invoice_id": null,
"invoice_number": "INV-24492",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Send an invoice by email.
requires authentication
Sends the invoice to the client's email address and transitions a draft invoice to sent status. Returns 422 if the client has no email configured.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/send" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/send"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1610,
"uuid": "c15f8d2e-94cf-4858-b434-429303945b48",
"client_id": 166,
"recurring_invoice_id": null,
"invoice_number": "INV-14650",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Mark an invoice as sent without emailing.
requires authentication
Transitions the invoice status to sent and logs the activity, but does not send any email to the client.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/mark-sent" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/mark-sent"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1611,
"uuid": "9b5f355b-edb9-48c6-863a-589f17b401ca",
"client_id": 167,
"recurring_invoice_id": null,
"invoice_number": "INV-62492",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Mark an invoice as fully paid.
requires authentication
Sets amount_paid to the invoice total and amount_due to zero in a single transaction, then transitions the status to paid.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/mark-paid" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/mark-paid"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1612,
"uuid": "501f9d28-97c4-4bfb-b2af-1d9e6114fda7",
"client_id": 168,
"recurring_invoice_id": null,
"invoice_number": "INV-45193",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Record a payment against an invoice.
requires authentication
Records a partial or full payment and updates amount_paid and amount_due. Automatically transitions the invoice to partial or paid status based on the remaining balance.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/record-payment" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"amount\": 1,
\"payment_date\": \"2026-04-05T19:38:19\",
\"payment_method\": \"card\",
\"reference\": \"n\",
\"notes\": \"g\"
}"
const url = new URL(
"https://tidybill.app/api/invoices/1088/record-payment"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"amount": 1,
"payment_date": "2026-04-05T19:38:19",
"payment_method": "card",
"reference": "n",
"notes": "g"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1613,
"uuid": "8f637055-efeb-4f8b-9aaa-8e46e57b8fac",
"client_id": 169,
"recurring_invoice_id": null,
"invoice_number": "INV-45117",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Void an invoice.
requires authentication
Transitions the invoice to cancelled status and marks any linked time entries as unbilled so they can be re-invoiced.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/void" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/void"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1614,
"uuid": "a712eab5-a5ad-4a1b-8496-4966297be422",
"client_id": 170,
"recurring_invoice_id": null,
"invoice_number": "INV-30805",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Duplicate an invoice.
requires authentication
Creates a new draft invoice with the same line items and client as the original, assigned the next available invoice number.
Example request:
curl --request POST \
"https://tidybill.app/api/invoices/1088/duplicate" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/duplicate"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1615,
"uuid": "526b85dc-f533-4d45-85fc-ea5483106ae6",
"client_id": 171,
"recurring_invoice_id": null,
"invoice_number": "INV-93082",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Download invoice as PDF.
requires authentication
Generates the invoice PDF on demand and streams it inline using the company's default template.
Example request:
curl --request GET \
--get "https://tidybill.app/api/invoices/1088/pdf" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invoices/1088/pdf"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, PDF file download):
Binary data -
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Quotes
Bulk action on quotes.
requires authentication
Supported actions: archive, unarchive, mark-sent (draft quotes only), delete (draft quotes only). Returns the count of affected records.
Example request:
curl --request POST \
"https://tidybill.app/api/quotes/bulk-action" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"ids\": [
16
],
\"action\": \"mark-sent\"
}"
const url = new URL(
"https://tidybill.app/api/quotes/bulk-action"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"ids": [
16
],
"action": "mark-sent"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"affected": 3
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List quotes.
requires authentication
Supports filtering by status, client_id, and quote_number. Results are paginated and include active/archived counts in the meta.
Example request:
curl --request GET \
--get "https://tidybill.app/api/quotes?filter%5Bstatus%5D=sent&filter%5Bclient_id%5D=1&filter%5Bquote_number%5D=QUO-001&sort=-issue_date" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes"
);
const params = {
"filter[status]": "sent",
"filter[client_id]": "1",
"filter[quote_number]": "QUO-001",
"sort": "-issue_date",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 154,
"uuid": "3a2924fe-1de9-4cf1-a89c-03baf0e76d42",
"client_id": 172,
"quote_number": "QUO-92439",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
},
{
"id": 155,
"uuid": "307265bb-89f7-4c75-b5e8-cc58585bbe7f",
"client_id": 173,
"quote_number": "QUO-09313",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a quote.
requires authentication
Creates a draft quote. Pass a line_items array to create and attach line items in the same request.
Example request:
curl --request POST \
"https://tidybill.app/api/quotes" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"client_id\": \"architecto\",
\"issue_date\": \"2026-04-05T19:38:19\",
\"expiry_date\": \"2052-04-28\",
\"currency\": \"ngz\",
\"discount_type\": \"percentage\",
\"discount_value\": 16,
\"notes\": \"n\",
\"terms\": \"g\",
\"footer\": \"z\",
\"is_archived\": true,
\"line_items\": [
{
\"description\": \"Velit et fugiat sunt nihil accusantium.\",
\"quantity\": 52,
\"unit_price\": 8,
\"tax_name\": \"k\",
\"tax_rate\": 14,
\"sort_order\": 16
}
]
}"
const url = new URL(
"https://tidybill.app/api/quotes"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"client_id": "architecto",
"issue_date": "2026-04-05T19:38:19",
"expiry_date": "2052-04-28",
"currency": "ngz",
"discount_type": "percentage",
"discount_value": 16,
"notes": "n",
"terms": "g",
"footer": "z",
"is_archived": true,
"line_items": [
{
"description": "Velit et fugiat sunt nihil accusantium.",
"quantity": 52,
"unit_price": 8,
"tax_name": "k",
"tax_rate": 14,
"sort_order": 16
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 156,
"uuid": "36c78c49-6db6-49a2-89b1-14e1472123d9",
"client_id": 174,
"quote_number": "QUO-26855",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a quote.
requires authentication
Includes the client and all line items.
Example request:
curl --request GET \
--get "https://tidybill.app/api/quotes/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 157,
"uuid": "5c547376-ab85-43b3-a8d4-78a9825f8192",
"client_id": 175,
"quote_number": "QUO-47352",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a quote.
requires authentication
If line_items is provided, the full set of line items is synced. Omit line_items to update quote fields only without touching line items.
Example request:
curl --request PUT \
"https://tidybill.app/api/quotes/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"issue_date\": \"2026-04-05T19:38:20\",
\"expiry_date\": \"2052-04-28\",
\"currency\": \"ngz\",
\"discount_type\": \"percentage\",
\"discount_value\": 16,
\"notes\": \"n\",
\"terms\": \"g\",
\"footer\": \"z\",
\"is_archived\": false,
\"line_items\": [
{
\"description\": \"Velit et fugiat sunt nihil accusantium.\",
\"quantity\": 52,
\"unit_price\": 8,
\"tax_name\": \"k\",
\"tax_rate\": 14,
\"sort_order\": 16
}
]
}"
const url = new URL(
"https://tidybill.app/api/quotes/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"issue_date": "2026-04-05T19:38:20",
"expiry_date": "2052-04-28",
"currency": "ngz",
"discount_type": "percentage",
"discount_value": 16,
"notes": "n",
"terms": "g",
"footer": "z",
"is_archived": false,
"line_items": [
{
"description": "Velit et fugiat sunt nihil accusantium.",
"quantity": 52,
"unit_price": 8,
"tax_name": "k",
"tax_rate": 14,
"sort_order": 16
}
]
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 158,
"uuid": "be67fa0b-ce1b-4e12-b309-0ff89a74305e",
"client_id": 176,
"quote_number": "QUO-23164",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a quote.
requires authentication
Only draft quotes can be deleted. Returns 422 for any other status.
Example request:
curl --request DELETE \
"https://tidybill.app/api/quotes/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List line items for a quote.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/quotes/1/line-items" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/line-items"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1512,
"service_id": null,
"time_entry_id": null,
"description": "Nostrum qui commodi incidunt iure.",
"quantity": 1.02,
"unit_price": "13053.000000",
"amount": 13314,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
},
{
"id": 1513,
"service_id": null,
"time_entry_id": null,
"description": "Nemo voluptate accusamus ut et.",
"quantity": 8.73,
"unit_price": "36405.000000",
"amount": 317816,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add a line item to a quote.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/quotes/1/line-items" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"description\": \"Eius et animi quos velit et.\",
\"quantity\": 60,
\"unit_price\": 42,
\"tax_name_1\": \"l\",
\"tax_rate_1\": 19,
\"tax_name_2\": \"n\",
\"tax_rate_2\": 5,
\"sort_order\": 16
}"
const url = new URL(
"https://tidybill.app/api/quotes/1/line-items"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"description": "Eius et animi quos velit et.",
"quantity": 60,
"unit_price": 42,
"tax_name_1": "l",
"tax_rate_1": 19,
"tax_name_2": "n",
"tax_rate_2": 5,
"sort_order": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1514,
"service_id": null,
"time_entry_id": null,
"description": "Quos velit et fugiat sunt nihil accusantium harum.",
"quantity": 9.96,
"unit_price": "11278.000000",
"amount": 112329,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a line item on a quote.
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/quotes/1/line-items/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"description\": \"Eius et animi quos velit et.\",
\"quantity\": 60,
\"unit_price\": 42,
\"tax_name_1\": \"l\",
\"tax_rate_1\": 19,
\"tax_name_2\": \"n\",
\"tax_rate_2\": 5,
\"sort_order\": 16
}"
const url = new URL(
"https://tidybill.app/api/quotes/1/line-items/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"description": "Eius et animi quos velit et.",
"quantity": 60,
"unit_price": 42,
"tax_name_1": "l",
"tax_rate_1": 19,
"tax_name_2": "n",
"tax_rate_2": 5,
"sort_order": 16
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1515,
"service_id": null,
"time_entry_id": null,
"description": "Quos velit et fugiat sunt nihil accusantium harum.",
"quantity": 9.96,
"unit_price": "11278.000000",
"amount": 112329,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a line item from a quote.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/quotes/1/line-items/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/line-items/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Archive a quote.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/quotes/1/archive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/archive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Unarchive a quote.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/quotes/1/unarchive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/unarchive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Send a quote by email.
requires authentication
Sends the quote to the client's email address and transitions the status to sent. Returns 422 if the client has no email configured.
Example request:
curl --request POST \
"https://tidybill.app/api/quotes/1/send" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/send"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 163,
"uuid": "f102d993-55e1-4722-9374-5c984455862a",
"client_id": 181,
"quote_number": "QUO-35644",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Mark a quote as sent without emailing.
requires authentication
Transitions the quote status to sent and logs the activity, but does not send any email to the client.
Example request:
curl --request POST \
"https://tidybill.app/api/quotes/1/mark-sent" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/mark-sent"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 164,
"uuid": "746befd7-b5a8-45fe-b63b-5a0e68a5f1d9",
"client_id": 182,
"quote_number": "QUO-08317",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Convert a quote to an invoice.
requires authentication
Creates a new draft invoice from the quote's line items and client. The quote must be in sent, viewed, or accepted status.
Example request:
curl --request POST \
"https://tidybill.app/api/quotes/1/convert-to-invoice" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/convert-to-invoice"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1616,
"uuid": "91cb4b60-aff6-4487-b436-46de2e36ed56",
"client_id": 183,
"recurring_invoice_id": null,
"invoice_number": "INV-98251",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Convert a quote to a project.
requires authentication
Creates a new project from the quote details. The quote must be in sent, viewed, or accepted status.
Example request:
curl --request POST \
"https://tidybill.app/api/quotes/1/convert-to-project" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/convert-to-project"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 43,
"client_id": 184,
"name": "implement intuitive e-tailers",
"status": "active",
"is_archived": false,
"billing_method": "hourly",
"hourly_rate": 14351,
"budget_hours": null,
"budget_amount": null,
"start_date": null,
"end_date": null,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Duplicate a quote.
requires authentication
Creates a new draft quote with the same line items and client as the original, assigned the next available quote number.
Example request:
curl --request POST \
"https://tidybill.app/api/quotes/1/duplicate" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/duplicate"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 165,
"uuid": "4b5d9dda-6611-407e-8472-ad4f39c91403",
"client_id": 185,
"quote_number": "QUO-82093",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Download quote as PDF.
requires authentication
Generates the quote PDF on demand and streams it inline using the company's default template.
Example request:
curl --request GET \
--get "https://tidybill.app/api/quotes/1/pdf" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/quotes/1/pdf"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, PDF file download):
Binary data -
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Projects
Bulk action on projects.
requires authentication
Supported actions: archive, unarchive, complete, delete. Returns the count of affected records.
Example request:
curl --request POST \
"https://tidybill.app/api/projects/bulk-action" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"ids\": [
16
],
\"action\": \"archive\"
}"
const url = new URL(
"https://tidybill.app/api/projects/bulk-action"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"ids": [
16
],
"action": "archive"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"affected": 3
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List projects.
requires authentication
Supports filtering by status, client_id, and name search. Results are paginated and include per-status and archived counts in the meta.
Example request:
curl --request GET \
--get "https://tidybill.app/api/projects" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/projects"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 37,
"client_id": 139,
"name": "implement intuitive e-tailers",
"status": "active",
"is_archived": false,
"billing_method": "hourly",
"hourly_rate": 14351,
"budget_hours": null,
"budget_amount": null,
"start_date": null,
"end_date": null,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
},
{
"id": 38,
"client_id": 140,
"name": "benchmark one-to-one infrastructures",
"status": "active",
"is_archived": false,
"billing_method": "hourly",
"hourly_rate": 21548,
"budget_hours": null,
"budget_amount": null,
"start_date": null,
"end_date": null,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a project.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/projects" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"client_id\": \"architecto\",
\"name\": \"n\",
\"status\": \"active\",
\"billing_method\": \"flat_rate\",
\"hourly_rate\": 84,
\"budget_hours\": 12,
\"budget_amount\": 77,
\"start_date\": \"2026-04-05T19:38:15\",
\"end_date\": \"2052-04-28\",
\"is_archived\": true
}"
const url = new URL(
"https://tidybill.app/api/projects"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"client_id": "architecto",
"name": "n",
"status": "active",
"billing_method": "flat_rate",
"hourly_rate": 84,
"budget_hours": 12,
"budget_amount": 77,
"start_date": "2026-04-05T19:38:15",
"end_date": "2052-04-28",
"is_archived": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 39,
"client_id": 141,
"name": "exploit scalable supply-chains",
"status": "active",
"is_archived": false,
"billing_method": "hourly",
"hourly_rate": 17320,
"budget_hours": null,
"budget_amount": null,
"start_date": null,
"end_date": null,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a project.
requires authentication
Includes the client and project members with their hourly rates.
Example request:
curl --request GET \
--get "https://tidybill.app/api/projects/7" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/projects/7"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 40,
"client_id": 142,
"name": "implement intuitive e-tailers",
"status": "active",
"is_archived": false,
"billing_method": "hourly",
"hourly_rate": 14351,
"budget_hours": null,
"budget_amount": null,
"start_date": null,
"end_date": null,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a project.
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/projects/7" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"status\": \"archived\",
\"billing_method\": \"flat_rate\",
\"hourly_rate\": 39,
\"budget_hours\": 84,
\"budget_amount\": 12,
\"start_date\": \"2026-04-05T19:38:15\",
\"end_date\": \"2052-04-28\",
\"is_archived\": false
}"
const url = new URL(
"https://tidybill.app/api/projects/7"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"status": "archived",
"billing_method": "flat_rate",
"hourly_rate": 39,
"budget_hours": 84,
"budget_amount": 12,
"start_date": "2026-04-05T19:38:15",
"end_date": "2052-04-28",
"is_archived": false
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 41,
"client_id": 143,
"name": "exploit scalable supply-chains",
"status": "active",
"is_archived": false,
"billing_method": "hourly",
"hourly_rate": 17320,
"budget_hours": null,
"budget_amount": null,
"start_date": null,
"end_date": null,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a project.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/projects/7" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/projects/7"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Move project to another company.
requires authentication
Moves the project (and its time entries) to a company the authenticated user has access to. Detaches all project members and clears the client association.
Example request:
curl --request POST \
"https://tidybill.app/api/projects/7/move" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"target_company_id\": 16
}"
const url = new URL(
"https://tidybill.app/api/projects/7/move"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"target_company_id": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 42,
"client_id": 144,
"name": "exploit scalable supply-chains",
"status": "active",
"is_archived": false,
"billing_method": "hourly",
"hourly_rate": 17320,
"budget_hours": null,
"budget_amount": null,
"start_date": null,
"end_date": null,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Archive a project.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/projects/7/archive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/projects/7/archive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Unarchive a project.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/projects/7/unarchive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/projects/7/unarchive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get project stats.
requires authentication
Returns hours breakdown (total, billed, unbilled) and budget info for the project.
Example request:
curl --request GET \
--get "https://tidybill.app/api/projects/7/stats" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/projects/7/stats"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"total_hours": 10.5,
"billed_hours": 4,
"unbilled_hours": 6.5,
"budget_hours": 20,
"budget_amount": 300000
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List project members.
requires authentication
Returns each member's user details and their project-specific hourly rate from the pivot table.
Example request:
curl --request GET \
--get "https://tidybill.app/api/projects/7/members" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/projects/7/members"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"name": "Jane Smith",
"email": "[email protected]",
"hourly_rate": 15000
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add a project member.
requires authentication
The user must be a member of the current company. Accepts an optional hourly_rate (in cents) to override the company default for this project.
Example request:
curl --request POST \
"https://tidybill.app/api/projects/7/members" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"user_id\": 16,
\"hourly_rate\": 39
}"
const url = new URL(
"https://tidybill.app/api/projects/7/members"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"user_id": 16,
"hourly_rate": 39
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"message": "Member added."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Remove a project member.
requires authentication
Detaches the user from the project. The user must be a member of the current company.
Example request:
curl --request DELETE \
"https://tidybill.app/api/projects/7/members/2" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/projects/7/members/2"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Time Tracking
Manage time entries for tracking billable and non-billable work. Time entries store a date and duration
(in seconds). The started_at, ended_at, and is_running fields in the response are read-only and only
populated when using the Timer endpoints (start/stop/current/discard). To create a manual time entry,
provide date and duration only. The hourly_rate is auto-resolved from project, service, or company
defaults if not provided. All money values (hourly_rate, calculated_amount) are integers in cents.
Resolve hourly rate.
requires authentication
Returns the hourly rate that would be auto-applied for a given project/service combination. Resolution order: project rate, service rate, company default rate.
Example request:
curl --request GET \
--get "https://tidybill.app/api/time-entries/resolve-rate?project_id=1&service_id=1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/time-entries/resolve-rate"
);
const params = {
"project_id": "1",
"service_id": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"rate": 15000,
"source": "project"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List unbilled time entries.
requires authentication
Returns billable entries that have not yet been added to an invoice. Excludes running timers.
Example request:
curl --request GET \
--get "https://tidybill.app/api/time-entries/unbilled" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/time-entries/unbilled"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 4818,
"user_id": 10,
"client_id": 145,
"project_id": null,
"service_id": null,
"date": "2026-03-11",
"duration": 5783,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 15266,
"notes": "Qui commodi incidunt iure odit.",
"calculated_amount": 24523,
"duration_hours": 1.61,
"created_at": "2026-04-05T19:38:16.000000Z",
"updated_at": "2026-04-05T19:38:16.000000Z"
},
{
"id": 4819,
"user_id": 11,
"client_id": 146,
"project_id": null,
"service_id": null,
"date": "2026-03-30",
"duration": 17654,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 5977,
"notes": "Ex repellendus assumenda et tenetur ab reiciendis.",
"calculated_amount": 29311,
"duration_hours": 4.9,
"created_at": "2026-04-05T19:38:16.000000Z",
"updated_at": "2026-04-05T19:38:16.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Bulk create time entries.
requires authentication
Create up to 50 time entries in a single request. Each entry uses the same schema as the single create endpoint.
Example request:
curl --request POST \
"https://tidybill.app/api/time-entries/bulk" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"entries\": [
{
\"client_id\": 1,
\"project_id\": 1,
\"service_id\": 1,
\"date\": \"2026-03-29\",
\"duration\": 3600,
\"is_billable\": true,
\"hourly_rate\": 15000,
\"notes\": \"API integration work\"
}
]
}"
const url = new URL(
"https://tidybill.app/api/time-entries/bulk"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"entries": [
{
"client_id": 1,
"project_id": 1,
"service_id": 1,
"date": "2026-03-29",
"duration": 3600,
"is_billable": true,
"hourly_rate": 15000,
"notes": "API integration work"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": []
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Bulk delete time entries.
requires authentication
Delete up to 100 time entries. Only deletes entries owned by the authenticated user.
Example request:
curl --request DELETE \
"https://tidybill.app/api/time-entries/bulk" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"ids\": [
1,
2,
3
]
}"
const url = new URL(
"https://tidybill.app/api/time-entries/bulk"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"ids": [
1,
2,
3
]
};
fetch(url, {
method: "DELETE",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List time entries.
requires authentication
Returns a paginated list of time entries, sorted by date descending by default.
Example request:
curl --request GET \
--get "https://tidybill.app/api/time-entries" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/time-entries"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 4820,
"user_id": 12,
"client_id": 147,
"project_id": null,
"service_id": null,
"date": "2026-03-11",
"duration": 5783,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 15266,
"notes": "Qui commodi incidunt iure odit.",
"calculated_amount": 24523,
"duration_hours": 1.61,
"created_at": "2026-04-05T19:38:16.000000Z",
"updated_at": "2026-04-05T19:38:16.000000Z"
},
{
"id": 4821,
"user_id": 13,
"client_id": 148,
"project_id": null,
"service_id": null,
"date": "2026-03-12",
"duration": 21598,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 12880,
"notes": "Repellendus assumenda et tenetur ab reiciendis.",
"calculated_amount": 77273,
"duration_hours": 6,
"created_at": "2026-04-05T19:38:17.000000Z",
"updated_at": "2026-04-05T19:38:17.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a time entry.
requires authentication
Create a manual time entry. Provide date (YYYY-MM-DD) and duration (in seconds, e.g. 3600 = 1 hour).
Do NOT send started_at, ended_at, or is_running - those are managed by the Timer endpoints.
If hourly_rate is omitted, it is auto-resolved from the project, service, or company default rate.
Example request:
curl --request POST \
"https://tidybill.app/api/time-entries" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"client_id\": 1,
\"project_id\": 1,
\"service_id\": 1,
\"date\": \"2026-03-29\",
\"duration\": 3600,
\"is_billable\": true,
\"is_billed\": false,
\"hourly_rate\": 15000,
\"notes\": \"API integration work\"
}"
const url = new URL(
"https://tidybill.app/api/time-entries"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"client_id": 1,
"project_id": 1,
"service_id": 1,
"date": "2026-03-29",
"duration": 3600,
"is_billable": true,
"is_billed": false,
"hourly_rate": 15000,
"notes": "API integration work"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 4822,
"user_id": 14,
"client_id": 149,
"project_id": null,
"service_id": null,
"date": "2026-03-16",
"duration": 1838,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 14575,
"notes": "Sunt nihil accusantium harum mollitia.",
"calculated_amount": 7441,
"duration_hours": 0.51,
"created_at": "2026-04-05T19:38:17.000000Z",
"updated_at": "2026-04-05T19:38:17.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a time entry.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/time-entries/5" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/time-entries/5"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 4823,
"user_id": 15,
"client_id": 150,
"project_id": null,
"service_id": null,
"date": "2026-03-11",
"duration": 5783,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 15266,
"notes": "Qui commodi incidunt iure odit.",
"calculated_amount": 24523,
"duration_hours": 1.61,
"created_at": "2026-04-05T19:38:17.000000Z",
"updated_at": "2026-04-05T19:38:17.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a time entry.
requires authentication
Same body schema as create. Only the entry owner or a company admin/owner can update.
Example request:
curl --request PUT \
"https://tidybill.app/api/time-entries/5" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"project_id\": \"architecto\",
\"service_id\": \"architecto\",
\"date\": \"2026-04-05T19:38:17\",
\"duration\": 39,
\"is_billable\": false,
\"is_billed\": false,
\"hourly_rate\": 84,
\"notes\": \"z\"
}"
const url = new URL(
"https://tidybill.app/api/time-entries/5"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"project_id": "architecto",
"service_id": "architecto",
"date": "2026-04-05T19:38:17",
"duration": 39,
"is_billable": false,
"is_billed": false,
"hourly_rate": 84,
"notes": "z"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 4824,
"user_id": 16,
"client_id": 151,
"project_id": null,
"service_id": null,
"date": "2026-03-16",
"duration": 1838,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 14575,
"notes": "Sunt nihil accusantium harum mollitia.",
"calculated_amount": 7441,
"duration_hours": 0.51,
"created_at": "2026-04-05T19:38:17.000000Z",
"updated_at": "2026-04-05T19:38:17.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a time entry.
requires authentication
Only the entry owner or a company admin/owner can delete.
Example request:
curl --request DELETE \
"https://tidybill.app/api/time-entries/5" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/time-entries/5"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Start a timer.
requires authentication
Starts a new running timer. If a timer is already running, it is stopped first.
The started_at is set to the current time. Duration is calculated when the timer is stopped.
Example request:
curl --request POST \
"https://tidybill.app/api/timer/start" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"client_id\": 1,
\"project_id\": 1,
\"service_id\": 1,
\"notes\": \"Working on API integration\",
\"is_billable\": true,
\"hourly_rate\": 15000
}"
const url = new URL(
"https://tidybill.app/api/timer/start"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"client_id": 1,
"project_id": 1,
"service_id": 1,
"notes": "Working on API integration",
"is_billable": true,
"hourly_rate": 15000
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 4825,
"user_id": 17,
"client_id": 152,
"project_id": null,
"service_id": null,
"date": "2026-04-01",
"duration": 21771,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 15990,
"notes": "Et fugiat sunt nihil accusantium.",
"calculated_amount": 96700,
"duration_hours": 6.05,
"created_at": "2026-04-05T19:38:18.000000Z",
"updated_at": "2026-04-05T19:38:18.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Stop the running timer.
requires authentication
Stops the current user's running timer, calculates the duration from started_at to now,
and sets ended_at. Returns 404 if no timer is running.
Example request:
curl --request PUT \
"https://tidybill.app/api/timer/stop" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/timer/stop"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "PUT",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 4826,
"user_id": 18,
"client_id": 153,
"project_id": null,
"service_id": null,
"date": "2026-03-11",
"duration": 5783,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 15266,
"notes": "Qui commodi incidunt iure odit.",
"calculated_amount": 24523,
"duration_hours": 1.61,
"created_at": "2026-04-05T19:38:18.000000Z",
"updated_at": "2026-04-05T19:38:18.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get current running timer.
requires authentication
Returns the current user's running timer, or {"data": null} if no timer is running.
Example request:
curl --request GET \
--get "https://tidybill.app/api/timer/current" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/timer/current"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 4827,
"user_id": 19,
"client_id": 154,
"project_id": null,
"service_id": null,
"date": "2026-03-11",
"duration": 5783,
"started_at": null,
"ended_at": null,
"is_running": false,
"is_billable": true,
"is_billed": false,
"invoice_id": null,
"hourly_rate": 15266,
"notes": "Qui commodi incidunt iure odit.",
"calculated_amount": 24523,
"duration_hours": 1.61,
"created_at": "2026-04-05T19:38:19.000000Z",
"updated_at": "2026-04-05T19:38:19.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Discard the running timer.
requires authentication
Deletes the running timer without saving. Returns 204 whether or not a timer was running.
Example request:
curl --request DELETE \
"https://tidybill.app/api/timer/discard" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/timer/discard"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Services
Bulk action on services.
requires authentication
Supported actions: activate, deactivate, delete. Returns the count of affected records.
Example request:
curl --request POST \
"https://tidybill.app/api/services/bulk-action" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"ids\": [
16
],
\"action\": \"deactivate\"
}"
const url = new URL(
"https://tidybill.app/api/services/bulk-action"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"ids": [
16
],
"action": "deactivate"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"affected": 3
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List services.
requires authentication
Supports filtering by is_active, type, and name search. Results are paginated with active/inactive counts in the meta.
Example request:
curl --request GET \
--get "https://tidybill.app/api/services" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/services"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 71,
"type": "service",
"name": "Development",
"description": "Quidem nostrum qui commodi incidunt iure odit.",
"default_rate": 14003,
"unit": "hour",
"tax_name_1": null,
"tax_rate_1": null,
"tax_name_2": null,
"tax_rate_2": null,
"is_active": true,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
},
{
"id": 72,
"type": "service",
"name": "Consulting",
"description": "Facere tempora ex voluptatem laboriosam.",
"default_rate": 14572,
"unit": "item",
"tax_name_1": null,
"tax_rate_1": null,
"tax_name_2": null,
"tax_rate_2": null,
"is_active": true,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a service.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/services" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"description\": \"Et animi quos velit et fugiat.\",
\"default_rate\": 42,
\"type\": \"product\",
\"unit\": \"ljnikhwaykcmyuwp\",
\"tax_name_1\": \"w\",
\"tax_rate_1\": 89,
\"tax_name_2\": \"v\",
\"tax_rate_2\": 3,
\"is_active\": false
}"
const url = new URL(
"https://tidybill.app/api/services"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"description": "Et animi quos velit et fugiat.",
"default_rate": 42,
"type": "product",
"unit": "ljnikhwaykcmyuwp",
"tax_name_1": "w",
"tax_rate_1": 89,
"tax_name_2": "v",
"tax_rate_2": 3,
"is_active": false
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 73,
"type": "service",
"name": "Consulting",
"description": "Et et modi ipsum nostrum.",
"default_rate": 13789,
"unit": "day",
"tax_name_1": null,
"tax_rate_1": null,
"tax_name_2": null,
"tax_rate_2": null,
"is_active": true,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a service.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/services/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/services/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 74,
"type": "service",
"name": "Development",
"description": "Quidem nostrum qui commodi incidunt iure odit.",
"default_rate": 14003,
"unit": "hour",
"tax_name_1": null,
"tax_rate_1": null,
"tax_name_2": null,
"tax_rate_2": null,
"is_active": true,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a service.
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/services/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"description\": \"Et animi quos velit et fugiat.\",
\"default_rate\": 42,
\"type\": \"product\",
\"unit\": \"ljnikhwaykcmyuwp\",
\"tax_name_1\": \"w\",
\"tax_rate_1\": 89,
\"tax_name_2\": \"v\",
\"tax_rate_2\": 3,
\"is_active\": false
}"
const url = new URL(
"https://tidybill.app/api/services/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"description": "Et animi quos velit et fugiat.",
"default_rate": 42,
"type": "product",
"unit": "ljnikhwaykcmyuwp",
"tax_name_1": "w",
"tax_rate_1": 89,
"tax_name_2": "v",
"tax_rate_2": 3,
"is_active": false
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 75,
"type": "service",
"name": "Consulting",
"description": "Et et modi ipsum nostrum.",
"default_rate": 13789,
"unit": "day",
"tax_name_1": null,
"tax_rate_1": null,
"tax_name_2": null,
"tax_rate_2": null,
"is_active": true,
"created_at": "2026-04-05T19:38:15.000000Z",
"updated_at": "2026-04-05T19:38:15.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a service.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/services/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/services/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Expenses
Bulk action on expenses.
requires authentication
Supported actions: archive, unarchive, delete. Returns the count of affected records.
Example request:
curl --request POST \
"https://tidybill.app/api/expenses/bulk-action" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"ids\": [
16
],
\"action\": \"unarchive\"
}"
const url = new URL(
"https://tidybill.app/api/expenses/bulk-action"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"ids": [
16
],
"action": "unarchive"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"affected": 3
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List expenses.
requires authentication
Supports filtering by category, client_id, project_id, is_billable, is_billed, and date range. Meta includes aggregate totals for active expenses.
Example request:
curl --request GET \
--get "https://tidybill.app/api/expenses" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/expenses"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"user_id": 20,
"client_id": null,
"project_id": null,
"invoice_id": null,
"category": "other",
"vendor": null,
"description": null,
"amount": 5000,
"currency": "USD",
"tax_amount": 0,
"tax_name": null,
"tax_rate": 0,
"expense_date": "2026-04-05",
"status": "pending",
"is_archived": false,
"is_billable": false,
"is_billed": false,
"receipt_path": null,
"reference": null,
"created_at": "2026-04-05T19:38:20.000000Z",
"updated_at": "2026-04-05T19:38:20.000000Z"
},
{
"id": 2,
"user_id": 21,
"client_id": null,
"project_id": null,
"invoice_id": null,
"category": "other",
"vendor": null,
"description": null,
"amount": 5000,
"currency": "USD",
"tax_amount": 0,
"tax_name": null,
"tax_rate": 0,
"expense_date": "2026-04-05",
"status": "pending",
"is_archived": false,
"is_billable": false,
"is_billed": false,
"receipt_path": null,
"reference": null,
"created_at": "2026-04-05T19:38:21.000000Z",
"updated_at": "2026-04-05T19:38:21.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create an expense.
requires authentication
Creates an expense attributed to the authenticated user. Currency defaults to the company currency if not provided.
Example request:
curl --request POST \
"https://tidybill.app/api/expenses" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"category\": \"meals\",
\"vendor\": \"b\",
\"description\": \"Et animi quos velit et fugiat.\",
\"amount\": 26,
\"currency\": \"l\",
\"tax_amount\": 9,
\"tax_name\": \"n\",
\"tax_rate\": 5,
\"expense_date\": \"2026-04-05T19:38:21\",
\"is_billable\": false,
\"reference\": \"k\"
}"
const url = new URL(
"https://tidybill.app/api/expenses"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"category": "meals",
"vendor": "b",
"description": "Et animi quos velit et fugiat.",
"amount": 26,
"currency": "l",
"tax_amount": 9,
"tax_name": "n",
"tax_rate": 5,
"expense_date": "2026-04-05T19:38:21",
"is_billable": false,
"reference": "k"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 3,
"user_id": 22,
"client_id": null,
"project_id": null,
"invoice_id": null,
"category": "other",
"vendor": null,
"description": null,
"amount": 5000,
"currency": "USD",
"tax_amount": 0,
"tax_name": null,
"tax_rate": 0,
"expense_date": "2026-04-05",
"status": "pending",
"is_archived": false,
"is_billable": false,
"is_billed": false,
"receipt_path": null,
"reference": null,
"created_at": "2026-04-05T19:38:21.000000Z",
"updated_at": "2026-04-05T19:38:21.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get an expense.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/expenses/16" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/expenses/16"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 4,
"user_id": 23,
"client_id": null,
"project_id": null,
"invoice_id": null,
"category": "other",
"vendor": null,
"description": null,
"amount": 5000,
"currency": "USD",
"tax_amount": 0,
"tax_name": null,
"tax_rate": 0,
"expense_date": "2026-04-05",
"status": "pending",
"is_archived": false,
"is_billable": false,
"is_billed": false,
"receipt_path": null,
"reference": null,
"created_at": "2026-04-05T19:38:21.000000Z",
"updated_at": "2026-04-05T19:38:21.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update an expense.
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/expenses/16" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"category\": \"advertising\",
\"vendor\": \"b\",
\"description\": \"Et animi quos velit et fugiat.\",
\"amount\": 26,
\"currency\": \"l\",
\"tax_amount\": 9,
\"tax_name\": \"n\",
\"tax_rate\": 5,
\"expense_date\": \"2026-04-05T19:38:21\",
\"is_billable\": true,
\"reference\": \"k\"
}"
const url = new URL(
"https://tidybill.app/api/expenses/16"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"category": "advertising",
"vendor": "b",
"description": "Et animi quos velit et fugiat.",
"amount": 26,
"currency": "l",
"tax_amount": 9,
"tax_name": "n",
"tax_rate": 5,
"expense_date": "2026-04-05T19:38:21",
"is_billable": true,
"reference": "k"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 5,
"user_id": 24,
"client_id": null,
"project_id": null,
"invoice_id": null,
"category": "other",
"vendor": null,
"description": null,
"amount": 5000,
"currency": "USD",
"tax_amount": 0,
"tax_name": null,
"tax_rate": 0,
"expense_date": "2026-04-05",
"status": "pending",
"is_archived": false,
"is_billable": false,
"is_billed": false,
"receipt_path": null,
"reference": null,
"created_at": "2026-04-05T19:38:21.000000Z",
"updated_at": "2026-04-05T19:38:21.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete an expense.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/expenses/16" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/expenses/16"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Archive an expense.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/expenses/16/archive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/expenses/16/archive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Unarchive an expense.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/expenses/16/unarchive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/expenses/16/unarchive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Upload a receipt for an expense.
requires authentication
Accepts JPEG, PNG, GIF, WebP, or PDF files up to 5 MB.
Example request:
curl --request POST \
"https://tidybill.app/api/expenses/16/receipt" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"receipt\": \"b\"
}"
const url = new URL(
"https://tidybill.app/api/expenses/16/receipt"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"receipt": "b"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 6,
"user_id": 25,
"client_id": null,
"project_id": null,
"invoice_id": null,
"category": "other",
"vendor": null,
"description": null,
"amount": 5000,
"currency": "USD",
"tax_amount": 0,
"tax_name": null,
"tax_rate": 0,
"expense_date": "2026-04-05",
"status": "pending",
"is_archived": false,
"is_billable": false,
"is_billed": false,
"receipt_path": null,
"reference": null,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Download an expense receipt.
requires authentication
Streams the uploaded receipt file as a download. Returns 404 if no receipt is attached.
Example request:
curl --request GET \
--get "https://tidybill.app/api/expenses/16/receipt" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/expenses/16/receipt"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
file
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Credits
Bulk action on credits.
requires authentication
Supported actions: archive, unarchive, delete. Returns the count of affected records.
Example request:
curl --request POST \
"https://tidybill.app/api/credits/bulk-action" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"ids\": [
16
],
\"action\": \"unarchive\"
}"
const url = new URL(
"https://tidybill.app/api/credits/bulk-action"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"ids": [
16
],
"action": "unarchive"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"affected": 3
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List credits.
requires authentication
Supports filtering by client_id, type, and has_balance. Results are paginated with active/archived counts in the meta.
Example request:
curl --request GET \
--get "https://tidybill.app/api/credits" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/credits"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"client_id": 186,
"invoice_id": null,
"type": "credit_note",
"is_archived": false,
"amount": 26768,
"balance": 26768,
"currency": "USD",
"description": "Quidem nostrum qui commodi incidunt iure odit.",
"date": "2026-04-05T00:00:00.000000Z",
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
},
{
"id": 2,
"client_id": 187,
"invoice_id": null,
"type": "credit_note",
"is_archived": false,
"amount": 13913,
"balance": 13913,
"currency": "USD",
"description": "Ratione nemo voluptate accusamus ut et recusandae modi rerum.",
"date": "2026-04-05T00:00:00.000000Z",
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a credit.
requires authentication
Creates a credit note for a client. The initial balance is automatically set equal to amount. Currency defaults to the company currency if not provided.
Example request:
curl --request POST \
"https://tidybill.app/api/credits" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"client_id\": \"architecto\",
\"type\": \"overpayment\",
\"amount\": 22,
\"currency\": \"gzm\",
\"description\": \"Et fugiat sunt nihil accusantium.\",
\"date\": \"2026-04-05T19:38:22\"
}"
const url = new URL(
"https://tidybill.app/api/credits"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"client_id": "architecto",
"type": "overpayment",
"amount": 22,
"currency": "gzm",
"description": "Et fugiat sunt nihil accusantium.",
"date": "2026-04-05T19:38:22"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 3,
"client_id": 188,
"invoice_id": null,
"type": "credit_note",
"is_archived": false,
"amount": 21638,
"balance": 21638,
"currency": "USD",
"description": "Modi deserunt aut ab provident perspiciatis.",
"date": "2026-04-05T00:00:00.000000Z",
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a credit.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/credits/16" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/credits/16"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 4,
"client_id": 189,
"invoice_id": null,
"type": "credit_note",
"is_archived": false,
"amount": 26768,
"balance": 26768,
"currency": "USD",
"description": "Quidem nostrum qui commodi incidunt iure odit.",
"date": "2026-04-05T00:00:00.000000Z",
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Archive a credit.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/credits/16/archive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/credits/16/archive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Unarchive a credit.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/credits/16/unarchive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/credits/16/unarchive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get client credit balance.
requires authentication
Returns the total available credit balance and list of credits with a remaining balance for the given client.
Example request:
curl --request GET \
--get "https://tidybill.app/api/clients/4/credits" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/clients/4/credits"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"client_id": 1,
"available_balance": 5000,
"credits": []
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Apply credit to an invoice.
requires authentication
Applies part or all of a credit's balance to an outstanding invoice. Both must belong to the same client. The invoice must be in a payable state (sent, viewed, partial, or overdue). Automatically transitions the invoice to paid or partial based on the resulting amount_due.
Example request:
curl --request POST \
"https://tidybill.app/api/credits/16/apply" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"invoice_id\": \"architecto\",
\"amount\": 22
}"
const url = new URL(
"https://tidybill.app/api/credits/16/apply"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"invoice_id": "architecto",
"amount": 22
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 5,
"client_id": 190,
"invoice_id": null,
"type": "credit_note",
"is_archived": false,
"amount": 11278,
"balance": 11278,
"currency": "USD",
"description": "Quos velit et fugiat sunt nihil accusantium harum.",
"date": "2026-04-05T00:00:00.000000Z",
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Recurring Invoices
Bulk action on recurring invoices.
requires authentication
Supported actions: activate, pause, archive, delete. Returns the count of affected records.
Example request:
curl --request POST \
"https://tidybill.app/api/recurring-invoices/bulk-action" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"ids\": [
16
],
\"action\": \"architecto\"
}"
const url = new URL(
"https://tidybill.app/api/recurring-invoices/bulk-action"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"ids": [
16
],
"action": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"affected": 3
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List recurring invoices.
requires authentication
Supports filtering by status, client_id, and frequency. Results are paginated and include per-status counts in the meta.
Example request:
curl --request GET \
--get "https://tidybill.app/api/recurring-invoices?filter%5Bstatus%5D=active&filter%5Bclient_id%5D=1&filter%5Bfrequency%5D=monthly&sort=next_generate_date" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices"
);
const params = {
"filter[status]": "active",
"filter[client_id]": "1",
"filter[frequency]": "monthly",
"sort": "next_generate_date",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 38,
"client_id": 191,
"title": "Adipisci quidem nostrum qui.",
"status": "active",
"is_archived": false,
"frequency": "monthly",
"frequency_label": "Monthly",
"interval": 1,
"start_date": "2026-04-05",
"end_date": null,
"next_generate_date": "2026-04-05",
"last_generated_at": null,
"auto_send": false,
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"payment_terms": 30,
"occurrences_generated": 0,
"max_occurrences": null,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
},
{
"id": 39,
"client_id": 192,
"title": "Qui repudiandae laboriosam.",
"status": "active",
"is_archived": false,
"frequency": "monthly",
"frequency_label": "Monthly",
"interval": 1,
"start_date": "2026-04-05",
"end_date": null,
"next_generate_date": "2026-04-05",
"last_generated_at": null,
"auto_send": false,
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"payment_terms": 30,
"occurrences_generated": 0,
"max_occurrences": null,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a recurring invoice.
requires authentication
Creates an active recurring invoice template. The next_generate_date is set to start_date and invoices are generated automatically on schedule. Pass line_items to attach them in the same request.
Example request:
curl --request POST \
"https://tidybill.app/api/recurring-invoices" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"client_id\": \"architecto\",
\"title\": \"n\",
\"frequency\": \"quarterly\",
\"interval\": 67,
\"start_date\": \"2026-04-05T19:38:22\",
\"end_date\": \"2052-04-28\",
\"auto_send\": false,
\"currency\": \"ngz\",
\"discount_type\": \"percentage\",
\"discount_value\": 16,
\"notes\": \"n\",
\"payment_terms\": 84,
\"next_generate_date\": \"2026-04-05T19:38:22\",
\"max_occurrences\": 66,
\"line_items\": [
{
\"description\": \"Velit et fugiat sunt nihil accusantium.\",
\"quantity\": 52,
\"unit_price\": 8,
\"tax_name\": \"k\",
\"tax_rate\": 14,
\"sort_order\": 16
}
]
}"
const url = new URL(
"https://tidybill.app/api/recurring-invoices"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"client_id": "architecto",
"title": "n",
"frequency": "quarterly",
"interval": 67,
"start_date": "2026-04-05T19:38:22",
"end_date": "2052-04-28",
"auto_send": false,
"currency": "ngz",
"discount_type": "percentage",
"discount_value": 16,
"notes": "n",
"payment_terms": 84,
"next_generate_date": "2026-04-05T19:38:22",
"max_occurrences": 66,
"line_items": [
{
"description": "Velit et fugiat sunt nihil accusantium.",
"quantity": 52,
"unit_price": 8,
"tax_name": "k",
"tax_rate": 14,
"sort_order": 16
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 40,
"client_id": 193,
"title": "Et animi quos.",
"status": "active",
"is_archived": false,
"frequency": "monthly",
"frequency_label": "Monthly",
"interval": 1,
"start_date": "2026-04-05",
"end_date": null,
"next_generate_date": "2026-04-05",
"last_generated_at": null,
"auto_send": false,
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"payment_terms": 30,
"occurrences_generated": 0,
"max_occurrences": null,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a recurring invoice.
requires authentication
Includes line items, client, and the list of invoices generated from this template.
Example request:
curl --request GET \
--get "https://tidybill.app/api/recurring-invoices/6" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices/6"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 41,
"client_id": 194,
"title": "Adipisci quidem nostrum qui.",
"status": "active",
"is_archived": false,
"frequency": "monthly",
"frequency_label": "Monthly",
"interval": 1,
"start_date": "2026-04-05",
"end_date": null,
"next_generate_date": "2026-04-05",
"last_generated_at": null,
"auto_send": false,
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"payment_terms": 30,
"occurrences_generated": 0,
"max_occurrences": null,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a recurring invoice.
requires authentication
If line_items is provided, the full set of line items is synced. Omit line_items to update template fields only without touching line items.
Example request:
curl --request PUT \
"https://tidybill.app/api/recurring-invoices/6" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"title\": \"b\",
\"frequency\": \"quarterly\",
\"interval\": 22,
\"start_date\": \"2026-04-05T19:38:22\",
\"end_date\": \"2052-04-28\",
\"auto_send\": false,
\"currency\": \"ngz\",
\"discount_type\": \"percentage\",
\"discount_value\": 16,
\"notes\": \"n\",
\"payment_terms\": 84,
\"next_generate_date\": \"2026-04-05T19:38:22\",
\"max_occurrences\": 66,
\"line_items\": [
{
\"description\": \"Velit et fugiat sunt nihil accusantium.\",
\"quantity\": 52,
\"unit_price\": 8,
\"tax_name\": \"k\",
\"tax_rate\": 14,
\"sort_order\": 16
}
]
}"
const url = new URL(
"https://tidybill.app/api/recurring-invoices/6"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"title": "b",
"frequency": "quarterly",
"interval": 22,
"start_date": "2026-04-05T19:38:22",
"end_date": "2052-04-28",
"auto_send": false,
"currency": "ngz",
"discount_type": "percentage",
"discount_value": 16,
"notes": "n",
"payment_terms": 84,
"next_generate_date": "2026-04-05T19:38:22",
"max_occurrences": 66,
"line_items": [
{
"description": "Velit et fugiat sunt nihil accusantium.",
"quantity": 52,
"unit_price": 8,
"tax_name": "k",
"tax_rate": 14,
"sort_order": 16
}
]
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 42,
"client_id": 195,
"title": "Et animi quos.",
"status": "active",
"is_archived": false,
"frequency": "monthly",
"frequency_label": "Monthly",
"interval": 1,
"start_date": "2026-04-05",
"end_date": null,
"next_generate_date": "2026-04-05",
"last_generated_at": null,
"auto_send": false,
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"payment_terms": 30,
"occurrences_generated": 0,
"max_occurrences": null,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a recurring invoice.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/recurring-invoices/6" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices/6"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List line items for a recurring invoice.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/recurring-invoices/6/line-items" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/line-items"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 87,
"service_id": null,
"time_entry_id": null,
"description": "Nostrum qui commodi incidunt iure.",
"quantity": 1.02,
"unit_price": "13053.000000",
"amount": 13314,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
},
{
"id": 88,
"service_id": null,
"time_entry_id": null,
"description": "Nemo voluptate accusamus ut et.",
"quantity": 8.73,
"unit_price": "36405.000000",
"amount": 317816,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add a line item to a recurring invoice.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/recurring-invoices/6/line-items" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"description\": \"Eius et animi quos velit et.\",
\"quantity\": 60,
\"unit_price\": 42,
\"tax_name_1\": \"l\",
\"tax_rate_1\": 19,
\"tax_name_2\": \"n\",
\"tax_rate_2\": 5,
\"sort_order\": 16
}"
const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/line-items"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"description": "Eius et animi quos velit et.",
"quantity": 60,
"unit_price": 42,
"tax_name_1": "l",
"tax_rate_1": 19,
"tax_name_2": "n",
"tax_rate_2": 5,
"sort_order": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 89,
"service_id": null,
"time_entry_id": null,
"description": "Quos velit et fugiat sunt nihil accusantium harum.",
"quantity": 9.96,
"unit_price": "11278.000000",
"amount": 112329,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a line item on a recurring invoice.
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/recurring-invoices/6/line-items/4" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"description\": \"Eius et animi quos velit et.\",
\"quantity\": 60,
\"unit_price\": 42,
\"tax_name_1\": \"l\",
\"tax_rate_1\": 19,
\"tax_name_2\": \"n\",
\"tax_rate_2\": 5,
\"sort_order\": 16
}"
const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/line-items/4"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"description": "Eius et animi quos velit et.",
"quantity": 60,
"unit_price": 42,
"tax_name_1": "l",
"tax_rate_1": 19,
"tax_name_2": "n",
"tax_rate_2": 5,
"sort_order": 16
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 90,
"service_id": null,
"time_entry_id": null,
"description": "Quos velit et fugiat sunt nihil accusantium harum.",
"quantity": 9.96,
"unit_price": "11278.000000",
"amount": 112329,
"tax_name_1": null,
"tax_rate_1": null,
"tax_amount_1": 0,
"tax_name_2": null,
"tax_rate_2": null,
"tax_amount_2": 0,
"sort_order": 0,
"is_late_fee": false,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a line item from a recurring invoice.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/recurring-invoices/6/line-items/4" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/line-items/4"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Move a recurring invoice to another company.
requires authentication
Reassigns the template to the target company. Clears the client association, nullifies service references on line items, and unlinks previously generated invoices in the source company.
Example request:
curl --request POST \
"https://tidybill.app/api/recurring-invoices/6/move" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"target_company_id\": 16
}"
const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/move"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"target_company_id": 16
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 47,
"client_id": 200,
"title": "Et animi quos.",
"status": "active",
"is_archived": false,
"frequency": "monthly",
"frequency_label": "Monthly",
"interval": 1,
"start_date": "2026-04-05",
"end_date": null,
"next_generate_date": "2026-04-05",
"last_generated_at": null,
"auto_send": false,
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"payment_terms": 30,
"occurrences_generated": 0,
"max_occurrences": null,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Archive a recurring invoice.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/recurring-invoices/6/archive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/archive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Unarchive a recurring invoice.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/recurring-invoices/6/unarchive" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/unarchive"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Pause a recurring invoice.
requires authentication
Stops automatic invoice generation. Only active recurring invoices can be paused.
Example request:
curl --request POST \
"https://tidybill.app/api/recurring-invoices/6/pause" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/pause"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 48,
"client_id": 201,
"title": "Adipisci quidem nostrum qui.",
"status": "active",
"is_archived": false,
"frequency": "monthly",
"frequency_label": "Monthly",
"interval": 1,
"start_date": "2026-04-05",
"end_date": null,
"next_generate_date": "2026-04-05",
"last_generated_at": null,
"auto_send": false,
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"payment_terms": 30,
"occurrences_generated": 0,
"max_occurrences": null,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Resume a paused recurring invoice.
requires authentication
Resumes automatic invoice generation by setting the status back to active. Only paused recurring invoices can be resumed.
Example request:
curl --request POST \
"https://tidybill.app/api/recurring-invoices/6/resume" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/resume"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 49,
"client_id": 202,
"title": "Adipisci quidem nostrum qui.",
"status": "active",
"is_archived": false,
"frequency": "monthly",
"frequency_label": "Monthly",
"interval": 1,
"start_date": "2026-04-05",
"end_date": null,
"next_generate_date": "2026-04-05",
"last_generated_at": null,
"auto_send": false,
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"payment_terms": 30,
"occurrences_generated": 0,
"max_occurrences": null,
"created_at": "2026-04-05T19:38:22.000000Z",
"updated_at": "2026-04-05T19:38:22.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Manually generate an invoice from a recurring invoice.
requires authentication
Immediately generates the next invoice regardless of the scheduled date. Returns 422 if the maximum occurrence limit has been reached.
Example request:
curl --request POST \
"https://tidybill.app/api/recurring-invoices/6/generate" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/recurring-invoices/6/generate"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1617,
"uuid": "5134a4d7-1b9b-43c7-bc68-9dbc2f15bb1f",
"client_id": 203,
"recurring_invoice_id": null,
"invoice_number": "INV-52976",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"due_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"amount_paid": 0,
"amount_due": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"late_fee_amount": 0,
"late_fee_applied_at": null,
"reminders_sent": 0,
"created_at": "2026-04-05T19:38:23.000000Z",
"updated_at": "2026-04-05T19:38:23.000000Z"
},
"status": "201"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Billing
List Stripe prices for all plans.
requires authentication
Returns active recurring prices per plan keyed by plan name. Cached for one hour.
Example request:
curl --request GET \
--get "https://tidybill.app/api/billing/prices" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/billing/prices"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"starter": [
{
"price_id": "price_starter_monthly",
"amount": 900,
"currency": "usd",
"interval": "month",
"interval_count": 1
},
{
"price_id": "price_starter_annual",
"amount": 700,
"currency": "usd",
"interval": "month",
"interval_count": 1
}
],
"pro": [
{
"price_id": "price_pro_monthly",
"amount": 1800,
"currency": "usd",
"interval": "month",
"interval_count": 1
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get current subscription.
requires authentication
Returns the authenticated user's active subscription or a default free plan object.
Example request:
curl --request GET \
--get "https://tidybill.app/api/billing/subscription" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/billing/subscription"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"plan": "starter",
"status": "active",
"trial_ends_at": null,
"current_period_end": "2026-05-01T00:00:00Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List available plans.
requires authentication
Returns all plan tiers and their feature limits.
Example request:
curl --request GET \
--get "https://tidybill.app/api/billing/plans" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/billing/plans"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"plan": "free",
"max_clients": 5,
"max_invoices_per_month": 5,
"max_projects": 3,
"max_members": 1,
"max_companies": 1,
"feature_api_access": false,
"feature_recurring_invoices": false,
"feature_time_tracking": false,
"feature_quotes": false,
"feature_client_portal": false
},
{
"id": 2,
"plan": "starter",
"max_clients": 25,
"max_invoices_per_month": 50,
"max_projects": 10,
"max_members": 3,
"max_companies": 2,
"feature_api_access": false,
"feature_recurring_invoices": true,
"feature_time_tracking": true,
"feature_quotes": true,
"feature_client_portal": true
},
{
"id": 3,
"plan": "pro",
"max_clients": null,
"max_invoices_per_month": null,
"max_projects": null,
"max_members": 25,
"max_companies": 5,
"feature_api_access": true,
"feature_recurring_invoices": true,
"feature_time_tracking": true,
"feature_quotes": true,
"feature_client_portal": true
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get current plan usage.
requires authentication
Returns usage counts for the authenticated user's companies against their plan limits.
Example request:
curl --request GET \
--get "https://tidybill.app/api/billing/usage" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/billing/usage"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"clients": 12,
"invoices_this_month": 8,
"projects": 4,
"team_members": 2,
"companies": 1,
"max_companies": 2
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List Stripe billing invoices.
requires authentication
Returns the last 12 Stripe invoices for the authenticated user's subscription. Returns an empty array if the user has no Stripe customer ID.
Example request:
curl --request GET \
--get "https://tidybill.app/api/billing/invoices" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/billing/invoices"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": "in_xxx",
"number": "INV-0001",
"amount": 900,
"currency": "USD",
"status": "paid",
"date": "2026-03-01",
"pdf_url": "https://pay.stripe.com/invoice/xxx/pdf"
}
]
}
Example response (200, No Stripe customer):
{
"data": []
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a Stripe Checkout session.
requires authentication
Creates a Stripe Checkout session for the given plan and price. Returns the hosted checkout URL.
Example request:
curl --request POST \
"https://tidybill.app/api/billing/checkout" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"plan\": \"starter\",
\"price_id\": \"price_abc123\"
}"
const url = new URL(
"https://tidybill.app/api/billing/checkout"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"plan": "starter",
"price_id": "price_abc123"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"checkout_url": "https://checkout.stripe.com/pay/cs_live_xxx",
"plan": "starter"
}
}
Example response (422):
{
"message": "Plan not configured."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Stripe Billing Portal URL.
requires authentication
Returns a URL to the Stripe Customer Portal where the user can manage their subscription.
Returns null if the user has no Stripe customer ID.
Example request:
curl --request POST \
"https://tidybill.app/api/billing/portal" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/billing/portal"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"portal_url": "https://billing.stripe.com/session/xxx"
}
}
Example response (200, No Stripe customer):
{
"data": {
"portal_url": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Settings
Get company settings.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 178,
"uuid": "53a3ef0f-7120-4769-9543-8498f8da2a40",
"name": "Price Ltd",
"legal_name": null,
"email": "[email protected]",
"phone": null,
"address_line_1": null,
"address_line_2": null,
"city": null,
"state": null,
"postal_code": null,
"country": null,
"currency": "USD",
"tax_number": null,
"logo_path": null,
"invoice_prefix": "INV-",
"quote_prefix": "QUO-",
"invoice_layout": "classic",
"default_payment_terms": 30,
"default_hourly_rate": null,
"default_tax_name_1": null,
"default_tax_rate_1": null,
"default_tax_name_2": null,
"default_tax_rate_2": null,
"late_fee_type": null,
"late_fee_value": null,
"late_fee_days": null,
"created_at": "2026-04-05T19:38:23.000000Z",
"updated_at": "2026-04-05T19:38:23.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update company settings.
requires authentication
Updates general company settings including address, currency, invoice/quote prefix, default tax rates, default payment terms, and hourly rate. All fields are optional (PATCH semantics).
Example request:
curl --request PUT \
"https://tidybill.app/api/settings" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"legal_name\": \"n\",
\"email\": \"[email protected]\",
\"phone\": \"v\",
\"address_line_1\": \"d\",
\"address_line_2\": \"l\",
\"city\": \"j\",
\"state\": \"n\",
\"postal_code\": \"ikhwaykcmyuwpwlv\",
\"country\": \"qw\",
\"currency\": \"rsi\",
\"tax_number\": \"t\",
\"invoice_prefix\": \"cpscql\",
\"quote_prefix\": \"dzsnrw\",
\"default_payment_terms\": 19,
\"default_hourly_rate\": 33,
\"default_tax_name_1\": \"j\",
\"default_tax_rate_1\": 17,
\"default_tax_name_2\": \"v\",
\"default_tax_rate_2\": 24,
\"invoice_layout\": \"classic\"
}"
const url = new URL(
"https://tidybill.app/api/settings"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"legal_name": "n",
"email": "[email protected]",
"phone": "v",
"address_line_1": "d",
"address_line_2": "l",
"city": "j",
"state": "n",
"postal_code": "ikhwaykcmyuwpwlv",
"country": "qw",
"currency": "rsi",
"tax_number": "t",
"invoice_prefix": "cpscql",
"quote_prefix": "dzsnrw",
"default_payment_terms": 19,
"default_hourly_rate": 33,
"default_tax_name_1": "j",
"default_tax_rate_1": 17,
"default_tax_name_2": "v",
"default_tax_rate_2": 24,
"invoice_layout": "classic"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 179,
"uuid": "303015f8-8550-415a-a3b8-eee3d4976f0e",
"name": "Considine LLC",
"legal_name": null,
"email": "[email protected]",
"phone": null,
"address_line_1": null,
"address_line_2": null,
"city": null,
"state": null,
"postal_code": null,
"country": null,
"currency": "USD",
"tax_number": null,
"logo_path": null,
"invoice_prefix": "INV-",
"quote_prefix": "QUO-",
"invoice_layout": "classic",
"default_payment_terms": 30,
"default_hourly_rate": null,
"default_tax_name_1": null,
"default_tax_rate_1": null,
"default_tax_name_2": null,
"default_tax_rate_2": null,
"late_fee_type": null,
"late_fee_value": null,
"late_fee_days": null,
"created_at": "2026-04-05T19:38:23.000000Z",
"updated_at": "2026-04-05T19:38:23.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get branding settings.
requires authentication
Returns the company logo path and default invoice template branding settings.
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/branding" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/branding"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"logo_path": "logos/1/logo.png",
"template": {
"id": 1,
"layout": "modern",
"primary_color": "#1a1a2e",
"secondary_color": "#1e40af",
"accent_color": "#3b82f6",
"font_family": "Helvetica",
"show_logo": true,
"header_html": null,
"footer_html": null,
"custom_css": null
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update branding settings.
requires authentication
Creates or updates the default invoice template for the company. HTML fields are sanitized.
Example request:
curl --request PUT \
"https://tidybill.app/api/settings/branding" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"layout\": \"modern\",
\"primary_color\": \"bngzmiy\",
\"secondary_color\": \"vdljnik\",
\"accent_color\": \"hwaykcm\",
\"font_family\": \"y\",
\"show_logo\": false,
\"header_html\": \"u\",
\"footer_html\": \"w\",
\"custom_css\": \"p\"
}"
const url = new URL(
"https://tidybill.app/api/settings/branding"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"layout": "modern",
"primary_color": "bngzmiy",
"secondary_color": "vdljnik",
"accent_color": "hwaykcm",
"font_family": "y",
"show_logo": false,
"header_html": "u",
"footer_html": "w",
"custom_css": "p"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1,
"layout": "modern",
"primary_color": "#1a1a2e",
"secondary_color": "#1e40af",
"accent_color": "#3b82f6",
"font_family": "Helvetica",
"show_logo": true,
"header_html": null,
"footer_html": null,
"custom_css": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Upload company logo.
requires authentication
Accepts a JPEG, PNG, GIF, or WebP image up to 2 MB. Replaces any existing logo.
Example request:
curl --request POST \
"https://tidybill.app/api/settings/branding/logo" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: multipart/form-data" \
--header "Accept: application/json" \
--form "logo=@/tmp/phpljs7hujdqualdjIIhip" const url = new URL(
"https://tidybill.app/api/settings/branding/logo"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "multipart/form-data",
"Accept": "application/json",
};
const body = new FormData();
body.append('logo', document.querySelector('input[name="logo"]').files[0]);
fetch(url, {
method: "POST",
headers,
body,
}).then(response => response.json());Example response (200):
{
"data": {
"logo_path": "logos/1/logo.png"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get late fee settings.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/late-fees" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/late-fees"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"late_fee_type": "percentage",
"late_fee_value": 150,
"late_fee_days": 30
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update late fee settings.
requires authentication
late_fee_value is stored in cents (flat) or basis points (percentage). late_fee_days is
the number of days past due before the fee applies.
Example request:
curl --request PUT \
"https://tidybill.app/api/settings/late-fees" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"late_fee_type\": \"flat\",
\"late_fee_value\": 27,
\"late_fee_days\": 22
}"
const url = new URL(
"https://tidybill.app/api/settings/late-fees"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"late_fee_type": "flat",
"late_fee_value": 27,
"late_fee_days": 22
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"late_fee_type": "percentage",
"late_fee_value": 150,
"late_fee_days": 30
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List payment reminders.
requires authentication
Returns all payment reminders for the current company ordered by reminder number.
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/reminders" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/reminders"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"reminder_number": 1,
"days_offset": -3,
"subject": "Invoice due soon",
"body": "Your invoice is due in 3 days.",
"is_active": true
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a payment reminder.
requires authentication
days_offset is relative to the due date: negative values send before due, positive values
send after due.
Example request:
curl --request POST \
"https://tidybill.app/api/settings/reminders" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"reminder_number\": 1,
\"days_offset\": 22,
\"subject\": \"g\",
\"body\": \"z\",
\"is_active\": false
}"
const url = new URL(
"https://tidybill.app/api/settings/reminders"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"reminder_number": 1,
"days_offset": 22,
"subject": "g",
"body": "z",
"is_active": false
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"id": 2,
"reminder_number": 2,
"days_offset": 7,
"subject": "Invoice overdue",
"body": "Your invoice is 7 days overdue.",
"is_active": true
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a payment reminder.
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/settings/reminders/16" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"reminder_number\": 1,
\"days_offset\": 22,
\"subject\": \"g\",
\"body\": \"z\",
\"is_active\": true
}"
const url = new URL(
"https://tidybill.app/api/settings/reminders/16"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"reminder_number": 1,
"days_offset": 22,
"subject": "g",
"body": "z",
"is_active": true
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 1,
"reminder_number": 1,
"days_offset": -3,
"subject": "Invoice due soon",
"body": "Your invoice is due in 3 days.",
"is_active": true
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a payment reminder.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/settings/reminders/16" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/reminders/16"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Bank Accounts
List bank accounts.
requires authentication
Returns all bank accounts for the current company ordered by sort_order. Not paginated.
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/bank-accounts" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/bank-accounts"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 4,
"name": "Primary",
"details": "Quidem nostrum qui commodi incidunt iure odit. Et modi ipsum nostrum omnis autem et consequatur. Dolores enim non facere tempora. Voluptatem laboriosam praesentium quis adipisci.",
"sort_order": 0,
"created_at": "2026-04-05T19:38:23.000000Z",
"updated_at": "2026-04-05T19:38:23.000000Z"
},
{
"id": 5,
"name": "Primary",
"details": "Corporis dolorem mollitia deleniti nemo odit quia officia. Dignissimos neque blanditiis odio.",
"sort_order": 0,
"created_at": "2026-04-05T19:38:23.000000Z",
"updated_at": "2026-04-05T19:38:23.000000Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a bank account.
requires authentication
Example request:
curl --request POST \
"https://tidybill.app/api/settings/bank-accounts" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"details\": \"n\",
\"sort_order\": 84
}"
const url = new URL(
"https://tidybill.app/api/settings/bank-accounts"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"details": "n",
"sort_order": 84
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 6,
"name": "Primary",
"details": "Velit et fugiat sunt nihil accusantium. Mollitia modi deserunt aut ab provident perspiciatis quo. Nostrum aut adipisci quidem nostrum. Commodi incidunt iure odit.",
"sort_order": 0,
"created_at": "2026-04-05T19:38:23.000000Z",
"updated_at": "2026-04-05T19:38:23.000000Z"
}
}
Example response (201):
{}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Show a bank account.
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/bank-accounts/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/bank-accounts/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 7,
"name": "Primary",
"details": "Quidem nostrum qui commodi incidunt iure odit. Et modi ipsum nostrum omnis autem et consequatur. Dolores enim non facere tempora. Voluptatem laboriosam praesentium quis adipisci.",
"sort_order": 0,
"created_at": "2026-04-05T19:38:23.000000Z",
"updated_at": "2026-04-05T19:38:23.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a bank account.
requires authentication
Example request:
curl --request PUT \
"https://tidybill.app/api/settings/bank-accounts/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"b\",
\"details\": \"n\",
\"sort_order\": 84
}"
const url = new URL(
"https://tidybill.app/api/settings/bank-accounts/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "b",
"details": "n",
"sort_order": 84
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 8,
"name": "Primary",
"details": "Velit et fugiat sunt nihil accusantium. Mollitia modi deserunt aut ab provident perspiciatis quo. Nostrum aut adipisci quidem nostrum. Commodi incidunt iure odit.",
"sort_order": 0,
"created_at": "2026-04-05T19:38:23.000000Z",
"updated_at": "2026-04-05T19:38:23.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a bank account.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/settings/bank-accounts/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/bank-accounts/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Imports
FreshBooks OAuth callback.
Handles the authorization code callback from FreshBooks. Exchanges the code for access tokens, fetches the FreshBooks identity, and syncs company settings. Redirects to the frontend settings page on success.
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/freshbooks/callback?code=authcode123" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/freshbooks/callback"
);
const params = {
"code": "authcode123",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (302, Redirect to settings on success):
Example response (400):
{
"message": "Missing authorization code or session cookie. Please try connecting again."
}
Example response (400):
Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: https://tidybill.app
access-control-allow-credentials: true
{
"message": "Missing authorization code or session cookie. Please try connecting again."
}
Example response (502):
{
"message": "Failed to exchange code for tokens."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Initiate FreshBooks OAuth.
requires authentication
Redirects the user to FreshBooks' OAuth authorization page. Credentials must be saved first. Identity is preserved via a signed cookie (FreshBooks does not support state parameter).
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/freshbooks/connect?company_id=1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/freshbooks/connect"
);
const params = {
"company_id": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (302, Redirect to FreshBooks):
Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: https://tidybill.app
access-control-allow-credentials: true
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Save FreshBooks OAuth credentials.
requires authentication
Stores the FreshBooks app client ID and secret for the current user/company. Required before initiating the OAuth flow.
Example request:
curl --request POST \
"https://tidybill.app/api/settings/freshbooks/credentials" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"client_id\": \"abc123\",
\"client_secret\": \"secret456\"
}"
const url = new URL(
"https://tidybill.app/api/settings/freshbooks/credentials"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"client_id": "abc123",
"client_secret": "secret456"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"message": "Credentials saved"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Preview FreshBooks import counts.
requires authentication
Fetches resource counts from FreshBooks (clients, invoices, etc.) without importing. Updates
the import status to previewing.
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/freshbooks/preview" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/freshbooks/preview"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"clients": 42,
"invoices": 210,
"payments": 185,
"expenses": 67
}
}
Example response (422):
{
"data": {
"message": "FreshBooks is not connected."
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Start FreshBooks import.
requires authentication
Queues the import job for the connected FreshBooks account. Optionally restrict to specific resource types. Returns 409 if an import is already in progress.
Example request:
curl --request POST \
"https://tidybill.app/api/settings/freshbooks/import" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"selected_resources\": [
\"clients\",
\"invoices\"
],
\"reimport_existing\": false
}"
const url = new URL(
"https://tidybill.app/api/settings/freshbooks/import"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"selected_resources": [
"clients",
"invoices"
],
"reimport_existing": false
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (202):
{
"data": {
"message": "Import started"
}
}
Example response (409):
{
"data": {
"message": "Import is already in progress."
}
}
Example response (422):
{
"data": {
"message": "FreshBooks is not connected."
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get FreshBooks import status.
requires authentication
Returns the current state of the import record for the authenticated user and company.
Returns null data if no import record exists.
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/freshbooks/status" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/freshbooks/status"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"status": "importing",
"progress": {
"clients": 42,
"invoices": 0
},
"preview_data": null,
"error_message": null,
"started_at": "2026-04-05T08:00:00+00:00",
"completed_at": null,
"has_credentials": true,
"has_tokens": true,
"freshbooks_account_id": "ABC123",
"selected_resources": [
"clients",
"invoices"
]
}
}
Example response (200, No import record):
{
"data": null
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Reset import to preview state.
requires authentication
Clears any in-progress or errored import so a new import can be started from the preview step.
Example request:
curl --request POST \
"https://tidybill.app/api/settings/freshbooks/reset" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/freshbooks/reset"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"message": "Ready for new import"
}
}
Example response (422):
{
"data": {
"message": "FreshBooks is not connected."
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Disconnect FreshBooks.
requires authentication
Clears stored OAuth tokens and account identifiers from the import record. The credentials (client ID/secret) are preserved.
Example request:
curl --request POST \
"https://tidybill.app/api/settings/freshbooks/disconnect" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/freshbooks/disconnect"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"message": "Disconnected from FreshBooks."
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Stripe Connect
Stripe Connect OAuth callback.
Handles the OAuth callback from Stripe. Exchanges the authorization code for tokens, stores the connected account ID on the company, then redirects to the frontend settings page.
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/stripe/callback?code=ac_xxx&state=base64encodedstate" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/stripe/callback"
);
const params = {
"code": "ac_xxx",
"state": "base64encodedstate",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (302, Redirect to settings on success):
Example response (400):
{
"message": "Missing authorization code or state. Please try connecting again."
}
Example response (400):
Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: https://tidybill.app
access-control-allow-credentials: true
{
"message": "Invalid session. Please try connecting again."
}
Example response (502):
{
"message": "Failed to connect Stripe account."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Initiate Stripe Connect OAuth.
requires authentication
Redirects the user to Stripe's OAuth authorization page. Requires company_id as a query
parameter. The user must have access to the specified company.
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/stripe/connect?company_id=1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/stripe/connect"
);
const params = {
"company_id": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (302, Redirect to Stripe):
Example response (401):
Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: https://tidybill.app
access-control-allow-credentials: true
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Stripe Connect status.
requires authentication
Returns the connection status of the company's Stripe Connect account. Fetches live status from Stripe and syncs it locally; falls back to cached values if Stripe is unreachable.
Example request:
curl --request GET \
--get "https://tidybill.app/api/settings/stripe/status" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/stripe/status"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Connected):
{
"data": {
"connected": true,
"account_name": "Acme Corp",
"payouts_enabled": true,
"charges_enabled": true,
"connected_at": "2026-01-15T10:00:00+00:00"
}
}
Example response (200, Not connected):
{
"data": {
"connected": false
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Disconnect Stripe Connect.
requires authentication
Deauthorizes the connected Stripe account and clears the connection from the company record. Proceeds even if the Stripe deauthorization API call fails.
Example request:
curl --request POST \
"https://tidybill.app/api/settings/stripe/disconnect" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/settings/stripe/disconnect"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"message": "Stripe account disconnected."
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
API Tokens
List API tokens.
requires authentication
Returns all personal access tokens for the authenticated user. The token value is never returned after creation.
Example request:
curl --request GET \
--get "https://tidybill.app/api/api-tokens" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/api-tokens"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"name": "My Token",
"last_used_at": "2026-04-01T10:00:00Z",
"created_at": "2026-03-01T10:00:00Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create an API token.
requires authentication
Requires a Pro plan. The token field is only returned on creation and cannot be retrieved
again.
Example request:
curl --request POST \
"https://tidybill.app/api/api-tokens" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"CI Deploy Token\"
}"
const url = new URL(
"https://tidybill.app/api/api-tokens"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "CI Deploy Token"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"id": 2,
"name": "CI Deploy Token",
"token": "1|abc123plaintext",
"created_at": "2026-04-05T08:00:00Z"
}
}
Example response (403):
{
"message": "API access requires a Pro plan. Please upgrade."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Revoke an API token.
requires authentication
Example request:
curl --request DELETE \
"https://tidybill.app/api/api-tokens/architecto" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/api-tokens/architecto"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, No content):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Invitations
Get invitation details.
Returns the company name, role, invitee email, and inviter name for the given invitation token. Returns 404 if the token is not found, 410 if expired, and 422 if already accepted.
Example request:
curl --request GET \
--get "https://tidybill.app/api/invitations/abc123token/details" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invitations/abc123token/details"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"company_name": "Acme Corp",
"role": "member",
"email": "[email protected]",
"inviter": "John Smith"
}
}
Example response (410):
{
"message": "This invitation has expired."
}
Example response (422):
{
"message": "This invitation has already been accepted."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Accept an invitation.
requires authentication
Adds the authenticated user to the company with the invited role and marks the invitation as accepted. Runs inside a transaction. Returns the company ID on success.
Example request:
curl --request POST \
"https://tidybill.app/api/invitations/abc123token/accept" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/invitations/abc123token/accept"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"message": "Invitation accepted.",
"data": {
"company_id": 5
}
}
Example response (200, Already a member):
{
"message": "You are already a member of this company.",
"data": {
"company_id": 5
}
}
Example response (410):
{
"message": "This invitation has expired."
}
Example response (422):
{
"message": "This invitation has already been accepted."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Client Portal
Get invoice for client portal.
Public endpoint accessed via invoice UUID. Marks the invoice as viewed on first access. Also returns Stripe payment availability and bank account details if configured.
Example request:
curl --request GET \
--get "https://tidybill.app/api/portal/invoices/550e8400-e29b-41d4-a716-446655440000" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/portal/invoices/550e8400-e29b-41d4-a716-446655440000"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 42,
"invoice_number": "INV-0042",
"status": "sent",
"total": 150000,
"amount_due": 150000,
"currency": "USD"
},
"payment_available": true,
"bank_details": null
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Download invoice PDF.
Returns the invoice as a PDF file. Public endpoint accessed via UUID.
Example request:
curl --request GET \
--get "https://tidybill.app/api/portal/invoices/550e8400-e29b-41d4-a716-446655440000/pdf" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/portal/invoices/550e8400-e29b-41d4-a716-446655440000/pdf"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, PDF file):
{
"Content-Type": "application/pdf"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get quote for client portal.
Public endpoint accessed via quote UUID. Marks the quote as viewed on first access.
Example request:
curl --request GET \
--get "https://tidybill.app/api/portal/quotes/550e8400-e29b-41d4-a716-446655440001" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/portal/quotes/550e8400-e29b-41d4-a716-446655440001"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 151,
"uuid": "13fb884a-24bf-4790-9731-a88368d2068a",
"client_id": 125,
"quote_number": "QUO-66650",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:13.000000Z",
"updated_at": "2026-04-05T19:38:13.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Download quote PDF.
Returns the quote as a PDF file. Public endpoint accessed via UUID.
Example request:
curl --request GET \
--get "https://tidybill.app/api/portal/quotes/550e8400-e29b-41d4-a716-446655440001/pdf" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/portal/quotes/550e8400-e29b-41d4-a716-446655440001/pdf"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, PDF file):
{
"Content-Type": "application/pdf"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Accept a quote.
Transitions the quote to the accepted status. Public endpoint accessed via UUID.
Example request:
curl --request POST \
"https://tidybill.app/api/portal/quotes/550e8400-e29b-41d4-a716-446655440001/accept" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/portal/quotes/550e8400-e29b-41d4-a716-446655440001/accept"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 152,
"uuid": "2398d4ba-97d1-45dc-9fa1-fa2af7b601cc",
"client_id": 126,
"quote_number": "QUO-77300",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:13.000000Z",
"updated_at": "2026-04-05T19:38:13.000000Z"
}
}
Example response (422):
{
"message": "Quote cannot be accepted in its current status."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Decline a quote.
Transitions the quote to the declined status. Public endpoint accessed via UUID.
Example request:
curl --request POST \
"https://tidybill.app/api/portal/quotes/550e8400-e29b-41d4-a716-446655440001/decline" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/portal/quotes/550e8400-e29b-41d4-a716-446655440001/decline"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 153,
"uuid": "94349ce5-6058-456d-9288-03f3dc8e70cc",
"client_id": 127,
"quote_number": "QUO-10778",
"status": "draft",
"is_archived": false,
"status_label": "Draft",
"status_color": "gray",
"issue_date": "2026-04-05",
"expiry_date": "2026-05-05",
"currency": "USD",
"subtotal": 0,
"tax_total": 0,
"discount_type": null,
"discount_value": null,
"discount_total": 0,
"total": 0,
"notes": null,
"terms": null,
"footer": null,
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"declined_at": null,
"converted_to_invoice_id": null,
"converted_to_project_id": null,
"pdf_path": null,
"created_at": "2026-04-05T19:38:13.000000Z",
"updated_at": "2026-04-05T19:38:13.000000Z"
}
}
Example response (422):
{
"message": "Quote cannot be declined in its current status."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Initiate portal payment.
Creates a Stripe Checkout session for the invoice (using the company's Connect account) or returns bank transfer details if Stripe is not connected. Public endpoint accessed via UUID.
Example request:
curl --request POST \
"https://tidybill.app/api/portal/invoices/550e8400-e29b-41d4-a716-446655440000/checkout" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/portal/invoices/550e8400-e29b-41d4-a716-446655440000/checkout"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200, Stripe checkout):
{
"data": {
"checkout_url": "https://checkout.stripe.com/pay/cs_live_xxx"
}
}
Example response (200, Bank transfer):
{
"data": {
"payment_method": "bank_details",
"bank_accounts": [
{
"name": "Main Account",
"details": "BSB 123-456, Account 987654321"
}
],
"reference": "INV-0042",
"amount": 150000,
"currency": "USD"
}
}
Example response (422):
{
"message": "Invoice is not payable."
}
Example response (502):
{
"message": "Payment service unavailable. Please try again later."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Dashboard
Get dashboard stats.
requires authentication
Returns outstanding and overdue invoice totals (by currency), plus unbilled time entry hours and estimated value for the current company.
Example request:
curl --request GET \
--get "https://tidybill.app/api/dashboard/stats" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/dashboard/stats"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"outstanding_by_currency": {
"USD": 250000
},
"overdue_by_currency": {
"USD": 75000
},
"unbilled_hours": 12.5,
"unbilled_amount": 187500
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get revenue chart data.
requires authentication
Returns monthly revenue grouped by currency for the last 12 months.
Example request:
curl --request GET \
--get "https://tidybill.app/api/dashboard/revenue-chart" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/dashboard/revenue-chart"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"month": "May 2025",
"revenue_by_currency": {
"USD": 120000
}
},
{
"month": "Apr 2026",
"revenue_by_currency": {
"USD": 180000
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get recent activity.
requires authentication
Returns the 10 most recently updated non-cancelled, non-archived invoices.
Example request:
curl --request GET \
--get "https://tidybill.app/api/dashboard/recent-activity" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/dashboard/recent-activity"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"type": "invoice",
"id": 42,
"description": "Invoice INV-0042 to Acme Corp",
"status": "sent",
"amount": 150000,
"date": "2026-04-04T09:00:00Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get accounts receivable aging.
requires authentication
Returns outstanding invoice totals bucketed by age (0-30, 31-60, 61-90, 91+ days), grouped by currency.
Example request:
curl --request GET \
--get "https://tidybill.app/api/dashboard/aging" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/dashboard/aging"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"USD": {
"overdue_total": 75000,
"outstanding_total": 175000,
"total": 250000,
"buckets": [
{
"label": "0-30 Days",
"amount": 100000
},
{
"label": "31-60 Days",
"amount": 75000
},
{
"label": "61-90 Days",
"amount": 50000
},
{
"label": "91+ Days",
"amount": 25000
}
]
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get unbilled hours by client.
requires authentication
Returns the top 10 clients with unbilled billable time entries, ordered by total unbilled hours.
Example request:
curl --request GET \
--get "https://tidybill.app/api/dashboard/unbilled-by-client" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/dashboard/unbilled-by-client"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"total_hours": 34.5,
"clients": [
{
"client_id": 7,
"client_name": "Acme Corp",
"hours": 18,
"seconds": 64800
},
{
"client_id": 3,
"client_name": "Globex",
"hours": 16.5,
"seconds": 59400
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get revenue by client.
requires authentication
Returns the top 10 clients by total invoiced amount (year to date), grouped by currency.
Example request:
curl --request GET \
--get "https://tidybill.app/api/dashboard/revenue-by-client" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/dashboard/revenue-by-client"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"year": 2026,
"clients": [
{
"client_id": 7,
"client_name": "Acme Corp",
"currency": "USD",
"total": 480000
},
{
"client_id": 3,
"client_name": "Globex",
"currency": "USD",
"total": 210000
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Reports
Profit and loss report.
requires authentication
Returns monthly invoiced/collected revenue and expenses for the given date range. All monetary values are in cents.
Example request:
curl --request GET \
--get "https://tidybill.app/api/reports/profit-loss?start_date=2026-01-01&end_date=2026-12-31" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"start_date\": \"2026-04-05T19:38:23\",
\"end_date\": \"2052-04-28\"
}"
const url = new URL(
"https://tidybill.app/api/reports/profit-loss"
);
const params = {
"start_date": "2026-01-01",
"end_date": "2026-12-31",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"start_date": "2026-04-05T19:38:23",
"end_date": "2052-04-28"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"period": {
"start": "2026-01-01",
"end": "2026-12-31"
},
"revenue": {
"invoiced": 600000,
"collected": 480000,
"by_month": [
{
"month": "Jan 2026",
"month_key": "2026-01",
"invoiced": 50000,
"collected": 40000,
"expenses": 10000,
"profit": 30000
}
]
},
"summary": {
"total_invoiced": 600000,
"total_collected": 480000,
"outstanding": 120000,
"total_expenses": 60000,
"net_profit": 420000
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Accounts receivable aging report.
requires authentication
Returns outstanding invoice amounts bucketed by age across all payable invoices, with a per-client breakdown.
Example request:
curl --request GET \
--get "https://tidybill.app/api/reports/account-aging" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://tidybill.app/api/reports/account-aging"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"buckets": [
{
"label": "Current",
"range": "0-30 days",
"amount": 100000,
"count": 5
},
{
"label": "31-60 days",
"range": "31-60 days",
"amount": 50000,
"count": 2
},
{
"label": "61-90 days",
"range": "61-90 days",
"amount": 25000,
"count": 1
},
{
"label": "90+ days",
"range": "90+ days",
"amount": 10000,
"count": 1
}
],
"total_outstanding": 185000,
"clients": [
{
"client_id": 7,
"client_name": "Acme Corp",
"current": 100000,
"days_31_60": 50000,
"days_61_90": 0,
"days_90_plus": 0,
"total": 150000
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Tax summary report.
requires authentication
Returns tax collected per tax name/rate combination for the given date range. Amounts in cents, tax rates in basis points (e.g. 1500 = 15%).
Example request:
curl --request GET \
--get "https://tidybill.app/api/reports/tax-summary?start_date=2026-01-01&end_date=2026-12-31" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"start_date\": \"2026-04-05T19:38:23\",
\"end_date\": \"2052-04-28\"
}"
const url = new URL(
"https://tidybill.app/api/reports/tax-summary"
);
const params = {
"start_date": "2026-01-01",
"end_date": "2026-12-31",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"start_date": "2026-04-05T19:38:23",
"end_date": "2052-04-28"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"period": {
"start": "2026-01-01",
"end": "2026-12-31"
},
"taxes": [
{
"tax_name": "GST",
"tax_rate": 1500,
"taxable_amount": 400000,
"tax_collected": 60000,
"invoice_count": 12
}
],
"total_tax_collected": 60000
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Team utilization report.
requires authentication
Returns billable vs non-billable hours and utilization rate per team member for the given date range.
Example request:
curl --request GET \
--get "https://tidybill.app/api/reports/team-utilization?start_date=2026-01-01&end_date=2026-12-31" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"start_date\": \"2026-04-05T19:38:23\",
\"end_date\": \"2052-04-28\"
}"
const url = new URL(
"https://tidybill.app/api/reports/team-utilization"
);
const params = {
"start_date": "2026-01-01",
"end_date": "2026-12-31",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"start_date": "2026-04-05T19:38:23",
"end_date": "2052-04-28"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"period": {
"start": "2026-01-01",
"end": "2026-12-31"
},
"members": [
{
"user_id": 1,
"name": "Jane Smith",
"total_hours": 160,
"billable_hours": 128,
"non_billable_hours": 32,
"utilization_rate": 80,
"billable_amount": 1920000,
"by_client": [
{
"client_id": 7,
"client_name": "Acme Corp",
"hours": 80,
"amount": 1200000
}
]
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Expense report.
requires authentication
Returns total expenses with breakdowns by category and by client (billable only) for the given date range. All amounts in cents.
Example request:
curl --request GET \
--get "https://tidybill.app/api/reports/expenses?from=2026-01-01&to=2026-12-31" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"from\": \"2026-04-05T19:38:23\",
\"to\": \"2052-04-28\"
}"
const url = new URL(
"https://tidybill.app/api/reports/expenses"
);
const params = {
"from": "2026-01-01",
"to": "2026-12-31",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"from": "2026-04-05T19:38:23",
"to": "2052-04-28"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"total": 85000,
"total_tax": 12750,
"billable_total": 60000,
"by_category": [
{
"category": "travel",
"total": 30000,
"count": 3
}
],
"by_client": [
{
"client": "Acme Corp",
"client_id": 7,
"total": 60000,
"count": 5
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Invoice summary report.
requires authentication
Returns invoice counts and totals grouped by currency, status, and client for the given date range. All amounts in cents.
Example request:
curl --request GET \
--get "https://tidybill.app/api/reports/invoice-summary?start_date=2026-01-01&end_date=2026-12-31&client_id=7" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"start_date\": \"2026-04-05T19:38:23\",
\"end_date\": \"2052-04-28\",
\"client_id\": 16
}"
const url = new URL(
"https://tidybill.app/api/reports/invoice-summary"
);
const params = {
"start_date": "2026-01-01",
"end_date": "2026-12-31",
"client_id": "7",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"start_date": "2026-04-05T19:38:23",
"end_date": "2052-04-28",
"client_id": 16
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"totals_by_currency": {
"USD": {
"total_invoiced": 600000,
"total_paid": 480000,
"total_outstanding": 95000,
"total_overdue": 25000,
"invoice_count": 24
}
},
"by_status": {
"paid": {
"count": 18,
"total": 480000
},
"sent": {
"count": 4,
"total": 95000
},
"overdue": {
"count": 2,
"total": 25000
}
},
"by_client": [
{
"client_id": 7,
"client_name": "Acme Corp",
"currency": "USD",
"invoiced": 300000,
"paid": 240000,
"outstanding": 60000
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Activity Log
List activity log entries
requires authentication
Example request:
curl --request GET \
--get "https://tidybill.app/api/activity-log?subject_type=invoice&subject_id=1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"subject_type\": \"architecto\",
\"subject_id\": 16
}"
const url = new URL(
"https://tidybill.app/api/activity-log"
);
const params = {
"subject_type": "invoice",
"subject_id": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"subject_type": "architecto",
"subject_id": 16
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 1,
"description": "created",
"event": "created",
"causer_name": "John",
"properties": {},
"created_at": "2026-04-05T00:00:00+00:00"
}
],
"meta": {
"current_page": 1,
"last_page": 1,
"total": 1
}
}
Example response (422, Invalid subject type):
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.