Handle form submissions with Python
Learn how to handle form submissions in Python using the Forminit SDK.
Form Backend API for Python
Section titled “Form Backend API for Python”Forminit is a form backend service that handles form submissions, data storage, and notifications — so you don’t have to.
With Forminit, you get all of this out of the box:
- No database required — Submissions are stored securely in Forminit’s infrastructure
- Built-in email notifications — Get notified instantly when forms are submitted
- Automatic validation — Email, phone, URL, and country fields are validated server-side
- File upload handling — Accept files up to 25 MB without configuring storage
- Spam protection — Integrates with reCAPTCHA, hCaptcha, and honeypot
- UTM tracking — Automatically capture marketing attribution data
- Dashboard access — View, search, and export submissions from a web interface
- Webhook support — Forward submissions to your own systems when needed
Forminit reduces form backend development from days to minutes. Focus on your application logic while Forminit handles the infrastructure.
Prerequisites
Section titled “Prerequisites”Before integrating Forminit with your Python application:
- Create a Forminit account at forminit.com
- Create a form in your dashboard
- Create an API key from Account → API Tokens
1. Install
Section titled “1. Install”pip install forminit
Requirements: Python 3.8+
2. Set Authentication Mode to Protected
Section titled “2. Set Authentication Mode to Protected”Set authentication to Protected for server-side integrations. This enables higher rate limits and requires the x-api-key header.
3. Create an API Token and Add to Environment
Section titled “3. Create an API Token and Add to Environment”Generate your secret API token from Account → API Tokens in the Forminit dashboard.
# .env
FORMINIT_API_KEY="fi_your_secret_api_key"
4. Send a Form Submission
Section titled “4. Send a Form Submission”Forminit supports two submission formats: structured blocks (JSON) and flat form data (dictionary with fi- prefixed keys).
For complete documentation on all available blocks, field naming conventions, and validation rules, see the Form Blocks Reference.
Structured Blocks Submission
Section titled “Structured Blocks Submission”import os
from forminit import ForminitClient
FORM_ID = "YOUR_FORM_ID"
with ForminitClient(api_key=os.environ["FORMINIT_API_KEY"]) as client:
response = client.submit(FORM_ID, {
"blocks": [
{
"type": "sender",
"properties": {
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
},
},
{
"type": "text",
"name": "message",
"value": "Hello from Python!",
},
],
})
if response.get("error"):
print("Error:", response["error"]["message"])
else:
print("Submission ID:", response["data"]["hashId"])
Flat Form Data Submission
Section titled “Flat Form Data Submission”You can also submit using flat fi- prefixed keys:
with ForminitClient(api_key=os.environ["FORMINIT_API_KEY"]) as client:
response = client.submit(FORM_ID, {
"fi-sender-firstName": "John",
"fi-sender-lastName": "Doe",
"fi-sender-email": "john@example.com",
"fi-text-message": "Hello from Python!",
})
Field Naming Patterns:
| Block Type | Pattern | Example |
|---|---|---|
| Sender properties | fi-sender-{property} | fi-sender-email |
| Field blocks | fi-{type}-{name} | fi-text-message |
With Tracking Parameters
Section titled “With Tracking Parameters”Pass UTM parameters and ad click IDs via the tracking argument:
response = client.submit(FORM_ID, data, tracking={
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "spring_2026",
"gclid": "EAIaIQobChMI...",
})
Async Usage
Section titled “Async Usage”import asyncio
from forminit import AsyncForminitClient
async def main():
async with AsyncForminitClient(api_key=os.environ["FORMINIT_API_KEY"]) as client:
response = await client.submit(FORM_ID, {
"blocks": [
{
"type": "sender",
"properties": {"email": "john@example.com"},
},
{
"type": "text",
"name": "message",
"value": "Hello async!",
},
],
})
print("Submission ID:", response["data"]["hashId"])
asyncio.run(main())
Response Structure
Section titled “Response Structure”Success Response
Section titled “Success Response”The SDK returns a dict with data and optional redirectUrl. On successful submission:
{
"data": {
"hashId": "7LMIBoYY74JOCp1k",
"date": "2026-01-01 21:10:24",
"blocks": {
"sender": {
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com"
},
"message": "Hello world"
}
},
"redirectUrl": "https://forminit.com/thank-you"
}
| Field | Type | Description |
|---|---|---|
data.hashId | str | Unique submission identifier |
data.date | str | Submission timestamp (YYYY-MM-DD HH:mm:ss) |
data.blocks | dict | All submitted field values |
redirectUrl | str | Thank you page URL |
Error Response
Section titled “Error Response”When submission fails, error contains:
{
"error": {
"error": "FI_SCHEMA_FORMAT_EMAIL",
"code": 400,
"message": "Invalid email format"
}
}
| Field | Type | Description |
|---|---|---|
error.error | str | Error code identifier |
error.code | int | HTTP status code |
error.message | str | Human-readable error message |
Error Handling
Section titled “Error Handling”Always check for errors in the response:
response = client.submit(FORM_ID, data)
if response.get("error"):
error = response["error"]
error_code = error.get("error")
if error_code == "FI_SCHEMA_FORMAT_EMAIL":
print("Invalid email address")
elif error_code == "FI_RULES_PHONE_INVALID":
print("Invalid phone number format")
elif error_code == "TOO_MANY_REQUESTS":
print("Rate limit exceeded, please wait")
else:
print(f"Error: {error.get('message')}")
else:
print(f"Success! Submission ID: {response['data']['hashId']}")
Common Error Codes
Section titled “Common Error Codes”| Error Code | HTTP Status | Description |
|---|---|---|
FORM_NOT_FOUND | 404 | Form ID doesn’t exist or was deleted |
FORM_DISABLED | 403 | Form is disabled by owner |
MISSING_API_KEY | 401 | API key required but not provided |
EMPTY_SUBMISSION | 400 | No fields with values submitted |
FI_SCHEMA_FORMAT_EMAIL | 400 | Invalid email format |
FI_RULES_PHONE_INVALID | 400 | Invalid phone number format |
FI_SCHEMA_RANGE_RATING | 400 | Rating not between 1-5 |
FI_DATA_COUNTRY_INVALID | 400 | Invalid country code |
TOO_MANY_REQUESTS | 429 | Rate limit exceeded |
Rate Limits
Section titled “Rate Limits”Authenticated requests are limited to 5 requests per second.
When rate limited, you’ll receive a 429 status code with a TOO_MANY_REQUESTS error.
Security Best Practices
Section titled “Security Best Practices”- Store API keys in environment variables — Never hardcode keys in source code
- Use
.gitignore— Exclude.envfiles from version control - Validate input server-side — Don’t rely solely on client-side validation
- Use HTTPS — Always use secure connections in production
- Use context managers — Ensures the HTTP client is properly closed
- Call
set_user_info()— Pass the real user’s IP, user agent, and referer for accurate tracking
Framework Quickstarts
Section titled “Framework Quickstarts”- Flask — Handle form submissions with Flask
- Django — Handle form submissions with Django
- FastAPI — Handle form submissions with FastAPI
Related Documentation
Section titled “Related Documentation”- Python SDK Reference — Full API reference for the Python SDK
- Form Blocks Reference — Complete reference for all block types
- File Uploads — Detailed file upload guide
- API Reference — Full REST API documentation
- reCAPTCHA Integration — Add reCAPTCHA protection
- hCaptcha Integration — Add hCaptcha protection
Was this page helpful?
Thanks for your feedback.