← All action domains

Forms

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

FieldTypeRequiredDescription
idstring (uuid)requiredThe form to archive. A live form is taken offline by this.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.archive \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

Over MCP the same operation is the tool forms_archive at https://app.chirply.io/api/mcp, same bearer token, same input.

Create form

forms.createwrite

Create a new draft standalone form from a built-in starting point. This does not publish it or contact anyone.

Parameters

FieldTypeRequiredDescription
namestringrequiredThe internal name shown in the form library.
starter"lead" | "feedback" | "quiz" | "application" | "blank"optionalThe built-in starting structure to copy. Default: "lead"

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.create \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Example"
  }'
Test with your API key

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.

Parameters

FieldTypeRequiredDescription
idstring (uuid)requiredThe form to permanently delete.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.delete \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

Over MCP the same operation is the tool forms_delete at https://app.chirply.io/api/mcp, same bearer token, same input.

Duplicate form

forms.duplicatewrite

Create a private draft copy of a form's questions, rules, settings, and design. Responses are not copied and nothing is published.

Parameters

FieldTypeRequiredDescription
idstring (uuid)requiredThe form to copy.
namestringoptionalOptional name for the copy.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.duplicate \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

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

FieldTypeRequiredDescription
form_idstring (uuid)requiredThe form whose responses to export.
status"partial" | "completed"optionalExport only responses with this completion status. Omit to export both, which is what the app's button does.
limitintegeroptionalMost recent responses to include, newest first (1-5000). The app's own screen shows 500. Default: 500

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.export_responses \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "form_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

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.

Parameters

FieldTypeRequiredDescription
idstring (uuid)requiredThe form's id.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.get \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

Over MCP the same operation is the tool forms_get at https://app.chirply.io/api/mcp, same bearer token, same input.

Open response

forms.get_responseread

Fetch one form response with its answers, completion status, score, outcome, and linked CRM contact. This only reads data.

Parameters

FieldTypeRequiredDescription
idstring (uuid)requiredThe response id.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.get_response \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

Over MCP the same operation is the tool forms_get_response at https://app.chirply.io/api/mcp, same bearer token, same input.

List forms

forms.listread

List the account's standalone forms, surveys, quizzes, and applications, newest first. This only reads data.

Parameters

FieldTypeRequiredDescription
limitintegeroptionalMax rows to return (1–100). Default: 25
offsetintegeroptionalRows to skip. Default: 0
status"draft" | "published" | "archived"optionalOnly forms with this status.
kind"form" | "survey" | "quiz"optionalOnly forms of this kind.
querystringoptionalText to match in the form name or description.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.list \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "limit": 25,
    "offset": 0
  }'
Test with your API key

Over MCP the same operation is the tool forms_list at https://app.chirply.io/api/mcp, same bearer token, same input.

List responses

forms.list_responsesread

List partial or completed responses for a form, including answers, score, outcome, and linked CRM contact. This only reads data.

Parameters

FieldTypeRequiredDescription
limitintegeroptionalMax rows to return (1–100). Default: 25
offsetintegeroptionalRows to skip. Default: 0
form_idstring (uuid)requiredThe form whose responses to list.
status"partial" | "completed"optionalOnly responses with this completion status.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.list_responses \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "form_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

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.

Parameters

FieldTypeRequiredDescription
idstring (uuid)requiredThe form to publish.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.publish \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

Over MCP the same operation is the tool forms_publish 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.

Parameters

FieldTypeRequiredDescription
idstring (uuid)requiredThe archived form to restore.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.restore \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

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

FieldTypeRequiredDescription
form_idstring (uuid)requiredThe published form being filled in.
session_idstringrequiredA 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.
answersmap of string → objectrequiredThe 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"}}

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.save_partial_response \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "form_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11",
    "session_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11",
    "answers": {}
  }'
Test with your API key

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

FieldTypeRequiredDescription
idstring (uuid)requiredThe form to set the closing message on. Works whether the form is live, draft or archived.
closed_titlestringoptionalThe heading on the closed page, e.g. 'This assessment is closed'. Leave out to keep the current one.
closed_messagestringoptionalThe 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.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.set_closed_message \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

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

FieldTypeRequiredDescription
form_idstring (uuid)requiredThe form that owns the question.
question_idstringrequiredThe question's `id` from the form's definition — read it with forms.get, never guess.
contact_field"first_name" | "last_name" | "email" | "phone" | "company" | ""optionalThe 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_fieldbooleanoptionalOFF 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_consentbooleanoptionalConsent 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_channelsarray of ("email" | "sms")optionalConsent 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_permissionbooleanoptionalConsent 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'.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.set_question_mapping \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "form_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11",
    "question_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

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.

Parameters

FieldTypeRequiredDescription
form_idstring (uuid)requiredThe form to report on.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.stats \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "form_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

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

FieldTypeRequiredDescription
form_idstring (uuid)requiredThe published form receiving the response.
session_idstringrequiredA 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.
answersmap of string → objectrequiredThe 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"}}

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.submit_response \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "form_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11",
    "session_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11",
    "answers": {}
  }'
Test with your API key

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.

Parameters

FieldTypeRequiredDescription
idstring (uuid)requiredThe form to take offline.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.unpublish \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

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

FieldTypeRequiredDescription
idstring (uuid)requiredThe form to update.
namestringoptionalNew internal form name.
descriptionstring (or null)optionalShort internal/public description, or null to clear it.
kind"form" | "survey" | "quiz"optionalNew experience type.
definitionmap of string → objectoptionalThe 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}]}
settingsmap of string → objectoptionalThe 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."}
thememap of string → objectoptionalThe 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"}

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.update \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11"
  }'
Test with your API key

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

FieldTypeRequiredDescription
form_idstring (uuid)requiredThe published form the file is being uploaded for.
session_idstringrequiredThe respondent session this file belongs to — the SAME value used for forms.save_partial_response and forms.submit_response.
question_idstringrequiredThe id of the question being answered. It must exist in the form's published definition and be of type 'file'.
filenamestringrequiredThe 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_typestringoptionalADVISORY 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_base64stringrequiredThe file's bytes, base64-encoded (no data: URL prefix). Must decode to 10 MB or less.

Example

curl -X POST https://app.chirply.io/api/v1/actions/forms.upload_response_file \
  -H "Authorization: Bearer chp_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "form_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11",
    "session_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11",
    "question_id": "2f6a1c1e-6c3b-4c62-9f6e-8a2d4b7c9e11",
    "filename": "Example",
    "content_base64": "example"
  }'
Test with your API key

Over MCP the same operation is the tool forms_upload_response_file at https://app.chirply.io/api/mcp, same bearer token, same input.

The machine-readable version of this page is GET https://app.chirply.io/api/v1/actions?domain=forms — same operations, with full JSON Schemas. Authentication, errors and rate limits are covered in the API documentation home.