Contact API
POST /api/contact — submit a project brief.
Submit a project brief. Used by the contact page.
POST /api/contact
Content-Type: multipart/form-dataFields
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | 2–120 characters |
email | string | yes | Valid email address |
company | string | no | Up to 160 characters |
role | string | no | Up to 120 characters |
projectType | enum | yes | e.g. Web application, AI system, SaaS platform |
budget | enum | yes | e.g. $75k – $250k, Not defined yet |
timeline | enum | yes | e.g. 3 – 6 months, Exploring |
description | string | yes | 30–6000 characters |
consent | "on" | yes | Consent to be contacted |
brief | file | no | PDF, DOCX, TXT, MD, PNG or JPG, up to 10 MB |
The file type is determined from the file's bytes, not its declared MIME type.
Responses
{ "ok": true, "reference": "UF-3FA91C0B", "stored": true, "forwarded": ["email"] }forwarded lists the notification channels that actually succeeded (webhook, email). It is empty when no channel is configured — the brief is still stored in the inbox.
| Status | Meaning |
|---|---|
200 | Stored |
403 | Cross-origin request rejected |
413 | Payload too large |
422 | Validation failed — fields maps field names to messages |
429 | Rate limited — see the Retry-After header |
503 | Storage unavailable |
Example
curl -X POST https://uframework.com/api/contact \
-F name="Ada Lovelace" \
-F email="ada@example.com" \
-F projectType="AI system" \
-F budget="Not defined yet" \
-F timeline="Exploring" \
-F description="We want an assistant that answers questions over our internal documentation." \
-F consent=onLimits
Five submissions per IP address per ten minutes.