Create new version of proposal
curl --request POST \
--url https://api.proposales.com/v3/proposals/{uuid} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"company_id": 123,
"language": "<string>",
"title_md": "<string>",
"description_md": "<string>",
"recipient": {},
"contact_email": "<string>",
"data": {},
"blocks": [
{}
],
"attachments": [
{}
],
"background_image": {},
"background_video": {},
"invoicing_enabled": true,
"invoicing": {},
"tax_options": {}
}
'import requests
url = "https://api.proposales.com/v3/proposals/{uuid}"
payload = {
"company_id": 123,
"language": "<string>",
"title_md": "<string>",
"description_md": "<string>",
"recipient": {},
"contact_email": "<string>",
"data": {},
"blocks": [{}],
"attachments": [{}],
"background_image": {},
"background_video": {},
"invoicing_enabled": True,
"invoicing": {},
"tax_options": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
company_id: 123,
language: '<string>',
title_md: '<string>',
description_md: '<string>',
recipient: {},
contact_email: '<string>',
data: {},
blocks: [{}],
attachments: [{}],
background_image: {},
background_video: {},
invoicing_enabled: true,
invoicing: {},
tax_options: {}
})
};
fetch('https://api.proposales.com/v3/proposals/{uuid}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.proposales.com/v3/proposals/{uuid}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'company_id' => 123,
'language' => '<string>',
'title_md' => '<string>',
'description_md' => '<string>',
'recipient' => [
],
'contact_email' => '<string>',
'data' => [
],
'blocks' => [
[
]
],
'attachments' => [
[
]
],
'background_image' => [
],
'background_video' => [
],
'invoicing_enabled' => true,
'invoicing' => [
],
'tax_options' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.proposales.com/v3/proposals/{uuid}"
payload := strings.NewReader("{\n \"company_id\": 123,\n \"language\": \"<string>\",\n \"title_md\": \"<string>\",\n \"description_md\": \"<string>\",\n \"recipient\": {},\n \"contact_email\": \"<string>\",\n \"data\": {},\n \"blocks\": [\n {}\n ],\n \"attachments\": [\n {}\n ],\n \"background_image\": {},\n \"background_video\": {},\n \"invoicing_enabled\": true,\n \"invoicing\": {},\n \"tax_options\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.proposales.com/v3/proposals/{uuid}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"company_id\": 123,\n \"language\": \"<string>\",\n \"title_md\": \"<string>\",\n \"description_md\": \"<string>\",\n \"recipient\": {},\n \"contact_email\": \"<string>\",\n \"data\": {},\n \"blocks\": [\n {}\n ],\n \"attachments\": [\n {}\n ],\n \"background_image\": {},\n \"background_video\": {},\n \"invoicing_enabled\": true,\n \"invoicing\": {},\n \"tax_options\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.proposales.com/v3/proposals/{uuid}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"company_id\": 123,\n \"language\": \"<string>\",\n \"title_md\": \"<string>\",\n \"description_md\": \"<string>\",\n \"recipient\": {},\n \"contact_email\": \"<string>\",\n \"data\": {},\n \"blocks\": [\n {}\n ],\n \"attachments\": [\n {}\n ],\n \"background_image\": {},\n \"background_video\": {},\n \"invoicing_enabled\": true,\n \"invoicing\": {},\n \"tax_options\": {}\n}"
response = http.request(request)
puts response.read_body{
"proposal": {
"uuid": "<string>",
"url": "<string>"
}
}Proposals
Create new version of proposal
Create a new draft version of an existing proposal in the same proposal series.
POST
/
v3
/
proposals
/
{uuid}
Create new version of proposal
curl --request POST \
--url https://api.proposales.com/v3/proposals/{uuid} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"company_id": 123,
"language": "<string>",
"title_md": "<string>",
"description_md": "<string>",
"recipient": {},
"contact_email": "<string>",
"data": {},
"blocks": [
{}
],
"attachments": [
{}
],
"background_image": {},
"background_video": {},
"invoicing_enabled": true,
"invoicing": {},
"tax_options": {}
}
'import requests
url = "https://api.proposales.com/v3/proposals/{uuid}"
payload = {
"company_id": 123,
"language": "<string>",
"title_md": "<string>",
"description_md": "<string>",
"recipient": {},
"contact_email": "<string>",
"data": {},
"blocks": [{}],
"attachments": [{}],
"background_image": {},
"background_video": {},
"invoicing_enabled": True,
"invoicing": {},
"tax_options": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
company_id: 123,
language: '<string>',
title_md: '<string>',
description_md: '<string>',
recipient: {},
contact_email: '<string>',
data: {},
blocks: [{}],
attachments: [{}],
background_image: {},
background_video: {},
invoicing_enabled: true,
invoicing: {},
tax_options: {}
})
};
fetch('https://api.proposales.com/v3/proposals/{uuid}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.proposales.com/v3/proposals/{uuid}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'company_id' => 123,
'language' => '<string>',
'title_md' => '<string>',
'description_md' => '<string>',
'recipient' => [
],
'contact_email' => '<string>',
'data' => [
],
'blocks' => [
[
]
],
'attachments' => [
[
]
],
'background_image' => [
],
'background_video' => [
],
'invoicing_enabled' => true,
'invoicing' => [
],
'tax_options' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.proposales.com/v3/proposals/{uuid}"
payload := strings.NewReader("{\n \"company_id\": 123,\n \"language\": \"<string>\",\n \"title_md\": \"<string>\",\n \"description_md\": \"<string>\",\n \"recipient\": {},\n \"contact_email\": \"<string>\",\n \"data\": {},\n \"blocks\": [\n {}\n ],\n \"attachments\": [\n {}\n ],\n \"background_image\": {},\n \"background_video\": {},\n \"invoicing_enabled\": true,\n \"invoicing\": {},\n \"tax_options\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.proposales.com/v3/proposals/{uuid}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"company_id\": 123,\n \"language\": \"<string>\",\n \"title_md\": \"<string>\",\n \"description_md\": \"<string>\",\n \"recipient\": {},\n \"contact_email\": \"<string>\",\n \"data\": {},\n \"blocks\": [\n {}\n ],\n \"attachments\": [\n {}\n ],\n \"background_image\": {},\n \"background_video\": {},\n \"invoicing_enabled\": true,\n \"invoicing\": {},\n \"tax_options\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.proposales.com/v3/proposals/{uuid}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"company_id\": 123,\n \"language\": \"<string>\",\n \"title_md\": \"<string>\",\n \"description_md\": \"<string>\",\n \"recipient\": {},\n \"contact_email\": \"<string>\",\n \"data\": {},\n \"blocks\": [\n {}\n ],\n \"attachments\": [\n {}\n ],\n \"background_image\": {},\n \"background_video\": {},\n \"invoicing_enabled\": true,\n \"invoicing\": {},\n \"tax_options\": {}\n}"
response = http.request(request)
puts response.read_body{
"proposal": {
"uuid": "<string>",
"url": "<string>"
}
}Use this endpoint to create a new draft version of a proposal that has already been sent. The new draft is a fresh copy of the source proposal in the same proposal series, with a new UUID, and the request body lets you override fields on the new draft. For partial in-place updates of a draft you have not sent yet, use
Patch proposal (draft). For an entirely new proposal series, use Create proposal.
When to use this endpoint
Pick the right v3 endpoint based on the current state of the proposal on Proposales’ side:- Proposal does not exist yet →
POST /v3/proposals. - Proposal exists and is still
draftortemplate→PATCH /v3/proposals/{uuid}(in-place update, same UUID). - Proposal exists and has already been sent (
active,accepted,replaced,expired, orrejected) → this endpoint (POST /v3/proposals/{uuid}) — it creates a new draft version in the same proposal series.
draft is allowed, but it creates a separate draft alongside the existing one rather than a true new version. For changes to a proposal you have not yet sent, prefer PATCH /v3/proposals/{uuid}.
Eligibility
- The source proposal must exist.
- The caller must have access to the source proposal’s company.
body.company_idmust match the source proposal’s company.- The source proposal must not be
withdrawn. Any other status is accepted (draft,template,active,accepted,replaced,expired,rejected).
Idempotency
Calling this endpoint repeatedly for the same source UUID returns the same draft UUID until that draft is sent or archived. Each call applies your latest body to that draft, so callers should treat this endpoint as “create-or-replace the next-version draft” rather than “always create a new draft”. Once the draft is sent (or archived), a subsequent call mints a fresh new-version draft.What carries over from the source
The new draft inherits the following from the source proposal, unless overridden in the request body:- Blocks, including any title, description, and image customisations made in the editor
- Attachments
- Recipient
- Language
- Title and description
- Tax options
- Background image and background video
- Payment configuration
- Proposal metadata (
data) - Creator and contact person
Path
uuid
required
The UUID of the source proposal to create a new version from.
Request body
The body shape mirrorsCreate proposal; most fields are optional and override the value carried over from the source, but company_id and language are required (see Eligibility). The body is strict — extra keys return 400 with error.issues describing each problem.
creator_emailis ignored on this route. The creator is preserved from the previous version.
number
required
Must equal the source proposal’s
company_id.string
required
ISO-style language code (e.g.
en, sv).string
Proposal title as Markdown. Overrides the carried-over title.
string
Proposal description as Markdown. Overrides the carried-over description.
object
Same shape as Create proposal.
string
Email of the internal contact person. Same rules as Create proposal.
object
Proposal metadata. Replaces the carried-over
data.array
Same block shape as Create proposal. When provided, the array replaces the carried-over block list on the new draft.
array
Same attachment shapes as Create proposal. When provided, replaces the carried-over attachments.
object
object
boolean
object
object
Same shape as Create proposal.
Response
object
Show properties
Show properties
string
The UUID of the new draft version (always different from the path UUID; the same draft UUID is returned on repeated calls until that draft is sent or archived — see Idempotency).
string
Editor URL for the new draft.
Status codes
| Code | Meaning |
|---|---|
200 | New draft version created (or existing one returned and updated under the idempotency rule). |
400 | Body validation failed. Response includes error.issues with per-field details. |
401 | Missing or invalid API token. |