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, 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:nameandcompanyup to 200 characters,phoneup to 50,company_sizeup to 200,messageup 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 carryretry_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