The Better Proposals API always returns a JSON as the response body.
The response always includes a status key, which can have one of two values:
success or error
That lets you know whether your request was successful.
If the status is "success", the response will always include a data key containing the requested information.
This error means that the request you've sent to the server is not formatted correctly.
This type of error usually occurs when you don't include the access token in the request headers.
{
"status": "error",
"message": "Malformed request"
}
This error occurs when the token you passed is not valid anymore. View API Authentication details
{
"status": "error",
"message": "Invalid token"
}
This error occurs when your free trial ends.
To continue using our API, you need to upgrade to the Premium or Enterprise plan.
{
"status": "error",
"message": "Your trial has expired."
}
After your free trial, you can only use the Better Proposals API on the Premium or Enterprise plan. This error indicates that your current plan doesn't support API use.
{
"status": "error",
"message": "It isn't possible to use the Better Proposals API on your current plan."
}
This error occurs when an endpoint url is not found or is not available in the current version of the API.
You can view available API endpoints in the API documentation menu.
{
"status": "error",
"message": "Not found"
}
| Status code | Description | ||
|---|---|---|---|
| Status code | 2XX | Description | Success |
| Status code | 4XX | Description | Client error |
| Status code | 5XX | Description | Server error |
The API currently supports header authorization.
Simply include Bptoken: YOUR_API_TOKEN in your request headers.
{
"status": "success",
"data": {
// This object depends on created proposal's details, plus the links:
"ProposalView": "https://betterproposals.io/2/proposals/view?id=...",
"Preview": "https://betterproposals.io/proposal/index?ProposalID=...",
"Editor": "https://betterproposals.io/2/editor/proposal-editor?id=..." // Open the document in the editor to check and edit it
}
}
{
"status": "success",
"data": {}, // This object depends on created cover's details.
}
Updates the settings of an existing proposal. Only the provided fields are changed. When Contacts are provided they replace the existing ones, following the same rules as the recipients on the send page: contacts that already signed are never removed or modified.
{
"status": "success",
"data": {
// This object depends on edited proposal's details, plus the links:
"ProposalView": "https://betterproposals.io/2/proposals/view?id=...",
"Preview": "https://betterproposals.io/proposal/index?ProposalID=...",
"Editor": "https://betterproposals.io/2/editor/proposal-editor?id=..." // Open the document in the editor to check and edit it
}
}
{
"status": "success",
"data": {}, // This object depends on edited cover's details.
}
Adds the pricing block to a proposal with its pricing tables and line items, exactly like the editor does. A document can contain only one pricing block: when the document already has one (for example because it was created from a template) the request edits it instead, so the same endpoint adds and updates pricing. When the block is added it goes in an existing section (Section = section ID), in a new section (Section = name of the new section) or in a new untitled section, at the end of the section or at the given Position.
Tables and line items without an ID are created. Tables and line items with an ID (returned by this endpoint and by Get proposal details) are updated with the provided fields, or deleted when they carry Delete: true. Changing the UnitCost or Quantity of a line item recalculates its total, changing the Cost recalculates its unit cost. The quote totals of the document are recalculated after every change.
{
"status": "success",
"data": {
"ProposalID": 12345,
"Action": "created", // "updated" when the document already had a pricing block
"PageID": 111,
"PageName": "Investment",
"NewSection": true,
"Block": {
"Type": "pricing",
"ElementType": "quote",
"ElementID": 222,
"ProposalElementID": 333,
"DisplayOrder": 1
},
"PriceTables": [
{
"ID": 444,
"Title": "Services",
"Show": true,
"ForceClientChoice": false,
"DateCreated": "2026-09-15 10:00:00",
"DisplayOrder": 1,
"Items": [
{
"ID": 555,
"Label": "Website design",
"Description": "Design of the new website",
"UnitCost": "1000.00",
"Cost": "1000.00",
"RecurringType": "One Time Payment",
"Quantity": 1,
"Optional": false,
"DisplayOrder": 1
// ... the same fields returned by Get proposal details
}
]
}
],
"Preview": "https://betterproposals.io/proposal/index?ProposalID=...",
"Editor": "https://betterproposals.io/2/editor/proposal-editor?id=..."
}
}
Generates the personal link of every recipient of the proposal without sending any email, like the "generate the document link" option of the send page, so you can deliver the links yourself. Recipients are saved as document contacts with their signing order. Use RequiredToSign on each recipient to require their signature and SignOrder to force recipients to sign in the order they appear. Recipients that already signed cannot be removed or modified. If Recipients is omitted, the links are generated for the existing contacts of the document. Generating the links counts as a send: it counts towards the monthly sending limit of your plan and follows the same rules as the send page (documents pending approval can't be sent by users that require approval, and repeated sends of the same document in a short period are blocked).
{
"status": "success",
"data": {
"ProposalID": 12345,
"SignOrder": true,
"DateSent": "2026-09-15 10:00:00",
"Recipients": [
{
"FirstName": "John",
"Surname": "Doe",
"Email": "john@doe.com",
"RequiredToSign": true,
"Link": "https://betterproposals.io/proposal/index?ProposalID=...&ContactID=..."
},
{
"FirstName": "Jane",
"Surname": "Doe",
"Email": "jane@doe.com",
"RequiredToSign": false,
"Link": "https://betterproposals.io/proposal/index?ProposalID=...&ContactID=..."
}
],
"ProposalView": "https://betterproposals.io/2/proposals/view?id=..."
}
}
{
"status": "success",
"data": {}, // This object depends on created quote's details.
}
{
"status": "success",
"data": {}, // This object depends on created company's details.
}