The InvoiceCast API

A REST API for the same clients, invoices, quotes, receipts and expenses you manage in InvoiceCast. On the Business plan, connect your own tools with an API key.

Overview

Every endpoint lives under one base URL. Use the domain you sign in on:

https://invoicecast.ca/api/v1

Authentication

Send a credential as a bearer token on every request, with an X-IC-Client header naming your app and its version:

Authorization: Bearer ick_live_…
X-IC-Client: my-app/1.0

API keys (Business plan)

Account owners and admins create keys in Settings → API keys. Each key has a name, an optional expiry date and a list of abilities, such as invoices:read or clients:write, so an integration can do only what it needs. The key is shown once, when you create it. Store it like a password. Revoking a key takes effect on its next request.

A key acts for the account. It can read and write the account's data within its abilities, but it cannot change account settings or manage users, billing or other keys.

Quickstart

List your clients:

curl https://invoicecast.ca/api/v1/clients \
  -H "Authorization: Bearer $INVOICECAST_API_KEY" \
  -H "X-IC-Client: my-app/1.0"

Create a client. Every POST and PATCH that changes your data needs an Idempotency-Key: a UUID you generate and reuse if you retry the same request.

curl -X POST https://invoicecast.ca/api/v1/clients \
  -H "Authorization: Bearer $INVOICECAST_API_KEY" \
  -H "X-IC-Client: my-app/1.0" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "Northwind Ltd", "email": "[email protected]"}'

Create a draft invoice for that client, then send it:

curl -X POST https://invoicecast.ca/api/v1/invoices \
  -H "Authorization: Bearer $INVOICECAST_API_KEY" \
  -H "X-IC-Client: my-app/1.0" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "client_uuid": "<client uuid>",
    "issue_date": "2026-10-25",
    "currency": "CAD",
    "items": [{"description": "Consulting", "quantity": 2, "unit_price": 150.00}]
  }'

curl -X POST https://invoicecast.ca/api/v1/invoices/<invoice uuid>/send \
  -H "Authorization: Bearer $INVOICECAST_API_KEY" \
  -H "X-IC-Client: my-app/1.0" \
  -H "Idempotency-Key: $(uuidgen)"

Working with the API

Identify your app

Every request carries an X-IC-Client header naming your app and its version, such as acme-sync/3.1: at most 100 printable ASCII characters. Without it you get 400 client_header_required. We use it to find your requests when you contact us and to warn you before a change affects your version, and it is recorded in the account's audit log next to the key that made each change. Only the OpenAPI document and the signed PDF download links are exempt.

Pagination

Lists return up to one page of results with meta.next_cursor and meta.has_more. Pass the cursor back as ?cursor= to get the next page. Page numbers (?page=) are not supported and return a 400.

Keeping data in sync

Every list also returns meta.server_time. Store it, and next time send it as ?updated_since= to receive only what changed, including deleted records, which come back with deleted_at set.

Safe retries

Replaying a POST or PATCH with the same Idempotency-Key and body within 24 hours returns the original response instead of acting twice. Reusing a key with a different body is a 422.

Conflicting edits

A PATCH is partial: send only the fields you change, and the rest stay as they are. Single records carry an ETag. Send it back as If-Match on a PATCH: if the record changed in the meantime you get 409 stale with the current version, instead of overwriting someone else's edit. If-None-Match on a GET returns 304 when nothing changed.

Multiple businesses

If your account has more than one business profile, choose which one issues a document with the X-IC-Business-Profile header (a business profile UUID). Without it, an API key uses the account's default business.

Versioning

Version 1 only grows: we add endpoints and fields, but we never rename, remove or change the meaning of existing ones. Ignore fields you do not recognise. A breaking change would ship as /api/v2.

Rate limits

Limits are per key, per minute. When you exceed one you get 429 rate_limited with a Retry-After header in seconds.

Credential Reads Writes
API key 120 30

PDF downloads are limited to 20 per minute.

Errors

Every error has the same shape. code is stable and safe to branch on. message is translated and safe to show to a person. Quote request_id if you contact us.

{
  "error": {
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "details": {"errors": {"name": ["The name field is required."]}},
    "request_id": "b3b918fb-0b34-4018-9e85-54b770f8980d"
  }
}
Status Codes Meaning
400 bad_request client_header_required client_header_invalid idempotency_key_required idempotency_key_invalid The request is malformed: a missing or invalid X-IC-Client or Idempotency-Key, a bad filter value or a page number.
401 unauthenticated invalid_credentials The credential is missing, wrong, expired or revoked.
403 forbidden insufficient_scope upgrade_required limit_reached Not allowed: the key lacks the ability, the plan does not include the feature, or a plan limit is reached.
404 not_found The record does not exist, or belongs to another account.
409 stale not_editable idempotency_key_in_progress The record changed since you read it, the document can no longer be edited, or the same Idempotency-Key is still being processed.
422 validation_failed idempotency_key_reused A field failed validation (details under error.details.errors), or an Idempotency-Key was reused with a different body.
429 rate_limited Too many requests. Wait for Retry-After seconds.

OpenAPI description

The full list of endpoints, fields, abilities and plan requirements is published as an OpenAPI 3.1 document, generated from the same code that serves the API. Load it into Postman, Insomnia or a client generator:

https://invoicecast.ca/api/v1/openapi.json

Questions

Something missing or unclear? Contact us and include the request_id of any request in question.