API and webhooks

Create scoped API keys, read clients and invoices over the REST API, and receive signed webhooks when invoices and clients change.

Tillage has a small REST API and outgoing webhooks for connecting your own tools: send paid invoices to your books, sync clients to a CRM, or create usage-based invoices from a billing system. API keys also work with the MCP server. Everything here is managed in Team Settings → Developers.

API keys

Create a key

  1. Go to Team Settings → Developers.
  2. Under API Keys, enter a name, such as the integration it’s for.
  3. Tick the scopes it needs, then click Create Key. Or use a preset:
    • Create MCP Key selects read and write for clients, quotes, invoices and contracts.
    • Create Gauge Key selects Read invoices and Write invoices.
  4. Copy the key. It starts with tk_live_ and is shown only once.

Keys belong to the team they were created in and can only see that team’s data. To stop a key working, click Revoke next to it.

Scopes

Scope Label Grants
clients:read Read clients View clients and their contacts
clients:write Write clients Create and update clients
quotes:read Read quotes View quotes and line items
quotes:write Write quotes Create and update quotes
invoices:read Read invoices View invoices and payment status
invoices:write Write invoices Create and update invoices
contracts:read Read contracts View contracts and contract templates
contracts:write Write contracts Create, update and send contracts and templates
webhooks:read Read webhooks Reserved for webhook management
webhooks:write Write webhooks Reserved for webhook management

Give each key only the scopes it needs.

REST API

Send the key as a bearer token:

curl https://app.tillage.ai/api/v1/invoices?limit=50 \
  -H "Authorization: Bearer tk_live_..."

Endpoints

Method and path Scope What it does
GET /api/v1/clients clients:read List clients with their contacts
GET /api/v1/invoices invoices:read List invoices with status, payment status, dates and billing schedule details
POST /api/v1/invoices invoices:write Create a usage invoice

Listing and paging

Both list endpoints accept:

Parameter Description
limit Results per page, 1–100. Default 25.
starting_after The ID to continue after. Use next_cursor from the previous page.
updated_after Only records updated at or after this time.
status Filter by status. For clients, defaults to active clients; pass all for every client, or a comma-separated list.
payment_status Invoices only. Filter by payment status.

Responses include data, has_more, next_cursor and a ready-made next_page_url. Keep requesting until has_more is false.

Each invoice also includes recurring_frequency, counts_toward_mrr and counts_toward_arr, so you can rebuild recurring revenue in your own tools.

Create a usage invoice

POST /api/v1/invoices is for billing systems that meter usage and need Tillage to invoice and collect it. Send JSON with:

Field Required Notes
team_id Yes Must match the key’s team.
client_id Yes
title Yes
type No Must be usage if sent.
amount, total_amount Yes Non-negative numbers.
due_date Yes
idempotency_key Yes Sending the same key again returns the original invoice instead of creating a second one.
status No issued (default), draft or queued.
auto_send No Defaults to true. A draft sent with auto_send on becomes queued and is emailed on its issue date.
auto_charge No Defaults to true: the client’s saved payment method is charged when due, if their auto-pay consent isn’t Never.
issue_date, description, line_items, tax_amount, payment_terms, source, source_data No payment_terms defaults to “Due on receipt”.
allow_credit_cards, allow_bank_transfers No Payment methods allowed online.

Rate limits

Each key can make 120 requests a minute. Responses include an X-RateLimit-Limit header. Over the limit, you’ll get a 429 with a Retry-After header.

Webhooks

Webhooks send an HTTPS POST to your server when something changes in Tillage.

Add an endpoint

  1. Go to Team Settings → Developers.
  2. Under Webhooks, enter your endpoint URL and click Add Endpoint.
  3. Copy the signing secret. It’s copied to your clipboard when the endpoint is created and starts with whsec_. Store it now; you won’t see it again.
  4. Click Test to send a webhook.test event.

The endpoint must use HTTPS, and private or local network addresses aren’t allowed. New endpoints receive every event below. The card shows whether each endpoint is enabled, its failure count, and recent deliveries.

Events

Event When
invoice.created An invoice is created
invoice.updated An invoice changes
invoice.paid An invoice is paid
invoice.payment_failed An online payment fails
client.created A client is created
client.updated A client changes
contact.created A contact is added
contact.updated A contact changes
contact.deleted A contact is removed
webhook.test You click Test

Payload

{
  "id": "evt_...",
  "type": "invoice.paid",
  "created": "2026-11-02T14:05:11.000Z",
  "teamId": "...",
  "clientId": "...",
  "data": { }
}

data holds the record that changed. Use id to ignore an event you’ve already processed.

Verify the signature

Each request carries these headers:

Header Value
X-Tillage-Event The event type
X-Tillage-Event-Id The event ID
X-Tillage-Timestamp Unix time in seconds when the request was signed
X-Tillage-Signature Hex HMAC-SHA256 of {timestamp}.{raw body}, keyed with your signing secret

To verify, compute the HMAC over the timestamp, a period and the raw request body, then compare it with the header:

import crypto from "node:crypto";

function isValidTillageWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-tillage-timestamp"];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const received = headers["x-tillage-signature"] || "";
  return (
    expected.length === received.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
  );
}

Use the raw body exactly as received, before any JSON parsing. Rejecting requests with an old timestamp protects you against replays.

Retries

Respond with any 2xx status within 10 seconds. Otherwise Tillage retries, up to 6 attempts in total, waiting 1 minute, 5 minutes, 30 minutes, 2 hours and then 12 hours between attempts. After 20 failures in a row, the endpoint is disabled.