# Server keys

Send a form from your own server with more room, and count each visitor separately. A server key is optional: every form
keeps working without one.

Without a key, Formerie sees your server's address, so all your visitors share one limit. With a key:

- **Your server can name the visitor,** so the limit per visitor counts each person.
- **Your server gets higher limits.** See [What changes](#what-changes).
- **Everything else stays on.** Validation, spam checks, human verification, schedules, quotas and your own limits all still
  apply.

A key can only do what a visitor can: read a form's schema, check answers and submit. It can't read submissions or change a form.

## Make a key

In the app, open your workspace, then **Server keys**. Workspace owners and admins can do this.

1. Choose **New key** and give it a name, such as the site that will use it.
2. Choose whether it works on every form in the workspace or on one form. One form is safer.
3. Choose how long it lasts: 90 days, 1 year or 2 years.
4. Copy the key and put it in your server's secrets. **It's shown this once.** Formerie keeps only a fingerprint of it, so nobody can show it to you again.

A key starts with `fmc_`. Keep it out of your code and your repository, and read it from an environment variable.

## Use a key

Send the key in the `Authorization` header, and the visitor's address in `X-Formerie-Visitor-Ip`:

```bash
curl https://forms.formerie.com/fm_t77txbvg43nmar/submit \
  -H "Authorization: Bearer $FORMERIE_SERVER_KEY" \
  -H "X-Formerie-Visitor-Ip: 203.0.113.42" \
  -H "Content-Type: application/json" \
  -d '{"fields":{"email":"visitor@example.com","message":"Hello"}}'
```

It works the same on all three routes in the [API reference](/guides/api-reference/).

## Keep it on your server

**A browser can't use a key.** Formerie refuses any request that has a key and an `Origin` or `Sec-Fetch-Site` header, with
`API_KEY_FROM_BROWSER`, even from a site on your allowed list. Don't send `Origin` from your server along with a key.

If a real key arrives that way, the **Server keys** tab shows a warning. A key that reached a web page has to be treated as public, so rotate it.

## The visitor's address

Send one IPv4 or IPv6 address, the address of the person using your site. Your server has to take it from a source it trusts, usually the address your own load balancer or host gives you.

| You send | What happens |
| --- | --- |
| A public address, with a key | It's used for the limit per visitor and sent to the human verification provider. A submission keeps only a hash of it. Request logs, kept for up to 7 days, can hold the address, as they do for anyone who reaches a form directly |
| Nothing, with a key | Limits per address don't apply, since Formerie doesn't count your server as a visitor. No address is stored |
| A private address, such as `10.0.0.8` or `127.0.0.1`, with a key | It's ignored as if you sent nothing, and the answer has the notice `VISITOR_IP_NOT_PUBLIC`. Testing from your own computer works this way |
| Anything else, such as a whole `X-Forwarded-For` list, with a key | Refused with `INVALID_VISITOR_IP`, so you find out on your first try |
| Any address, with no key | It's ignored, and the answer has the notice `VISITOR_IP_IGNORED`. Only a key makes Formerie trust it |

You're passing on your visitors' addresses, so make sure your own privacy notice covers it.

## What changes

| Limit | Without a key | With a key |
| --- | --- | --- |
| Short-term limit on public routes | 120 requests a minute per form from one address | 120 a minute per visitor, up to about 1,200 a minute per form for the key |
| Default limit per visitor | 30 every 10 minutes, shared by everyone on your server | 30 every 10 minutes for each visitor |
| Default limit per form | 1,000 an hour | 10,000 an hour per key |

Limits you set on a form yourself still apply as you set them, and a limit per address needs the visitor's address to count anything. On the Free plan, a form still accepts at most 1,000 submissions an hour, with or without a key. See [Plans and limits](/guides/plans-and-limits/).

## Human verification

You don't need to forward `Origin` with a key. Send the token as `verificationToken`, and Formerie accepts a token solved on any site in the form's [allowed origins](/guides/spam-protection/#allowed-origins). You still need your own provider keys, as described in [Verification on the server path](/guides/framework-entry-points/#verification-on-the-server-path).

## Rotate a key

Choose **Rotate** on the key. You get a new key, shown once, with the same name and form. **Both keys work for the next 24 hours,** so you can put the new one on your server with nothing refused. After that, the old one stops.

A month before a key expires, the workspace's owner gets an email, so there's time to rotate it.

## Revoke a key

Choose **Revoke** on the key. It stops working at once. If a key has leaked, revoke it instead of rotating it, then make a new one.

A key that fails is always refused. Your server's request never goes through without it, so a mistake shows up instead of quietly sharing one limit again. Keep what your visitor typed and show a calm message when this happens, and match on these codes:

| Code | Status | Means |
| --- | --- | --- |
| `API_KEY_INVALID` | 401 | The key is wrong or the header is malformed |
| `API_KEY_EXPIRED` | 401 | The key's date has passed, or the 24 hours after a rotation are over |
| `API_KEY_REVOKED` | 401 | Someone revoked the key |
| `API_KEY_WRONG_FORM` | 403 | The key belongs to another form or workspace |
| `API_KEY_FROM_BROWSER` | 403 | The request came with a browser's headers |
| `INVALID_VISITOR_IP` | 400 | The visitor's address isn't a single address |

## Keep track of keys

The **Server keys** tab shows each key's name, the start of the key, its form, who made it, when it was last used and when it expires. Making, rotating and revoking a key each appear in the workspace's activity, and each submission records which key sent it.

A key belongs to the workspace, so it keeps working when the person who made it leaves. AI tools connected through MCP can't see or make keys.

The official packages don't have a key option yet. Until they do, send the two headers yourself.
