Form Builder API: Push Submissions and Read Responses From Your Own App

Google Forms' API has 0 methods to create a response. How a form builder API with per-form keys lets your app push submissions, read approved ones and moderate.

Shivam Singh
· 14 min read

The short version

  • Most forms APIs only read. Google's Forms API offers get and list on responses and no method to create one; Typeform's Responses API also retrieves rather than submits, as of October 2026.
  • Would You Rather's API is per form: one x-api-key header, 7 endpoints, and only the questions you give a field key ever appear in it.
  • Submissions pushed from your app start as pending and stay out of the public read endpoint until someone approves them in the results page.
  • It is not a voice-recording backend yet. The API moves text, choices and photos (JPEG, PNG or WebP up to 5 MB). Spoken answers stay in the hosted form, results and Google Sheets.

The obvious idea is to use a form builder as the backend for your own app's voice recordings. We get asked about it, and today the honest answer is no: Would You Rather's form builder API moves text, choices and photos, not audio. What it does do is rarer than you might expect. It lets your app push submissions in, not just read responses out.

Google's Forms API has 0 methods for creating a response. Most forms APIs are built for exporting. This post documents every endpoint of our per-form API as it exists in the code today, compares it with Google Forms and Typeform, and is straight about where it falls short.

0
Methods in the Google Forms API that create or submit a response
30 min
Window in which recent Typeform responses may be missing from the Responses API
7
Endpoints in the Would You Rather API, all behind one per-form key
Source: Would You Rather source code
Short answer

A form builder API lets your app read and write a form's data with code. Use one when you want a visual form, results page and moderation, but your own interface or app. Check three things first: whether it accepts submissions or only reads them, how keys are scoped, and which field types it actually returns.

Most forms APIs read. Few accept submissions

Form builders grew up as hosted pages. Their APIs reflect that: the form is where answers come in, and the API is how you get them out. That is fine for analytics and a poor fit for an app that wants its own interface.

Google Forms: read responses, watch for changes

The Google Forms API can create a form, update it with batchUpdate, and read it. For responses, it offers get and list and nothing else. A list call returns at most 5,000 responses when you leave page size unset, and filters only by submission timestamp. It authorises with Google OAuth scopes such as forms.responses.readonly, not a simple key.

For near-real-time updates, Google offers watches. A watch lasts seven days, notifies a Cloud Pub/Sub topic at most once every 30 seconds, and carries no response data. Your app then calls list again to fetch what changed.

Typeform: retrieve responses, or receive webhooks

Typeform's Responses API retrieves responses from a form with GET requests and a personal access token sent as a Bearer token. Its walkthrough is specific: you get 25 responses by default and up to 1,000 with page_size. Its reference warns that responses from roughly the last 30 minutes may not be returned yet.

For real-time data, Typeform points you to webhooks, which push each new response to your URL. Typeform retries failed deliveries and disables a webhook that keeps failing. As of October 2026, neither Typeform's Responses API pages nor Google's reference document a way to create a response from your own code.

What the Would You Rather API does

Our API is opt-in per form. You switch it on from the form's Share page, which generates a key for that one form. Every endpoint lives under the form's slug, the short ID in its public link:

https://wyr.humanbit.in/api/v1/forms/{slug}

There are 7 endpoints. Two serve your app directly, four are self-service for the person who submitted, and one uploads photos.

Method and pathWhat it doesSuccess response
GET /profilesLists every approved, completed response, flattened to one object each{ "profiles": [...] }
POST /submissionsCreates a submission from your app; it starts as pending201 { "id", "status": "pending" }
GET /submissions?respondentKey=...Returns one submitter's own submissions, in any status{ "submissions": [...] }
PATCH /submissions/{id}Replaces a submitter's answers in place, keeping its status{ "id", "status" }
DELETE /submissions/{id}?respondentKey=...Hard-deletes a submitter's own submission{ "id", "deleted": true }
POST /submissions/{id}/delete-requestFlags a submission for deletion; an admin confirms or dismisses{ "id", "deleteRequested": true }
POST /uploadsUploads one JPEG, PNG or WebP image up to 5 MB201 { "url" }

Errors always come back as { "error": "message" } with status 401, 404, 422 or 500.

Only the questions you name are exposed

A question appears in the API only when you give it a field key in the builder, for example name or city. Unnamed questions never leave the product through the API. That is the right default: adding a sensitive question to a form should not silently publish it.

Each named question can also be marked private, which makes it write-only. Your app can submit a value for it, but no read endpoint ever returns it, not even as null. The builder suggests this for identity numbers and similar data.

Choice answers accept labels or IDs

For single and multiple choice questions, you can send the option's label or its internal ID. The API stores the ID and returns the label on reads. Everything else must be a string, number or boolean, and strings are capped at 10,000 characters. Unknown field keys are rejected, and the error message lists the valid ones.

Authentication, CORS and what a key can do

Every request sends the form's key in one header:

x-api-key: fk_...

The form must be published and have a key. Otherwise every call returns 404, so a form's API stays invisible until you turn it on. A wrong key returns 401. Rotating or disabling the key on the Share page stops the old key immediately.

The API sends CORS headers for any origin and allows the content-type and x-api-key headers. We checked this on 8 October 2026 with a preflight request to the live API: it returned 204 with those headers, and an unknown form returned 404 with the error message above. That means a browser app can call the API directly.

Treat a browser key as visible

A key that runs in a browser can be read by anyone who opens the developer tools. We designed for that rather than against it. The key only touches one form, it never grants access to your account, and submissions it creates wait for approval. We still would not put a form with private data behind a key that ships to browsers. Use a server-side proxy for those.

The respondent key is the submitter's password

Self-service calls rely on a respondentKey you choose when you create a submission, up to 64 characters. Whoever holds the form's key and a respondent key can read, edit and delete that submission. Our view is firm: use an unguessable ID from your own auth system, not a phone number or email address. Wrong respondent keys get a 404, so the API does not reveal whether a submission exists.

Push a submission and read responses: example requests

Here is the full flow with curl. Replace the slug, key and field keys with your form's own.

Create a submission:

curl -X POST "https://wyr.humanbit.in/api/v1/forms/abc123xyz0/submissions" \
  -H "x-api-key: $WYR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"respondentKey":"usr_7f3a9c","answers":{"name":"Asha","city":"Pune","plan":"Team"}}'

That returns 201 with {"id":"...","status":"pending"}. Read everything that has been approved:

curl -H "x-api-key: $WYR_KEY" \
  "https://wyr.humanbit.in/api/v1/forms/abc123xyz0/profiles"

Each item has your field keys plus id and submittedAt. Unanswered fields come back as null. The endpoint name comes from the first app built on the API, which showed member profiles. It works the same for any form.

The same two calls with fetch, from a browser or Node:

const base = "https://wyr.humanbit.in/api/v1/forms/abc123xyz0";
const headers = { "x-api-key": WYR_KEY, "Content-Type": "application/json" };

const created = await fetch(`${base}/submissions`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    respondentKey: "usr_7f3a9c",
    answers: { name: "Asha", city: "Pune", plan: "Team" },
  }),
}).then((r) => r.json());

const { profiles } = await fetch(`${base}/profiles`, { headers }).then((r) => r.json());

Attach a photo

Photos are a two-step upload. You pass the returned URL as the answer to a text question. Send the file as multipart form data with one part named file.

curl -X POST "https://wyr.humanbit.in/api/v1/forms/abc123xyz0/uploads" \
  -H "x-api-key: $WYR_KEY" \
  -F "file=@photo.jpg;type=image/jpeg"

The response is 201 with a url. That URL is a long-lived signed link to the stored image, valid for years rather than minutes, so you can store it and show it in your app.

Let submitters edit or delete their own entry

A submitter can fetch their own entries with GET /submissions?respondentKey=usr_7f3a9c, including pending and rejected ones. PATCH /submissions/{id} takes the same body as create and replaces every answer. Private fields you leave out keep their stored values, because your app could never have read them back.

For deletion you have two choices. DELETE removes the submission and its answers at once. POST /submissions/{id}/delete-request hides it from the public list immediately and leaves the final call to the form's admins, who confirm or dismiss it in the results page.

The integration flow, end to end

Here is the order we would follow for a new app. Every step maps to a real screen or endpoint.

From a form in the builder to live data in your app
  1. 1
    Build and publish the form
    Add the questions in the builder. Publish it: the API answers 404 for unpublished forms.
  2. 2
    Give each question your app needs a field key
    Name them as your code expects, such as name or city. Mark identity numbers and similar fields private so reads never return them.
  3. 3
    Enable the API on the Share page
    Copy the generated key into your app's environment variables. The Share page also lists copyable curl examples.
  4. 4
    Upload photos first, if any
    POST each image to /uploads, then use the returned URL as a field's value.
  5. 5
    Create the submission with a respondent key
    POST to /submissions with an unguessable ID from your auth system. It arrives as pending.
  6. 6
    Approve or reject in the results page
    Filter by status, review, and approve. There is no API call for approval, by design.
  7. 7
    Read approved data back
    GET /profiles returns every approved, completed response in one list. Cache it on your side.
Moderation sits in the middle on purpose. Your app can write, but only a person on your team can make an entry public.

Moderation is the safety net, not an afterthought

Every API submission starts as pending, and /profiles shows only approved ones. Responses collected through the hosted form are approved by default, so they appear in /profiles without review. Approval happens in the results page, where you can filter by pending, approved and rejected. We think keeping approval out of the API is correct, because a leaked key then cannot publish anything.

Privacy gates hide one field per response

Sometimes a submitter wants their entry listed but one detail hidden, such as a phone number. The API supports that with a naming convention. Add a question whose field key is the original key plus "Privacy", for example phonePrivacy. If a response answers it with any truthy value, /profiles returns phone as null for that response and drops phonePrivacy from the output. The submitter still sees both through their own /submissions call.

How it compares with Google Forms and Typeform

This table compares the documented API behaviour as of October 2026. It is not a comparison of the form builders overall. We do that in our Typeform comparison and our Google Forms comparison.

Would You Rather APIGoogle Forms APITypeform Responses API
AuthPer-form key in x-api-keyGoogle OAuth scopesPersonal access token or OAuth app
Create a response from codeYes, as pendingNo documented methodNo documented method
Read responsesAll approved responses in one callUp to 5,000 per call, timestamp filter25 by default, up to 1,000 per page
Real-time notice of new responsesNone; poll /profilesPub/Sub watches, no payloadWebhooks with response data
Where each one is clearly aheadPer-form keys, writes, built-in moderationMature quotas, Google Cloud toolingWebhooks, search, filters, file downloads

The last row matters. If you need webhooks, rich filtering or a well-documented quota system, Typeform and Google are ahead today. Google publishes per-minute quotas, and its limits page counts responses.list as an expensive read with its own lower cap.

Listing responses has its own lower quota than ordinary reads. Our API has no published quota at all, which is simpler today and less predictable at scale.

Where this API is weakest

It is not a voice backend. Voice answers recorded in the hosted form are stored with the response, but the API returns only the optional typed fallback for a voice question, or null. The upload endpoint takes images only. If your app needs spoken answers, send people to the form's public link for that part, then collect recordings from the results page or the Google Sheets sync, which includes audio links. There is no documented way to tie a hosted-form recording to your app's respondent key today.

File and media uploads from the hosted form are not served as usable links through the API either. Only images uploaded through /uploads come back as URLs.

There is no pagination. /profiles returns every approved response in one list, which suits hundreds or a few thousand entries and gets heavy beyond that. There are no webhooks, so your app polls. There is no published rate limit, which is not the same as no limit.

Edits skip review. A PATCH keeps the submission's current status, so an approved entry stays approved after its owner changes it. That suits profile-style apps where people fix their own details. It is the wrong choice where every change must be checked, and we would not use the API for that case today.

Validation covers only named questions. A required question with no field key is not enforced on API submissions. Give every required question a key if your app submits through the API.

Where to start

If your app needs a structured, moderated list, such as a directory, a sign-up wall or a set of listings, try the API on one small form. Name three questions, enable the key, and push one test submission with curl before writing any app code. Approve it in the results page and watch it appear in /profiles.

If what you really want is spoken answers, start with the hosted form instead. Our guides to collecting voice recordings online and adding audio questions to forms cover that path. Use branching logic to keep the voice questions short, and a QR code to get people there from the physical world.

References

  1. Google, Google Forms API REST reference, accessed 8 October 2026. Used for the list of form, response and watch methods.
  2. Google, forms.responses.list, accessed 8 October 2026. Used for the 5,000-response default, timestamp filtering and OAuth scopes.
  3. Google, Forms API push notifications, accessed 8 October 2026. Used for watch duration, Pub/Sub delivery and payload contents.
  4. Google, Forms API usage limits, last updated 3 September 2026. Used for the per-minute quota chart.
  5. Typeform, Retrieve responses and Responses API walkthroughs, accessed 8 October 2026. Used for the endpoint, Bearer token, page size and the 30-minute delay.
  6. Typeform, Webhooks, accessed 8 October 2026. Used for real-time delivery and retry behaviour.
  7. Would You Rather source code, src/app/api/v1/, src/lib/api/ and src/proxy.ts, checked 8 October 2026, plus one live preflight and one 404 request to the production API. Used for every claim about our endpoints, auth, CORS and limits.

The weakest point in this source base: our own API has no external documentation or independent testing yet, so every claim about it rests on our reading of our code. Competitor pages can also change without notice.

Questions people ask

What is a form builder API?

A form builder API lets your own code talk to forms you build in a visual tool. Most offer read access, so you can pull responses into a database or dashboard. Some also accept submissions, so your app can use the form as its data store while keeping the builder's results page, moderation and exports. Check which direction a given API supports before you design around it.

Can you submit a Google Form response through the API?

Not through the Google Forms API. Its responses resource offers only get and list, which read existing responses. The API can create and update forms, and it can set up watches that notify a Cloud Pub/Sub topic when new responses arrive. To collect answers from your own interface, you need a different tool or a store you control.

How do I get responses from Typeform with the API?

Send a GET request to the Responses API at api.typeform.com/forms/{form_id}/responses with a personal access token as a Bearer token. It returns 25 responses by default and up to 1,000 per page with page_size. Typeform's docs warn that responses from roughly the last 30 minutes may be missing, and point to webhooks for real-time data.

Is it safe to call a forms API from the browser?

Only if the key is designed for it. A browser app exposes any key it sends, so assume users can read it. A per-form key limits the damage to one form, and moderation keeps forged submissions out of public listings. Never ship an account-wide token, such as a personal access token, to a browser. Proxy those calls through your server instead.

Does the Would You Rather API return voice recordings?

No, not today. The API returns text, numbers, choices and the URLs of photos uploaded through it. Voice answers recorded in the hosted form reach you through the results page and the Google Sheets sync, which includes audio links. The API's upload endpoint accepts JPEG, PNG and WebP images only, so your app cannot push audio through it either.

How do I authenticate to the Would You Rather API?

Enable the API on the form's Share page, which generates a key for that form only. Send it in an x-api-key header with every request. The form must be published, or every call returns 404. Rotating or disabling the key on the same page stops the old key immediately, so update your app's environment variable first.

Drafted with AI from published developer documentation and Would You Rather's own source code, then edited and approved by Shivam Singh.

Would you rather hear the answer?

Make a voice form in a minute. It’s free.

Create a form

Keep reading