Using the REST API
A webhook pushes a submission to you the second it lands. The REST API works the other way round: your code asks when it suits — a nightly job pulling yesterday's callout reports into a rota system, a script that spins up a fresh intake form per client.
Everything sits under https://app.formformform.com. Authentication is a bearer token you fetch by signing in over the API; there is no API-key screen in Settings, and the token reaches as far as your account does. Resources come back wrapped in a data key.
Sign in for a token
You exchange your account password for a token once, then send that token on every later call. Nothing expires on a timer, so a token left in a cron job keeps working until POST /api/v1/auth/logout revokes it — and that call revokes only the token that made it.
- 1
POST to https://app.formformform.com/api/v1/auth/login with a JSON body holding email and password.
- 2
Add device_name to label the token — "rota-sync", say. Left out, it is labelled "mobile".
- 3
With two-factor authentication on, the reply holds two_factor: true and a challenge_token instead. POST that token and your six-digit code to /api/v1/auth/two-factor-challenge; a recovery_code works in place of code, and is spent once used.
- 4
Store token where your code can read it and others cannot.
- 5
Send it as Authorization: Bearer <token>, alongside Accept: application/json.
- 6
Confirm it works with GET /api/v1/auth/me.
Five wrong passwords for the same email and IP inside a minute returns 429. The account's email must also be verified: an unverified account gets 403 on forms and submissions even though login itself succeeds.
What the API covers
Under /api/v1, with your token: forms (list, create, read, update, delete, plus /duplicate and /toggle-active), a form's fields (create, update, delete, reorder), submissions (list, read, delete, export), and /api/v1/templates. Under /api/v1/public, with no token: read an active form and post an answer to it.
Which identifier goes where is what trips people up. A form is addressed by its uuid, already in the editor's address bar at /form-builder/<uuid>/edit, and a submission by its own uuid. Fields are the exception, taking the integer id the API hands back. Public routes use a third value, the short public_id from the share link.
Creating a form needs only title; description, thank_you_message, redirect_url, notify_on_submission, is_active and template are optional, template taking a slug (default blank) to seed the fields. slug is derived from the title, public_id never changes, and both are ignored if sent.
Read responses
GET /api/v1/forms/<uuid>/submissions returns newest first, 25 to a page, per_page adjustable up to 100, with today_count alongside. Each row holds uuid, created_at, ip_address, user_agent and a values array — one entry per answered field, giving field_uuid, field_type, field_label and value. Fields left blank or hidden by conditional logic are absent rather than null, so read by field_uuid, never by position.
Fetching one submission adds prev and next uuids for walking the list, plus the armoured encrypted_payload on an encrypted form. /submissions/export streams the same CSV as the Submissions tab: a column per input field, then Submitted At, IP Address, Filled At and Was Offline.
A File Upload answer's value is the upload's uuid, not a link. The bytes come from the owner-only download route behind the Submissions tab, which reads a browser session rather than a bearer token.
Post a submission, uploads included
GET /api/f/<public_id> returns the live form: title, thank-you message, and every field with its integer id, type, options and is_required flag. POST back to the same address with a fields object keyed by those ids. The reply carries submission_uuid plus the thank-you message and any redirect. Throttled at 30 posts a minute per IP.
A file goes up on its own, before the submission exists. POST it as multipart to /f/<public_id>/uploads with form_field_id set to the File Upload field, read the uuid from the reply, then send that uuid twice: as the field's value inside fields, and in the upload_uuids array. Up to 50 files per submission. An encrypted form takes an armoured encrypted_payload in place of fields.
2 MB per file, enforced on the server: a field's settings.max_size accepts a larger number through the API, but anything above 2 MB is still refused. Prefer /api/f/<public_id> over /api/v1/public/forms/<public_id>/submissions when the form has a File Upload field — the v1 route stores the answers but does not attach the uploads.
Frequently asked questions
Is there an API key I can generate in Settings?
No. Settings holds Profile, Password, Two-Factor Auth, Appearance, Billing and Zapier, and none of them mints a key. The token comes from POST /api/v1/auth/login, with device_name as its only label. Call /api/v1/auth/logout with a token to revoke it.
Can I download the files people uploaded through the API?
No. The submission gives you each upload's uuid, but the download and preview routes are owner-only and read a browser session, not a bearer token. Open the submission in the Submissions tab instead. On an encrypted form the stored bytes are ciphertext anyway.
Can I set up a webhook through the API?
No. A webhook URL and its signing secret belong to one form and are set in the editor, under the Settings tab then the Integrations sub-tab. No endpoint reads or writes them, so a script that creates forms still leaves someone a trip to the editor.
Related articles
- Send submissions to a webhook
Point a form at a URL you own and every submission is posted to it as JSON, signed if you want it signed.
- Connect your forms to Zapier
Authorise your account once, then fire a Zap the moment one of your forms is submitted.
- Read and search your responses
Every response lands in the form's Submissions tab, where you can search it and open any one of them in full.
- Export responses to CSV
Download every response to a form as one CSV file, and know what each column holds before you open it.
Nothing here answering your question? The editor is quick enough that trying it is often faster than reading about it.
Open the editor