Proposal templates
A proposal template is the starting point for new proposals: a quote with no project, with its own draft budget, quote sections, notes, and optionally a Word document and a custom rate card. A template is usually linked to a project template, which gives its budget its deliverables.
These endpoints are in early access, and they follow the rules for every setup endpoint. They reach proposal templates only. A live proposal’s id returns 404 proposal_template_not_found. Live proposals and their budgets are in Proposal budgets.
Find template ids with GET /account_settings.
Endpoints
| Method and path | Does |
|---|---|
GET /api/v1/proposal_templates/:id |
Reads a template with its budget, notes and quote sections |
POST /api/v1/proposal_templates |
Creates a template, or copies one |
PATCH /api/v1/proposal_templates/:id |
Changes a template’s fields and budget settings |
DELETE /api/v1/proposal_templates/:id |
Deletes a template |
PATCH /api/v1/proposal_templates/:id/restore |
Restores a deleted template |
POST, PATCH, DELETE /api/v1/proposal_templates/:id/quote_sections |
One quote section at a time |
POST, PATCH, DELETE /api/v1/proposal_templates/:id/quote_lines |
One quote line at a time |
POST, PATCH, DELETE /api/v1/proposal_templates/:id/notes |
One note at a time |
GET, POST, PATCH, DELETE /api/v1/proposal_templates/:id/rate_card |
The template’s custom rate card |
PATCH /api/v1/proposal_templates/:id/rate_card/restore |
Restores an earlier custom rate card |
POST /api/v1/proposal_templates/:id/rate_card/apply_rates |
Puts the custom card’s rates on every budget resource line |
POST, PATCH, DELETE /api/v1/proposal_templates/:id/budget/<rows> |
One budget row at a time |
POST /api/v1/proposal_templates/:id/budget/change_structure |
Rebuilds the budget from a template |
POST /api/v1/proposal_templates/:id/budget/sync_to_quote |
Rebuilds the quote sections from the budget |
POST /api/v1/proposal_templates/:id/budget/apply_current_rates |
Updates out-of-date rate card rates |
POST /api/v1/proposal_templates/:id/budget/deliverables/:deliverable_id/sync_from_template |
Rebuilds one deliverable from its template |
POST /api/v1/proposal_templates/:id/budget/deliverables/:deliverable_id/apply_current_rates |
Updates out-of-date rates on one deliverable |
The proposal template document
GET /api/v1/proposal_templates/:id returns the template. A deleted template returns 404 proposal_template_not_found.
Needs api_read, read_quotes and read_budgets.
{
"id": 7702,
"name": "Residential fee proposal",
"description": "Standard fee proposal for new homes",
"project_template": {"id": 655, "name": "Residential new build"},
"currency": "AUD",
"default_sent_days": 0,
"default_expiry_days": 30,
"template_document": {"filename": "fee-proposal.docx", "byte_size": 48211},
"deleted": false,
"budget": {
"id": 3301,
"name": "Residential fee proposal",
"description": null,
"status": "draft",
"rate_card": {"id": 95, "name": "2026 rates"},
"custom_rate_card": null,
"currency": "AUD",
"phases": [
{"id": 510, "record_kind": "budget_phase", "name": "Design", "description": null, "colour": "blue", "position": 1}
],
"deliverables": [
{
"id": 8840,
"record_kind": "budget_deliverable",
"name": "Concept design",
"description": null,
"colour": "sky",
"budget_type": "fixed_price",
"position": 1,
"budget_phase_id": 510,
"items": [
{
"id": 61021,
"record_kind": "budget_item",
"kind": "dynamic",
"name": "Site analysis",
"item_type": "task",
"position": 1,
"description": null,
"delivery_task_name": null,
"quantity": null,
"quantity_unit": null,
"billable_rate": null,
"contract_value": {"cents": 288000, "currency": "AUD", "formatted": "$2,880.00"},
"internal_budget": {"cents": 288000, "currency": "AUD", "formatted": "$2,880.00"},
"resources": [
{
"id": 90311,
"record_kind": "budget_item_resource",
"resource_type": {"id": 88, "name": "Senior Engineer"},
"preferred_member": null,
"quantity": 16.0,
"quantity_unit": "hour",
"rate": {"cents": 18000, "currency": "AUD", "formatted": "$180.00"},
"total": {"cents": 288000, "currency": "AUD", "formatted": "$2,880.00"},
"source_rate_card": {"id": 95, "name": "2026 rates"},
"notes": null,
"position": 1
}
],
"costs": []
}
],
"template_status": "in_sync"
}
]
},
"notes": [
{"id": 1201, "record_kind": "quote_note", "content": "Fees exclude council lodgement charges.", "financial": true}
],
"quote_sections": [
{
"id": 4402,
"record_kind": "quote_section",
"name": "Concept design",
"description": null,
"position": 1,
"total_excluding_tax": {"cents": 288000, "currency": "AUD", "formatted": "$2,880.00"},
"lines": [
{
"id": 9910,
"record_kind": "quote_line",
"line_type": "line",
"line_item_code": null,
"name": "Site analysis",
"quantity": 1.0,
"price": {"cents": 288000, "currency": "AUD", "formatted": "$2,880.00"},
"finance_account": {"id": 200, "name": "Sales"},
"tax_rate": {"id": 12, "name": "GST on Income"},
"position": 1,
"total_excluding_tax": {"cents": 288000, "currency": "AUD", "formatted": "$2,880.00"}
}
]
}
],
"warnings": []
}
| Field | Meaning |
|---|---|
name |
The template’s name |
project_template |
The linked project template, or null |
default_sent_days, default_expiry_days |
The number of days from today that a new proposal from this template takes as its sent date and its expiry date |
template_document |
The Word document, as {filename, byte_size}, or null. The file itself is not returned |
budget |
The template’s draft budget. Its rows have the same shape as a live proposal budget (see Proposal budgets) |
budget.rate_card |
The account rate card the budget uses |
budget.custom_rate_card |
The template’s own rate card in use, or null |
notes |
Notes printed on proposals. financial marks a fee note |
quote_sections |
The fee sections and their lines |
On a template, each budget deliverable also has:
| Field | Meaning |
|---|---|
template_status |
How the deliverable compares with its project template deliverable: in_sync, template_changed, customised, customised_template_changed, template_removed, not_tracked or unlinked |
out_of_date_rates |
Only when the rate card now gives other rates. A list of {budget_item_id, budget_item_resource_id, resource_type, preferred_member, rate, rate_card_rate} |
Template sync
A proposal template built from a project template follows it. When a deliverable template, the project template’s deliverables or a rate card change, Drum updates each budget deliverable that nobody changed in the proposal template. A deliverable that was changed is left alone, and template_status and the warnings say so. Use Sync a deliverable from its template or Apply current rates to bring it up to date.
Warnings
code |
Means |
|---|---|
empty_dynamic_item |
Dynamic budget items with no lines, so their value is zero. count and subject_ids (item ids) |
proposal_template_drift |
The budget is missing deliverables of its project template. subject_ids are project template deliverable ids, which from_project_template_deliverable_ids takes |
proposal_template_deliverables_template_updated |
Deliverables changed in this template whose project template deliverable has changed since. subject_ids are budget deliverable ids |
proposal_template_rates_out_of_date |
Deliverables with rate card rates that the rate card has changed since. subject_ids are budget deliverable ids |
no_billable_path |
The budget has no billable task or event |
Create a proposal template
POST /api/v1/proposal_templates creates a template with a draft budget. With project_template_id, the budget gets the project template’s deliverables and the quote gets a matching section for each, as the New Proposal Template form does.
Needs api_create and create_quotes. Sending budget also needs update_budgets.
| Field | Required | Rules |
|---|---|---|
name |
Yes, unless copy_from_id is sent |
Cannot be blank |
description |
No | Text, or null |
project_template_id |
No | A kept project template |
currency |
No | An ISO 4217 code. Default: the account’s currency |
default_sent_days, default_expiry_days |
No | Integers |
template_document |
No | A Word document (see Template document) |
copy_from_id |
No | Copies this template first (see below) |
notes |
No | New notes: [{content, financial}] |
quote_sections |
No | New sections, each with its lines (see Quote sections, lines and notes) |
budget |
No | Budget settings and new rows (see below) |
budget takes:
| Field | Rules |
|---|---|
name, description |
The budget’s name and description |
rate_card_id |
The account rate card the budget uses |
reprice_resources |
true moves resource lines still on the old card’s rate for their role onto the new card’s rate, when rate_card_id changes. Manual rates stay |
phases |
New phases: [{name, description, colour}] |
deliverables |
New deliverables with their items, and each item with its resources and costs. A deliverable can name a phase of the same request with budget_phase_name. A deliverable can instead be a source, with the keys of POST .../budget/deliverables |
The rows are added in this order: notes, budget phases, budget deliverables, quote sections. An error in a row names it in field, such as budget.deliverables[1].items[0].name.
Copy a template. copy_from_id copies a kept proposal template with its quote sections, lines, notes, Word document, budget and custom rate card. The copy is named after the source with “ (copy)” added unless you send name. Other fields and rows you send then apply to the copy.
POST /api/v1/proposal_templates
{
"name": "Residential fee proposal",
"project_template_id": 655,
"default_expiry_days": 30,
"notes": [{"content": "Fees exclude council lodgement charges.", "financial": true}],
"budget": {"rate_card_id": 95}
}
Returns 201 with the template document.
Change a proposal template
PATCH /api/v1/proposal_templates/:id changes name, description, project_template_id, currency, default_sent_days, default_expiry_days and template_document, and the budget settings budget.name, budget.description, budget.rate_card_id and budget.reprice_resources. Fields you leave out stay the same.
Needs api_update and update_quotes. Sending budget also needs update_budgets.
project_template_idchanges the link only. The budget keeps its rows. Use Change the budget structure to rebuild it.{"budget": {"custom_rate_card_id": null}}stops using the template’s custom rate card without deleting it.custom_rate_card_idaccepts onlynull, and only on aPATCH.budget.reprice_resourceson its own does nothing. Send it withbudget.rate_card_id.copy_from_id,notes,quote_sections,budget.phases,budget.deliverablesandsync_quoteare refused with422 invalid_input. Use the row and workflow endpoints.- A finalised budget (
approvedorrejected) refuses budget changes with409 budget_finalised.
Returns 200 with the template document.
Template document
template_document is the Word document new proposals start from: a .docx file of 10 MB or less. Send it one of three ways:
- In JSON:
{"filename": "fee-proposal.docx", "content_base64": "..."}. - As a multipart file field named
template_document. {"from_proposal_template_id": 7650}copies the document of another kept proposal template. The copy is a separate file.
A file that is not .docx returns 422 invalid_file_type. A file over 10 MB returns 422 file_too_large. A source template that has no document returns 422 template_document_missing. The document cannot be removed through the API.
Delete and restore a proposal template
DELETE /api/v1/proposal_templates/:id soft deletes the template. It needs api_delete and delete_quotes. PATCH /api/v1/proposal_templates/:id/restore restores it. It needs api_update and update_quotes. Both return a summary:
{"id": 7702, "name": "Residential fee proposal", "description": null, "project_template": {"id": 655, "name": "Residential new build"}, "currency": "AUD", "deleted": true, "warnings": []}
Deleting a deleted template, or restoring a kept one, returns 404 proposal_template_not_found.
Quote sections, lines and notes
| Path | Row | Fields |
|---|---|---|
/api/v1/proposal_templates/:id/quote_sections |
A section | name, description, position. A new section also takes lines |
/api/v1/proposal_templates/:id/quote_lines |
A line | quote_section_id (required on POST), line_item_code_id, name, quantity, price_cents, finance_account_id, tax_rate_id, position |
/api/v1/proposal_templates/:id/notes |
A note | content, financial |
POSTadds a row and returns201.PATCH .../:row_idchanges the fields you send.DELETE .../:row_iddeletes the row.- A
PATCHof a section refuseslines.quote_section_idon a linePATCHmoves the line to another section of the same template. - Deleting a section deletes its lines. Deleted sections and lines are gone for good. A deleted note cannot be restored through the API.
- A new line takes the account’s default income account and tax rate. A
line_item_code_idbrings the code’s price, finance account and tax rate, and afinance_account_idbrings that account’s tax rate. A price or tax rate you send in the same request wins. price_centsis integer cents, tax exclusive.quantityis a number.
Fees that follow the budget. When a template’s fees follow its budget (for example, a proposal builder template after Sync the budget to the quote), its sections and lines are built from the budget. Adding, deleting or moving a section or line returns 422 live_pricing. You can still change a section’s name and description, and a line’s name, finance_account_id and tax_rate_id. Change the budget to change the fees.
Section and line writes need update_quotes. Note writes need read_quotes. Each also needs the API permission for its method.
A row write returns {proposal_template_id, row_type, record_kind, row, warnings}, where row has the shape of that row in the document. A DELETE returns {proposal_template_id, row_type, id, deleted, permanently_deleted, warnings}.
POST /api/v1/proposal_templates/7702/quote_sections
{
"name": "Documentation",
"lines": [{"name": "Construction documentation", "quantity": 1, "price_cents": 1450000}]
}
The template rate card
/api/v1/proposal_templates/:id/rate_card is the template’s own (custom) rate card, as on the template’s proposal settings screen. Its rates lay over the budget’s account rate card for new resource lines.
| Request | Does | Needs |
|---|---|---|
GET .../rate_card |
Reads the cards | api_read, read_budgets |
POST .../rate_card |
Creates a new custom card and uses it. The previous card stays in rate_cards |
api_create, update_budgets |
PATCH .../rate_card |
Changes the custom card in use | api_update, update_budgets |
DELETE .../rate_card |
Deletes the custom card in use. Rates already on budget lines stay | api_delete, update_budgets |
PATCH .../rate_card/restore |
Restores the card named by rate_card_id and uses it again |
api_update, update_budgets |
POST .../rate_card/apply_rates |
Puts the custom card’s rates on every budget resource line, manual rates included | api_update (not api_create), update_budgets |
POST and PATCH take name, description and entries. Entries work as on an account rate card: they merge, a write never deletes an entry, and a rate of 0 needs allow_zero_value: true. status, effective_date and is_default are refused: a template card is always active. Delete one entry with DELETE /rate_card_entries/:id.
Every response is the rate card document:
{
"proposal_template_id": 7702,
"proposal_budget_id": 3301,
"account_rate_card": {"id": 95, "name": "2026 rates"},
"rate_card": {
"id": 140,
"name": "Residential rates",
"description": null,
"status": "active",
"effective_date": "2026-10-13",
"is_default": false,
"default_on_activation": false,
"project": null,
"proposal_budget": {"id": 3301, "name": "Residential fee proposal"},
"entries": [
{"id": 5201, "record_kind": "rate_card_entry", "rateable_type": "resource_type", "rateable": {"id": 88, "name": "Senior Engineer"}, "billable_rate": {"cents": 17000, "currency": "AUD", "formatted": "$170.00"}, "allow_zero_value": false}
],
"deleted": false
},
"rate_cards": [],
"warnings": []
}
rate_card is the custom card in use, or null. rate_cards lists every custom card the template has had, deleted ones included. apply_rates returns the warning rates_applied_to_existing with count, the number of lines changed.
PATCH, DELETE and apply_rates on a template with no custom card in use return 404 proposal_template_rate_card_not_found. To stop using the card without deleting it, send {"budget": {"custom_rate_card_id": null}} to PATCH /proposal_templates/:id.
Budget rows
The template’s budget is edited one row at a time, with the same operation and the same row fields as a live proposal’s budget. Proposal budgets documents each row’s fields and rules. On a template, the paths start with /api/v1/proposal_templates/:id/budget/ instead of /api/v1/proposals/:id/budget/:
| Path | Row | POST also needs |
|---|---|---|
.../budget/phases |
A phase | |
.../budget/deliverables |
A deliverable, with its items |
|
.../budget/items |
An item (task), with its resources and costs |
budget_deliverable_id |
.../budget/resources |
A resource line | budget_item_id |
.../budget/costs |
A cost line | budget_item_id |
Every budget row write needs update_budgets, and the API permission for its method. A finalised budget refuses every write with 409 budget_finalised. A row write returns {proposal_template_id, row_type, record_kind, row, warnings}, and a DELETE returns {proposal_template_id, row_type, id, deleted, permanently_deleted, warnings}.
Differences on a template:
- A new deliverable from
from_project_template_deliverable_idsalso takesinclude_quote_sections: true, which adds a matched quote section for each new deliverable with billable items, after the existing sections. It needsupdate_quotesas well. - Deliverables read back with
template_statusandout_of_date_rates(see The proposal template document).
POST /api/v1/proposal_templates/7702/budget/deliverables
{"from_project_template_deliverable_ids": [4410, 4411], "budget_phase_name": "Design", "include_quote_sections": true}
Change the budget structure
POST /api/v1/proposal_templates/:id/budget/change_structure rebuilds the budget from a template, as Change Structure does in the budget editor. Every budget deliverable is removed first.
| Field | Rules |
|---|---|
template_type |
Required. project_template or proposal_template |
template_id |
An extra (non-baseline) project template, or another kept proposal template. Without it, the budget is rebuilt from the template’s own project template |
With project_template, the source’s phases merge into the budget’s phases by name. With proposal_template, the source’s phases replace them.
Needs api_update and api_delete (not api_create), and update_budgets. Returns 200 with the proposal template document.
Sync the budget to the quote
POST /api/v1/proposal_templates/:id/budget/sync_to_quote rebuilds the quote sections from the budget, as Sync Budget does.
| Field | Rules |
|---|---|
method |
Required. matched (a section for each deliverable, a line for each item), multiple_stages (a section for each deliverable, one total line each) or single_stage (one section, a line for each deliverable) |
include_non_budgeted |
true also adds deliverables and items with no billable budget (such as process-only items), as zero-priced lines. Default false |
Every section is replaced, so text written in the sections is lost. Needs api_update and api_delete (not api_create), and update_quotes. Returns 200 with the proposal template document.
Sync a deliverable from its template
POST /api/v1/proposal_templates/:id/budget/deliverables/:deliverable_id/sync_from_template rebuilds one budget deliverable from its project template deliverable, as Sync from template does. Changes made to it in the proposal template are lost.
Needs api_update and api_delete (not api_create), and update_budgets. Returns the deliverable as a row result.
| Status | code |
When |
|---|---|---|
422 |
deliverable_not_linked |
The deliverable was not built from a project template deliverable |
422 |
template_deliverable_removed |
The project template no longer has the deliverable |
Apply current rates
POST /api/v1/proposal_templates/:id/budget/apply_current_rates replaces every out-of-date rate card rate in the budget (the out_of_date_rates) with the rate card’s current rate, as Apply current rates does. Typed rates and every other change stay. It returns the proposal template document.
POST /api/v1/proposal_templates/:id/budget/deliverables/:deliverable_id/apply_current_rates does the same for one deliverable and returns it as a row result.
Both need api_update (not api_create) and update_budgets. The warning rates_unchanged means no rate was out of date.
Permissions
| Endpoint | API permission | Record permission |
|---|---|---|
GET /proposal_templates/:id |
api_read |
read_quotes and read_budgets |
POST /proposal_templates |
api_create |
create_quotes, and update_budgets with budget |
PATCH /proposal_templates/:id |
api_update |
update_quotes, and update_budgets with budget |
DELETE /proposal_templates/:id |
api_delete |
delete_quotes |
PATCH /proposal_templates/:id/restore |
api_update |
update_quotes |
| Quote sections and lines | Method’s permission | update_quotes |
| Notes | Method’s permission | read_quotes |
| Rate card | See The template rate card | read_budgets to read, update_budgets to write |
| Budget rows | Method’s permission | update_budgets |
change_structure, sync_from_template |
api_update and api_delete |
update_budgets |
sync_to_quote |
api_update and api_delete |
update_quotes |
apply_current_rates, rate_card/apply_rates |
api_update |
update_budgets |
Errors
Error bodies are {"error", "code"}, with field on input and lookup errors (see Errors).
| Status | code |
When |
|---|---|---|
403 |
not_authorized |
A missing API or record permission |
404 |
not_found |
The setup endpoints are not turned on for the account |
404 |
proposal_template_not_found |
The template is not in the account, is deleted, or is a live proposal |
404 |
quote_section_not_found, quote_line_not_found, quote_note_not_found |
The row is not on this template |
404 |
budget_phase_not_found, budget_deliverable_not_found, budget_item_not_found, budget_item_resource_not_found, budget_item_cost_not_found |
The row is not in this template’s budget |
404 |
proposal_template_rate_card_not_found, rate_card_not_found, project_template_not_found, line_item_code_not_found, finance_account_not_found, tax_rate_not_found |
A record a field names cannot be used |
409 |
budget_finalised |
The budget is approved or rejected |
422 |
missing_input, invalid_input |
A required field is missing, a field is unknown or has the wrong type, or a rule above refused the value |
422 |
live_pricing, live_pricing_total_line |
The fees follow the budget, so the section or line cannot change by hand |
422 |
invalid_file_type, file_too_large, template_document_missing |
The Word document cannot be used |
422 |
validation_failed |
The record would not save. errors lists why |