# ava42 forms API The same operations as the dashboard, over a small REST API. JSON in, JSON out. ## Base URL https://ava42.com ## Auth Every /v1 request needs a per-org API key: Authorization: Bearer ava_xxxxxxxx Create one in the dashboard: Organization -> API -> New key (shown once). All /v1 calls are scoped to that key's organization. ## Conventions - Request bodies and responses are JSON (Content-Type: application/json). - Errors: non-2xx with body {"error":"..."}. - A "form id" is the id returned by create/list (path: /v1/forms/{id}). ## Endpoints GET /v1/me -> {"org":{"id","name","plan"}} (plan: "free" | "pro") GET /v1/forms -> {"forms":[{"id","slug","title","fields","submissions","url"}]} POST /v1/forms body: -> 201 {"id","slug","url"} (402 if the free 1-form limit is hit) GET /v1/forms/{id} -> {"slug","schema":} PUT /v1/forms/{id} body: -> {"slug","url"} DELETE /v1/forms/{id} -> {"deleted":true} GET /v1/forms/{id}/submissions -> {"form","count","submissions":[{"id","submitted_at":,"data":{...}}]} DELETE /v1/forms/{id}/submissions/{sid} -> {"deleted":true} GET /v1/forms/{id}/submissions.csv -> text/csv (a column per field, newest first) GET /v1/members -> {"members":[{"email","name","role"}],"pending":[{"email","role"}]} POST /v1/members body: {"email":"...", "role":"member"|"owner"} -> {"email","role","invite_url"} (also emails the invite) DELETE /v1/members/{email} (URL-encode the email) -> {"removed":true} (409 if it is the org's last owner) ## FormSchema Full reference - every field type, conditional logic (visible_when), recall, and multi-step forms: GET /schema.md { "title": "Contact us", "description": "optional", "submit_label": "Send", // optional "success_message": "Thanks!", // optional "webhook_url": "https://…", // optional: POST each response as JSON "theme_accent": "#e11d48", // optional: brand accent (hex) "fields": [ {"type":"text","name":"name","label":"Your name","required":true}, {"type":"email","name":"email","label":"Email","required":true}, {"type":"select","name":"topic","label":"Topic","options":["Sales","Support"]}, {"type":"textarea","name":"message","label":"Message"} ] } field.type: text | email | number | tel | url | date | textarea | select | radio | checkbox | multichoice | rating | scale | ranking | file | statement | step field keys: type, name (snake_case, unique), label, required, placeholder, options (for select/radio/multichoice/ranking), min, max, min_label, max_label (for scale/rating), max (for file: how many files, 1-3). ## File uploads {"type":"file","name":"photo","label":"A photo","required":true,"max":2} 20 MB per file on every plan, 3 files and 30 MB per response, allowlisted types (images, PDF, Office, text) verified by extension AND by content sniffing. Stored on disk, not in the response JSON; the response holds the filename. Download at /app/files/{id} - signed-in org members only, always as an attachment. ## Prefilling questions from the URL (zero config) Add a query parameter named after a question and it is filled in for the respondent. Works for every field type except file: https://ava42.com/f/{slug}?email=ada@example.com&plan=Pro&topics=Sales,Support text/email/number/tel/url/date/textarea the value, as-is select / radio must match an option exactly checkbox (single) 1, yes, true or on ticks it multichoice comma-separated option values rating / scale the number file not possible - see below A file question cannot be prefilled: browsers only let a person choose a file, so no link can attach one on their behalf. The parameter is ignored. The respondent's own answer always wins: a parameter only fills a question they left blank, and a URL can never tick a box the browser did not send. Without JavaScript nothing is prefilled visibly, but the value still reaches the server. ## Which parameter name? (it's the field's `name`) The parameter is a question's `name`, not its label. Read them from the schema - GET /v1/forms/{id} - or from Share & embed in the app, which lists every parameter beside its question and builds the link for you. `name` is assigned from the label on first save and then pinned, so renaming a question never changes its parameter (or orphans its answers). ## Hidden data (zero config) A parameter with no matching question is stored with the response anyway - no fields to define. e.g. ?utm_source=newsletter&lead_id=42 appears as extra keys in the submissions JSON and extra columns in the CSV. ## Examples # list forms curl https://ava42.com/v1/forms -H "Authorization: Bearer ava_xxx" # create a form curl -X POST https://ava42.com/v1/forms \ -H "Authorization: Bearer ava_xxx" -H "Content-Type: application/json" \ -d '{"title":"Contact","fields":[{"type":"email","name":"email","label":"Email","required":true}]}' # read responses curl https://ava42.com/v1/forms/FORM_ID/submissions -H "Authorization: Bearer ava_xxx"