Clearmargin

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_here

x-api-key header (alternative):

x-api-key: sk_live_your_api_key_here

Organization 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/api

All endpoint paths below are relative to this base URL.

Rate Limits

API requests are rate-limited to ensure platform stability:

LimitWindow
100 requestsPer 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/clients

List all clients

GET
/api/clients/:id

Get a single client

POST
/api/clients

Create a new client

PATCH
/api/clients/:id

Update a client

DELETE
/api/clients/:id

Delete a client

Projects

Client projects with scope tracking

GET
/api/projects

List all projects

GET
/api/projects/:id

Get a single project

POST
/api/projects

Create a new project

PATCH
/api/projects/:id

Update a project

DELETE
/api/projects/:id

Delete a project

Invoices

Client invoices and billing

GET
/api/invoices

List all invoices

GET
/api/invoices/:id

Get a single invoice

POST
/api/invoices

Create a new invoice

PATCH
/api/invoices/:id

Update a invoice

DELETE
/api/invoices/:id

Delete a invoice

Proposals

Client proposals with line items

GET
/api/proposals

List all proposals

GET
/api/proposals/:id

Get a single proposal

POST
/api/proposals

Create a new proposal

PATCH
/api/proposals/:id

Update a proposal

DELETE
/api/proposals/:id

Delete a proposal

Time Entries

Time logged against projects

GET
/api/time-entries

List all time entries

GET
/api/time-entries/:id

Get a single time entrie

POST
/api/time-entries

Create a new time entrie

PATCH
/api/time-entries/:id

Update a time entrie

DELETE
/api/time-entries/:id

Delete a time entrie

Transactions

Unified cost and revenue ledger

GET
/api/transactions

List all transactions

GET
/api/transactions/:id

Get a single transaction

POST
/api/transactions

Create a new transaction

PATCH
/api/transactions/:id

Update a transaction

DELETE
/api/transactions/:id

Delete a transaction

Catalog Items

Service and product catalog

GET
/api/catalog-items

List all catalog items

GET
/api/catalog-items/:id

Get a single catalog item

POST
/api/catalog-items

Create a new catalog item

PATCH
/api/catalog-items/:id

Update a catalog item

DELETE
/api/catalog-items/:id

Delete a catalog item

Contacts

Contact people on clients

GET
/api/contacts

List all contacts

GET
/api/contacts/:id

Get a single contact

POST
/api/contacts

Create a new contact

PATCH
/api/contacts/:id

Update a contact

DELETE
/api/contacts/:id

Delete a contact

Milestones

Project and proposal milestones

GET
/api/milestones

List all milestones

GET
/api/milestones/:id

Get a single milestone

POST
/api/milestones

Create a new milestone

PATCH
/api/milestones/:id

Update a milestone

DELETE
/api/milestones/:id

Delete a milestone

Calendar Events

Schedule calendar events

GET
/api/calendar-events

List all calendar events

GET
/api/calendar-events/:id

Get a single calendar event

POST
/api/calendar-events

Create a new calendar event

PATCH
/api/calendar-events/:id

Update a calendar event

DELETE
/api/calendar-events/:id

Delete a calendar event

Query Parameters

List endpoints support filtering, sorting, and pagination via query parameters.

ParameterTypeDescription
whereObjectFilter results using Payload query syntax
sortStringField name to sort by. Prefix with - for descending
limitNumberNumber of results per page (default: 10, max: 100)
pageNumberPage number for pagination (starts at 1)
depthNumberRelationship 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_here

Available operators

OperatorDescription
equalsExact match
not_equalsNot equal to
greater_thanGreater than
greater_than_equalGreater than or equal
less_thanLess than
less_than_equalLess than or equal
likeCase-insensitive partial match
containsString contains
inValue is in array
existsField 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:

HeaderDescription
X-SignatureHMAC-SHA256 signature: sha256=hex_digest
X-Webhook-TimestampUnix 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

EventDescription
proposal_sentA proposal has been sent to a client
proposal_expiringA proposal is about to expire
proposal_acceptedA client has accepted a proposal
proposal_rejectedA client has rejected a proposal
proposal_expiredA proposal has passed its expiration date

Invoice & Payment Events

EventDescription
invoice_sentAn invoice has been sent to a client
invoice_due_soonAn invoice is approaching its due date
invoice_overdueAn invoice has passed its due date
invoice_paidA client has paid an invoice
invoice_refundedA payment has been refunded
invoice_payment_failedA payment attempt has failed
invoice_disputedA payment has been disputed
schedule_deposit_readyA deposit invoice is ready
schedule_installment_dueAn installment payment is due
schedule_installment_paidAn installment payment has been received
schedule_completedAll payments in a schedule are complete

Receipt Events

EventDescription
receipt_sentA 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.

StatusDescription
200Success
201Created (for POST requests)
400Bad request (invalid parameters or body)
401Unauthorized (missing or invalid API key)
403Forbidden (key lacks required permissions)
404Resource not found
429Rate limited (too many requests)
500Internal 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.