Documentation

Submitbox gives any HTML form a backend. You get an access key, point your form at the Submitbox endpoint, and we handle storage, email delivery, spam filtering, and more — no server required on your side.

Quick start

1. Create a free account and a form to get your access key.
2. Add the access key to a hidden field in your HTML form.
3. Set the form action to your submit endpoint. Done.

<form action="https://YOUR-API/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>
Your exact endpoint and access key are shown on the Setup & code tab of every form in your dashboard, with copy-paste snippets.

The endpoint

All submissions are sent to a single endpoint:

POST /submit
Content-Type: application/x-www-form-urlencoded | multipart/form-data | application/json

You may also post to POST /s/{access_key} if you prefer the key in the URL instead of a hidden field.

Form fields

Every field in your form (except reserved ones) is captured and included in the notification email and dashboard. Field names are arbitrary — name them whatever makes sense for your form.

Reserved fields

These field names control Submitbox behavior instead of being stored as data:

FieldPurpose
access_keyRequired. Identifies which form receives the submission.
redirectOverride the redirect URL for this submission.
subjectOverride the notification email subject.
from_nameOverride the sender display name.
botcheckHoneypot. Must stay empty — bots that fill it are silently rejected.
cf-turnstile-response / h-captcha-response / g-recaptcha-responseCAPTCHA tokens, verified server-side when a provider is configured.

AJAX / JSON submissions

Send JSON (or add Accept: application/json) to get a structured JSON response instead of a redirect — ideal for single-page apps.

const res = await fetch("https://YOUR-API/submit", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Accept": "application/json" },
  body: JSON.stringify({
    access_key: "YOUR_ACCESS_KEY",
    email: "jane@example.com",
    message: "Hello!"
  })
});
const data = await res.json();   // { success: true, message: "...", id: "..." }

File uploads

Files are stored encrypted and surfaced as expiring download links in your dashboard (and in the webhook payload). Attachments up to 25 MB each, 5 per submission.

Recommended: the drop-in script

Add one script tag and your forms handle file uploads automatically — any size up to 25 MB, in what looks like a single submit. No extra code, no size cliff:

<form action="https://submitbox.dev/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
  <input type="email" name="email">
  <input type="file" name="resume">          <!-- up to 25 MB -->
  <button type="submit">Apply</button>
</form>

<script src="https://submitbox.dev/submitbox.js" defer></script>

The script uploads files straight to storage (bypassing request-size limits) and submits the form referencing them — all transparently. Forms without files are left untouched. It dispatches submitbox:uploading, submitbox:success, and submitbox:error events, and honours a data-redirect="/thanks" attribute or a hidden redirect field.

Without the script

Plain multipart/form-data works with no JavaScript for attachments up to ~4 MB total (the inline request limit):

<form action="https://submitbox.dev/submit" method="POST" enctype="multipart/form-data">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
  <input type="file" name="resume">
  <button type="submit">Apply</button>
</form>

For larger files without our script, do the two-step presigned upload yourself: POST /upload-urlPUT the file to the returned URL → submit with submitbox_uploads: [{ key, filename, size }]. (That's exactly what the drop-in script automates for you.)

Uploads that are never referenced by a submission are automatically deleted within a day. A complete working example is on the example form.

Spam protection

Three layers, on by default:

<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off">

CAPTCHA

Enable Cloudflare Turnstile, hCaptcha, or reCAPTCHA in form settings and paste the provider's secret key. Include the widget in your form; Submitbox verifies the token server-side and rejects failures.

Auto-responder

Turn on the auto-responder in settings to automatically email the submitter a confirmation. Submitbox reads the recipient address from the field you specify (default email).

Webhooks

Set a webhook URL in settings and Submitbox will POST each submission as JSON to your endpoint. If you set a signing secret, requests include an X-Submitbox-Signature: sha256=… HMAC header so you can verify authenticity.

{
  "event": "submission.created",
  "accessKey": "...",
  "formName": "Contact form",
  "submission": { "id": "...", "data": { ... }, "createdAt": "..." }
}

Responses

StatusMeaning
200Submission accepted (JSON mode).
303Redirect to your thank-you page (non-AJAX mode).
400Bad request — missing fields or failed CAPTCHA.
403Origin not in the form's domain allowlist.
404Invalid or inactive access key.
429Rate limited — too many submissions.

Framework examples

React

async function onSubmit(e) {
  e.preventDefault();
  const form = new FormData(e.target);
  const res = await fetch("https://YOUR-API/submit", {
    method: "POST",
    headers: { Accept: "application/json" },
    body: form,
  });
  const data = await res.json();
  setSent(data.success);
}

Plain HTML (Netlify, GitHub Pages, Hugo, …)

Just the form from the quick start — no build step or JavaScript needed.

MCP server (for AI agents)

Submitbox runs a remote Model Context Protocol server so agents — Claude and any MCP-capable client — can manage your forms and read submissions directly. It speaks JSON-RPC 2.0 over the Streamable HTTP transport at /mcp.

There are two ways to connect:

Option A — OAuth (one click, no token to paste). Submitbox runs a full OAuth 2.1 authorization server with dynamic client registration and PKCE. In a client that supports remote MCP with OAuth (e.g. Claude's custom connectors), just enter the MCP URL (/mcp) — the client registers itself, you approve in the browser, and it's connected. Discovery is automatic via /.well-known/oauth-authorization-server.

Option B — manual bearer token. In the dashboard open Connect agents to copy your MCP endpoint and a long-lived bearer token (or mint one with POST /api/auth/mcp-token), then point your agent at the endpoint with the token as a bearer credential:

{
  "mcpServers": {
    "submitbox": {
      "type": "http",
      "url": "https://YOUR-HOST/mcp",
      "headers": { "Authorization": "Bearer YOUR_MCP_TOKEN" }
    }
  }
}

Tools exposed: list_forms, create_form, get_form, list_submissions, get_submission, create_submission, get_account_usage.

Unauthenticated requests receive 401 with a WWW-Authenticate header pointing at /.well-known/oauth-protected-resource, per the MCP authorization spec.

llms.txt & OpenAPI

For agent discoverability, Submitbox publishes a curated /llms.txt map and a machine-readable /openapi.json spec. Crawlers are welcome (see /robots.txt).

Create your access key →