Documentation

Everything you need to send submissions to Intake and get them out again.

Sending submissions

Each form has its own address, shown on its Install tab:

POST https://intake.starstorm.dev/api/form/<form id>/submission

It accepts three kinds of body:

<form action="https://intake.starstorm.dev/api/form/<form id>/submission" method="POST">
  <input name="email" type="email" required>
  <textarea name="message"></textarea>
  <!-- the honeypot: hide it with CSS; bots fill it in -->
  <input name="fc_honeypot" style="display:none" tabindex="-1" autocomplete="off">
  <button>Send</button>
</form>
const res = await fetch("https://intake.starstorm.dev/api/form/<form id>/submission", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "ada@example.com", message: "Hello" }),
});
const submission = await res.json(); // { id, formId, data, domain, createdAt, ... }

Errors

JSON callers get an HTTP status and {"message": "...", "name": "..."}:

Over your monthly limit, submissions are held, not refused: the answer is 200 with "held": true, and the submission appears once the quota allows.

Spam protection

Hosted pages include it automatically. On your own pages, add the honeypot above, and turn on proof of work under the form's spam rules, then mark the form and load the script:

<form action="https://intake.starstorm.dev/api/form/<form id>/submission" method="POST"
      data-intake-pow="https://intake.starstorm.dev/api/form/<form id>/challenge">
  ...
</form>
<script src="https://intake.starstorm.dev/pow.js" defer></script>
<!-- with fetch(): body.altcha = await IntakePow.solve(challengeUrl) -->

Anything caught is stored as spam with the reason, never dropped, and can be released from the inbox.

Opening, closing and results

Under the form's Settings, set when it opens and closes, and how many responses it takes. Outside those times, or once it is full, posts get 403 with "name": "formClosedError" and a message you can show as it is; the hosted page shows it instead of the form. Spam does not take a place.

The Results tab counts the answers to choice fields (select and checkbox under Expected fields), with percentages. For encrypted forms the counting happens in your browser after you unlock the vault.

Anonymous follow-up

For whistleblowing and other reports where people stay anonymous but need an answer. Turn on Settings → Anonymous follow-up and each person who submits is given a follow-up code, once. At https://intake.starstorm.dev/f/<form id>/status they enter it to read your replies and answer, with no account or email address. You reply from the submission on the Submissions tab; they see "The team", not your name.

Browser posts show the code on Intake's thank-you page (with a link on to your own). With fetch(), the response has "followUpCode": show it to the person, and do not store or log it. Intake keeps only a fingerprint, so a lost code cannot be recovered. Not yet available on end-to-end encrypted forms.

Your team is emailed when a reporter replies, and reminded daily about reports with no reply after 5 days or no update for 80 days, in line with the EU Whistleblowing Directive's 7-day acknowledgement and 3-month feedback. The emails never include what anyone wrote.

API

Create a key under API keys and send it in the Authorization header. Keys can be limited to one form and to these scopes:

curl -H "Authorization: <key>" "https://intake.starstorm.dev/public/v1/form?limit=10"
curl -H "Authorization: <key>" "https://intake.starstorm.dev/public/v1/form/<form id>/submission?getSpam=true"

The full reference is an OpenAPI 3.1 description at /public/v1/openapi.json; import it into Postman, Insomnia or your code generator. Errors use the same {"message", "name"} shape as above.

AI agents (MCP)

Intake is also an MCP server, so an agent such as Claude can set up forms and actions for you:

claude mcp add --transport http intake https://intake.starstorm.dev/mcp --header "Authorization: Bearer <key>"

Give the agent a key with forms:write. Add submissions:read only if it should read what visitors typed: that text is untrusted and could try to instruct the agent. Encrypted forms cannot be read by agents at all.

Next