POST /ideabank/proposals
Submit a new proposal to an idea bank on behalf of a panelist. Requires the canSubmitProposal capability (see Get Idea Bank). When the idea bank collects contact info, the panelist’s contact details can be attached to the submission.
Request
POST /public/ideabank/proposals
Headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | Compound idea-bank token from Tenant API |
| Content-Type | Yes | application/json |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Proposal title |
| description | string | Yes | Proposal description |
| contactInfo | string | No | Panelist contact value (e.g. email), stored when the idea bank collects it |
| contactInfoLabel | string | No | Label identifying the kind of contact info (matches contactInfoCollection.label) |
{
"title": "Dark mode",
"description": "Please add a dark theme for night-time use.",
"contactInfo": "panelist@example.com",
"contactInfoLabel": "email"
}
contactInfo is only stored when both contactInfo and contactInfoLabel are supplied. Use the contactInfoCollection object from the Get Idea Bank response to decide whether to collect and which label to send.
Response
{
"proposalId": "p-abc123",
"title": "Dark mode",
"description": "Please add a dark theme for night-time use.",
"votes": 0,
"totalVotes": 0
}
Response Fields
| Field | Type | Description |
|---|---|---|
| proposalId | string | Unique identifier of the created proposal |
| title | string | Proposal title as stored |
| description | string | Proposal description as stored |
| votes | integer | Net vote score (0 for a new proposal) |
| totalVotes | integer | Total number of votes cast (0 for a new proposal) |
Examples
cURL
curl -X POST \
"https://api.votito.com/public/ideabank/proposals" \
-H "Authorization: abc123:xyz789:ses456" \
-H "Content-Type: application/json" \
-d '{"title":"Dark mode","description":"Please add a dark theme."}'
JavaScript
const submitProposal = async (publicKey, { title, description, contactInfo, contactInfoLabel } = {}) => {
const response = await fetch('https://api.votito.com/public/ideabank/proposals', {
method: 'POST',
headers: {
'Authorization': publicKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ title, description, contactInfo, contactInfoLabel })
});
if (!response.ok) {
throw new Error(`Failed to submit proposal: ${response.status}`);
}
return response.json();
};
// Usage
const proposal = await submitProposal(publicKey, {
title: 'Dark mode',
description: 'Please add a dark theme.'
});
console.log(`Created proposal ${proposal.proposalId}`);
Python
import requests
def submit_proposal(public_key, title, description, contact_info=None, contact_info_label=None):
body = {"title": title, "description": description}
if contact_info and contact_info_label:
body["contactInfo"] = contact_info
body["contactInfoLabel"] = contact_info_label
response = requests.post(
"https://api.votito.com/public/ideabank/proposals",
headers={
"Authorization": public_key,
"Content-Type": "application/json"
},
json=body
)
response.raise_for_status()
return response.json()
# Usage
proposal = submit_proposal(public_key, "Dark mode", "Please add a dark theme.")
print(proposal["proposalId"])
Error Handling
| Status | Error | Description |
|---|---|---|
| 400 | field-required | title or description is missing. details.field names the missing field. |
| 401 | Unauthorized | Missing or invalid token |
| 403 | Forbidden | Token expired, or the panelist cannot submit proposals |
| 429 | resource-busy | Server is busy. Retry after a short delay. |
Notes
- Both
titleanddescriptionare mandatory; the server rejects empty values withfield-required. - A new proposal starts with
votesandtotalVotesequal to 0. - Whether a panelist may submit is controlled by the
canSubmitProposalcapability; gate the submission form on it.