The web runtime for AI agents. 5 capabilities. One API.
You send JSON, you deploy pages, collect data, and visualize results. Pages, forms, webhooks, embeds -- everything is an API call. Built for developers and AI agents.
Free API key, first form, under a minute. No signup.
curl -X POST https://sutrena.com/api/trialReturns an st_trial_ key with a 24-hour claim window. The response includes a claimUrl -- visit it in a browser to sign in and keep your data permanently.
curl -X POST https://sutrena.com/api/pipeline \
-H "Authorization: Bearer st_trial_xxx" \
-H "Content-Type: application/json" \
-d '{"name":"Waitlist","fields":[{"name":"email","label":"Email","type":"email","required":true}]}'One call. You get back a form with a submit URL. The response includes submitUrl and hostedFormUrl.
curl -X POST https://sutrena.com/api/forms \
-H "Authorization: Bearer st_trial_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Beta Signup",
"fields": [
{"name": "email", "label": "Email", "type": "email", "required": true},
{"name": "company", "label": "Company", "type": "text", "required": false},
{"name": "role", "label": "Role", "type": "select", "required": true, "options": ["Developer", "Designer", "PM", "Other"]}
]
}'curl -X POST https://sutrena.com/api/forms/FORM_ID/submit \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "company": "Acme", "role": "Developer"}'Submit is public -- no API key needed. CORS is on, so browsers can POST directly.
Three ways to authenticate. Pick one.
Authorization: Bearer YOUR_KEYPOST /api/trial to get an st_trial_ key. No expiry. 5 per 24 hours per IP.
Sign in with GitHub or Google, then create an st_live_ key via POST /api/keys (needs a session cookie) or from the dashboard settings. Never expires unless you revoke it.
OAuth sign-in sets a sutrena_session cookie. The dashboard and key management endpoints use this automatically.
CRUD for forms. Each one gets fields, a public submit URL, and a hosted page.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/forms | Create a new form |
| GET | /api/forms | List all your forms |
| GET | /api/forms/:id | Get form details |
| PUT | /api/forms/:id | Update a form |
| DELETE | /api/forms/:id | Delete a form |
Send name + fields. Or use POST /api/pipeline for form + webhooks in one call.
POST /api/forms
{
"name": "Contact Form",
"fields": [
{"name": "name", "label": "Name", "type": "text", "required": true},
{"name": "email", "label": "Email", "type": "email", "required": true},
{"name": "message", "label": "Message", "type": "textarea", "required": true}
],
"successMessage": "Thanks! We'll be in touch.",
"submitLabel": "Send",
"closesAt": "2026-12-31T23:59:59Z",
"uniqueBy": ["email"],
"maxSubmissions": 1000,
"publicResults": false,
"redirectUrl": "https://example.com/thanks",
"webhookIds": ["uuid-of-webhook"]
}| Type | Validation options | Notes |
|---|---|---|
| text | minLength, maxLength, pattern | General text input |
| minLength, maxLength | Validates email format | |
| textarea | minLength, maxLength | Multi-line text |
| number | min, max | Numeric value |
| select | options (required) | Dropdown, single choice |
| multiselect | options (required) | Multiple choice (value is string[]) |
| checkbox | -- | Boolean toggle |
| url | minLength, maxLength, pattern | Validates URL format |
| tel | minLength, maxLength, pattern | Phone number |
| date | -- | Date picker |
| hidden | -- | Not shown to user, sent with submission |
| file | accept, maxFileSize | File upload (max 50 MB per file) |
PUT /api/forms/:id
{
"fields": [...],
"successMessage": "Updated message",
"submitLabel": "Submit",
"closesAt": null,
"uniqueBy": null,
"maxSubmissions": null,
"publicResults": true,
"redirectUrl": null,
"webhookIds": ["uuid"]
}Everything is optional. Set a value to null to clear it.
Submit from browsers, mobile apps, or server-side code. The submit endpoint is public with CORS on.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/forms/:id/submit | Submit data (public, no auth, CORS enabled) |
| GET | /api/forms/:id/submissions | Search and list submissions (auth required) |
| DELETE | /api/forms/:id/submissions?email=X | GDPR deletion by email (Pro+) |
| GET | /api/forms/:id/export | CSV export (Pro+) |
| GET | /api/forms/:id/results | Public aggregated results (if enabled) |
POST JSON with field values as keys. No auth. Validated against the form's field definitions.
curl -X POST https://sutrena.com/api/forms/FORM_ID/submit \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "name": "Alice"}'Filter with query params. Cursor-based pagination.
| Parameter | Type | Description |
|---|---|---|
| search | string | Full-text search across all fields |
| field | string | Filter by a specific field name |
| value | string | Filter by field value (use with field) |
| from | ISO 8601 | Start date filter |
| to | ISO 8601 | End date filter |
| status | string | "clean" or "spam" |
| limit | number | Results per page (default varies) |
| cursor | string | Pagination cursor from previous response |
curl "https://sutrena.com/api/forms/FORM_ID/submissions?field=email&[email protected]&limit=10" \
-H "Authorization: Bearer YOUR_KEY"Deletes all submissions matching an email. Pro plan and above.
curl -X DELETE "https://sutrena.com/api/forms/FORM_ID/[email protected]" \
-H "Authorization: Bearer YOUR_KEY"All submissions as CSV. Pro plan and above.
curl "https://sutrena.com/api/forms/FORM_ID/export" \
-H "Authorization: Bearer YOUR_KEY" \
-o submissions.csvWhen publicResults: true, anyone can see aggregated counts for select, checkbox, and multiselect fields. No auth needed. Good for polls.
curl https://sutrena.com/api/forms/FORM_ID/resultsReturns total count and per-field breakdowns:
{
"total": 142,
"fields": {
"rating": [
{"value": "5", "count": 58},
{"value": "4", "count": 41},
{"value": "3", "count": 28},
{"value": "2", "count": 10},
{"value": "1", "count": 5}
]
}
}Two-step presigned URL flow. Get a URL, upload to it, include the object ID in your submission. 50 MB max per file.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/forms/:id/upload | Get a presigned upload URL (public, rate-limited) |
| GET | /api/forms/:id/files/:objectId | Download a file (302 redirect) |
curl -X POST https://sutrena.com/api/forms/FORM_ID/upload \
-H "Content-Type: application/json" \
-d '{
"filename": "resume.pdf",
"contentType": "application/pdf",
"fieldName": "resume"
}'Returns uploadUrl and objectId. The URL expires quickly, so upload right away.
curl -X PUT "UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @resume.pdfUse the objectId as the value for the file field.
curl -X POST https://sutrena.com/api/forms/FORM_ID/submit \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "[email protected]", "resume": "OBJECT_ID"}'Deploy static HTML pages via API. Served at /p/:slug with no auth needed to view. Pages deploy to a subdomain. You control which subdomain by passing subdomainId (optional).
curl -X POST https://sutrena.com/api/pages \
-H "Authorization: Bearer st_trial_xxx" \
-H "Content-Type: application/json" \
-d '{
"slug": "my-landing",
"title": "My Landing Page",
"html": "<h1>Welcome</h1><p>Sign up below.</p>",
"css": "body { font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 2rem; }"
}'Returns a pageUrl at /p/my-landing. No deploy step. If subdomainId is omitted, deploys to your first/default subdomain.
# Step 1: List all your subdomains
curl https://sutrena.com/api/account/subdomains \
-H "Authorization: Bearer st_trial_xxx"
# Step 2: Create page with subdomainId
curl -X POST https://sutrena.com/api/pages \
-H "Authorization: Bearer st_trial_xxx" \
-H "Content-Type: application/json" \
-d '{
"slug": "my-landing",
"title": "My Landing Page",
"html": "<h1>Welcome</h1>",
"subdomainId": "sub_xyz"
}'Workflow: List subdomains → pick one → create page with that subdomainId. Use this to deploy pages to different subdomains (e.g. alice.sutrena.com vs blog.sutrena.com).
Slug: lowercase alphanumeric + hyphens, 3-100 chars. HTML max: 512KB. CSS max: 128KB. Pages count toward the project pool. Free: 10 projects. Pro: 100. Scale: unlimited.
| Method | Path | Notes |
|---|---|---|
| POST | /api/pages | Create page |
| GET | /api/pages | List your pages |
| GET | /api/pages/:id | Page details with full HTML/CSS |
| PUT | /api/pages/:id | Update HTML/CSS/title (slug immutable) |
| DELETE | /api/pages/:id | Delete page |
Serve pages on your own subdomain. Available on all plans. Each subdomain is a separate site. You have full control over which subdomain each page deploys to.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/account/subdomains | List all subdomains with page counts |
| POST | /api/account/subdomains | Create a new subdomain |
| GET | /api/account/subdomain | Get default subdomain (deprecated) |
| PUT | /api/account/subdomain | Change default subdomain (deprecated) |
curl https://sutrena.com/api/account/subdomains \
-H "Authorization: Bearer YOUR_KEY"Returns all your subdomains with page counts. Use the id field when creating pages to specify which subdomain to deploy to.
curl -X POST https://sutrena.com/api/account/subdomains \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "blog"}'Creates blog.sutrena.com. Then use the returned id when creating pages to deploy them to this subdomain.
1. GET /api/account/subdomains — see all your subdomains
2. POST /api/account/subdomains — create new ones if needed
3. POST /api/pages with subdomainId — deploy to specific subdomain
4. If subdomainId is omitted, page deploys to your first/default subdomain
Example: alice.sutrena.com (personal site), blog.sutrena.com (blog), docs.sutrena.com (documentation) — all from one API key.
curl -X PUT https://sutrena.com/api/account/subdomain \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"subdomain": "alice"}'Pages are then available at alice.sutrena.com/slug. The root path (alice.sutrena.com/) serves the page with slug index. Note: This changes your default subdomain. Prefer using POST /api/account/subdomains + specifying subdomainId on pages for better control.
3-30 characters. Lowercase alphanumeric and hyphens only. Cannot start or end with a hyphen.
Point your own domain at Sutrena pages. Pro plan and above.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/account/domains | Add a domain (returns CNAME instructions) |
| GET | /api/account/domains | List domains with DNS/SSL status |
| GET | /api/account/domains/:id | Check domain status |
| DELETE | /api/account/domains/:id | Remove a domain |
curl -X POST https://sutrena.com/api/account/domains \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "pages.example.com"}'Returns CNAME instructions. Point your domain to cname.sutrena.com. SSL is provisioned automatically once DNS propagates.
Pro: 5 custom domains. Scale: unlimited.
Upload images, CSS, and other static files for your pages. Served from a CDN.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/pages/assets | Presign an upload (returns uploadUrl + publicUrl) |
| GET | /api/pages/assets | List assets (optional ?pageId= filter) |
| DELETE | /api/pages/assets/:id | Delete an asset |
Step 1: Get a presigned upload URL and a public CDN URL.
curl -X POST https://sutrena.com/api/pages/assets \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "hero.png", "contentType": "image/png"}'Returns uploadUrl and publicUrl. Upload the file to the presigned URL, then reference the public URL in your page HTML.
Step 2: Upload the file to the presigned URL.
curl -X PUT "UPLOAD_URL" \
-H "Content-Type: image/png" \
--data-binary @hero.png| Plan | Max file size | Total storage |
|---|---|---|
| Free | 2 MB | 20 MB |
| Pro | 20 MB | 2 GB |
| Scale | 50 MB | Unlimited |
Insert or update a submission by external ID. Pro plan and above.
curl -X PUT https://sutrena.com/api/forms/FORM_ID/submissions/upsert \
-H "Authorization: Bearer st_live_xxx" \
-H "Content-Type: application/json" \
-d '{"externalId": "customer-123", "payload": {"name": "Jane", "score": 95}}'
# First call: { "data": submission, "created": true }
# Second call (same externalId): { "data": submission, "created": false }externalId is unique per form. Use for CRM sync, inventory tracking, leaderboards, or any data that changes over time. Free plan returns 403 with upgrade hint.
Three high-level endpoints that compose multiple primitives into single calls. Deploy a full site, spin up a data collection pipeline, or get an account snapshot -- each in one request.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/launch | Deploy pages + analytics in one call |
| POST | /api/pipeline | Create form + webhooks in one call |
| GET | /api/status | Unified account snapshot |
Deploy one or more pages and optionally create an analytics site in a single call.
curl -X POST https://sutrena.com/api/launch -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" -d '{
"subdomain": "my-site",
"pages": [
{"slug": "index", "title": "Home", "html": "<h1>Welcome</h1>"},
{"slug": "about", "title": "About", "html": "<h1>About Us</h1>"}
],
"analytics": true,
"domain": "mysite.com"
}'Pass subdomain to scope pages to a project (finds existing or creates new). Pass domain to auto-link a custom domain (Pro+).
Create a form with optional webhook in one call.
curl -X POST https://sutrena.com/api/pipeline -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" -d '{
"name": "Contact Form",
"fields": [
{"name": "name", "label": "Name", "type": "text", "required": true},
{"name": "email", "label": "Email", "type": "email", "required": true},
{"name": "message", "label": "Message", "type": "textarea", "required": true}
],
"subdomain": "my-site"
}'Returns submitUrl and hostedFormUrl.
Unified account snapshot. Returns usage, page list, form list, analytics sites, and automations. Pass ?subdomain=name to filter to a specific project.
curl "https://sutrena.com/api/status?subdomain=my-site" -H "Authorization: Bearer YOUR_KEY"All compound tool responses include a _next array with contextual suggestions for follow-up actions. Each seed contains a pre-filled set of parameters so agents and scripts can chain operations without guessing.
Get notified when submissions come in. HMAC-SHA256 signed. Slack and Discord templates built in. Custom field mapping if you need different key names.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/webhooks | Create a webhook (returns secret once) |
| GET | /api/webhooks | List your webhooks |
| GET | /api/webhooks/:id | Get webhook details |
| PATCH | /api/webhooks/:id | Update URL, events, active status, or field mapping |
| DELETE | /api/webhooks/:id | Delete a webhook |
| POST | /api/webhooks/:id/test | Send a test delivery (Pro+) |
| GET | /api/webhooks/:id/deliveries | View delivery history (Pro+) |
curl -X POST https://sutrena.com/api/webhooks \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hook",
"events": ["form.submission"],
"description": "Notify on new submissions",
"template": "default",
"fieldMapping": {"email": "user_email", "name": "user_name"}
}'The response includes a secret for HMAC verification. Save it -- it is only shown once.
Set template to "slack" or "discord" and the payload formats itself for that platform. "default" gives you raw JSON.
Pass webhookIds when creating or updating a form.
curl -X POST https://sutrena.com/api/forms -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" -d '{
"name": "Contact Form",
"fields": [
{"name": "name", "label": "Name", "type": "text", "required": true},
{"name": "email", "label": "Email", "type": "email", "required": true},
{"name": "message", "label": "Message", "type": "textarea", "required": true}
],
"webhookIds": ["WEBHOOK_ID"]
}'Check the signature against your webhook secret to confirm the request came from Sutrena.
import crypto from "crypto";
function verifyWebhook(body: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your webhook handler:
app.post("/hook", (req, res) => {
const signature = req.headers["x-sutrena-signature-256"];
const rawBody = JSON.stringify(req.body);
if (!verifyWebhook(rawBody, signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Process the webhook...
res.sendStatus(200);
});67 MCP tools at /api/mcp. AI agents (Claude Code, Cursor, Windsurf, Codex) can create forms, read submissions, and manage webhooks directly.
MCP config lives at /.well-known/mcp.json.
claude mcp add sutrena --transport streamable-http https://sutrena.com/api/mcp \
--header "Authorization: Bearer YOUR_KEY"Add to .cursor/mcp.json or equivalent:
{
"mcpServers": {
"sutrena": {
"url": "https://sutrena.com/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_KEY"
}
}
}
}| Tool | Description |
|---|---|
| sutrena_launch | Deploy pages + analytics in one call |
| sutrena_collect | Create form + webhooks in one call |
| sutrena_status | Get unified account snapshot |
| sutrena_create_form | Create a new form with fields |
| sutrena_list_forms | List all your forms |
| sutrena_update_form | Update form fields or settings |
| sutrena_delete_form | Delete a form |
| sutrena_search_submissions | Search submissions with filters |
| sutrena_export_csv | Export submissions as CSV |
| sutrena_upsert_submission | Update or insert a submission by unique key |
| sutrena_create_webhook | Create a webhook endpoint |
| sutrena_list_webhooks | List your webhooks |
| sutrena_delete_webhook | Delete a webhook |
| sutrena_test_webhook | Send a test delivery |
| sutrena_get_webhook_deliveries | View delivery history |
| sutrena_create_page | Create an HTML page |
| sutrena_update_page | Update a page's HTML or metadata |
| sutrena_list_pages | List all pages |
| sutrena_delete_page | Delete a page |
| sutrena_rollback_page | Rollback page to previous snapshot |
| sutrena_upload_page_assets | Batch presign page asset uploads |
| sutrena_list_page_assets | List page assets |
| sutrena_delete_page_asset | Delete a page asset |
| sutrena_deploy_site | Deploy a static site from zip |
| sutrena_deploy_site_process | Process a deployed zip |
| sutrena_set_subdomain | Set your subdomain |
| sutrena_add_custom_domain | Add a custom domain (Pro+) |
| sutrena_list_custom_domains | List custom domains with status |
| sutrena_delete_custom_domain | Remove a custom domain |
| sutrena_create_analytics_site | Create an analytics site |
| sutrena_list_analytics_sites | List analytics sites |
| sutrena_delete_analytics_site | Delete an analytics site |
| sutrena_track_event | Track an analytics event |
| sutrena_analytics_query | Query analytics metrics |
| sutrena_analytics_funnel | Run a funnel analysis |
| sutrena_analytics_retention | Run a retention analysis |
| sutrena_create_automation | Create an automation pipeline |
| sutrena_list_automations | List automations |
| sutrena_get_automation | Get automation details |
| sutrena_update_automation | Update an automation |
| sutrena_delete_automation | Delete an automation |
| sutrena_test_automation | Test-run an automation |
| sutrena_create_folder | Create a folder |
| sutrena_list_folders | List folders |
| sutrena_update_folder | Update a folder |
| sutrena_delete_folder | Delete a folder |
| sutrena_organize_folder | Move resources into a folder |
| sutrena_check_usage | Check plan usage and quotas |
| sutrena_update_account | Update account settings |
Streamable HTTP. POST JSON-RPC to /api/mcp with your Authorization header. Sessions are managed automatically -- max 5 per user, 30-minute inactivity timeout.
Check all your quotas in a single call. Returns current usage for forms, pages, webhooks, custom domains, and storage.
Requires authentication (Bearer token or session).
curl -H "Authorization: Bearer st_trial_xxx" \
https://sutrena.com/api/account/usage
# Response:
{
"data": {
"plan": "free",
"claimed": false,
"usage": {
"forms": { "current": 2, "limit": 3, "remaining": 1 },
"pages": { "current": 1, "limit": 3, "remaining": 2 },
"webhooks": { "current": 0, "limit": 1, "remaining": 1 },
"customDomains": { "current": 0, "limit": 0, "remaining": 0 },
"storage": {
"current": 0, "limit": 10485760, "remaining": 10485760,
"currentFormatted": "0B", "limitFormatted": "10MB"
}
},
"restrictions": ["no_export", "no_upsert"]
},
"_meta": { ... }
}When an agent creates an account via POST /api/trial, the user gets a 24-hour window to claim their data by signing in with GitHub or Google. Unclaimed accounts are automatically deleted.
1. Agent calls POST /api/trial — response includes claimUrl and claimDeadline.
2. Agent builds forms and pages with the key.
3. Every API response includes _meta.claim with remaining time.
4. Agent tells the user: "Visit this URL to keep your Sutrena data."
5. User visits the claimUrl in a browser → signs in with GitHub/Google → data migrated → permanent account.
6. Unclaimed accounts are cleaned up every 30 minutes after the 24-hour deadline.
Key management needs a session cookie (sign in first). The account endpoint works with either API key or session.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/account | Get plan info, email, provider, subscription status |
| PUT | /api/account | Update account settings |
| POST | /api/keys | Generate a new API key (session auth only) |
| GET | /api/keys | List all your API keys (session auth only) |
| DELETE | /api/keys/:id | Revoke an API key (session auth only) |
| POST | /api/keys/:id/rotate | Atomically rotate a key (deactivates old, creates new) |
curl https://sutrena.com/api/account \
-H "Authorization: Bearer YOUR_KEY"Example response:
{
"data": {
"plan": "pro",
"email": "[email protected]",
"name": "Alice",
"provider": "github",
"subscriptionStatus": "active",
"createdAt": "2026-01-15T10:00:00Z"
},
"_meta": {
"plan": "pro",
"limits": {"forms": 20, "webhooks": 3}
}
}Needs a session cookie. The raw key is shown once and never again.
curl -X POST https://sutrena.com/api/keys \
-H "Content-Type: application/json" \
-H "Cookie: sutrena_session=SESSION_ID" \
-d '{"label": "production"}'Kills the old key, creates a new one with the same label. Atomic. New raw key returned once.
curl -X POST https://sutrena.com/api/keys/KEY_ID/rotate \
-H "Authorization: Bearer YOUR_KEY"Free plan has no expiry. Paid plans start at $29/month.
| Feature | Free ($0) | Pro ($29/mo) | Scale ($99/mo) |
|---|---|---|---|
| Duration | No expiry | Unlimited | Unlimited |
| Projects (forms + pages + analytics + automations) | 5 | 100 | Unlimited |
| Submissions | 100 per form | Unlimited | Unlimited |
| Analytics events/month | 5K | 500K | Unlimited |
| Webhooks | 1 | Unlimited | Unlimited |
| CSV export | -- | Yes | Yes |
| GDPR deletion | -- | Yes | Yes |
| MCP tools | Yes | Yes | Yes |
| Upsert API | -- | Yes | Yes |
| Custom Domains | -- | 5 | Unlimited |
| Storage | 20 MB | 2 GB | Unlimited |
| Max Asset Size | 2 MB | 20 MB | 50 MB |
| Hosted forms & embeds | Yes | Yes | Yes |
Two options. Drop-in embed snippet, or build your own form and POST to the submit endpoint. Pick whichever fits.
Paste this HTML. It loads an iframe with auto-resizing.
<div data-sutrena-form="FORM_ID"></div>
<script src="https://sutrena.com/embed.js"></script>Custom host, if you need it:
<div data-sutrena-form="FORM_ID" data-sutrena-host="https://your-domain.com"></div>
<script src="https://sutrena.com/embed.js"></script>Your HTML, your styling. Just POST JSON to the submit URL. No SDK.
<form id="my-form">
<input name="email" type="email" required />
<input name="name" type="text" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
<script>
document.getElementById("my-form").addEventListener("submit", async (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.target));
const res = await fetch("https://sutrena.com/api/forms/FORM_ID/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (res.ok) {
e.target.innerHTML = "<p>Thank you!</p>";
}
});
</script>The embed iframe talks to the parent page via postMessage. Three events:
| Event type | Data | Description |
|---|---|---|
| sutrena:resize | { height: number } | Iframe requests height change for content fit |
| sutrena:submitted | {} | Form was successfully submitted |
| sutrena:redirect | { url: string } | Form requests a redirect (if redirectUrl is set) |
// Listen for embed events
window.addEventListener("message", (e) => {
if (e.data?.type === "sutrena:submitted") {
console.log("Form submitted!");
// Track conversion, show thank-you, etc.
}
});Also fires a sutrena:submitted CustomEvent on the container element:
document.querySelector("[data-sutrena-form]")
.addEventListener("sutrena:submitted", () => {
console.log("Form submitted!");
});