How to send a proposal through the API
Use the API to send a proposal when another system, or an AI agent, prepares the proposal and you want Drum to email it and record the client’s answer.
The email goes to people outside Drum and cannot be recalled. Check each step’s result before you send.
Before you begin
You need:
- An API token (see Create an API token) whose role has
api_read,api_createandapi_update, plusread_quotes,update_quotesand read access to the opportunity or project. Addapi_deleteif you will set the pricing layout, andcustomisationto read signed proposals. - The proposal builder turned on for your account.
- A proposal on an opportunity or project, built in the proposal builder, that is not awaiting approval. Approve it in Drum first if it needs approval.
- The proposal id: the number in the proposal’s address in Drum. For
https://app.getdrum.com/quotes/18342, the id is18342.
The examples keep the token in DRUM_API_TOKEN.
1. Read the proposal
curl -H "Authorization: Bearer $DRUM_API_TOKEN" \
"https://app.getdrum.com/api/v1/proposals/18342"
Check status and total_excluding_tax. If warnings lists empty_dynamic_item or no_billable_path, fix the budget first (see Proposal budgets).
2. Read the document
curl -H "Authorization: Bearer $DRUM_API_TOKEN" \
"https://app.getdrum.com/api/v1/proposals/18342/document"
Note:
- Each section’s
idandlock_version. You need both to change a section. - The
stages. A fee table names a stage by itsid. sections_has_more. If it istrue, read the next page withsections_offsetset tonext_sections_offset.
Open links.preview_url in Drum to see the document as the client will.
3. Set the pricing layout (optional)
Do this only when the fee table should follow the proposal budget. The first time, Drum rebuilds the fee stages and lines from the budget and deletes the others.
curl -X PATCH "https://app.getdrum.com/api/v1/proposals/18342/document" \
-H "Authorization: Bearer $DRUM_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pricing": {"layout": "single_stage"}}'
The response lists the new stages. Use their ids in the next step.
4. Adjust the document
To add a section with text, a fee table and a signature block:
curl -X POST "https://app.getdrum.com/api/v1/proposals/18342/document/sections" \
-H "Authorization: Bearer $DRUM_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Fees",
"body": "Our fee for is set out below.",
"blocks": [{"type": "fee_table", "stage_id": 90240}, {"type": "signature"}]
}'
To change the text of an existing section, send the lock_version from your read:
curl -X PATCH "https://app.getdrum.com/api/v1/proposals/18342/document/sections/5521" \
-H "Authorization: Bearer $DRUM_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"lock_version": 4, "body": "We will design the ground floor fit-out in three stages."}'
If the answer is 409 proposal_section_stale, someone changed the section after your read. Read the document again and redo your change. The section formats and block types are in Add sections.
5. Read the send draft
curl -H "Authorization: Bearer $DRUM_API_TOKEN" \
"https://app.getdrum.com/api/v1/proposals/18342/send_draft"
Check:
to: the default recipient. Eachcontact:<id>value is a contact you can send to with{"contact_id": <id>}.blank_merge_fields: merge fields that would show empty. Fill in the missing details in Drum, or change your text, before you send.
6. Send the proposal
Create a new idempotency key for this send, and keep it until you have a result:
SEND_KEY=$(uuidgen)
curl -X POST "https://app.getdrum.com/api/v1/proposals/18342/sends" \
-H "Authorization: Bearer $DRUM_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $SEND_KEY" \
-d '{"to": [{"contact_id": 3307}], "cc": ["tom@acme.example"]}'
Drum answers 201 with the new version. Leaving out subject and message uses the defaults from the send draft. The first To recipient becomes the signatory, the person who can accept or decline online, unless the proposal contact is among the To recipients or you send signatory.
If the request times out, send it again with the same SEND_KEY and the same body. Drum answers 200 with the first version and sends nothing more.
Read warnings in the response. merge_field_blank means the client saw an empty value.
7. Check delivery
Use the version id from the send:
curl -H "Authorization: Bearer $DRUM_API_TOKEN" \
"https://app.getdrum.com/api/v1/proposals/18342/versions/2207"
Each recipient’s status moves from sending to sent, delivered, opened and viewed. A bounced or failed recipient did not get the email. pdf_url links to the PDF that was sent. When pdf_status is pending, read again in a minute.
8. Find the signed proposal
After the client answers, the version’s response is filled in and its state is accepted or declined. To get the full record and the signed PDF:
curl -H "Authorization: Bearer $DRUM_API_TOKEN" \
"https://app.getdrum.com/api/v1/signed_proposals?proposal_id=18342&kind=accepted"
Take the id of the row, then read it:
curl -H "Authorization: Bearer $DRUM_API_TOKEN" \
"https://app.getdrum.com/api/v1/signed_proposals/914"
Download the signed PDF from signed_pdf_url within 5 minutes. If signed_pdf_status is pending, Drum is still making it: read again later.
Troubleshooting
The send answers 422 quote_awaiting_approval
The proposal must be approved before it is sent. Approve it in Drum, then send again. A refused send stores no key, so you can reuse the same one.
The send answers 422 proposal_fee_table_stage_missing
A fee table names a stage that no longer exists, often after a pricing layout change. Read the document, then change the section’s content so the fee table names a stage from stages.
The send answers 429 send_rate_limited
A user can send to 50 recipients an hour, and an account to 200 a day. Wait for the Retry-After seconds.
The document endpoints answer 404 proposal_not_found
The token’s user cannot read the opportunity or project, or the proposal uses a Word document instead of the proposal builder.