Skip to content

Handle form submissions with Python

Learn how to handle form submissions in Python using the Forminit SDK.

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.


Before integrating Forminit with your Python application:

  1. Create a Forminit account at forminit.com
  2. Create a form in your dashboard
  3. Create an API key from Account → API Tokens

pip install forminit

Requirements: Python 3.8+

Set authentication to Protected for server-side integrations. This enables higher rate limits and requires the x-api-key header.

Forminit Authentication Mode Protected

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"

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.

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"])

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 TypePatternExample
Sender propertiesfi-sender-{property}fi-sender-email
Field blocksfi-{type}-{name}fi-text-message

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...",
})
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())

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"
}
FieldTypeDescription
data.hashIdstrUnique submission identifier
data.datestrSubmission timestamp (YYYY-MM-DD HH:mm:ss)
data.blocksdictAll submitted field values
redirectUrlstrThank you page URL

When submission fails, error contains:

{
    "error": {
        "error": "FI_SCHEMA_FORMAT_EMAIL",
        "code": 400,
        "message": "Invalid email format"
    }
}
FieldTypeDescription
error.errorstrError code identifier
error.codeintHTTP status code
error.messagestrHuman-readable error message

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']}")
Error CodeHTTP StatusDescription
FORM_NOT_FOUND404Form ID doesn’t exist or was deleted
FORM_DISABLED403Form is disabled by owner
MISSING_API_KEY401API key required but not provided
EMPTY_SUBMISSION400No fields with values submitted
FI_SCHEMA_FORMAT_EMAIL400Invalid email format
FI_RULES_PHONE_INVALID400Invalid phone number format
FI_SCHEMA_RANGE_RATING400Rating not between 1-5
FI_DATA_COUNTRY_INVALID400Invalid country code
TOO_MANY_REQUESTS429Rate limit exceeded

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.


  1. Store API keys in environment variables — Never hardcode keys in source code
  2. Use .gitignore — Exclude .env files from version control
  3. Validate input server-side — Don’t rely solely on client-side validation
  4. Use HTTPS — Always use secure connections in production
  5. Use context managers — Ensures the HTTP client is properly closed
  6. Call set_user_info() — Pass the real user’s IP, user agent, and referer for accurate tracking

  • Flask — Handle form submissions with Flask
  • Django — Handle form submissions with Django
  • FastAPI — Handle form submissions with FastAPI