---
title: "Forminit Python SDK Reference"
description: "Install and use the Forminit Python SDK to submit forms from Flask, Django, FastAPI, or any Python application."
url: https://forminit.com/docs/python-sdk/
---

# Forminit Python SDK Reference

The official Python SDK for [Forminit](https://forminit.com) form backend service. For framework walkthroughs, see [Flask](https://forminit.com/docs/flask/), [Django](https://forminit.com/docs/django/) and [FastAPI](https://forminit.com/docs/fastapi/).

---

## Installation

```bash
pip install forminit
```

**Requirements:** Python 3.8+

---

## Quick Start

### Synchronous

```python
from forminit import ForminitClient

client = ForminitClient(api_key="fi_your_secret_api_key")

response = client.submit("YOUR_FORM_ID", {
    "blocks": [
        {
            "type": "sender",
            "properties": {
                "email": "john@example.com",
                "firstName": "John",
                "lastName": "Doe",
            },
        },
        {
            "type": "text",
            "name": "message",
            "value": "Hello world",
        },
    ],
})

if response.get("error"):
    print("Error:", response["error"]["message"])
else:
    print("Submission ID:", response["data"]["hashId"])

client.close()
```

### Asynchronous

```python
from forminit import AsyncForminitClient

async with AsyncForminitClient(api_key="fi_your_secret_api_key") as client:
    response = await client.submit("YOUR_FORM_ID", {
        "blocks": [
            {
                "type": "sender",
                "properties": {
                    "email": "john@example.com",
                    "firstName": "John",
                },
            },
            {
                "type": "text",
                "name": "message",
                "value": "Hello world",
            },
        ],
    })
```

---

## Configuration

| Option | Type | Description |
|--------|------|-------------|
| `api_key` | str | Server-side API key for authentication |
| `proxy_url` | str | Client-side proxy URL to protect API key |
| `base_url` | str | Custom base URL (default: `https://forminit.com`) |

```python
# Server-side with API key
client = ForminitClient(api_key="fi_your_secret_api_key")

# With proxy URL
client = ForminitClient(proxy_url="http://localhost:8000/api/forminit")
```

---

## Class: ForminitClient

Synchronous client using `httpx`. Supports context manager protocol.

### Constructor

```python
ForminitClient(api_key=None, proxy_url=None, base_url="https://forminit.com")
```

### Methods

#### submit()

```python
client.submit(form_id, data, tracking=None)
```

Submits form data to Forminit.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `form_id` | str | Your Forminit form ID |
| `data` | dict | Form data (structured blocks or flat dict) |
| `tracking` | dict \| None | Optional tracking parameters |

**Returns:** `FormResponse` dict with `data` or `error`

#### set_user_info()

```python
client.set_user_info(ip=None, user_agent=None, referer=None)
```

Sets user information for server-side submissions. Used when submitting on behalf of a user.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `ip` | str \| None | User's IP address |
| `user_agent` | str \| None | User's browser user agent |
| `referer` | str \| None | Referring page URL |

#### close()

```python
client.close()
```

Closes the underlying HTTP client. Called automatically when using context manager.

### Context Manager

```python
with ForminitClient(api_key="fi_your_secret_api_key") as client:
    response = client.submit("YOUR_FORM_ID", data)
# client is automatically closed
```

---

## Class: AsyncForminitClient

Asynchronous client using `httpx.AsyncClient`. Supports async context manager protocol.

### Constructor

```python
AsyncForminitClient(api_key=None, proxy_url=None, base_url="https://forminit.com")
```

### Methods

Same as `ForminitClient`, but all methods are `async`:

```python
async with AsyncForminitClient(api_key="fi_your_secret_api_key") as client:
    response = await client.submit("YOUR_FORM_ID", data)
```

---

## Submission Formats

### Structured Blocks (JSON)

Use the blocks format for typed, validated submissions:

```python
response = client.submit("YOUR_FORM_ID", {
    "blocks": [
        {
            "type": "sender",
            "properties": {
                "email": "john@example.com",
                "firstName": "John",
                "lastName": "Doe",
                "phone": "+12025550123",
                "company": "Acme Corp",
            },
        },
        {
            "type": "text",
            "name": "message",
            "value": "Hello world",
        },
        {
            "type": "select",
            "name": "plan",
            "value": "Enterprise",
        },
        {
            "type": "rating",
            "name": "urgency",
            "value": 4,
        },
    ],
})
```

### Flat Form Data

Use `fi-` prefixed keys for flat dictionary submissions. The SDK sends these as URL-encoded form data:

```python
response = client.submit("YOUR_FORM_ID", {
    "fi-sender-firstName": "John",
    "fi-sender-lastName": "Doe",
    "fi-sender-email": "john@example.com",
    "fi-text-message": "Hello world",
})
```

**Field Naming Patterns:**

| Block Type | Pattern | Example |
|------------|---------|---------|
| Sender properties | `fi-sender-{property}` | `fi-sender-email` |
| Text | `fi-text-{name}` | `fi-text-message` |
| Number | `fi-number-{name}` | `fi-number-quantity` |
| Email | `fi-email-{name}` | `fi-email-invitee` |
| Phone | `fi-phone-{name}` | `fi-phone-emergency` |
| URL | `fi-url-{name}` | `fi-url-website` |
| Date | `fi-date-{name}` | `fi-date-appointment` |
| Rating | `fi-rating-{name}` | `fi-rating-satisfaction` |
| Select | `fi-select-{name}` | `fi-select-plan` |
| Country | `fi-country-{name}` | `fi-country-shipping` |

---

## Block Types

### Sender Block (Object Block)

Collects submitter information. Can only appear once per submission.

```python
{
    "type": "sender",
    "properties": {
        "email": "john@example.com",       # Email address
        "firstName": "John",               # First name
        "lastName": "Doe",                 # Last name
        "fullName": "John Doe",            # Full name
        "phone": "+12025550123",           # Phone (E.164 format)
        "userId": "usr_123",               # User ID from your system
        "company": "Acme Corp",            # Company name
        "position": "Developer",           # Job title
        "address": "123 Main St",          # Street address
        "city": "New York",                # City
        "country": "US",                   # Country (ISO alpha-2)
        "title": "Mr",                     # Title (Mr, Mrs, Dr, Prof)
    },
}
```

At least one of `userId`, `email`, `phone`, `firstName`, `lastName`, or `fullName` is required.

### Tracking Block (Object Block)

Captures UTM parameters and attribution data. Can only appear once per submission.

```python
{
    "type": "tracking",
    "properties": {
        "utmSource": "google",
        "utmMedium": "cpc",
        "utmCampaign": "spring_2026",
        "utmTerm": "form backend",
        "utmContent": "hero_cta",
        "gclid": "EAIaIQobChMI...",      # Google Ads
        "fbclid": "IwAR3x...",            # Facebook
        "msclkid": "a1b2c3...",           # Microsoft Ads
        "ttclid": "E.C.P...",             # TikTok
        "twclid": "abc123...",            # X (Twitter)
    },
}
```

You can also pass tracking data via the `tracking` parameter on `submit()`:

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

### Field Blocks

Each field block requires a unique `name` identifier.

```python
# Text
{"type": "text", "name": "message", "value": "Hello world"}

# Number
{"type": "number", "name": "quantity", "value": 10}

# Email
{"type": "email", "name": "invitee", "value": "friend@example.com"}

# Phone (E.164 format)
{"type": "phone", "name": "emergency", "value": "+12025550123"}

# URL
{"type": "url", "name": "website", "value": "https://example.com"}

# Date (ISO 8601)
{"type": "date", "name": "appointment", "value": "2026-01-15"}

# Rating (1-5)
{"type": "rating", "name": "satisfaction", "value": 5}

# Select (single or multi)
{"type": "select", "name": "plan", "value": "Pro"}
{"type": "select", "name": "services", "value": ["web", "seo"]}

# Country (ISO 3166-1 alpha-2)
{"type": "country", "name": "shipping", "value": "US"}
```

---

## Response

### Success Response

```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 |
| `data.blocks` | dict | Submitted field values |
| `redirectUrl` | str | Thank you page URL |

### Error Response

```python
{
    "error": {
        "success": False,
        "error": "FI_SCHEMA_FORMAT_EMAIL",
        "code": 400,
        "message": "Invalid email format"
    }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `error.error` | str | Error code |
| `error.code` | int | HTTP status code |
| `error.message` | str | Human-readable message |

---

## User Info (Server-Side Tracking)

When submitting on behalf of a user from your server, pass their IP, user agent, and referer so Forminit can capture geolocation and attribution data:

```python
client.set_user_info(
    ip=request.headers.get("X-Forwarded-For", request.remote_addr),
    user_agent=request.headers.get("User-Agent"),
    referer=request.headers.get("Referer"),
)

response = client.submit("YOUR_FORM_ID", data)
```

The SDK sends this information via `X-Forwarded-For`, `X-Real-IP`, `User-Agent`, and `Referer` headers.

---

## Error Codes

| Error Code | Status | Description |
|------------|--------|-------------|
| `MISSING_API_KEY` | 401 | Server-side call without API key |
| `FORM_NOT_FOUND` | 404 | Form ID does not exist |
| `FORM_DISABLED` | 403 | Form is currently disabled |
| `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

| Authentication | Rate Limit |
|----------------|------------|
| With API Key | 5 requests per second per IP address |
| Without API Key | 1 request per 5 seconds per IP address |

---

## Best Practices

1. **Store API keys in environment variables** — Never hardcode keys in source code.
2. **Use context managers** — Ensures the HTTP client is properly closed.
3. **Always use the sender block** to identify form submitters.
4. **Handle errors gracefully** — Check for `error` in the response.
5. **Use E.164 format for phone numbers** (e.g., `+12025550123`).
6. **Use ISO 3166-1 alpha-2 codes for countries** (e.g., `US`, `GB`, `TR`).
7. **Call `set_user_info()`** when submitting on behalf of users to enable geolocation and attribution tracking.

---

## Resources

- [Form Blocks Reference](https://forminit.com/docs/form-blocks/)
- [File Uploads Guide](https://forminit.com/docs/file-upload/)
- [API Reference](https://forminit.com/docs/submit-form-api/)
- [Python Quickstart](https://forminit.com/docs/python/)
