# Lead and newsletter contracts

Status: Live
Updated: 2026-09-30
Verified against: ecosystem cecd4003

`POST https://paintaire.com/api/leads` and `POST https://paintaire.com/api/subscribe` are the routes behind Paintaire’s public forms. Browser forms and agents reach the same rules: the Agent API validates with these bounds and submits through `/api/leads`. **AI agents should use the [Agent API](https://paintaire.com/developers/agent-api), which adds a previewed, user-confirmed, idempotent step, rather than posting to `/api/leads` directly.**

## Encodings

Both routes accept JSON (`Content-Type: application/json`, a flat object of string values) and native form posts (`application/x-www-form-urlencoded` or `multipart/form-data`). On `/api/leads` file uploads are rejected: a multipart file part returns 400. `/api/subscribe` ignores file parts (and any other non-string value) and processes the text fields. Both routes treat a filled `bot-field` honeypot as success and forward nothing; leave it empty. Successful JSON requests return `{ "ok": true }` with 200; successful native form posts return 303 to `/thank-you`. Upstream statuses and bodies are never passed through.

## Leads: `POST /api/leads`

Every form needs a valid `email`. The `form` field selects the form; a missing or unrecognized value is treated as `newsletter`, which needs only the email and is filed as a subscriber, not a sales lead. Prefer `/api/subscribe` for newsletter signups.

| `form` | Required besides `email` | Optional |
|---|---|---|
| `homeowner_quote` | `name`, `postal_code` | `phone`, `project_type`, `timeline`, `message` |
| `contractor_signup` | `name`, `company` | `phone`, `company_size`, `message` |
| `contact` | `name`, `subject`, `message` | `phone` |

Every homeowner lead is routed to the one contractor currently on the platform; the caller cannot choose a recipient.

## Limits

- Every value is a string of at most 2,000 characters. At most 20 fields per request, field names up to 100 characters.
- For `contractor_signup`: `name` and `company` up to 200 characters, `phone` up to 50, `company_size` up to 200, `message` up to 2,000.
- Native form bodies over 32,000 bytes are refused with 413; a value over 2,000 characters in a native form is also 413, while in JSON it is 400.
- Five valid submissions per client address per 15 minutes. Beyond that the route returns 429 with `Retry-After` (seconds); JSON bodies also carry `retry_after`.
- When submissions are not configured on the deployment the route returns 503 before the rate limit counts the attempt.

## Newsletter: `POST /api/subscribe`

| Field | Rule |
|---|---|
| `email` | Required, valid address, at most 254 characters; lower-cased |
| `privacy` | Required consent: `on`, `true`, `yes` or `1`. The consent time is recorded |
| `contractor` | Optional lowercase slug of the site the person signed up through; lower-cased; anything else becomes `direct` |

A non-string JSON value on `/api/leads` returns 400, while `/api/subscribe` ignores it; native form bodies over 8,000 bytes are refused with 413. The route has its own rate-limit bucket (five per client address per 15 minutes, separate from `/api/leads`). A confirmation email is sent to the subscriber. Subscribing an address already on the list returns the same success, so the route cannot be used to test membership.

## Responses

Applies to both routes unless noted. JSON errors are `{ "error": "<message>" }`; native form errors are a short HTML page.

| Status | Meaning |
|---|---|
| 200 | Accepted (JSON requests) |
| 303 | Accepted (native form posts); redirects to `/thank-you` |
| 400 | Invalid JSON or body, invalid email, missing required field (`fields` lists them), field over its bound, non-string JSON value or file upload (`/api/leads` only), or missing consent (subscribe) |
| 413 | Native form body or value too large |
| 429 | Rate limit exceeded; honor `Retry-After` |
| 502 | The upstream service could not confirm the submission |
| 503 | Submissions are not configured on this deployment |

Human page: https://paintaire.com/developers/leads
