# API and webhooks

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

- URL: https://tillage.ai/docs/integrations/api-and-webhooks
- Section: [Integrations](https://tillage.ai/docs/integrations)

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](https://tillage.ai/docs/integrations/claude-and-chatgpt). 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:

```bash
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](https://tillage.ai/docs/invoices/autopay-and-saved-cards#set-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

```json
{
  "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:

```js
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.

## Related

- [Claude, ChatGPT and other AI assistants](https://tillage.ai/docs/integrations/claude-and-chatgpt)
- [Auto-pay and saved cards](https://tillage.ai/docs/invoices/autopay-and-saved-cards)
- [Dashboard and analytics](https://tillage.ai/docs/reports/dashboard-and-analytics)

---

All docs: https://tillage.ai/docs.md  
Site index for AI assistants: https://tillage.ai/llms.txt  
Sign up free: https://app.tillage.ai/signup
