Home

Retrieving Invoices and Recording Payments

This guide covers listing and retrieving invoices, then recording a manual payment against one.

Authentication uses the same Authorization and AccessKey headers as every other endpoint — see the Authentication guide if you don’t have these set up yet.

Listing Invoices

GET https://api.karbonhq.com/v3/Invoices
Authorization: Bearer {token}
AccessKey: {key}

Filtering Invoices

Use $filter to narrow results down to invoices in a particular state. The /v3/Invoices endpoint supports filtering by InvoiceStatus:

GET /v3/Invoices?$filter=InvoiceStatus eq 'Approved'

Supported InvoiceStatus values: Approved, AwaitingPayment, Paid, Exported, Voided. InvoiceStatus is the only field you can filter by.

Sorting Invoices

# Most recent invoices first
GET /v3/Invoices?$orderby=InvoiceDate desc

# By invoice number
GET /v3/Invoices?$orderby=InvoiceNumber

# Recently created or updated
GET /v3/Invoices?$orderby=CreatedAt desc
GET /v3/Invoices?$orderby=UpdatedAt desc

Paginating Through Results

Use $top and $skip or follow the @odata.nextLink in the response:

GET /v3/Invoices?$top=50

Response:

{
  "@odata.count": 214,
  "@odata.nextLink": "https://api.karbonhq.com/v3/Invoices?$skip=50",
  "value": [ ... ]
}

@odata.count is returned automatically. Always use the @odata.nextLink URL directly rather than manually incrementing $skip. See the Pagination guide for details.

Retrieving a Specific Invoice

Each invoice in the list response includes an InvoiceKey. Use it to fetch a single invoice:

GET https://api.karbonhq.com/v3/Invoices/{InvoiceKey}
Authorization: Bearer {token}
AccessKey: {key}

The response includes the invoice total, subtotal, tax total, status, currency, payment due date, client details, and a TaxLineItems array breaking down individual tax components.

It also includes InvoiceMagicLink, a link you can send to the client to pay the invoice through Karbon Payments, and InvoicePdfUrl, a link to the rendered invoice PDF you can preview, download, or forward to the client. Both are only returned once the invoice has been posted or sent — otherwise they’re null — and both are only available on the single-invoice response, not on the /v3/Invoices list.

The token in InvoicePdfUrl is valid for 15 minutes, so fetch the invoice again for a fresh link instead of storing it.

A 404 response means no invoice exists with that key — either it was deleted, or the key is wrong.

Asking for more than the invoice summary

By default the invoice response is a summary. To pull line items, payments, or the source breakdown back in the same request, add $expand= and list the sections you want:

Add to the URL What comes back
?$expand=LineItems The invoice line items — the rows that appear on the invoice itself.
?$expand=Payments Any payments already recorded against the invoice.
?$expand=Data The underlying items the invoice is billing for. Ad hoc invoices return the Work Items and Expenses behind each charge; recurring fixed-fee invoices return the Services being billed for.

Combine sections by comma-separating them:

GET /v3/Invoices/{InvoiceKey}?$expand=LineItems,Payments,Data

$expand only works when retrieving a single invoice — it is not supported on the /v3/Invoices list endpoint. $filter and $orderby on the list endpoint can be combined freely with each other and with pagination.

Only ask for what you need — additional data will result in a larger payload and take longer to process. See Avoid unnecessary $expand for more.

Where to read the tax breakdown

Tax can be recorded against either the line items or the billed items, depending on the invoice. Check TaxLocation to know which array holds the tax fields:

  • TaxLocation=Presentation — tax is on each LineItems entry; tax fields in Data are null. All current ad hoc invoices.
  • TaxLocation=Data — tax is on each Data entry; tax fields in LineItems are null. All recurring billed invoices, and future ad hoc invoices.

If you need tax regardless of where it lives, request both LineItems and Data and read whichever side TaxLocation points to.

What the Data array looks like

For an ad hoc invoice, each entry is a Work Item or Client (for client time/expenses), with Work and/or Expenses populated and Service always null:

"Data": [
  {
    "EntityKey": "M4QTnt1srdD",
    "EntityType": "Work Item",
    "Billed": 800,
    "Service": null,
    "Work": { ... },
    "Expenses": { ... }
  }
]

To add an expense so that it appears here, see Recording an expense below.

For a recurring fixed-fee invoice, each entry is a Service, with Work and Expenses both null:

"Data": [
  {
    "EntityKey": "4lQzzVNBdHwq",
    "EntityType": "Service",
    "Billed": 150,
    "Service": { ... },
    "Work": null,
    "Expenses": null
  }
]

Recording an Expense

Expenses are one of the things an ad hoc invoice bills for, and you can add them through the API:

POST https://api.karbonhq.com/v3/Expenses
Authorization: Bearer {token}
AccessKey: {key}
{
  "Timeline": {
    "EntityType": "WorkItem",
    "EntityKey": "2m6pSFxRzcF2"
  },
  "ExpenseDate": "2026-08-14T00:00:00Z",
  "Value": 240.5,
  "BillableValue": 240.5,
  "Description": "Courier fees for year-end documents"
}

Timeline is the entity the expense belongs to — EntityType is WorkItem, Contact or Organization, and EntityKey the key of that entity. Every other field is required too: Value is what the expense cost you, and BillableValue the portion you intend to bill your client. Set BillableValue to 0 for an expense you’re absorbing, or to the same amount as Value to pass it on in full. Neither can be negative, and Description is limited to 250 characters.

The response is 201 Created with the new ExpenseKey. From there the expense is treated exactly like one entered in Karbon — billable expenses are available to invoice and show up in the Expenses section of the invoice’s Data.

Expenses can’t yet be listed, updated or deleted through the API.

Listing Existing Payments

To see payments already recorded:

GET https://api.karbonhq.com/v3/Payments
Authorization: Bearer {token}
AccessKey: {key}

Recording a Manual Payment

Once you have the InvoiceKey, record a payment against it:

POST https://api.karbonhq.com/v3/ManualPayments
Authorization: Bearer {token}
AccessKey: {key}
Content-Type: application/json

{
  "InvoiceKey": "8fRnKqT3mXvP",
  "PaymentMethod": "EFT",
  "PaymentDate": "2026-03-31",
  "TotalAmount": 1500.00
}

Required fields:

Field Description
InvoiceKey The key of the invoice being paid
PaymentMethod One of: Check, Cash, EFT, Credit Card, BankTransfer, DirectDebit, Other
PaymentDate ISO 8601 date (e.g., 2026-03-31)
TotalAmount The amount being paid (decimal)

TotalAmount is recorded in the Karbon account’s configured currency, and PaymentDate is interpreted in the account’s configured date/timezone — both are inherited from Karbon settings rather than passed in the request.

A successful 201 Created response returns the new payment record including its PaymentKey.

Reversing a Manual Payment

Reversing a payment leaves a record behind — it appears in the payments list as a Manual Payment Reversed entry, preserving the audit trail. Deleting a payment removes it entirely, with no record that it ever existed. Reach for reversal when something was recorded incorrectly after the fact; reach for delete only when the payment should never have been recorded at all.

To reverse, call the ReverseManualPayment endpoint with the PaymentKey from the original payment and the date the reversal should be recorded:

POST https://api.karbonhq.com/v3/ReverseManualPayment
Authorization: Bearer {token}
AccessKey: {key}
Content-Type: application/json

{
  "PaymentKey": "5hWjNbL2cYqR",
  "ReversalDate": "2026-04-01T00:00:00Z"
}

Deleting a Manual Payment

Alternatively, delete it directly:

DELETE https://api.karbonhq.com/v3/ManualPayments/{PaymentKey}
Authorization: Bearer {token}
AccessKey: {key}

Subscribing to Invoice Webhooks

To be notified when invoices are created or updated without polling, subscribe to invoice webhooks:

POST https://api.karbonhq.com/v3/WebhookSubscriptions
Authorization: Bearer {token}
AccessKey: {key}
Content-Type: application/json

{
  "WebhookType": "Invoice",
  "TargetUrl": "https://your-app.example.com/webhooks/karbon",
  "SigningKey": "your-secret-signing-key-min-16-chars"
}

Your endpoint will receive payloads in this format:

{
  "ResourcePermaKey": "{InvoiceKey}",
  "ResourceType": "Invoice",
  "ActionType": "Updated",
  "TimeStamp": "2026-03-31T14:22:00Z"
}

ActionType is always Updated for invoice webhooks — Karbon does not emit separate Created, Paid, or Voided events. Fetch the full invoice using the ResourcePermaKey when you receive a notification to see what changed.

The SigningKey you provided when subscribing is used to verify each payload came from Karbon — see the Webhooks guide for how to validate signatures on your endpoint.

Important: You can only have one webhook subscription per type. If your endpoint fails to respond with a 2xx status code (e.g. 200 OK) ten times in a row, the subscription is automatically cancelled. Check for a 404 on your subscription to detect this:

GET /v3/WebhookSubscriptions/Invoice

A 404 means the subscription was auto-cancelled and needs to be re-created.