21 operations. Call each with POST https://app.chirply.io/api/v1/actions/<name> and a bearer token; the response is { "data": { "action", "summary", "result" } }. A read badge means the operation changes nothing; write requires the credential’s write scope.
Archive form
forms.archivewriteconfirm
Retire a finished form. Every partial and completed response is KEPT, the form id does not change, and any workflow pointed at it stays connected — nothing is deleted. What changes is two things: it leaves the main Forms list for the Archived tab, and its public link stops accepting submissions and serves the form's closing message instead of the form. Use forms.restore to bring it back as a draft.
Marked confirm: this operation is irreversible, reaches real people, or spends money. Holding a credential is itself the confirmation for API and MCP callers — call it only when you mean it. The in-app assistant refuses to run it without a human approving first.
Also answers to retire this form, file this form away, close this form for good.
Parameters
Field
Type
Required
Description
id
string (uuid)
required
The form to archive. A live form is taken offline by this.
Over MCP the same operation is the tool forms_create at https://app.chirply.io/api/mcp, same bearer token, same input.
Delete form
forms.deletewriteconfirm
Permanently delete a form and every partial and completed response it collected. This cannot be undone.
Marked confirm: this operation is irreversible, reaches real people, or spends money. Holding a credential is itself the confirmation for API and MCP callers — call it only when you mean it. The in-app assistant refuses to run it without a human approving first.
Over MCP the same operation is the tool forms_duplicate at https://app.chirply.io/api/mcp, same bearer token, same input.
Export responses CSV
forms.export_responsesread
Export a form's responses as CSV text, byte-for-byte what the Results screen's Export CSV button downloads: a header row of Status, Started, Completed, Score followed by one column per question (statement blocks are skipped because nobody answers them), then one row per response. Multi-select answers are joined with '; ' and an uploaded file shows as its filename. Columns follow the DRAFT question list, so a question added since a response came in appears as an empty column for that row. Returns the CSV as a string for you to save — nothing is emailed or uploaded anywhere. This only reads data.
Parameters
Field
Type
Required
Description
form_id
string (uuid)
required
The form whose responses to export.
status
"partial" | "completed"
optional
Export only responses with this completion status. Omit to export both, which is what the app's button does.
limit
integer
optional
Most recent responses to include, newest first (1-5000). The app's own screen shows 500. Default: 500
Over MCP the same operation is the tool forms_export_responses at https://app.chirply.io/api/mcp, same bearer token, same input.
Open a form
forms.getread
Fetch one standalone form in full: its draft question definition, behavior settings, design theme, and the separate published snapshot visitors currently see. The `definition.questions[].id` values in the result are what a response's `answers` object is keyed by, so this is where you get them before submitting an answer — and `definition`/`settings`/`theme` here are exactly the objects forms.update expects back, so read this first whenever you intend to edit one. This only reads data.
Over MCP the same operation is the tool forms_get_response at https://app.chirply.io/api/mcp, same bearer token, same input.
Share a form
forms.get_share_linksread
Get everything needed to put a form in front of people: its hosted public URL, the ready-to-paste <iframe> embed snippet for someone else's website, and whether it is actually published yet. This is the app's Share tab, which until now was the only place the slug was ever joined into a URL. A DRAFT form still returns its would-be link, but that link does not work for visitors until forms.publish has been run — check `published` before handing the URL to anyone. This only reads data and shares nothing on your behalf.
Over MCP the same operation is the tool forms_list_responses at https://app.chirply.io/api/mcp, same bearer token, same input.
Publish
forms.publishwriteconfirm
Publish the current draft as a new public snapshot at the form's hosted URL. Real visitors can immediately submit it, create CRM contacts, and trigger configured workflows.
Marked confirm: this operation is irreversible, reaches real people, or spends money. Holding a credential is itself the confirmation for API and MCP callers — call it only when you mean it. The in-app assistant refuses to run it without a human approving first.
Over MCP the same operation is the tool forms_publish at https://app.chirply.io/api/mcp, same bearer token, same input.
Record consent answer
forms.record_consent_answerwriteconfirm
Record one person's answer to a form's consent question as if they had just submitted it, and apply it to their real marketing permission. THIS SENDS OR STOPS REAL MESSAGES BY CONSEQUENCE: granted true switches the channel back on so campaigns and automations can reach them again, granted false switches it off immediately and every campaign, automation and bulk send on that channel stops. It is written to the same switch the unsubscribe center uses, filed as the CONTACT's own answer on a public form, and recorded on their communication-preferences history with the date and the exact wording the question shows. Use it for consent captured outside the hosted form — a paper form, a phone call against the same wording, an import — not as a way to opt people in without an answer. A granted SMS consent never overrides a carrier-level STOP: that opt-out stands until the person texts START, and the response says so. When the account's double opt-in covers form consent, a granted EMAIL answer is not granted outright: the address is held off marketing email and immediately sent a real confirmation email from the account's own sending address, and permission is granted only when they click it. That includes an address with an earlier email opt-out (the hold replaces it and it stays in the consent history), but never one that bounced or reported spam.
Marked confirm: this operation is irreversible, reaches real people, or spends money. Holding a credential is itself the confirmation for API and MCP callers — call it only when you mean it. The in-app assistant refuses to run it without a human approving first.
Also answers to record that someone agreed on a form, withdraw consent captured by a form, log a paper consent form.
Parameters
Field
Type
Required
Description
form_id
string (uuid)
required
The form whose consent question was answered.
question_id
string
required
The consent question's `id` from the form's definition. It must already be mapped to at least one permission channel with forms.set_question_mapping.
granted
boolean
required
true when they agreed, false when they did not or have withdrawn. false is a real withdrawal, not 'no answer'.
contact_id
string (uuid)
optional
The contact who answered. Give this or email/phone; when given, the address on file is used.
email
string
optional
Their email address, when you are not giving a contact_id. Required for the 'email' channel — permission is stored against an address, so there is nothing to write without one.
phone
string
optional
Their phone number in any common format. Required for the 'sms' channel.
note
string
optional
Anything worth keeping about where this answer came from, such as 'paper form at the Dallas event, 4 June'. Stored on the permission record beside the consent wording.
Over MCP the same operation is the tool forms_record_consent_answer at https://app.chirply.io/api/mcp, same bearer token, same input.
Restore to draft
forms.restorewrite
Take a form back out of the archive. It returns as a DRAFT, never straight to live: reopening a form to the public is a deliberate act, so nothing is published and the link keeps showing the closing message until someone calls forms.publish. Nothing about the responses, the id or the connected workflows changes.
Also answers to unarchive a form, bring a form back.
Over MCP the same operation is the tool forms_restore at https://app.chirply.io/api/mcp, same bearer token, same input.
Save form progress
forms.save_partial_responsewrite
Save a respondent's answers SO FAR as a partial (unfinished) response — what the public form does in the background while someone is still typing, so a half-filled form is not lost. Deliberately quiet: unlike forms.submit_response this creates NO CRM contact and starts NO workflows, so nothing is emailed, texted, called or billed. The response is stored against your session_id and is upserted, so calling this repeatedly with the same session_id updates the same row as the person progresses; finish with forms.submit_response using that SAME session_id and the partial becomes the completed response. Required questions are not enforced until then. The form must be published. Note that the app only auto-saves partials when the form's 'Save partial responses' setting is on, whereas this capability — like the public route behind it — writes one either way.
Parameters
Field
Type
Required
Description
form_id
string (uuid)
required
The published form being filled in.
session_id
string
required
A caller-generated key identifying THIS respondent's sitting. Keep it identical across every save for the same person, then reuse it on forms.submit_response so the partial is completed rather than duplicated.
answers
map of string → object
required
The respondent's answers, keyed by QUESTION ID (the `id` field of each question in the form's definition — get them from forms.get or forms.get_share_links, never guess). A text/email/phone/date answer is a string, a number/rating/scale answer is a number, a consent answer is a boolean, a single_choice or dropdown answer is the chosen option's `value` (not its label), a multiple_choice answer is an array of those values, and a file answer is the object returned by forms.upload_response_file. Keys that do not match a question are discarded. Example: {"q_1":"jane@example.com","q_2":"11_plus","q_3":"Our follow-up is a mess","q_4":["email","sms"],"q_5":{"path":"org/form/session/q_5/1699-brief.pdf","name":"brief.pdf","size":10240,"type":"application/pdf"}}
Over MCP the same operation is the tool forms_save_partial_response at https://app.chirply.io/api/mcp, same bearer token, same input.
Closing message
forms.set_closed_messagewrite
Set what the form's public link says once it stops taking answers — after Unpublish, or after Archive. Visitors who already hold the link see this instead of a 'not found' page, under the account's own branding, and submissions are still refused. Changing it takes effect immediately and does NOT republish the form: the closing message is read from the draft settings precisely so a closed form can be re-worded without accidentally reopening it. Harmless on a live form — it simply sits unused until the form is closed.
Also answers to closing message, what the form says when it is closed, paused form message.
Parameters
Field
Type
Required
Description
id
string (uuid)
required
The form to set the closing message on. Works whether the form is live, draft or archived.
closed_title
string
optional
The heading on the closed page, e.g. 'This assessment is closed'. Leave out to keep the current one.
closed_message
string
optional
The paragraph under the heading — the place to say when it reopens, or where to go instead, e.g. 'We reopen on Monday; email hello@example.com if it is urgent.' Leave out to keep the current one.
Over MCP the same operation is the tool forms_set_closed_message at https://app.chirply.io/api/mcp, same bearer token, same input.
Question mapping
forms.set_question_mappingwriteconfirm
Point one question at the CRM: which contact field its answer fills, whether a later submission may REPLACE what is already stored there, and — for a consent question — which marketing permission switches its latest answer controls. Configuration only; it writes nothing to any contact by itself, but it decides what every future submission of this form does. Mapping a consent question to a permission means a ticked box switches that channel on and an UNTICKED box switches it off and records a withdrawal, so the newest answer always wins — unless unticked_keeps_permission is on, in which case only a tick changes anything.
Marked confirm: this operation is irreversible, reaches real people, or spends money. Holding a credential is itself the confirmation for API and MCP callers — call it only when you mean it. The in-app assistant refuses to run it without a human approving first.
Also answers to map a form question to a contact field, make a consent box set email permission, overwrite contact fields from a form.
Parameters
Field
Type
Required
Description
form_id
string (uuid)
required
The form that owns the question.
question_id
string
required
The question's `id` from the form's definition — read it with forms.get, never guess.
The CRM field this answer fills. Only short-text, email and phone questions can carry one. Pass an empty string to unmap. Two questions cannot own the same field: the earlier one keeps it.
overwrite_contact_field
boolean
optional
OFF by default and that default is the safe one: the answer only fills the contact field when it is empty. ON makes every submission replace what is stored, which is what you want for a 'keep my details up to date' form. Accepted ONLY for first_name, last_name and company — a public form is unauthenticated, so letting it repoint an existing contact's email or phone would let anyone who knows an address redirect every future message to themselves.
explicit_consent
boolean
optional
Consent questions only. ON stores an untouched box as an explicit false instead of leaving the answer out, so 'declined' and 'never asked' stop being the same value. It also makes the question count as ANSWERED in your logic rules, which is why it is opt-in rather than automatic for forms that are already live.
permission_channels
array of ("email" | "sms")
optional
Consent questions only. The contact permission switches this question's LATEST answer controls: 'email' is Emails Allow, 'sms' is text messages. Ticked grants, unticked withdraws — both written to the same switch the unsubscribe center uses and both recorded on the contact with the date and the exact wording shown. Pass an empty array to stop the question controlling permission. Setting any channel forces explicit_consent on, because a withdrawal cannot be expressed by an answer that is not there — except with unticked_keeps_permission, which withdraws nothing.
unticked_keeps_permission
boolean
optional
Consent questions with permission_channels only. ON: only a tick changes permission — an unticked box, or a submission that leaves the answer out (for example a forms API caller that omits unticked boxes), leaves the contact's existing permission exactly as it is, so a later submission never unsubscribes someone who already agreed. Use it for an optional newsletter box on a form people send for other reasons. OFF, the default: the newest answer wins and an unticked box withdraws. The builder labels it 'Unticked leaves permission as it is'.
Over MCP the same operation is the tool forms_set_question_mapping at https://app.chirply.io/api/mcp, same bearer token, same input.
Form results
forms.statsread
Get the four headline numbers from a form's Results screen: how many times the public form was VIEWED, how many responses were completed, how many were left partial, and the conversion rate (completed as a percentage of views, rounded). forms.list_responses returns the responses but has no view count, so conversion cannot be worked out from it — this is the only place that number exists. Counts cover the form's whole lifetime. This only reads data.
Over MCP the same operation is the tool forms_stats at https://app.chirply.io/api/mcp, same bearer token, same input.
Submit response
forms.submit_responsewriteconfirm
Submit answers to a published form as a real completed response. This can create or update a CRM contact and immediately start any workflows listening for this form, which may send real email, SMS, or calls billed to the account.
Marked confirm: this operation is irreversible, reaches real people, or spends money. Holding a credential is itself the confirmation for API and MCP callers — call it only when you mean it. The in-app assistant refuses to run it without a human approving first.
Parameters
Field
Type
Required
Description
form_id
string (uuid)
required
The published form receiving the response.
session_id
string
required
A caller-generated idempotency key for this respondent session. Reusing one overwrites that person's earlier answers instead of adding a second response, so use a fresh random value per respondent.
answers
map of string → object
required
The respondent's answers, keyed by QUESTION ID (the `id` field of each question in the form's definition — get them from forms.get or forms.get_share_links, never guess). A text/email/phone/date answer is a string, a number/rating/scale answer is a number, a consent answer is a boolean, a single_choice or dropdown answer is the chosen option's `value` (not its label), a multiple_choice answer is an array of those values, and a file answer is the object returned by forms.upload_response_file. Keys that do not match a question are discarded. Example: {"q_1":"jane@example.com","q_2":"11_plus","q_3":"Our follow-up is a mess","q_4":["email","sms"],"q_5":{"path":"org/form/session/q_5/1699-brief.pdf","name":"brief.pdf","size":10240,"type":"application/pdf"}}
Over MCP the same operation is the tool forms_submit_response at https://app.chirply.io/api/mcp, same bearer token, same input.
Unpublish
forms.unpublishwriteconfirm
Take a public form offline. Existing responses remain available, but its hosted link stops accepting new ones.
Marked confirm: this operation is irreversible, reaches real people, or spends money. Holding a credential is itself the confirmation for API and MCP callers — call it only when you mean it. The in-app assistant refuses to run it without a human approving first.
Over MCP the same operation is the tool forms_unpublish at https://app.chirply.io/api/mcp, same bearer token, same input.
Save form
forms.updatewrite
Save a form's draft name, description, question definition, behavior, or design. WHOLE-OBJECT REPLACEMENT: `definition`, `settings` and `theme` are each stored as a single JSON blob, so passing one REPLACES it outright — send a definition with two questions and a form that had ten now has two, and the eight you left out are gone along with any logic rules that referenced them. Never assemble one of these from just the parts the request mentioned; read the current object with forms.get, change what you need, and send the whole thing back. Fields you omit entirely are untouched. Published visitors keep seeing the prior snapshot until Publish is run, so a mistake here is recoverable right up until then.
Parameters
Field
Type
Required
Description
id
string (uuid)
required
The form to update.
name
string
optional
New internal form name.
description
string (or null)
optional
Short internal/public description, or null to clear it.
kind
"form" | "survey" | "quiz"
optional
New experience type.
definition
map of string → object
optional
The COMPLETE question definition, which REPLACES the stored one entirely — anything you leave out is deleted, so read the current value with forms.get and edit that rather than sending a fragment. Three keys. `questions` is an ordered array; each has `id` (stable string; answers are keyed by it, so never renumber an existing question), `type` (one of short_text, long_text, email, phone, number, single_choice, multiple_choice, dropdown, rating, scale, date, file, signature, consent, statement), `title`, and optionally `description`, `placeholder`, `required`, `page`, `min`/`max`, `lowLabel`/`highLabel`, `options` (for choice types: `{id,label,value,points}` — `points` is what a quiz scores), and `contactField` (one of first_name, last_name, email, phone, company — this is what makes a completed response create or update a CRM contact; email and phone questions map themselves by default). Question `title` and `description` values show links on the public form: a bare https URL, or `[words](https://address)` to show the words as the link (e.g. a consent box reading `…as described in our [privacy policy](https://example.com/privacy).`). Only http(s) addresses become links. `logic` is an array of show/hide rules: `{id,sourceQuestionId,operator,value,action,targetQuestionId}` where operator is equals, not_equals, contains, answered or not_answered. `outcomes` scores a quiz into a result: `{id,title,message,minScore,maxScore,redirectUrl}`. Example: {"questions":[{"id":"q_1","type":"email","title":"Your work email","required":true,"contactField":"email"},{"id":"q_2","type":"single_choice","title":"How big is your team?","options":[{"id":"opt_1","label":"1-10","value":"1_10","points":5},{"id":"opt_2","label":"11 or more","value":"11_plus","points":10}]},{"id":"q_3","type":"long_text","title":"What are you trying to fix?"}],"logic":[{"id":"rule_1","sourceQuestionId":"q_2","operator":"equals","value":"11_plus","action":"show","targetQuestionId":"q_3"}],"outcomes":[{"id":"outcome_1","title":"Great fit","message":"We will be in touch.","minScore":10}]}
settings
map of string → object
optional
The COMPLETE behavior settings object, which REPLACES the stored one entirely — omitted keys fall back to defaults rather than keeping their current value, so read forms.get first. Keys: `submitLabel` (the submit button's wording), `successTitle` and `successMessage` (the thank-you screen), `redirectUrl` (send them to your own page instead), `showProgress`, `oneQuestionAtATime`, `savePartial` (keep half-finished answers), `allowMultiple` (let one person answer more than once), `collectContacts` (create/update CRM contacts from completed responses), `buttonAlignment` (left, center or full) and `privacyText`. Example: {"submitLabel":"Send response","successTitle":"You are all set","successMessage":"Thanks - your response has been received.","redirectUrl":"https://example.com/thanks","showProgress":true,"oneQuestionAtATime":false,"savePartial":true,"allowMultiple":true,"collectContacts":true,"buttonAlignment":"left","privacyText":"We never share your details."}
theme
map of string → object
optional
The COMPLETE visual theme, which REPLACES the stored one entirely. The five colors are six-digit hex strings and anything else is ignored: `brand` (buttons and accents), `background` (the page behind the form), `surface` (the card), `text`, `muted`. Plus `radius` (none, soft or round) and `font` (clean, friendly or editorial). Example: {"brand":"#f45d48","background":"#f4f5f7","surface":"#ffffff","text":"#172033","muted":"#667085","radius":"soft","font":"clean"}
Over MCP the same operation is the tool forms_update at https://app.chirply.io/api/mcp, same bearer token, same input.
Upload response file
forms.upload_response_filewriteconfirm
Upload one file as the answer to a form question of type 'file' — the only way to answer that question type, since an answer to it is a stored file rather than text. Limits match the public form exactly: at most 10 MB, and only JPEG, PNG or WebP images, PDFs, plain text, or Word documents (.doc/.docx). THE SERVER DECIDES THE FILE'S TYPE, from the extension on `filename` plus the uploaded bytes' own signature — nothing you declare is stored or served, and a file whose contents do not match its extension (an HTML page named .png) is refused outright rather than filed away under the type it claimed. The file is written to the account's private form-uploads storage and STAYS THERE permanently, counting against the account's storage; there is no capability that deletes it. This does not answer the question by itself — take the object it returns and put it in `answers` under that question's id when you call forms.save_partial_response or forms.submit_response.
Marked confirm: this operation is irreversible, reaches real people, or spends money. Holding a credential is itself the confirmation for API and MCP callers — call it only when you mean it. The in-app assistant refuses to run it without a human approving first.
Parameters
Field
Type
Required
Description
form_id
string (uuid)
required
The published form the file is being uploaded for.
session_id
string
required
The respondent session this file belongs to — the SAME value used for forms.save_partial_response and forms.submit_response.
question_id
string
required
The id of the question being answered. It must exist in the form's published definition and be of type 'file'.
filename
string
required
The original file name, e.g. 'brief.pdf'. Its EXTENSION is load-bearing: it selects which format the upload is checked against — one of jpg, jpeg, png, webp, pdf, txt, doc, docx — and for an image or PDF the bytes must actually be that format or the upload is refused. A .txt or .doc whose bytes are not what the name promised is stored as a plain download instead of being refused, since a text file in an older Windows encoding is still a real text file. If the name carries no extension at all, or one not on that list, the bytes are read instead and the file is accepted only if they are one of those same formats. Also shown to staff reading the response and in the CSV export; unusual characters are replaced.
content_type
string
optional
ADVISORY ONLY, and safe to omit. The type the file is stored and served as is derived from its own bytes, never from this value, so sending a wrong or malicious one changes nothing — it is neither stored nor echoed back. Accepted so existing integrations that send it keep working; read the `file.type` in the result for the type actually recorded.
content_base64
string
required
The file's bytes, base64-encoded (no data: URL prefix). Must decode to 10 MB or less.