API Reference
Programmatic access to your Clearmargin data via the REST API. Create integrations, automate workflows, and connect with third-party tools.
Authentication
All API requests require authentication via an API key. Create API keys in your Clearmargin dashboard settings.
Include your API key in the request using either method:
Authorization header (recommended):
Authorization: Bearer sk_live_your_api_key_herex-api-key header (alternative):
x-api-key: sk_live_your_api_key_hereOrganization scoping: API keys are scoped to your organization. All data returned is automatically filtered to your organization. You cannot access data from other organizations.
Base URL
https://app.clearmargin.app/apiAll endpoint paths below are relative to this base URL.
Rate Limits
API requests are rate-limited to ensure platform stability:
| Limit | Window |
|---|---|
| 100 requests | Per minute, per key |
When rate-limited, the API returns HTTP 429 with a Retry-After header indicating when you can retry.
Endpoints
Clearmargin exposes a RESTful API following standard CRUD patterns. Each resource supports list, get, create, update, and delete operations.
Clients
Client/company records
GET/api/clientsList all clients
GET/api/clients/:idGet a single client
POST/api/clientsCreate a new client
PATCH/api/clients/:idUpdate a client
DELETE/api/clients/:idDelete a client
Projects
Client projects with scope tracking
GET/api/projectsList all projects
GET/api/projects/:idGet a single project
POST/api/projectsCreate a new project
PATCH/api/projects/:idUpdate a project
DELETE/api/projects/:idDelete a project
Invoices
Client invoices and billing
GET/api/invoicesList all invoices
GET/api/invoices/:idGet a single invoice
POST/api/invoicesCreate a new invoice
PATCH/api/invoices/:idUpdate a invoice
DELETE/api/invoices/:idDelete a invoice
Proposals
Client proposals with line items
GET/api/proposalsList all proposals
GET/api/proposals/:idGet a single proposal
POST/api/proposalsCreate a new proposal
PATCH/api/proposals/:idUpdate a proposal
DELETE/api/proposals/:idDelete a proposal
Time Entries
Time logged against projects
GET/api/time-entriesList all time entries
GET/api/time-entries/:idGet a single time entrie
POST/api/time-entriesCreate a new time entrie
PATCH/api/time-entries/:idUpdate a time entrie
DELETE/api/time-entries/:idDelete a time entrie
Transactions
Unified cost and revenue ledger
GET/api/transactionsList all transactions
GET/api/transactions/:idGet a single transaction
POST/api/transactionsCreate a new transaction
PATCH/api/transactions/:idUpdate a transaction
DELETE/api/transactions/:idDelete a transaction
Catalog Items
Service and product catalog
GET/api/catalog-itemsList all catalog items
GET/api/catalog-items/:idGet a single catalog item
POST/api/catalog-itemsCreate a new catalog item
PATCH/api/catalog-items/:idUpdate a catalog item
DELETE/api/catalog-items/:idDelete a catalog item
Contacts
Contact people on clients
GET/api/contactsList all contacts
GET/api/contacts/:idGet a single contact
POST/api/contactsCreate a new contact
PATCH/api/contacts/:idUpdate a contact
DELETE/api/contacts/:idDelete a contact
Milestones
Project and proposal milestones
GET/api/milestonesList all milestones
GET/api/milestones/:idGet a single milestone
POST/api/milestonesCreate a new milestone
PATCH/api/milestones/:idUpdate a milestone
DELETE/api/milestones/:idDelete a milestone
Calendar Events
Schedule calendar events
GET/api/calendar-eventsList all calendar events
GET/api/calendar-events/:idGet a single calendar event
POST/api/calendar-eventsCreate a new calendar event
PATCH/api/calendar-events/:idUpdate a calendar event
DELETE/api/calendar-events/:idDelete a calendar event
Query Parameters
List endpoints support filtering, sorting, and pagination via query parameters.
| Parameter | Type | Description |
|---|---|---|
where | Object | Filter results using Payload query syntax |
sort | String | Field name to sort by. Prefix with - for descending |
limit | Number | Number of results per page (default: 10, max: 100) |
page | Number | Page number for pagination (starts at 1) |
depth | Number | Relationship population depth (default: 1, max: 3) |
Filter syntax
The where parameter uses Payload query syntax. Pass conditions as nested query parameters:
# Filter clients by status
GET /api/clients?where[status][equals]=active
# Filter invoices by amount greater than 1000
GET /api/invoices?where[total][greater_than]=1000
# Filter time entries by date range
GET /api/time-entries?where[date][greater_than_equal]=2026-01-01&where[date][less_than]=2026-02-01
# Combine filters with AND (default)
GET /api/projects?where[status][equals]=active&where[client][equals]=client_id_hereAvailable operators
| Operator | Description |
|---|---|
equals | Exact match |
not_equals | Not equal to |
greater_than | Greater than |
greater_than_equal | Greater than or equal |
less_than | Less than |
less_than_equal | Less than or equal |
like | Case-insensitive partial match |
contains | String contains |
in | Value is in array |
exists | Field exists / is not null |
Examples
List clients
curl -H "Authorization: Bearer sk_live_your_key" \
"https://app.clearmargin.app/api/clients?limit=10&sort=-createdAt"Create a client
curl -X POST \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Corp", "email": "billing@acme.com"}' \
"https://app.clearmargin.app/api/clients"Get unpaid invoices
curl -H "Authorization: Bearer sk_live_your_key" \
"https://app.clearmargin.app/api/invoices?where[status][not_equals]=paid&sort=dueDate"Log a time entry
curl -X POST \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"project": "project_id_here",
"hours": 2.5,
"date": "2026-03-14",
"description": "API integration work",
"billable": true
}' \
"https://app.clearmargin.app/api/time-entries"Response format
List endpoints return paginated results:
{
"docs": [
{
"id": "abc123",
"name": "Acme Corp",
"email": "billing@acme.com",
"status": "active",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-03-01T14:20:00.000Z"
}
],
"totalDocs": 42,
"limit": 10,
"totalPages": 5,
"page": 1,
"pagingCounter": 1,
"hasPrevPage": false,
"hasNextPage": true,
"prevPage": null,
"nextPage": 2
}Webhooks
Webhooks send real-time HTTP POST notifications to your server when events occur in Clearmargin. Configure webhooks in your organization settings.
Webhook payload format
{
"event": "invoice_paid",
"timestamp": "2026-03-14T15:30:00.000Z",
"data": {
"documentType": "invoice",
"documentId": "inv_abc123",
"documentNumber": "INV-0042",
"amount": 2500.00,
"client": {
"id": "client_xyz",
"name": "Acme Corp"
},
"contact": {
"id": "contact_123",
"name": "Jane Smith",
"email": "jane@acme.com"
}
}
}HMAC signature verification
When you configure a signing secret on your webhook, Clearmargin includes two headers for verification:
| Header | Description |
|---|---|
X-Signature | HMAC-SHA256 signature: sha256=hex_digest |
X-Webhook-Timestamp | Unix timestamp (seconds) for replay protection |
The signature is computed over {timestamp}.{body}. Verify it like this:
const crypto = require('crypto');
function verifyWebhookSignature(body, timestamp, signature, secret) {
// Reject old timestamps (> 5 minutes) to prevent replay attacks
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
throw new Error('Timestamp too old');
}
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
const received = signature.replace('sha256=', '');
if (!crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex')
)) {
throw new Error('Invalid signature');
}
}Webhook Event Catalog
All available webhook events you can subscribe to. Each event includes the standard payload format shown above with event-specific metadata.
Proposal Events
| Event | Description |
|---|---|
proposal_sent | A proposal has been sent to a client |
proposal_expiring | A proposal is about to expire |
proposal_accepted | A client has accepted a proposal |
proposal_rejected | A client has rejected a proposal |
proposal_expired | A proposal has passed its expiration date |
Invoice & Payment Events
| Event | Description |
|---|---|
invoice_sent | An invoice has been sent to a client |
invoice_due_soon | An invoice is approaching its due date |
invoice_overdue | An invoice has passed its due date |
invoice_paid | A client has paid an invoice |
invoice_refunded | A payment has been refunded |
invoice_payment_failed | A payment attempt has failed |
invoice_disputed | A payment has been disputed |
schedule_deposit_ready | A deposit invoice is ready |
schedule_installment_due | An installment payment is due |
schedule_installment_paid | An installment payment has been received |
schedule_completed | All payments in a schedule are complete |
Receipt Events
| Event | Description |
|---|---|
receipt_sent | A receipt has been sent to a client |
Example: invoice_paid event
{
"event": "invoice_paid",
"timestamp": "2026-03-14T15:30:00.000Z",
"data": {
"documentType": "invoice",
"documentId": "inv_abc123",
"documentNumber": "INV-0042",
"amount": 2500.00,
"client": {
"id": "client_xyz",
"name": "Acme Corp"
},
"contact": {
"id": "contact_123",
"name": "Jane Smith",
"email": "jane@acme.com"
},
"paymentMethod": "card",
"last4": "4242",
"brand": "visa"
}
}Error Handling
The API uses standard HTTP status codes to indicate success or failure.
| Status | Description |
|---|---|
200 | Success |
201 | Created (for POST requests) |
400 | Bad request (invalid parameters or body) |
401 | Unauthorized (missing or invalid API key) |
403 | Forbidden (key lacks required permissions) |
404 | Resource not found |
429 | Rate limited (too many requests) |
500 | Internal server error |
Error responses include a JSON body with an errors array describing what went wrong.
Integration Guides
Looking to connect Clearmargin with other tools? Check out our step-by-step integration guides.