Home

Docs

Quick start

Point a form at your endpoint:

POST https://api.submissionbuddy.io/f/{form_slug}

No API key, no SDK, no schema to register first. Send fields, they land in your dashboard.

Plain HTML:

<form action="https://api.submissionbuddy.io/f/your-form" method="POST">
  <input name="name" type="text" required />
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

JSON via fetch:

await fetch('https://api.submissionbuddy.io/f/your-form', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Ada', email: 'ada@example.com', message: 'Hi' }),
})

Both work against the same endpoint — pick whichever fits your form.

Using an AI assistant?

SubmissionBuddy publishes /llms.txt — a machine-readable summary of the endpoint, control fields, and limits that agents and AI coding tools can read directly.

If you're using an AI coding tool (Cursor, Copilot, Claude, etc.), you can hand it this prompt as-is:

Add a contact form to my site posting to my SubmissionBuddy endpoint https://api.submissionbuddy.io/f/YOUR-FORM — plain HTML with a _honeypot field and a _redirect to /thanks/.

Swap YOUR-FORM for your form's slug and it should produce working markup.

Endpoint reference

POST /f/{form_slug}

Accepted content types

Any other content type, an empty body, or a multipart request with no boundary returns 400.

Limits

Limit Value
Raw request body 1 MB
Fields per submission 100
Characters per field value 10,000

There is no field schema beyond these limits — send whatever fields your form has, add or remove fields without reconfiguring anything.

Success responses

Error responses

All error bodies look like {"ok": false, "error": "<message>"}.

Status When it fires
400 Body exceeds 1 MB
400 Unsupported or missing content type, empty body, or missing multipart boundary
400 Malformed request body (bad base64 or invalid JSON)
404 Form slug doesn't exist
422 Payload fails validation — too large after parsing, too many fields, or a field value too long
422 CAPTCHA is in Enforce mode and the token is missing — "CAPTCHA token required."
422 CAPTCHA is in Enforce mode and the token is invalid — "CAPTCHA validation failed."
429 Per-form rate limit exceeded — "Rate limit exceeded."
429 Global per-IP rate limit exceeded
429 Monthly plan quota exceeded — "Monthly submission limit reached for this form."

A mismatched request origin never produces an error — it only feeds the risk score (see Spam protection below).

Control fields

These field names are reserved. If present in your request, they're read as instructions and stripped out before the submission is stored — they never show up in your dashboard data.

Field Purpose
_redirect Send a browser here after a successful submission instead of returning JSON. Triggers a 303 with a Location header. Works even when a honeypot silently fakes success.
_honeypot Your form's honeypot field, named whatever your form is configured to use. See Spam protection.
_captcha_token (or altcha, the widget's default field name) The CAPTCHA proof-of-work token, if CAPTCHA is enabled on the form.

Rate limits

Two limits apply, both on a fixed 1-minute window:

Exceeding either returns:

{ "ok": false, "error": "Rate limit exceeded." }

with status 429.

Spam protection

Honeypot. Add a hidden field to your form (the field name is configurable per form). Real visitors never fill it in; bots that auto-fill every field do. If it's filled, the submission is discarded — nothing is written to storage, nothing is queued — but the response looks like a normal success (202 with a submission ID, or a 303 redirect if _redirect is set). The bot has no way to tell it failed.

CAPTCHA. SubmissionBuddy uses a self-hosted, invisible proof-of-work CAPTCHA (ALTCHA) — not a third-party service. The browser solves a small cryptographic puzzle and submits a token; verification happens entirely on our servers with no calls out to a third party and no cookies set. It has three modes, set per form:

Mode Behavior
Off No CAPTCHA logic runs.
Monitor Token is verified if present and recorded (verified / invalid / missing), but never blocks a submission. A failed or missing token adds a risk-score flag.
Enforce A missing or invalid token is rejected with 422.

CAPTCHA is available on Pro and Business plans.

Risk scoring. Every submission gets an informational risk score, visible in the dashboard, built from:

The score never blocks a submission by itself — it's a signal for you to triage borderline cases, not an enforcement mechanism.

What happens to a submission

  1. Your request is validated and passes spam checks.
  2. The raw payload is written to durable storage before you get a response. A 202 (or 303) is only returned after this write succeeds.
  3. A message referencing that stored payload — not the payload itself — is placed on a processing queue.
  4. A worker picks up the message and writes the submission into the database that backs your dashboard. Processing is idempotent: a redelivered message doesn't create a duplicate record.
  5. If a stage fails, it retries automatically rather than dropping the submission.
  6. A notification email is sent to the form's configured recipients (if any). On the Business plan, an auto-response to the submitter can also be sent.
  7. The processed record in your dashboard is kept indefinitely — it doesn't expire on its own. The raw payload copy in object storage is retained for 30 days, then deleted.

Plans & quotas

Plan Price Submissions/mo Forms CAPTCHA Auto-response Team
Free $0 100 2 no no no
Pro $8/mo 1,000 unlimited yes no no
Business $24/mo 20,000 unlimited yes yes yes

Going over your monthly submission quota returns 429 with "Monthly submission limit reached for this form." at the ingestion endpoint. Dashboard actions gated by plan (adding a form past your cap, inviting a team member, enabling auto-response or CAPTCHA) return 402 if your plan doesn't include them.

Dashboard

CORS

The ingestion endpoint accepts cross-origin POST requests from any origin — there's no allowlist to configure. Browser preflight (OPTIONS) requests are answered automatically, so a plain fetch() from any site works without extra setup.


Ready to try it? Start free — 100 submissions a month, no credit card.