Costs
A cost is a supplier bill or an internal expense on a project. Every cost is allocated to one task and has at least one line item. Drum works out the cost total from its line items.
Money is integer cents, tax exclusive. Dates are yyyy-mm-dd. created_at, updated_at and approved_at are timestamps.
Endpoints
| Method and path | Does |
|---|---|
GET /api/v1/projects/:project_id/costs |
Lists the costs on a project |
GET /api/v1/projects/:project_id/costs/:id |
Reads one cost |
POST /api/v1/projects/:project_id/costs |
Creates a cost and its line items |
PATCH /api/v1/projects/:project_id/costs/:id |
Changes a cost’s fields |
POST /api/v1/uploads |
Starts a direct upload of a file for a cost |
List costs
Newest issued date first, paginated (see Pagination). A cost raised by a variation is listed once the variation is approved. Deleted costs are left out.
| Parameter | Meaning |
|---|---|
start_date, end_date |
Issued on or after, or on or before, this date |
status |
draft, final, approved or planned |
task_id |
Costs on one task |
deliverable_id |
Costs on one deliverable |
cost_type_id |
Costs of one cost type |
billing_status |
unbilled, partial or billed |
billable |
billable, unbillable or direct_client_cost |
include_line_items |
true adds line_items to each row |
curl -H "Authorization: Bearer $DRUM_API_TOKEN" \
"https://app.getdrum.com/api/v1/projects/20488/costs?status=final&include_line_items=true"
The cost object
| Field | Meaning |
|---|---|
id, name, description, reference, invoice_number |
As recorded |
status |
draft, final, approved or planned |
billable |
billable, unbillable or direct_client_cost |
billing_status |
unbilled, partial or billed |
issued_date, due_date, paid_date |
Dates, or null |
tax_rule |
exclusive or inclusive. How the line item prices are read |
project_id, task_id, deliverable_id, cost_type_id |
Where the cost sits |
company_id, member_id |
The supplier company, or the member of staff who incurred it. One of the two is set |
supplier_name |
The company name, or the member’s name in the account |
task_name, cost_type_name |
Names for the ids above |
created_by_id, created_by_name, approved_by_id, approved_by_name, approved_at |
Who created and approved it, and when |
third_party_bill_status |
The status of the bill in the connected accounting system, or null |
purchase_order_id, purchase_order_number |
The linked purchase order when exactly one is linked, otherwise null |
purchase_order_ids |
Every linked purchase order |
expense_claim_item_id |
The expense claim item the cost belongs to, or null |
line_items |
Only with include_line_items=true, and always on create and update |
warnings |
On create and update only. Messages about a step after the save, such as a bill push that failed |
Money and markup fields:
| Fields | Shown with |
|---|---|
currency, total_cents, total_excluding_tax_cents, billed_amount_cents, remaining_billable_cents, amount_paid_cents |
view_project_metrics |
fixed_markup_cents, percentage_markup |
update_costs or approve_costs. Left out when zero |
total_with_markup_cents, total_with_markup_including_tax_cents |
Both of the above. Left out when the cost has no markup |
Line item fields: id, name, position, line_type (line or heading), quantity (a decimal, as a string), tax_rate_id, tax_rate_name, finance_account_id, finance_account_name, line_item_code_id, line_item_code_name. With view_project_metrics also price_cents, discount_cents, total_cents and total_excluding_tax_cents.
account_user_id and project_procedure_id are deprecated copies of member_id and deliverable_id. Use the new names.
Create a cost
POST /api/v1/projects/:project_id/costs creates the cost and its line items in one transaction. Send fields flat or under cost. The project must be a delivery project.
| Field | Required | Rules |
|---|---|---|
task_id |
Yes | A task of this project, from GET /projects/:id |
cost_type_id |
Yes | From GET /cost_types |
name |
Yes | Up to 255 characters |
line_items |
Yes | At least one line item (see below) |
company_id, member_id or new_supplier_name |
Yes, exactly one | A supplier from GET /companies, a member of staff from GET /members, or the name of a new supplier to create (needs create_companies) |
description, reference, invoice_number |
No | Up to 1,000 characters each |
issued_date, due_date |
No | yyyy-mm-dd |
status |
No | draft (default), final or planned. final can start the cost approval workflow. approved is refused: create the cost as final and approve it in Drum |
billable |
No | billable (default), unbillable or direct_client_cost |
deliverable_id |
No | A deliverable of this project |
fixed_markup_cents |
No | Integer cents, zero or more |
percentage_markup |
No | A percentage. 10 means 10 per cent. Cannot be sent with a positive fixed_markup_cents |
purchase_order_id |
No | A final or approved purchase order of this project |
supplier_invoice_total_cents |
No | The total printed on the supplier’s invoice, in integer cents |
document or document_signed_id |
No | A file. See Attach a file |
Line item fields:
| Field | Required | Rules |
|---|---|---|
name |
Yes | Up to 255 characters |
quantity |
Yes | Greater than zero. A decimal string such as "2.5" is accepted |
price_cents |
Yes | Integer cents per unit, zero or more |
finance_account_id |
No | A visible account from GET /finance_accounts. Default: the cost type’s default account |
tax_rate_id |
No | Default: the finance account’s tax rate |
line_type |
No | line (default) or heading |
A line item with any other key fails the request with 422 invalid_input.
Defaults to know about:
- A line with no finance account takes the cost type’s default account, then its line item code’s account. With neither, the cost saves, but it cannot go to the accounting system until an account is set in Drum.
- A cost type with a line item code gives a line sent with
price_cents: 0the code’s price. - A cost type set to add a summary line adds one more line with the project number.
- The cost takes the project’s currency.
Check the cost in the response to see the defaults that were applied.
POST /api/v1/projects/20488/costs
{
"cost": {
"task_id": 55120,
"cost_type_id": 610,
"name": "Steel package",
"invoice_number": "INV-4471",
"issued_date": "2026-09-08",
"due_date": "2026-10-08",
"status": "final",
"company_id": 902,
"line_items": [
{"name": "Steel supply", "quantity": 2, "price_cents": 125000, "finance_account_id": 310},
{"name": "Delivery", "quantity": 1, "price_cents": 15000, "finance_account_id": 310}
]
}
}
Returns 201 with the cost and its line items. Send an Idempotency-Key header to make retries safe (see Idempotency keys). A replay does not raise approvals or push a bill again.
A created cost sends the cost.created webhook event.
Add a receipt to an expense claim
Send expense_claim_id to add the cost as a receipt on a draft expense claim, as Add Item on the claim page does. Drum creates a claim item for total_cents, allocated in full to task_id. From then on the claim sets the cost’s name, supplier, amount and status.
- Required:
expense_claim_id,task_id,cost_type_id,name,total_cents. - Optional:
description,reference,issued_date, andcompany_idornew_supplier_name. - Not accepted:
line_items,purchase_order_id,member_id,invoice_number,due_date,status,billable,deliverable_id, markups,supplier_invoice_total_cents. - The claim must be one the token’s user can see and change. A claim that is not a draft returns
422 expense_claim_not_draft. The account must use expense claims.create_costsis not needed.
Change a cost
PATCH /api/v1/projects/:project_id/costs/:id changes the fields you send. Send fields flat or under cost. Line items cannot be changed here.
Fields: name, description, reference, invoice_number, issued_date, due_date, bill_issue_date, status, billable, tax_rule, task_id, deliverable_id, cost_type_id, one of company_id, member_id or new_supplier_name, fixed_markup_cents, percentage_markup, supplier_invoice_total_cents.
status: "approved"needsapprove_costs. The token’s user becomes the approver, and the automatic bill push runs. A failed push is reported inwarnings.status: "final"starts the approval workflow.- A cost with a bill in the accounting system stays approved (
422 status_locked_by_bill). A change to a field on the bill is sent to the accounting system. - The markup of an invoiced cost cannot change (
422 cost_invoiced). - On a cost from an expense claim, the fields the claim sets return
422 cost_on_expense_claim. Change the claim in Drum instead. new_supplier_namecreates a supplier and needscreate_companies.
Returns 200 with the cost and its line items.
Attach a file
A cost or a receipt can carry one PDF or image (JPEG, PNG, GIF, WebP or HEIC). Send one of:
document:{"filename": "receipt.pdf", "content_base64": "..."}, for a file of 1 MB or less. A larger file returns422 file_too_large.document_signed_id: thesigned_idfrom a direct upload, for a file of up to 20 MB.
For a direct upload, POST /api/v1/uploads with filename, byte_size (integer), checksum (base64 MD5 of the file) and content_type. It returns 201 with signed_id, url, headers and expires_at. PUT the file bytes to url with exactly those headers within 10 minutes, then send signed_id as document_signed_id within one day. A signed id works once, and only for the token’s user. An unknown, expired or used id returns 404 upload_not_found.
POST /uploads needs api_create and permission to create costs or expense claims. It accepts an Idempotency-Key.
Permissions
| Endpoint | Needs |
|---|---|
GET costs |
api_read, read_costs, and read access to the project |
POST costs |
api_create, create_costs, and read access to the project |
PATCH costs |
api_update, and permission to update that cost in Drum |
Errors
Error bodies are {"error", "code"}. Input and lookup errors add field, which for a line item reads like line_items[0].price_cents. Nothing is saved unless the whole request succeeds.
| Status | code |
When |
|---|---|---|
403 |
none | No create_costs ({"error": "Forbidden"}) |
403 |
not_authorized, approval_not_permitted, supplier_creation_not_permitted |
The token’s user may not make this change |
404 |
none | {"error": "Project not found"}: the project does not exist, is deleted, is an opportunity, or is hidden. {"error": "Cost not found"} on a read |
404 |
cost_not_found |
The cost is not on this project or is hidden (on PATCH) |
404 |
task_not_found, cost_type_not_found, company_not_found, member_not_found, deliverable_not_found, finance_account_not_found, tax_rate_not_found, purchase_order_not_found, expense_claim_not_found, upload_not_found |
A referenced record cannot be used |
409 |
cost_busy |
Another change to the cost is running. Retry |
409 |
idempotency_key_reused, retry_later, idempotent_record_deleted |
See Idempotency keys |
422 |
missing_input, invalid_input |
A required field is missing, or a value could not be read |
422 |
supplier_or_resource_required |
None, or more than one, of company_id, member_id and new_supplier_name |
422 |
conflicting_markup |
Both markups are positive |
422 |
cost_on_expense_claim, status_locked_by_bill, cost_invoiced, expense_claim_not_draft |
A business rule refused the change |
422 |
file_too_large, invalid_file_type |
The file cannot be used |
422 |
validation_failed |
The cost would not save. errors lists why |