---
title: "Add forms to Python"
description: "Add a form backend to Python apps. Works with Flask, Django, and FastAPI. Validation, notifications, and file handling included."
url: https://forminit.com/docs/python/
---

# Add forms to Python

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

## 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 Cloudflare Turnstile, 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

Before integrating Forminit with your Python application:

1. **Create a Forminit account** at [forminit.com](https://forminit.com)
2. **Create a form** in your dashboard
3. **Create an API key** from **Account → API Tokens**

---

## 1. Install

```bash
pip install forminit
```

**Requirements:** Python 3.8+

## 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.

<img src="/docs/auth-mode-protected.png" alt="Forminit Authentication Mode Protected" style="width: 450px;" />

## 3. Create an API Token and Add to Environment

Generate your secret API token from **Account → API Tokens** in the Forminit dashboard.

```bash
# .env
FORMINIT_API_KEY="fi_your_secret_api_key"
```

> **Caution**
>
> Never commit API keys to version control. Add `.env` to your `.gitignore` file.

## 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](https://forminit.com/docs/form-blocks/).

### Structured Blocks Submission

```python
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

You can also submit using flat `fi-` prefixed keys:

```python
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

Pass UTM parameters and ad click IDs via the `tracking` argument:

```python
response = client.submit(FORM_ID, data, tracking={
    "utm_source": "google",
    "utm_medium": "cpc",
    "utm_campaign": "spring_2026",
    "gclid": "EAIaIQobChMI...",
})
```

### Async Usage

```python
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

### Success Response

The SDK returns a dict with `data` and optional `redirectUrl`. On successful submission:

```python
{
    "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

When submission fails, `error` contains:

```python
{
    "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

Always check for errors in the response:

```python
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

| 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

Authenticated requests are limited to **5 requests per second** per IP address.

When rate limited, you'll receive a `429` status code with a `TOO_MANY_REQUESTS` error.

---

## Security Best Practices

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

---

## Framework Quickstarts

- [Flask](https://forminit.com/docs/flask/) — Handle form submissions with Flask
- [Django](https://forminit.com/docs/django/) — Handle form submissions with Django
- [FastAPI](https://forminit.com/docs/fastapi/) — Handle form submissions with FastAPI

---

## Related Documentation

- [Python SDK Reference](https://forminit.com/docs/python-sdk/) — Full API reference for the Python SDK
- [Form Blocks Reference](https://forminit.com/docs/form-blocks/) — Complete reference for all block types
- [File Uploads](https://forminit.com/docs/file-upload/) — Detailed file upload guide
- [API Reference](https://forminit.com/docs/submit-form-api/) — Full REST API documentation
- [Cloudflare Turnstile Integration](https://forminit.com/docs/cloudflare-turnstile/) — Add Turnstile protection
- [reCAPTCHA Integration](https://forminit.com/docs/recaptcha/) — Add reCAPTCHA protection
- [hCaptcha Integration](https://forminit.com/docs/hcaptcha/) — Add hCaptcha protection
