---
title: "POST /f/{formId}"
description: "The form address takes a POST of text fields from the visitor's browser, with the bot check. It needs no API key and answers the browser preflight."
url: "https://formparcel.live/docs/reference/endpoint"
---

# POST /f/{formId}

The form address takes a POST of text fields from the visitor's browser, with the bot check. It needs no API key and answers the browser preflight.

## The address

`POST https://f.formparcel.live/f/{formId}`

`{formId}` is the form’s own ID, which the dashboard shows on the form’s page as a ready-to-copy address. The address never changes, so you paste it into a client’s site once. It is the only thing that tells FormParcel which form a post is for.

## Where a post comes from

Send the post from the visitor’s browser: a plain HTML form, or `fetch` on the page. The page loads FormParcel’s bot check, which adds a proof to each submission.

A post from anywhere else, such as your server, a site builder’s webhook, a form plugin that forwards from its own server, or `curl`, carries no proof. It gets the same answer a lead gets, and the lead is held in your Spam folder instead of being delivered.

> **Bot check.** A lead sent without FormParcel's bot check is saved but held in your Spam folder, not delivered. That is every post from a server, a webhook or `curl`, and every post from a page that does not load the script. The dashboard then shows **Bot check not installed** under Needs action. Fix: keep the script line on every page that has the form. [Learn more](https://formparcel.live/docs/reference/bot-check)

- **Server key for webhooks and server posts** Coming soon: A per-form key for a site builder's webhook or your own server, so a lead posted from a server is delivered instead of held in Spam. Until it ships, post from the browser.

## Methods

| Method | What it does |
| --- | --- |
| `POST` | Sends a lead. This is the only method that stores anything. |
| `OPTIONS` | Answers the browser’s CORS preflight, so `fetch` from a client’s site works without a proxy. |
| `GET`, `HEAD` | Answered with [405](https://formparcel.live/docs/reference/responses#method-not-allowed) and an `Allow` header that lists `POST, OPTIONS`. A browser that opens the address gets a short page that says it is a form address. |
| Any other | Answered with 405 and an empty body, with no `Allow` header. |

## Request body

Send the fields as one of these content types. The body is text only.

- `application/x-www-form-urlencoded`
- `multipart/form-data`
- `application/json`

- Send UTF-8. A URL-encoded form in another declared charset is decoded as that charset. A JSON or multipart body that is not UTF-8 is refused ([`UNSUPPORTED_CHARSET`](https://formparcel.live/docs/reference/responses#unsupported-charset)).
- A JSON body is an object of field names and values. Its keys must be unique at the top level, and it may nest up to 4 levels in total.
- A file part is refused ([`FILE_UPLOAD_NOT_SUPPORTED`](https://formparcel.live/docs/reference/responses#file-upload-not-supported)). FormParcel does not take file uploads.
- Several fields with the same name, such as a group of checkboxes, are stored together as one value.
- A post is at most 16 KB, with at most 50 fields, a name of at most 64 characters and a value of at most 4,096 characters. Every limit, with the code it is refused with, is on [Limits and retention](https://formparcel.live/docs/reference/limits).

A few names are read by FormParcel and not stored as lead fields, such as `_next` and `_gotcha`. They are on [Special fields](https://formparcel.live/docs/reference/fields).

## No API key

The address takes no API key and no sign-in. A request needs only the address and a body.

FormParcel has no management API, command line or MCP server. This address is the only part of it that is documented. Its contract is also published as an [OpenAPI 3.1 file](https://formparcel.live/openapi.json).

## Calling from a browser

FormParcel answers the preflight request. It allows the methods `POST, OPTIONS` and the headers `Content-Type, Accept, Authorization`, and lets a browser keep the answer for 86,400 seconds. A 503 carries the same headers, so a browser can read it.

If you set allowed origins on the form, a request from any other origin is refused with 403 ([origin not allowed](https://formparcel.live/docs/reference/responses#origin-not-allowed)). How the list is matched is on [Limits and retention](https://formparcel.live/docs/reference/limits#allowed-origins).

## What it answers

A browser that submits a plain form gets a page or a redirect. Everything else, whether it is fetch, XHR, curl or a server, gets JSON, for success and for refusals. The rule, the 200 body and every refusal code are on [Responses and error codes](https://formparcel.live/docs/reference/responses).

## Security of the form address

The address is public by design: it ends up in the page’s HTML, and anyone who reads the page has it. Treat it as an address, not a secret.

- The ID in it is random and carries at least 128 bits, so an address cannot be guessed or counted up to. A wrong ID is answered with [form not found](https://formparcel.live/docs/reference/responses#form-not-found).
- What limits abuse is on the form, not in the address: the bot check, the hidden trap field, the rate limits, the allowed origins and, for a client site, the Cloudflare Turnstile check. See [Spam protection](https://formparcel.live/docs/setup/spam-protection) and [Limits and retention](https://formparcel.live/docs/reference/limits).
- A post is a stranger’s text, and what it holds is written to the client’s Sheet. Text that a spreadsheet would read as a formula is neutralised before it is written.
- Do not put a password, an API key or any other secret in a form page or in a hidden field. A hidden field is visible to anyone who reads the page.

## No idempotency key

FormParcel has no caller-supplied idempotency key yet. A request that times out leaves you not knowing whether the lead was accepted, and sending the same lead again can create a second receipt. The advice for that case is on [the fetch guide](https://formparcel.live/docs/guides/fetch#when-you-get-no-answer).
