Payloads and
OpenAPI.
Start with a complete payload. Change the values that belong to your workspace. Know exactly what a save will do.
A contract your tools can read.
The OpenAPI 3.1 specification includes every public submission and supported management operation, required scopes, request fields, response shapes and pagination parameters. Import the JSON into an OpenAPI-compatible client or use it to generate types.
Download OpenAPI JSONView the specification · Authentication and endpoint reference
Replace the sample UUIDs with your real recipient, rule and revision IDs. Replace example.com addresses and widget keys. All management examples use the staging API, a bearer token, and Content-Type: application/json. Send no cookies or Origin header. Request bodies are limited to 32 KiB; unknown properties are rejected.
The downloadable specification describes structural validation. Ownership, verified recipients, quota, live widget validity and current revisions are checked by the API at request time.
1. Create a form
Use GET /v1/recipients to find verified recipient IDs. A form needs a name of 1–80 characters and 1–5 recipients. Settings are optional; this example shows every setting.
{
"name": "Website contact",
"recipientIds": [
"11111111-1111-4111-8111-111111111111"
],
"settings": {
"subject": "A new website enquiry",
"messageRetentionDays": null,
"honeypot": "botcheck",
"allowedOrigins": [
"https://www.example.com"
],
"redirectUrl": "https://www.example.com/thanks",
"uploads": {
"enabled": false,
"retentionDays": 30
}
}
}| Setting | Contract |
|---|---|
subject | 1–150 characters, no line breaks. Defaults to “New form submission”. |
allowedOrigins | Up to 10 HTTPS origins, without paths or credentials. Empty allows any origin. When configured, submissions must include an exact matching Origin. |
redirectUrl | HTTPS URL without credentials, or an empty string for the hosted thank-you page. Applies to HTML responses. |
honeypot | A separate field name, 1–64 letters, numbers or underscores, starting with a letter. Defaults to botcheck. |
messageRetentionDays | Integer from 1–365, or null (the default) to keep messages until deletion. Applies only to new messages. Message expiry details. |
uploads | enabled defaults to false; retentionDays is an integer from 1–365, default 30. File limits and supported types. |
Change part of a form
{
"name": "Project enquiries",
"status": "active"
}Send only the fields you want to change. A settings patch merges into the saved settings, including the nested uploads object, so omitted members keep their current values. A PATCH must include at least one top-level field.
Add another notification address
{
"email": "hello@example.com"
}A new or unverified address returns 202 and must verify before you use its ID. Reposting it resends verification, at most once per minute. An already-verified address returns 200. A workspace supports up to 20 recipients.
2. Configure Turnstile
{
"siteKey": "REPLACE_WITH_YOUR_SITE_KEY",
"secretKey": "REPLACE_WITH_YOUR_SECRET_KEY",
"hostnames": [
"www.example.com"
],
"enabled": true
}Use the matching site key and secret from a customer-owned Cloudflare widget. Add 1–10 exact hostnames without protocols or paths. The widget must also allow those hostnames in Cloudflare. Staging and production require real widget keys and public website hostnames.
A new widget or changed site key requires secretKey. Omit it to retain the same widget’s stored secret; the API never returns it. Updating resets the previous verification timestamp. Submit from your website to verify the full connection. Turnstile setup and testing.
3. Preview and save routing
Read GET /v1/forms/{formId}/routing first. The first save uses revision: null. Later saves must use the exact revision from the latest response; a stale revision returns 409.
{
"revision": null,
"enabled": true,
"rules": [
{
"id": "22222222-2222-4222-8222-222222222222",
"name": "Project enquiries",
"field": "topic",
"operator": "equals",
"value": "project",
"recipientIds": [
"11111111-1111-4111-8111-111111111111"
]
}
]
}Up to 10 ordered rules, each with a unique UUID, a 1–60 character name and 1–5 distinct verified recipients. Use equals, contains or present. The first matching rule wins; matching ignores case and surrounding spaces. equals and contains need a nonempty value of at most 200 characters. present ignores value.
{
"config": {
"enabled": true,
"rules": [
{
"id": "22222222-2222-4222-8222-222222222222",
"name": "Project enquiries",
"field": "topic",
"operator": "equals",
"value": "project",
"recipientIds": [
"11111111-1111-4111-8111-111111111111"
]
}
]
},
"fields": {
"topic": "project",
"message": "Can we work together?"
}
}A save sends the same fields the GET returns plus the current revision. Preview wraps that configuration in config alongside sample fields, with no revision. It returns the selected recipients and reason without saving or sending. Rules cannot use reserved controls or the configured honeypot. Matching behavior and fallback recipients.
4. Configure an auto-reply
Read GET /v1/forms/{formId}/autoresponder. A null response means no configuration yet. Use revision: null on the first save, then the current revision on updates.
{
"revision": null,
"enabled": false,
"subject": "Thanks for your message",
"message": "Your note is with our team. We will reply to this address.",
"replyRecipientId": "11111111-1111-4111-8111-111111111111"
}Subject is 1–150 characters; message is 1–3,000 characters. Both are trimmed. replyRecipientId must be a verified workspace recipient. Start disabled while you review the copy.
{
"enabled": false,
"subject": "Thanks for your message",
"message": "Your note is with our team. We will reply to this address.",
"replyRecipientId": "11111111-1111-4111-8111-111111111111"
}The preview takes the same fields without a revision. It returns subject, replyTo and the rendered text with a placeholder opt-out link; it sends nothing.
Enable Turnstile before enabling visitor replies. Each visitor must explicitly consent through _formkoi_autoreply=true; eligibility, suppression and rate checks still apply. Consent and delivery rules.
5. Configure a webhook
{
"url": "https://hooks.example.com/formkoi",
"enabled": true
}Use a public HTTPS URL on port 443, at most 2,000 characters, with no credentials or fragment. Query parameters are supported. Creation returns 201 with a one-time signingSecret. Store it in your receiver’s secret store.
On updates, include the current revision from GET /v1/forms/{formId}/webhook. Updates return 200 without a signing secret. Changing configuration cancels queued events tied to the previous revision.
{
"revision": "33333333-3333-4333-8333-333333333333"
}Replace the sample revision with the current one. Rotation immediately invalidates the old secret, cancels its queued events and returns the new secret once.
{}No request body is needed. A 202 response includes deliveryId. Read its result under /v1/forms/{formId}/webhook/deliveries/{deliveryId}. Tests work even while an endpoint is disabled, at most once per minute and 100 per day.
Eligible failed webhook deliveries can be retried with a bare POST to the delivery’s /retry path. The event ID stays the same. Signatures, deduplication and retry conditions.
6. Manage messages
{
"folder": "archive",
"read": true
}Send folder, read, or both. Folder is inbox, archive or spam. The response includes fields and the notification plan; use GET for delivery history, attachments and the visitor-reply decision.
{
"acknowledgePossibleDuplicate": false
}Only failed or uncertain owner notifications can be retried, after one minute and outside spam. An uncertain send may already have arrived: use true only after accepting that a duplicate could be sent. Visitor replies cannot be manually resent.