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>
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:
| Field | Purpose |
|---|---|
access_key | Required. Identifies which form receives the submission. |
redirect | Override the redirect URL for this submission. |
subject | Override the notification email subject. |
from_name | Override the sender display name. |
botcheck | Honeypot. Must stay empty — bots that fill it are silently rejected. |
cf-turnstile-response / h-captcha-response / g-recaptcha-response | CAPTCHA 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-url → PUT the file to the returned URL →
submit with submitbox_uploads: [{ key, filename, size }]. (That's exactly what the drop-in
script automates for you.)
Spam protection
Three layers, on by default:
- Honeypot — add a hidden field named
botcheck. Real users leave it empty; bots fill it and get silently dropped. - Rate limiting — automatic per-IP and per-key throttling stops floods.
- Domain allowlist — lock a key to your own domains in form settings.
<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
| Status | Meaning |
|---|---|
| 200 | Submission accepted (JSON mode). |
| 303 | Redirect to your thank-you page (non-AJAX mode). |
| 400 | Bad request — missing fields or failed CAPTCHA. |
| 403 | Origin not in the form's domain allowlist. |
| 404 | Invalid or inactive access key. |
| 429 | Rate 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.
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).