Get a form — or an agent inbox — working in 30 seconds.
One API for everything your agent sends, receives, and tests: headless
forms, agent mailboxes (send, receive, reply, read OTP
codes), and disposable test inboxes for CI. Forms are the fastest
start — create one, copy the endpoint URL, point an HTML action= at it.
Everything below is the long tail, including agent mail.
- Base URL
-
https://login.ollastack.com— every/api/…path on this page is relative to it. Note: the bareollastack.comis the marketing site; the API lives on login.ollastack.com. - Authentication
-
Authorization: Bearer <your-api-key>— create a key in Dashboard → Settings → API tokens (shown once at creation). Anonymous form submits to/api/submit/<slug>need no key. - Discover every endpoint
-
GET https://login.ollastack.com/api/openapi.json— the full machine-readable contract (OpenAPI 3.1, public + CORS-open). Itsservers[]block names the canonical host, so an agent or MCP tool can self-configure from it.
All APIs, by what you're trying to do
One account, one token system. Every surface is published in the OpenAPI spec so agents can discover it. Pick your purpose:
| I want to… | API / endpoints | Guide |
|---|---|---|
| Collect form submissions | POST /api/submit/<slug> | Quickstart ↓ |
| Create & configure forms via API | /api/forms, /api/forms/{id}/builder | Forms API |
| Read / export submissions | /api/submissions, /api/forms/{id}/export | Exports ↓ |
| Let an AI agent submit forms | Bearer token, agent-tagged | For agents → |
| Give an agent an email identity (chosen address, spam-filtered, send / receive / reply) | /api/mailboxes/check-handle, /api/mailboxes, …/send, …/wait | Agent Mail → |
| Test emails / OTPs in CI (disposable inbox) | /api/mailboxes/{id}/wait | Agent Mail → |
| Manage tokens & scopes | /api/user/tokens | API tokens ↓ |
| Auto-discover the whole API (MCP / tool-use) | GET /api/openapi.json | OpenAPI ↓ |
Quickstart
- Sign up at login.ollastack.com/register and verify your email.
- Click Create form, name it, copy the endpoint URL — it looks like
https://login.ollastack.com/api/submit/<slug>. - Submit to it from anywhere. That's it.
HTML form
<form action="https://login.ollastack.com/api/submit/contact" method="POST"> <input name="email" type="email" required> <textarea name="message" required></textarea> <input type="text" name="_gotcha" style="display:none"> <button>Send</button> </form>
_gotcha is the honeypot — leave it empty in your real form, bots will fill it.
React
A typed @ollastack/react hook (useForm) is publishing to npm soon. Until then the HTML and fetch/curl approaches need no install and work everywhere. Preview of the hook:
import { useForm, ValidationError } from "@ollastack/react"; export function Contact() { const [state, handleSubmit] = useForm("contact"); if (state.succeeded) return <p>Thanks!</p>; return ( <form onSubmit={handleSubmit}> <input name="email" type="email" required/> <ValidationError errors={state.errors} field="email"/> <button disabled={state.submitting}>Send</button> </form> ); }
Field errors come back as state.errors.fieldErrors — keyed by your form's field IDs.
fetch / curl
curl -X POST https://login.ollastack.com/api/submit/contact \ -H "Content-Type: application/json" \ -d '{"email":"ada@example.com","message":"hi"}'
JSON and application/x-www-form-urlencoded are both accepted. Success returns 201 Created with {"success":true,"id":"sub_…","next":<url|null>}.
Hosted form page
Don't want to build your own UI? Every form gets a Ollastack-hosted page at
login.ollastack.com/f/<slug> — rendered server-side,
with your form's title, fields, and (on Starter+) your custom CSS. Successful
submits route to /thanks/<slug> or a redirect you configure.
Magic fields
Drop these special fields into your payload and we'll route or label things automatically.
| field | behavior |
|---|---|
_replyto / email | Reply-To on the notification + autoresponder target. |
_subject | Overrides the notification subject. |
_next | Override the success-redirect URL. Must be on an allowed origin or your form's host. |
_gotcha | Honeypot. Leave blank. Filled = bot. |
_cc / _bcc are not read from public payloads — that would let anonymous senders use your form as an email relay. Configure default CC/BCC in the form settings instead.
Notification recipients
Each form sends its submission notifications to a set of recipient
addresses — a primary notifyEmail plus optional CC and
BCC, configured in the form's settings. Two rules apply:
| plan | max recipients (To + CC + BCC combined) |
|---|---|
| Free | 2 |
| Paid | 4 |
Verified-email requirement. A form may only send
notifications to an address that is verified on the account that owns
the form. You must verify your own account email before you can set
any notification recipient, and every recipient must be that verified
address. This prevents a form from being pointed at a stranger's
inbox (a mail-bomb vector) and protects sender deliverability.
Setting an unverified or third-party address returns
400 VALIDATION_ERROR.
CAPTCHA
CAPTCHA protection has two layers. The provider is set
once for the whole platform (Cloudflare Turnstile is the active
provider). The per-form switch — captchaRequired,
toggled in the dashboard — decides whether a given form actually
enforces it. With the switch off, a form skips CAPTCHA entirely even
though a provider is configured.
When captchaRequired is on, every browser submission must
carry a valid CAPTCHA token or it's rejected with
403 AUTHORIZATION_ERROR. Add the Turnstile widget to your
form and pass its token via the X-Captcha-Token header or a
_captcha field in the payload.
<form action="https://login.ollastack.com/api/submit/contact" method="POST"> <input name="email" type="email" required> <textarea name="message" required></textarea> <!-- Turnstile renders a hidden input named cf-turnstile-response --> <div class="cf-turnstile" data-sitekey="0x4AAAAAADT1xrK0ChtVDEbe"></div> <button>Send</button> </form> <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
Turnstile writes a hidden cf-turnstile-response input into
the form; on a normal POST it's submitted automatically and
read as the _captcha token — no extra wiring needed. For
fetch/JSON submits, read the token yourself and send it in
the X-Captcha-Token header. The site key above is the
public Ollastack Turnstile key; the secret key never leaves the server.
Allowed origins
Set allowedOrigins on the form (comma-separated list, e.g. https://example.com,https://www.example.com) to lock the endpoint to those origins. Browser requests from other origins get 403 ORIGIN_NOT_ALLOWED. Authorized agent requests follow a different origin model — see /docs/agents.
Schema validation
Every field you add in the form builder has a type and required/optional flag. The submit endpoint validates the payload before saving — missing required fields, wrong types, or out-of-range values come back as 400 VALIDATION_ERROR with error.fieldErrors mapping each field id to a list of messages.
Multi-step forms + show-if conditional logic
On Starter+, the form builder supports step-based layouts and conditional visibility:
each field can have a showIf rule that references the value of another field
(equals, not-equals, contains). Hidden fields are
not validated and not stored on the submission.
Autoresponder
Toggle the autoresponder on the form's settings page and write a template
with {{field_name}} placeholders. When a submission's
payload contains a recognizable email field (_replyto, email,
etc.), we send the rendered template back to that address automatically.
Template variables are HTML-escaped — safe to interpolate user input.
Webhooks
Add one or more webhook URLs on the form's settings page. Every successful,
non-spam submission POSTs the payload with an
X-Ollastack-Signature header (HMAC-SHA256 of the raw body, hex).
Failed deliveries retry with exponential backoff up to 5 attempts; the
dashboard shows full delivery history per webhook. Toggle a webhook on/off
without losing its config.
crypto .createHmac("sha256", secret) .update(rawBody) .digest("hex"); // === request.headers["x-ollastack-signature"]
Third-party integrations
Per-form, attach one or more of these. Each runs alongside webhooks (not instead of them).
| type | config |
|---|---|
slack | Incoming-webhook URL (must be HTTPS). Posts a formatted submission card. |
discord | Incoming-webhook URL. Same payload format. |
telegram | botToken + chatId. DMs or channel post. |
mailchimp | apiKey + audienceId + dataCenter (e.g. us19). Adds the email field to your list. |
Need Gmail, Notion, Airtable, HubSpot, Sheets, or another 1,000+ destinations? Wire your webhook (above) into Zapier, Make, n8n, or Activepieces — they bring the destination, you bring the signed payload. Full walkthrough at /docs/integrations.
Exports
GET /api/forms/<formId>/export?format=csv (or json,
excel). Optional from and to ISO dates filter
by submitted-at. Returns a file attachment named after the form's slug.
Same data is available paginated via GET /api/forms/<formId>/submissions.
Analytics
GET /api/forms/<formId>/analytics?days=30 returns
{timeseries, spam, geo, days}: daily submission counts, spam-vs-total ratio,
and country breakdown. The dashboard renders these as charts on each form's detail page.
API tokens + audit log
Create personal API tokens in Dashboard → Settings → API tokens.
Tokens carry scopes (forms:read, forms:write, submissions:read,
submissions:write) and are shown only once at creation. Use them as
Authorization: Bearer <your-token>.
Every Bearer-authenticated API call writes a row to the audit log
(method, path, status, hashed IP, user-agent, timestamp). Expand
view activity on any token row to see its last 50 calls.
Per-token slide-window rate limit is 300 req/min — exceed it and you get
429 RATE_LIMIT_EXCEEDED with retryAfter.
AI agents
Ollastack treats AI agents as first-class clients. Authorize an agent in Dashboard → Settings → API tokens, give it a label, and your agent can submit cleanly while bots stay blocked. Submissions are tagged in the inbox with the agent's label. Each agent is scoped to your account — there's no path to use it against another account's forms. Full walkthrough at /docs/agents.
Custom SMTP
Per organization, configure your own SMTP server in Dashboard → Orgs. Submission notifications, autoresponders, and digests route through your SMTP — emails arrive from your domain. Credentials are encrypted at rest; if your SMTP server fails, we transparently fall back to the platform sender so notifications never silently drop. Full walkthrough at /docs/smtp.
OpenAPI 3.1 spec
The full machine-readable contract lives at login.ollastack.com/api/openapi.json — public and CORS-open so agent frameworks can consume it directly. Both API token and session authentication schemes are documented, and every endpoint declares its error shape and required scopes.
Error envelope
Every error returns the same shape:
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid input", "fieldErrors": { "email": ["Invalid email"] }, // optional "retryAfter": 60 // optional, seconds, on 429 } }
| status | code | meaning |
|---|---|---|
400 | VALIDATION_ERROR | fieldErrors maps field id → list of messages. |
401 | AUTHENTICATION_ERROR | Session missing/expired, or Bearer invalid. |
403 | ORIGIN_NOT_ALLOWED / AUTHORIZATION_ERROR | Origin not on the form's allowlist, or token missing required scope. |
404 | NOT_FOUND | Form/submission/token doesn't exist or isn't owned by caller. |
409 | CONFLICT | e.g. registering an email that already exists. |
429 | RATE_LIMIT_EXCEEDED | Per-IP, per-form, per-token, or org quota exceeded. retryAfter in seconds. |
500 | INTERNAL_ERROR | Server-side bug. We log and alert. |
Rate limits
Ollastack has four independent rate limits. Each emits 429 RATE_LIMIT_EXCEEDED with a retryAfter seconds field.
| scope | applies to | typical |
|---|---|---|
| Per-IP | Anonymous submits + login + password-reset | tight (seconds) |
| Per-form | Submits to one form (any caller) | tight (per minute) |
| Per-token | Any Bearer-authed API call | 300 / minute |
| Per-org / month | Counted submissions (post-spam-filter) | your plan's quota |
Agents (Bearer-authed submits) skip the per-IP bucket — they typically share an outbound IP with the agent host — but per-form, per-token, and per-org quotas still apply.
Need something not documented? Email hello@ollastack.com or check the OpenAPI spec.