curl --request POST \
--url https://api.protodesk.io/v1/authoring/articles/{id}/proposals \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"expectedRevision": 2,
"content": {
"title": "<string>",
"excerpt": "<string>",
"bodyMarkdown": "<string>"
}
}
'import requests
url = "https://api.protodesk.io/v1/authoring/articles/{id}/proposals"
payload = {
"expectedRevision": 2,
"content": {
"title": "<string>",
"excerpt": "<string>",
"bodyMarkdown": "<string>"
}
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
expectedRevision: 2,
content: {title: '<string>', excerpt: '<string>', bodyMarkdown: '<string>'}
})
};
fetch('https://api.protodesk.io/v1/authoring/articles/{id}/proposals', 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.protodesk.io/v1/authoring/articles/{id}/proposals",
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([
'expectedRevision' => 2,
'content' => [
'title' => '<string>',
'excerpt' => '<string>',
'bodyMarkdown' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>"
],
]);
$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.protodesk.io/v1/authoring/articles/{id}/proposals"
payload := strings.NewReader("{\n \"expectedRevision\": 2,\n \"content\": {\n \"title\": \"<string>\",\n \"excerpt\": \"<string>\",\n \"bodyMarkdown\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
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.protodesk.io/v1/authoring/articles/{id}/proposals")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"expectedRevision\": 2,\n \"content\": {\n \"title\": \"<string>\",\n \"excerpt\": \"<string>\",\n \"bodyMarkdown\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.protodesk.io/v1/authoring/articles/{id}/proposals")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"expectedRevision\": 2,\n \"content\": {\n \"title\": \"<string>\",\n \"excerpt\": \"<string>\",\n \"bodyMarkdown\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"kind": "update",
"articleSetId": "<string>",
"articleSetName": "<string>",
"newArticle": {
"slug": "<string>",
"collectionId": "<string>",
"tags": [
"<string>"
],
"featured": true,
"reviewNote": "<string>"
},
"id": "<string>",
"articleId": "<string>",
"status": "pending",
"baseRevision": 123,
"currentRevision": 123,
"content": {
"title": "<string>",
"excerpt": "<string>",
"bodyMarkdown": "<string>"
},
"reviewUrl": "<string>",
"publicationChanged": true
}{
"error": {
"code": "route.not_found",
"message": "Not found",
"requestId": "req_x6q5vl75f5d53m7oet2k4r5w6a"
}
}Propose immutable content changes without publishing
Requires help:read and help:drafts:write. Existing article only. Expected revision must match. Reusing the same key and payload for the same connection returns its existing proposal; another payload conflicts. Keys are retained with proposals. Replays recheck connection expiry, revocation, and creator membership. Never directly edits or publishes an article. Request body is capped by the public API write limit (64 KiB by default). The authoring connection and human creator are recorded server-side.
curl --request POST \
--url https://api.protodesk.io/v1/authoring/articles/{id}/proposals \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"expectedRevision": 2,
"content": {
"title": "<string>",
"excerpt": "<string>",
"bodyMarkdown": "<string>"
}
}
'import requests
url = "https://api.protodesk.io/v1/authoring/articles/{id}/proposals"
payload = {
"expectedRevision": 2,
"content": {
"title": "<string>",
"excerpt": "<string>",
"bodyMarkdown": "<string>"
}
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
expectedRevision: 2,
content: {title: '<string>', excerpt: '<string>', bodyMarkdown: '<string>'}
})
};
fetch('https://api.protodesk.io/v1/authoring/articles/{id}/proposals', 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.protodesk.io/v1/authoring/articles/{id}/proposals",
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([
'expectedRevision' => 2,
'content' => [
'title' => '<string>',
'excerpt' => '<string>',
'bodyMarkdown' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>"
],
]);
$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.protodesk.io/v1/authoring/articles/{id}/proposals"
payload := strings.NewReader("{\n \"expectedRevision\": 2,\n \"content\": {\n \"title\": \"<string>\",\n \"excerpt\": \"<string>\",\n \"bodyMarkdown\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
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.protodesk.io/v1/authoring/articles/{id}/proposals")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"expectedRevision\": 2,\n \"content\": {\n \"title\": \"<string>\",\n \"excerpt\": \"<string>\",\n \"bodyMarkdown\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.protodesk.io/v1/authoring/articles/{id}/proposals")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"expectedRevision\": 2,\n \"content\": {\n \"title\": \"<string>\",\n \"excerpt\": \"<string>\",\n \"bodyMarkdown\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"kind": "update",
"articleSetId": "<string>",
"articleSetName": "<string>",
"newArticle": {
"slug": "<string>",
"collectionId": "<string>",
"tags": [
"<string>"
],
"featured": true,
"reviewNote": "<string>"
},
"id": "<string>",
"articleId": "<string>",
"status": "pending",
"baseRevision": 123,
"currentRevision": 123,
"content": {
"title": "<string>",
"excerpt": "<string>",
"bodyMarkdown": "<string>"
},
"reviewUrl": "<string>",
"publicationChanged": true
}{
"error": {
"code": "route.not_found",
"message": "Not found",
"requestId": "req_x6q5vl75f5d53m7oet2k4r5w6a"
}
}Authorizations
Use a workspace API key in the Authorization header. Keys use pk_live_<key_id> for production and pk_test<key_id>_ for test mode.
Headers
8 - 128Path Parameters
Body
Response
Saved proposal or authorized retry, with a credential-free relative review URL. No article was published.
update, create Show child attributes
Show child attributes
pending, approved, dismissed Show child attributes
Show child attributes
