Core Resources

Expenses

Record the money your business spends and track it against customers and projects.

Overview

An expense is a single record of money spent: a software subscription, a flight, a lunch with a client, a payment processor fee. Unlike invoices, expenses have no line items: each one has an amount, a currency, an optional tax, a vendor, a category, and optional links to a customer or project so you can measure margin.

Each expense gets an auto-generated expense_number (e.g. EXP-000001). The prefix is configurable per company in Settings.

Expenses are an optional module. If an owner or admin turned it off in Settings > Modules, every expenses, vendors, and expense categories endpoint returns 403 with code expenses_module_disabled.

Endpoints

MethodPathPermissionDescription
GET/v1/expensesreadList expenses (cursor pagination, filters)
POST/v1/expenseswriteCreate an expense
GET/v1/expenses/{id}readRetrieve an expense by id or expense_number
POST/v1/expenses/{id}writeUpdate an expense (PATCH is accepted as an alias)
DELETE/v1/expenses/{id}adminDelete an expense (soft delete)

Create an Expense

1curl -X POST "https://api.corebill.io/v1/expenses" \
2 -H "Authorization: Bearer sk_live_your_api_key" \
3 -H "Content-Type: application/json" \
4 -H "Idempotency-Key: 7f1c2e0a-azure-2026-09" \
5 -d '{
6 "expense_date": "2026-09-01",
7 "amount": 120.00,
8 "currency": "USD",
9 "vendor_name": "Microsoft Azure",
10 "category_name": "Software & subscriptions",
11 "description": "Azure hosting - September",
12 "payment_method": "credit_card",
13 "tax_rate": 18,
14 "tax_inclusive": true,
15 "project_id": "prj_abc123",
16 "recurrence": "monthly"
17 }'

expense_date, amount, and currency are required. The response is the created Expense object with status 201. Expenses created through the API are recorded with source: "api".

You can pass vendor_name and category_name instead of ids. Corebill looks them up by name (case-insensitive) and creates them if they don't exist yet. If you send both, the name wins over vendor_id / category_id.

Amounts and tax

amount is the value you paid in the major unit of the currency with 2 decimals (120.5 = $120.50). By default it is tax inclusive: Corebill splits it into subtotal + tax_amount = total. Send tax_inclusive: false if amount is before tax; then amount becomes the subtotal and the tax is added on top.

Pass tax_id to use one of the company's configured taxes, or tax_rate (0-100) for a one-off percentage. tax_id takes precedence over tax_rate.

amount 118, tax_rate 18, tax_inclusive true
1{
2 "subtotal": 100.00,
3 "tax_rate": 18,
4 "tax_amount": 18.00,
5 "tax_inclusive": true,
6 "total": 118.00
7}

Recurring expenses

Set recurrence to weekly, monthly, or yearly for costs that repeat (subscriptions, rent). The expense you create is the first occurrence; Corebill creates the next copies automatically on recurrence_next_date until the optional recurrence_end_date. Generated copies have source: "recurring" and point to the original in recurring_parent_id.

List Expenses

Returns expenses most recently created first, using cursor pagination (limit, starting_after, ending_before).

1# Expenses of September 2026
2curl "https://api.corebill.io/v1/expenses?from=2026-09-01&to=2026-09-30" \
3 -H "Authorization: Bearer sk_live_your_api_key"
4
5# Expenses linked to a project
6curl "https://api.corebill.io/v1/expenses?project_id=prj_abc123" \
7 -H "Authorization: Bearer sk_live_your_api_key"
8
9# Paginate (cursor-based)
10curl "https://api.corebill.io/v1/expenses?limit=50&starting_after=exp_abc123" \
11 -H "Authorization: Bearer sk_live_your_api_key"
Query paramTypeDescription
fromdateOnly expenses with expense_date on or after this date (YYYY-MM-DD)
todateOnly expenses with expense_date on or before this date (YYYY-MM-DD)
customer_idstringFilter by linked customer (cus_...)
project_idstringFilter by linked project
category_idstringFilter by category (exc_...)
vendor_idstringFilter by vendor (ven_...)
limitinteger1-100. Default: 10
starting_after / ending_beforestringCursor (an expense id)

Retrieve an Expense

Accepts the expense id (exp_...) or its expense_number.

1curl "https://api.corebill.io/v1/expenses/EXP-000042" \
2 -H "Authorization: Bearer sk_live_your_api_key"

Update an Expense

Send only the fields you want to change. Amounts are recalculated when you change amount, tax_id, tax_rate, or tax_inclusive. Send null to clear an optional link (e.g. "project_id": null). Use POST like the rest of the API; PATCH is accepted as an alias.

1curl -X POST "https://api.corebill.io/v1/expenses/exp_abc123" \
2 -H "Authorization: Bearer sk_live_your_api_key" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "amount": 135.40,
6 "customer_id": "cus_abc123",
7 "notes": "Rebilled to the client"
8 }'

Delete an Expense

Deleting an expense is a soft delete (sets deleted_at). If the expense was recurring, it stops generating new copies. Requires admin permission.

1curl -X DELETE "https://api.corebill.io/v1/expenses/exp_abc123" \
2 -H "Authorization: Bearer sk_live_your_api_key"
JSON
1{
2 "id": "exp_abc123",
3 "object": "expense",
4 "deleted": true
5}

The Expense Object

JSON
1{
2 "id": "exp_a1b2c3d4e5f6",
3 "object": "expense",
4 "expense_number": "EXP-000042",
5 "expense_date": "2026-09-01",
6 "description": "Azure hosting - September",
7 "currency": "USD",
8 "subtotal": 101.69,
9 "tax_id": null,
10 "tax_rate": 18,
11 "tax_amount": 18.31,
12 "tax_inclusive": true,
13 "total": 120.00,
14 "payment_method": "credit_card",
15 "vendor_id": "ven_x1y2z3",
16 "vendor": { "id": "ven_x1y2z3", "name": "Microsoft Azure" },
17 "category_id": "exc_k7l8m9",
18 "category": { "id": "exc_k7l8m9", "name": "Software & subscriptions" },
19 "customer_id": null,
20 "project_id": "prj_abc123",
21 "notes": null,
22 "receipt": { "name": "azure-sep.pdf", "content_type": "application/pdf", "size": 48213 },
23 "source": "api",
24 "recurrence": "monthly",
25 "recurrence_next_date": "2026-10-01",
26 "recurrence_end_date": null,
27 "recurring_parent_id": null,
28 "created_at": "2026-09-01T14:30:00Z",
29 "updated_at": "2026-09-01T14:30:00Z"
30}
FieldTypeDescription
idstringUnique identifier (prefix: exp_)
expense_numberstringHuman-readable number (e.g. EXP-000042)
expense_datedateDate the money was spent (YYYY-MM-DD)
subtotal / tax_amount / totalnumberAmounts in major unit, 2 decimals. total = subtotal + tax_amount
tax_ratenumberTax percentage applied (0-100)
tax_inclusivebooleanWhether the amount you sent already included tax
vendor / categoryobject{ id, name } of the linked vendor / category, or null
receiptobjectMetadata of the attached receipt (name, content_type, size), or null. Receipts are uploaded from the dashboard
sourcestringmanual, api, agent, recurring, or stripe (processor fees imported automatically)
recurrencestringnone, weekly, monthly, or yearly
recurrence_next_datedateWhen the next copy will be created, or null
recurring_parent_idstringFor generated copies, the id of the original expense

Expense Fields

Fields accepted by create and update. Any other field in the body is ignored.

FieldTypeRequiredDescription
expense_datestringYes (create)YYYY-MM-DD
amountnumberYes (create)Greater than 0, major unit with 2 decimals. Tax included unless tax_inclusive is false
currencystringYes (create)ISO-4217 code (e.g. USD, EUR, PEN)
tax_inclusivebooleanNoDefault: true
tax_idstringNoOne of the company's configured taxes. Takes precedence over tax_rate
tax_ratenumberNoTax percentage 0-100 when no tax_id is sent
vendor_idstringNoExisting vendor (ven_...)
vendor_namestringNoVendor name. Found or created automatically
category_idstringNoExisting category (exc_...)
category_namestringNoCategory name. Found or created automatically
descriptionstringNoWhat the expense was for (max 500 chars)
payment_methodstringNobank_transfer, credit_card, debit_card, cash, check, paypal, stripe, wallet, other
customer_idstringNoCustomer this expense is for (cus_...)
project_idstringNoProject this expense is for
notesstringNoInternal notes
recurrencestringNonone (default), weekly, monthly, yearly
recurrence_end_datestringNoYYYY-MM-DD. Last date a copy can be created

All referenced ids (vendor, category, tax, customer, project) must belong to the same company as your API key. Otherwise the request fails with 400 and code resource_missing.

Errors

StatusCodeWhen
400parameter_missingexpense_date, amount, or currency missing on create, or an empty update
400invalid_expense_date, invalid_amount, invalid_currency, invalid_tax_rate, invalid_payment_method, invalid_recurrence, invalid_recurrence_end_dateA field has an invalid value (param names the field)
400resource_missingA referenced vendor, category, tax, customer, or project does not exist in this company
400invalid_datefrom or to query param is not YYYY-MM-DD
403permission_requiredThe API key lacks write (create/update) or admin (delete) permission
403expenses_module_disabledThe Expenses module is turned off for the company
404expense_not_foundNo expense with that id or expense_number in this company

Expenses emit expense.created, expense.updated and expense.deleted webhook events (including expenses created automatically from Stripe fees and recurring expenses). See Webhooks.