Node SDK: Expenses
These examples use @keito-ai/sdk 0.1.2 or later and an initialized Keito client. Discover project and expense-category IDs through the REST expense guide. IDs shown below are placeholders.
Create and read
const expense = await keito.expenses.create({
project_id: projectId,
expense_category_id: categoryId,
spent_date: '2026-10-30',
total_cost: 24.50,
notes: 'Receipt imported from my application',
source: 'api',
}, { idempotencyKey: 'receipt-import-unique-001' });
const expenseId = expense.id;
if (!expenseId) throw new Error("Expense response has no ID; reconcile before retrying.");
const saved = await keito.expenses.get(expenseId);
console.log(saved.total_cost, saved.currency);
Amount-based categories require total_cost. Per-unit categories require units instead and use their configured category rate. Do not send a top-level unit_price. Save the returned expense ID before uploading a receipt.
Upload, download and remove a receipt
import { readFile } from 'node:fs/promises';
const bytes = await readFile('/path/to/receipt.pdf');
await keito.expenses.uploadReceipt(
expenseId,
new Blob([new Uint8Array(bytes)], { type: 'application/pdf' }),
'receipt.pdf',
);
const receipt = await keito.expenses.downloadReceipt(expenseId);
console.log(receipt.type, receipt.size);
// When the user explicitly wants to remove the attachment:
// await keito.expenses.deleteReceipt(expenseId);
Uploads use multipart field file. The file must be nonempty and no larger than 9 MiB. PDF, JPEG, PNG, GIF, WebP, HEIC, HEIF and TIFF are supported. An upload replaces the stored receipt. Receipt upload retries are deliberate; they do not create another expense. Receipt presence does not automatically select it for invoice delivery.
List and change expenses
for await (const row of await keito.expenses.list({
project_id: projectId,
from: '2026-10-01',
to: '2026-10-31',
})) {
console.log(row.id, row.total_cost);
}
await keito.expenses.update(expenseId, { notes: 'Reviewed receipt' }, {
idempotencyKey: 'receipt-review-unique-001',
});
// Only on an explicit deletion decision:
// await keito.expenses.delete(expenseId, { idempotencyKey: 'expense-delete-unique-001' });
Permissions, approval, invoicing and timesheet locks apply to edits and receipt changes. Use the REST expense reference for refunds, reimbursement classification, category discovery and complete field rules. Creating an expense records bookkeeping data; it does not pay money.