Furrow Forms

Documentation

Quickstart

Every form gets a public submit URL. Point a <form> at it — no server code, no JS required.

contact.html
[ 3 lines ]
<form action="https://api.furrowforms.com/s/<public_key>" method="POST">
  <input name="email" type="email" required />
  <button>Send</button>
</form>

The submit endpoint

  • [ POST ]https://api.furrowforms.com/s/<public_key> accepts JSON, urlencoded, and multipart bodies.
  • [ 303 ]Classic HTML POSTs redirect to your configured thank-you page. fetch() calls get { "ok": true, "id": "…" } back.
  • [ FILES ]File uploads are opt-in per project and inherited by every form. Use a normal file input over multipart — multi-file fields take the [] name suffix (name="resume[]"), and JSON bodies can't carry files. Accepted by default: PDF, JPEG, PNG, WebP. Files land in a private per-project inbox; notification emails and webhooks link to it.
  • [ KEY ]The public key is not a secret — it's safe in client-side HTML. Protection comes from the spam stack below.
  • [ CORS ]Browser submissions are accepted from your project's allowed domains. Add the site's domain to the project before testing from a browser.

Spam stack (on by default)

Four layers, no configuration required. Two are hidden fields the generated snippets already include: a honeypot (default _gotcha — keep it hidden and empty) and a timing field (_ft — a hidden input your page sets to Date.now() on load). Then Cloudflare Turnstile with project-level keys, a per-project domain allowlist, and per-IP per-form rate limiting (default 10 requests / 60 seconds), plus server-side filtering we don't document.

Caught submissions get a normal 200 so bots learn nothing, and are quarantined in a spam folder — no notification email, no webhook, and they never count toward your quota. Clients that don't send_ft (JSON, agents, curl) are not penalized.

The hierarchy

Team → client (a company) → project (one website) → forms. Security and routing — Turnstile keys, allowed domains, notification emails, webhook, rate limits — live on the project and cascade to every form under it. Per-form overrides are opt-in. Turnstile keys live on the project only: rotate once, every form follows.

Security

Turnstile and webhook secrets are encrypted at rest and never echoed by the API. Visitor IPs are stored only as HMAC hashes. API tokens carry scopes (read, write, or full) enforced on every REST call and MCP tool, and only a team owner in the dashboard can mint or revoke them — a leaked token can't re-mint itself. Webhook URLs must be public HTTPS endpoints (no private ranges, no redirects), and every delivery is HMAC-SHA256 signed with a timestamp for replay protection. Submission bodies are size-capped and sanitized before anything is stored.

Go deeper