Overview
The Forms API (v4) lets your own code create polls and forms, publish them, collect responses and read the results. It is the API the StrawPoll editor itself uses.
- Base URL:
https://api.strawpoll.com/v4(also served athttps://strawpoll.com/api/v4) - Format: JSON in, JSON out. Timestamps are ISO 8601 strings, ids are UUIDs.
- Machine-readable spec: OpenAPI 3.1
Every form has a short public id, the slug, as in https://strawpoll.com/r/AZPg4n1Td3-hnVAj6Q8p.
All form endpoints take the slug.
Authentication
Create a key in Settings → API keys. It is shown once, so store it safely. Send it with every request:
curl https://api.strawpoll.com/v4/me
-H "X-API-Key: sp_your_key"Authorization: Bearer sp_your_key works as well. A key acts as you: it can manage every
form you can manage in the dashboard, including those in workspaces you belong to.
Delete a key in Settings to revoke it immediately.
The respondent endpoints, submit and verify, need no key from a browser. Requests from
scripts, HTTP libraries, headless browsers, bots and AI agents, recognized by their user agent,
are refused with automated unless they send a key.
Errors
Errors use the HTTP status and a JSON body:
{ "error": "This poll is closed.", "code": "closed" }error is written for people; code, when present, is meant for your code.
| Status | Meaning |
|---|---|
| 400 | The request is invalid. Vote errors also name the question in step. |
| 401 | No API key, or invalid_api_key. |
| 403 | You cannot manage this form, or it does not accept this response. |
| 404 | Not found. |
| 409 | Conflict, such as a duplicate response. |
| 429 | rate_limited: wait for Retry-After seconds. |
| 503 | busy: lots of votes are arriving at once; retry shortly. |
Rate limits
120 requests per minute per key. Without a key, requests from a browser count per signed-in
user or browser (its voter cookie), up to 3,000 per minute for all browsers on one IP address;
requests with neither count per IP address. Each response reports your budget in X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds).
How a form is built
A form is a list of sections. Question sections hold fields, and choice fields hold choices.
Section type | What it asks | Fields |
|---|---|---|
multiple_choice | Pick one or more options; properties.layout dropdown shows one answer as a select | one multiple_choice field with choices |
picture_choice | Pick images | one multiple_choice field, choices with mediaId |
ranking | Order options, points per place | one ranking field with choices |
ranked_choice | Ranked choice ballot, counted as an instant runoff | one ranked_choice field with choices |
meeting | Find a time | one date field per day (label YYYY-MM-DD), one choice per time (label 09:00-10:00 or All day) |
content | Text, or one question with properties.variant set to yes_no, rating, scale, text, long_text, name, email or word_cloud | one field for the question |
contact_info | Name and email | text_small and email fields |
page_break, welcome_screen, thank_you_screen, end_screen, redirect | Layout and endings | none |
Form-wide settings live in properties. The most used:
| Property | Values | Default |
|---|---|---|
resultsVisibility | public, public_after_response, public_after_close, private | public |
responsesVisibility | public lists names next to results, private does not | private |
responseDuplicateCheck | none, session, ip, name, email, code | ip |
responseDuplicateCheckInterval | minutes until the same voter may respond again, or null | null |
opensAt, closesAt | ISO timestamps, or null | null |
responseLimit | maximum number of responses, or null | null |
allowComments, commentsVisibility | true/false, public/private | false, private |
editResponsePermissions | admin_own_responses, own_responses, admin, nobody | admin_own_responses |
blockVPNUsers, useCaptchaVerification | true/false | true, false |
The OpenAPI spec lists every property of sections, fields and choices.
Create a poll
curl https://api.strawpoll.com/v4/forms
-H "X-API-Key: sp_your_key"
-H "Content-Type: application/json"
-d '{
"title": "Where should we go for lunch?",
"status": "published",
"properties": { "responseDuplicateCheck": "ip", "resultsVisibility": "public" },
"sections": [{
"type": "multiple_choice",
"fields": [{
"type": "multiple_choice",
"label": "Where should we go for lunch?",
"properties": { "isMultipleChoice": false },
"choices": [{ "label": "Pizza" }, { "label": "Sushi" }, { "label": "Tacos" }]
}]
}]
}'The response is the full form, with generated ids and its links in urls.formUrl and urls.resultsUrl. Without "status": "published" a form starts as a draft that only you
can see.
A meeting poll offers times on dates, in the section’s time zone:
{
"title": "Team sync",
"status": "published",
"sections": [
{
"type": "meeting",
"properties": { "timezone": "Europe/Berlin", "duration": 60, "location": "Room 1" },
"fields": [
{
"type": "date",
"label": "2026-11-02",
"choices": [{ "label": "09:00-10:00" }, { "label": "14:00-15:00" }]
},
{ "type": "date", "label": "2026-11-03", "choices": [{ "label": "09:00-10:00" }] }
]
}
]
}AI assistants (MCP)
StrawPoll runs a Model Context Protocol server, so AI assistants such as Claude, ChatGPT or Cursor can create polls and read results for you.
- Server URL:
https://strawpoll.com/mcp(Streamable HTTP) - Sign-in: OAuth. Add the URL as a custom connector in your assistant and sign in to
StrawPoll when it asks; you choose whether to allow it. Clients without OAuth can send an
API key as
X-API-KeyorAuthorization: Bearer sp_your_keyinstead.
| Tool | What it does |
|---|---|
create_poll | One question with options: a vote, a ranking or a meeting poll. Published. |
create_form | A survey with several questions, from multiple_choice to matrix. A draft. |
publish_poll | Publish a draft. |
list_polls | Your polls, newest first, with response counts. |
get_results | Votes per option, average ranks, meeting availability and recent text answers. |
set_poll_open | Close a poll or reopen it. |
Polls made by an assistant are yours like any other, and use your defaults for new polls. An assistant can create up to 30 an hour.
Poll links
Assistants without tools can hand you a link that opens the editor with the poll filled in, ready to check and publish:
https://strawpoll.com/forms/new?title=Where%20should%20we%20eat%3F&options=Pizza|Sushi|Tacosoptions is split on | (or repeat option=). Add type=ranking or type=meeting (options
like 2026-11-03 or 2026-11-03 14:00-15:00, with timezone=Europe/Berlin), multiple=1 to
allow several answers and other=1 for an own answer.
Endpoints
Account
GET/me | The key’s owner and their workspaces. |
Forms
GET/forms | Your forms, newest first. ?workspaceId= lists a workspace’s forms. |
POST/forms | Create a form. Optional workspaceId; defaults to your first workspace. |
GET/forms/{slug} | The live form with sections, fields, choices and tallies. |
PUT/forms/{slug} | Replace the live form (see below). |
PATCH/forms/{slug} | Change name, isOpen or properties only. |
DELETE/forms/{slug} | Delete the form and its responses for good. |
PUT replaces everything: sections, fields and choices you leave out are deleted, with
their answers. Send back the ids of the parts you keep, so their responses stay. Forms
taken down by moderation (deleted, blocked) answer 403.
Drafts and publishing
The editor saves changes as a draft and publishes them in one step. Respondents keep seeing the live form until then.
PUT/forms/{slug}/draft | Save title, themeId, properties and sections as the draft. |
DELETE/forms/{slug}/draft | Throw the draft away. |
POST/forms/{slug}/publish | Make the draft live. A body with sections is saved first. |
Responses
GET/forms/{slug}/responses | Responses, newest first. limit (up to 100) and offset. |
DELETE/forms/{slug}/responses | Reset: delete every response and zero the tallies. |
Totals per option are on the form itself: responseCount on each field and choice, and score for rankings. A response’s fields holds its answers in the shape described
under Submit a response.
Submit a response
POST/forms/{slug}/submit sends a response the way the voting page does, with every rule
of the form applied: schedule, limits, one response per person, captcha, VPN blocking, codes and
email verification. Browsers need no key; scripts and other automated clients must send one.
{
"fields": {
"<choice field id>": ["<choice id>"],
"<ranking field id>": ["<best choice id>", "<next choice id>", "<last choice id>"],
"<meeting date field id>": [{ "choiceId": "<choice id>", "status": "yes" }],
"<yes/no field id>": "yes",
"<text field id>": "Free text"
},
"name": "Ada",
"comment": "Optional",
"code": "Optional unique code"
}| Answer | Shape |
|---|---|
| Choice and picture questions | list of choice ids, plus one text when write-ins are allowed |
| Ranking | every choice id, best first |
| Ranked choice | the choice id in each place, best first, null for a place left empty |
| Meeting | per date field, a list of choiceId and status (yes, if_need_be, no); name is required |
| Yes/no | "yes" or "no" |
| Rating and scale | the number as a string, like "4" |
| Text and contact fields | a string |
A successful submit returns the response id. Refusals carry a code: upcoming, closed, full, unavailable, decided, captcha, vpn, code, verify_email, disposable, automated (all 403), duplicate (409) and busy (503).
The voter’s browser gets an sp_voter cookie. With it, PUT/forms/{slug}/submit and the
response id change that response, if the form allows voters to edit.
For forms limited to one response per email address, verify the address first with POST/forms/{slug}/verify: send action: "send" and the email, then action: "verify" with the 6-digit code the voter received.
Codes and invites
GET/forms/{slug}/codes | Unique codes. |
POST/forms/{slug}/codes | Generate count codes of length characters, with optional style and prefix. |
DELETE/forms/{slug}/codes | Delete codes by ids. |
GET/forms/{slug}/invites | Email invites. |
POST/forms/{slug}/invites | Add one invite, or up to 1000 as invites. |
PATCH/forms/{slug}/invites/{id} | Change an invite. |
DELETE/forms/{slug}/invites/{id} | Delete an invite. |
DELETE/forms/{slug}/invites | Delete invites by ids. |
POST/forms/{slug}/invites/import | Invite everyone on an invite list (inviteListId). |
POST/forms/{slug}/invites/send | Email invites with subject and message. |
Invite messages may use the placeholders name, form and link in double braces.
Media and themes
POST/media | Upload an image (multipart file, up to 5 MB). Use its id as a choice’s mediaId. |
GET/themes | Your themes, your workspaces’ themes and the built-in ones; ?workspaceId= for one workspace’s. |
POST/themes | Create a theme with name and colour properties; add workspaceId to share it with a workspace you own or manage. |
PUT/themes/{id} | Change a theme. workspaceId shares it with a workspace, null makes it the owner’s own again. |
DELETE/themes/{id} | Delete a theme. A workspace’s themes can be changed and deleted by its owner and admins. |
Coming from the Poll API (v3)
The Poll API v3 keeps working, with the same paths, keys and polls. See the v3 compatibility guide for what maps where.