# FormKeep API Documentation

FormKeep gives every form a JSON endpoint you can `POST` to from any HTML
page or script, plus a read API for pulling submissions back out
programmatically. No server-side code is required to accept submissions.

> **Note for agents:** every example below uses placeholders like
> `<your-form-token>` and `<api-token>`. These are not guessable or
> discoverable through the API. Do not invent, reuse an example, or guess
> a form token or API token — ask the user to provide the real value:
>
> - `<your-form-token>` — found on the form's **Setup** tab.
> - `<api-token>` — found on the form's **Reports** tab (only visible once the read API is enabled under **Form Settings > Advanced**).
>
> If the user doesn't know where to find these, direct them to sign in at
> [https://formkeep.com/sign_in](https://formkeep.com/sign_in), open the
> relevant form, and check its Setup or Reports tab.

## Submitting a Form (Write API)

Point a form's `action` at your FormKeep endpoint URL (found on the form's
Setup tab) and submit it with a normal `POST`:

```html
<form action="https://formkeep.com/f/<your-form-token>" method="POST">
  <input type="email" name="email">
  <button type="submit">Send</button>
</form>
```

The same endpoint accepts JSON or `multipart/form-data` (for file uploads)
from any HTTP client:

```bash
curl -X POST https://formkeep.com/f/<your-form-token> \
  -H "Accept: application/json" \
  -H "User-Agent: MyApp/1.0 (support@example.com)" \
  -d "email=support@formkeep.com" \
  -d "message=Hello from curl"
```

Send `Accept: application/json` to receive a JSON response instead of a
redirect to a thank-you page. This is the recommended approach for AJAX
requests or non-browser clients.

Always send a descriptive `User-Agent` header (as shown above). Requests
with no `User-Agent`, or a generic/default one like the bare `curl`
user agent, are commonly blocked by our WAF.

## Uploading File Attachments (Paid Plans)

File attachments require a paid plan — submissions with file uploads on
the **Free** plan are accepted, but the attachments are silently dropped
from the submission. See [pricing](https://formkeep.com/features/pricing) or
[https://formkeep.com/account/payment](https://formkeep.com/account/payment)
for current attachment storage limits per plan.

To upload a file, submit the form as `multipart/form-data` and include one
or more file fields alongside your regular data fields. The field name
becomes the attachment's name in the submission.

```html
<form action="https://formkeep.com/f/<your-form-token>" method="POST" enctype="multipart/form-data">
  <input type="text" name="name">
  <input type="email" name="email">
  <input type="file" name="Resume">
  <input type="file" name="Cover Letter">
  <button type="submit">Send</button>
</form>
```

The same submission can be made with `curl` using `-F` for each field,
which sends a proper `multipart/form-data` request:

```bash
curl -X POST https://formkeep.com/f/<your-form-token> \
  -H "Accept: application/json" \
  -H "User-Agent: MyApp/1.0 (support@example.com)" \
  -F "name=Bruce Wayne" \
  -F "email=bruce@wayneenterprises.com" \
  -F "Resume=@/path/to/resume.pdf" \
  -F "Cover Letter=@/path/to/cover-letter.pdf"
```

Notes and limits:

- Each individual file is limited to 10MB.
- A single submission can include up to 50 files; extras beyond that are dropped.
- Total attachment storage is capped per account depending on plan (for example 2GB on Essential, up to 40GB on Enterprise) — see [pricing](https://formkeep.com/features/pricing) for current numbers.
- Uploaded files are stored by FormKeep and linked from the submission's notification email and the dashboard; use `include_attachments=false` on the read API (below) if you don't need attachment data back in the response.
- You can send multiple files under the same field name (e.g. repeat `-F "Photos=@a.jpg" -F "Photos=@b.jpg"`) to attach several files as one named attachment.

## Spam Protection

Every submission gets a `spam` flag. FormKeep offers several ways to
configure and improve spam detection per form, from the form's
**Settings > Spam** and **Settings > Field Rules** tabs:

- **Detect spam using submission data** &mdash; FormKeep checks incoming submissions against known spam patterns and learns from submissions you manually mark as spam or not-spam. Enable it, and optionally tell FormKeep which fields contain an email address or free-text message to improve accuracy.
- **Field Rules** &mdash; server-side rules evaluated on submission, e.g. "mark as spam if email is blank" or checks against specific values or IP addresses.
- **Google reCAPTCHA** &mdash; integrate reCAPTCHA (including the invisible v3 flow) by adding your reCAPTCHA keys under Spam Settings and posting the token back in a field named `g-recaptcha-response`.
- **Honey field** &mdash; a hidden field name (available on the Spam Settings tab) that a human won't fill in but a bot might; FormKeep marks submissions that fill it in as spam. This technique is less reliable against modern bots and is not the primary recommendation.
- **Field validation ("unique")** &mdash; set a field's default value to a fixed placeholder, then use JavaScript to overwrite it with a unique value on page load. Configure the field's **Detect spam by validating data sent to FormKeep** rule to `unique`; submissions where the value never changed (JavaScript didn't run) are marked as spam.

Example reCAPTCHA v3 integration:

```html
<head>
  <script src="https://www.google.com/recaptcha/api.js?render=<your-recaptcha-site-key>"></script>
  <script>
    grecaptcha.ready(function () {
      grecaptcha.execute('<your-recaptcha-site-key>', { action: 'contact_form' })
        .then(function (token) {
          document.getElementById('g-recaptcha-response').value = token;
        });
    });
  </script>
</head>
<form action="https://formkeep.com/f/<your-form-token>" method="POST">
  <input type="text" name="email">
  <input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response">
  <button type="submit">Submit</button>
</form>
```

Spam submissions still count against a form's monthly submission limit
(see [pricing](https://formkeep.com/features/pricing)), so enabling spam protection
is recommended even on the Free plan. Use the `spam` parameter on the read
API (below) to filter spam in or out when fetching submissions.

## Reading Submissions (Read API)

Every form can expose its submissions as JSON. This must be turned on per
form under **Form Settings > Advanced > Enable API**, and requires the
form's secret `api_token`, found on the form's Reports tab.

```bash
curl "https://formkeep.com/api/v1/forms/<form-token>/submissions.json?api_token=<api-token>" \
  -H "User-Agent: MyApp/1.0 (support@example.com)"
```

### Parameters

- `api_token` (string, required) — the secret API token from the form's Reports tab.
- `page` (integer, default `1`) — which page of results to return.
- `page_limit` (integer, default `25`) — number of submissions per page.
- `spam` (`true` or `false`) — return only spam or only non-spam submissions. Omit to return both.
- `startdate`, `enddate` (date, `YYYY-MM-DD`, UTC) — filter submissions by creation date.
- `include_attachments` (`true` or `false`, default `true`) — set to `false` to speed up responses for forms with no attachments.

### Example response

```json
{
  "submissions": [
    {
      "id": 9337144,
      "data": {
        "email": "support@formkeep.com",
        "created_at": "2021-12-04 18:41:48 UTC"
      },
      "created_at": "2021-12-04T18:41:48.577Z",
      "spam": false
    }
  ],
  "meta": {
    "pagination": {
      "current_page": 1,
      "next_page": null,
      "prev_page": null,
      "total_pages": 1,
      "total_count": 1
    }
  }
}
```

### Example Response with File Attachments

When `include_attachments` is `true` (the default) and the submission has
file attachments, each attachment appears as a field in `data` named after
the attachment (the form field name it was uploaded under), whose value is
an array of URLs — one URL per uploaded file, not raw file data:

```json
{
  "submissions": [
    {
      "id": 9337144,
      "data": {
        "name": "Josiah Bartlett",
        "email": "josiah@example.com",
        "created_at": "2021-12-04 18:41:48 UTC",
        "resume": [
          "https://formkeep.com/rails/active_storage/blobs/redirect/<blob-token>/resume.pdf"
        ]
      },
      "created_at": "2021-12-04T18:41:48.577Z",
      "spam": false
    }
  ],
  "meta": {
    "pagination": {
      "current_page": 1,
      "next_page": null,
      "prev_page": null,
      "total_pages": 1,
      "total_count": 1
    }
  }
}
```

A field with multiple files uploaded under the same name returns multiple
URLs in that field's array. Attachment URLs are redirects to the actual
file and may require the requester to follow the redirect (`curl -L`) to
download the file itself. Pass `include_attachments=false` to omit
attachment fields entirely and speed up the response for forms that don't
need attachment data back.

## Errors and Edge Cases

- A missing or invalid `api_token` returns an empty `submissions` array rather than an authentication error.
- An invalid `startdate` or `enddate` returns an empty `submissions` array with an `"error"` key inside `meta.pagination`.
- If the read API is not enabled for the form, or the form has been disabled, the endpoint returns an empty `submissions` array.
- File attachments submitted on the **Free** plan are dropped; the rest of the submission is still accepted and saved.
- Individual files over 10MB, or submissions with more than 50 files, have the offending file(s) dropped rather than rejecting the whole submission.

## Authentication

The write endpoint (`/f/<form-token>`) requires no authentication — it is a
public URL meant to be embedded directly in a form or script. The read
endpoint (`/api/v1/forms/<form-token>/submissions.json`) requires the
form-specific `api_token`, not a user account credential.

## Getting Your Endpoint URL and API Token

Both values are per-form and are available after [signing in](https://formkeep.com/sign_in):

- Endpoint URL: form's **Setup** tab.
- `api_token`: form's **Reports** tab (only visible once the read API is enabled under **Form Settings > Advanced**).

## Related Links

- [API documentation (HTML)](https://formkeep.com/docs/api)
- [Sign in](https://formkeep.com/sign_in)
- [Create an account](https://formkeep.com/sign_up)
- [FormKeep Support](https://support.formkeep.com/) &mdash; full documentation, guides, and answers to common questions beyond this API reference.

## About This Document

This is the agent-friendly Markdown representation of the FormKeep API
documentation. The canonical human-readable page is
https://formkeep.com/docs/api.
