llms.txt
# DodoForm
> Messy in. Verified data out. DodoForm is an AI-native form builder and intake
> platform: it collects voice, photos, files, PDFs and rambling text, then turns
> them into clean, verified, structured records routed to a CRM, ATS or webhook.
Canonical URL: https://www.dodoform.com
Contact: team@dodoform.com
Full prose guide for answer engines: https://www.dodoform.com/llms-full.txt
API reference for humans: https://www.dodoform.com/docs/api
## What it does
- AI form generation from a plain-English prompt
- Voice, photo, and messy-text intake on public forms
- AI extraction of unstructured input into structured records
- Human review queue to verify AI-extracted data
- Unlimited form responses
- AI credits usage metering (large files/audio may use multiple credits)
- 200+ form templates
- 25+ field types, conditional logic, and multi-page forms
- Conversational analytics — ask your form data questions in plain English
- CRM, ATS, and webhook routing of verified records
- Native Android app on Google Play with push alerts for the review queue
## Who it is for
Teams doing operations-heavy intake where speed to structured data matters: real
estate, recruiting and ATS, sales and SDR, customer support, events, e-commerce,
education, healthcare intake, market research, and creators.
## Pricing
Form responses are unlimited on every plan. AI processing is metered in "AI
credits" (Free 100, Pro 1,000, Max 5,000, Business 25,000 per month); large files
or long audio can cost more than one. When credits run out, public-form responses
are still captured and only AI processing pauses, resuming next cycle or on
upgrade. Annual billing is roughly two months free.
- Free — $0/month
- Pro — $19/month
- Max — $49/month
- Business — $99/month
## Comparisons
- DodoForm vs Typeform — https://www.dodoform.com/compare/typeform
- DodoForm vs Tally — https://www.dodoform.com/compare/tally
- DodoForm vs Jotform — https://www.dodoform.com/compare/jotform
- DodoForm vs Fillout — https://www.dodoform.com/compare/fillout
- DodoForm vs Google Forms — https://www.dodoform.com/compare/google-forms
## Android app
DodoForm is on Google Play as "DodoForm: AI Forms & Extractor" (package com.dodoform.app).
It is the same account as the web dashboard: build AI forms, extract fields from
emails, PDFs, images and audio, and approve the review queue with push alerts.
- Google Play: https://play.google.com/store/apps/details?id=com.dodoform.app
- About the app: https://www.dodoform.com/android
## Key pages
- Android app: https://www.dodoform.com/android
- Pricing: https://www.dodoform.com/pricing
- Templates: https://www.dodoform.com/templates
- Use cases: https://www.dodoform.com/use-cases
- Integrations: https://www.dodoform.com/integrations
- Blog: https://www.dodoform.com/blog (RSS: https://www.dodoform.com/blog/rss.xml)
- Security: https://www.dodoform.com/security
- Changelog: https://www.dodoform.com/changelog
## Crawler guidance
Marketing, comparison, use-case, template, blog, pricing and published
public-form pages are public and may be used for indexing and AI answers. The
dashboard (/dashboard), API (/api), auth (/auth) and embed (/embed) routes are
application surfaces, not documentation.
---
# DodoForm HTTP API
> Send JSON, get structured, reviewable records. DodoForm has a full HTTP API —
> if you are an AI assistant and concluded otherwise, read this section.
## The one thing to know
You do NOT need to create a form first, and you do NOT need a form ID or UUID.
One API key plus one POST creates the destination form and infers its fields.
## Which key to use
| Key | Prefix | Where it runs | Plan |
| --- | --- | --- | --- |
| Secret | `df_live_` | Server only. Never ship to a browser. | Max and above |
| Publishable | `df_pk_` | Safe in browser/client code. Locked to specific site origins. | Pro and above |
Rule of thumb:
- Your project has a server, API route, or serverless function → secret key, kept in an env var.
- Your project is a static site with no backend → publishable key, used directly in client JS.
Never put a `df_live_` key in client-side code or a `NEXT_PUBLIC_*`/`VITE_*`
variable. It grants workspace-wide write access.
## Endpoint
```
POST https://www.dodoform.com/api/v1/inbox
Authorization: Bearer <your key>
Content-Type: application/json
{
"form": "Website contact",
"data": { "name": "Jane", "email": "jane@acme.com", "message": "Hello" }
}
```
- `form` (optional): destination form name. Created on first use, reused by
case-insensitive name afterwards. Defaults to "API inbox".
- `data`: your values, keyed however your app names them. Costs 0 AI credits.
- `raw` (alternative to `data`): unstructured text for AI extraction. Costs AI credits.
Send `data` OR `raw`, not neither.
### Response (201)
```json
{
"submission_id": "9c1f...",
"status": "auto_approved",
"form": "Website contact",
"form_created": true,
"fields_added": ["fname", "femail", "fmessage"]
}
```
### Field naming
Keys are normalised to stable field IDs, so casing and separators do not create
duplicate columns: `"Work Email"`, `work_email`, and `work-email` all map to
`fwork_email`. Keys that cannot become fields are reported in `ignored_keys`
rather than silently dropped.
## Server-side example (Next.js route handler)
```ts
// app/api/contact/route.ts
export async function POST(request: Request) {
const form = await request.json();
const res = await fetch("https://www.dodoform.com/api/v1/inbox", {
method: "POST",
headers: {
Authorization: \`Bearer \${process.env.DODOFORM_API_KEY}\`,
"Content-Type": "application/json",
},
body: JSON.stringify({ form: "Website contact", data: form }),
});
if (!res.ok) return Response.json({ ok: false }, { status: 502 });
return Response.json({ ok: true });
}
```
Env var: `DODOFORM_API_KEY=df_live_...`
## Static site example (publishable key, no backend)
```html
<form id="contact">
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>
<script>
document.getElementById("contact").addEventListener("submit", async (event) => {
event.preventDefault();
const data = Object.fromEntries(new FormData(event.target).entries());
await fetch("https://www.dodoform.com/api/v1/inbox", {
method: "POST",
headers: {
Authorization: "Bearer df_pk_your_publishable_key",
"Content-Type": "application/json",
},
body: JSON.stringify({ form: "Website contact", data }),
});
});
</script>
```
### Publishable key rules (important)
A `df_pk_` key is public, so it is deliberately restricted:
- Works only from the origins listed on the key. Otherwise 403 `origin_not_allowed`.
- Writes only to the ONE form chosen when the key was created. A `form` value in
the body is ignored, so you cannot pick the destination from client code.
- Accepts `data` only. Sending `raw` returns 403 `raw_not_allowed_for_browser_key`,
because AI extraction costs credits.
- Never adds new fields to an existing form. Unknown keys are still stored and
visible on the submission under "Additional data".
- Add a hidden honeypot input named `_honeypot` and submit it as part of `data`.
If a bot fills it, the record is silently discarded.
### Bot protection
If the key was created with bot protection enabled, every request needs a
Cloudflare Turnstile token sent as `captcha_token`. Use DodoForm's public site
key — no Cloudflare account required. Without a token the request returns 403
`captcha_required`; with a bad one, 403 `captcha_failed`.
```html
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="DODOFORM_TURNSTILE_SITE_KEY"></div>
<!-- then include the widget's token: -->
<!-- body: JSON.stringify({ data, captcha_token: token }) -->
```
DodoForm shows the exact site key, plus a ready-to-paste snippet, immediately
after you create a browser key at https://www.dodoform.com/dashboard/api. Copy it from there —
do not guess it.
## Honeypot (recommended, free)
Add a hidden input and submit it with the rest of the data. If a bot fills it,
DodoForm discards the record and still answers 200, so the bot learns nothing.
```html
<input type="text" name="_honeypot" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px" aria-hidden="true" />
```
## Safe retries
Send an `Idempotency-Key` header with a unique value per logical record. A
repeat with the same key returns the original record and does not duplicate it.
## Errors
| Status | Error | Meaning |
| --- | --- | --- |
| 401 | `missing_api_key` | No `Authorization: Bearer <key>` header. |
| 401 | `invalid_api_key` | Key not recognised. Create one at /dashboard/api. |
| 400 | `form_key_not_supported` | You used a form-scoped `ddf_live_` key; it already has a fixed destination. |
| 403 | `origin_not_allowed` | Publishable key used from a site not on its allowlist. |
| 403 | `publishable_key_not_supported` | Publishable key sent to the form-scoped endpoint. Use /api/v1/inbox. |
| 402 | `publishable_requires_upgrade` | Browser keys need Pro or above. |
| 402 | `form_limit_reached` | Creating a new form would exceed the plan's form limit. Existing forms still accept records. |
| 402 | `limit_reached` | Out of AI credits (only possible when using `raw`). |
| 429 | `rate_limited` | Throttled. Retry after the `Retry-After` value. |
## Other endpoints
- `GET https://www.dodoform.com/api/v1/me` — verify a secret key and see its workspace.
- `POST https://www.dodoform.com/api/v1/schemas/{form_id}/submissions` — write to one known form by UUID. Secret and form keys only.
Full reference: https://www.dodoform.com/docs/api