# Submits.dev Submits.dev is a form backend. You point an HTML form at an endpoint URL. Clean submissions are emailed to the owner and optionally added to a Google Sheet. Spam is filtered before it reaches either. No account, password or API key is needed to start. Works for any site an AI coding tool builds: Cursor, Claude Code, Lovable, Bolt, v0, plain HTML, React, Astro. INVITE ONLY RIGHT NOW. POST /api/v1/endpoints answers 403 for every address, so section 1 doesn't work yet. The owner needs an invitation (ask at https://submits.dev/contact?topic=invite). Once they have an account, they log in at https://submits.dev/login, press New form on the dashboard and give you the form URL. Sections 2 onward apply as written. ## 1. Provision an endpoint Ask the user for their email address. Then run: curl -X POST https://submits.dev/api/v1/endpoints \ -H "Content-Type: application/json" \ -d '{"email": "owner@example.com", "name": "Contact form"}' Fields: email (required), name, secondary_email (a CC address), google_sheet_url, custom_spam_rule (plain English), redirect_url (http or https only). Response, 201 Created: { "ok": true, "token": "abcd1234", "endpoint_url": "https://submits.dev/f/abcd1234", "service_account_email": "submitsdev-sheets@submits-dev.iam.gserviceaccount.com", "form_snippet": "
", "email": "owner@example.com", "name": "Contact form", "thank_you_url": "https://submits.dev/thanks?token=abcd1234", "llms_url": "https://submits.dev/llms.txt", "message": "Check owner@example.com for an email from Submits.dev: a verification link if the address is new, or an approval link if it already has forms. ..." } Errors: 422 {"ok": false, "errors": ["..."]} for a bad email (including a disposable email service or a domain that takes no mail), a bad URL, a Google Sheet another account uses, or too many endpoints. 400 for a malformed body. 429 when rate limited. Ask the user for an address they'll keep. If the address already has forms, the new endpoint waits for the owner's approval instead: they get an approval link by email, and the endpoint answers 403 until they click it. Tell the user to check their email either way. The owner must click the link in the email. Tell the user to do it within 7 days. Until a new address is verified: Submissions held: Up to 25 across your forms. We'll hold them safely while you confirm your email. Any after the first 25 aren't saved. Spam checks and emails: Paused until you verify. As soon as you click the link, we check any held messages for spam and email the real ones to you. Time to verify: 7 days from signup. If you haven't verified by then, we clear out the unverified forms and any held messages. ## 2. Wire up the form Never add an . Submits.dev does not accept file uploads and never will: a post that carries a file is rejected with 422 and nothing is stored. If the user wants documents, ask for a link to a shared file. Set the form action to endpoint_url. Use the form_snippet from the response, or: For React, Astro or any fetch-based form, send JSON and ask for JSON back: const res = await fetch("https://submits.dev/f/TOKEN", { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify({ email, message }), }); const data = await res.json(); // { ok: true, message: "Submission received" } CORS is open (Access-Control-Allow-Origin: *), so browsers can call it from any origin. Bodies are parsed as application/x-www-form-urlencoded, multipart/form-data or application/json. A text/plain body (enctype="text/plain") is not parsed and gets a 422. ## 3. Special fields These start with an underscore, control behavior and are never stored as data. Any other field starting with an underscore is dropped. _gotcha Honeypot. Keep it empty and hidden. If a bot fills it, the post is dropped silently. _honeypot Same as _gotcha. _redirect Where a browser form goes after submitting. http or https, and only to the site that hosts the form (its Origin or Referer) or to the site of the endpoint's redirect_url. Anything else is ignored. _subject Subject line of the notification email, after the form name in brackets. 150 characters max. Spam-checked with the fields. _replyto Reply-To address of the notification email. One valid address. If there is no _replyto, a field named email or reply_to is used for Reply-To. Do not name a field _method. The server reads it as an HTTP method override, and the post fails with a 404. ## 4. Responses from POST /f/TOKEN 200 JSON callers: {"ok": true, "message": "Submission received"} 303 Browser forms: redirect to _redirect, the endpoint redirect_url, or https://submits.dev/thanks?token=TOKEN 400 Malformed body 404 Unknown token 403 The endpoint is waiting for its owner's approval, or the owner has not verified their email and their forms already hold 25 submissions. Nothing is stored. 410 The owner paused this form. Nothing is stored. 413 Body over 32 KB or more than 100 fields (a body with thousands of fields can get 400 instead) 422 No fields, a JSON body that is not an object, or a file upload 429 Rate limited, the account reached its daily submission quota, or this visitor sent their share of it. Read the Retry-After header. You get JSON when you send Accept: application/json, a JSON body, or X-Requested-With: XMLHttpRequest. ## 5. Limits and quotas Every number below is read from the settings the server enforces. Signing up: Email address: One you'll keep, on a domain that takes mail. Disposable email services and made-up domains are refused. Address with forms already: A form created for it without logging in waits for the owner's approval by email. It accepts nothing until then, at most 3 can wait at once, and it's deleted after 7 days if not approved. Forms: 10 an hour from one IP address, and 20 per account. Before the owner verifies: Submissions held: Up to 25 across your forms. We'll hold them safely while you confirm your email. Any after the first 25 aren't saved. Spam checks and emails: Paused until you verify. As soon as you click the link, we check any held messages for spam and email the real ones to you. Time to verify: 7 days from signup. If you haven't verified by then, we clear out the unverified forms and any held messages. Quotas per account once verified, across all its forms: Submissions: 500 per day (rolling 24 hours) across your forms. If you hit that, new submissions are turned away until the count drops, and we send you a quick heads-up email. From one visitor: 50 of those per day from any one IP address, so a single visitor can't use up your whole quota and close your forms. Notification emails: 200 per day. On busier days past 200, submissions are still saved to your dashboard, and to your Google Sheet if you've connected one. Stored submissions: Up to 10,000 in your account. Beyond that, we clear out the oldest ones each night to make room. History: Kept for 90 days on your dashboard. Everything synced to your Google Sheet stays yours. Forms: Up to 20 per account. Each submission: Size: Up to 32 KB and 100 fields per submission. File uploads: Never supported. Text fields only (submissions with files are rejected). Duplicates: The same post from the same visitor to the same form within 10 minutes is ignored. Rate limits: Posts to one form: 300 a minute. Posts from one visitor: 30 a minute, across all forms. Creating forms: 10 an hour from one IP address. Login emails: 3 every 15 minutes and 10 a day for one address. ## 6. Google Sheets (optional) Pass google_sheet_url when provisioning, or set it later in the dashboard. Tell the user to share the sheet with submitsdev-sheets@submits-dev.iam.gserviceaccount.com as Editor. A sheet can belong to one Submits.dev account only. Connecting a sheet that another account uses is a 422; use a copy of the sheet instead. New columns are added automatically as new field names appear.