Skip to Content

The four moving parts

  1. Define the form on the character (via PATCH /api/ai-characters/{slug}/)
  2. Render it — either via the widget, or in your own UI by reading GET /api/agents/{slug}/appearance/
  3. Submit — widget/UI POSTs values to your submit_url, or to Oshara’s managed storage if submit_url: null
  4. Retrieve — for managed storage, list submissions via GET /api/agents/{slug}/form-responses/

Define forms via API

Forms live inside widget_appearance.forms on the character:

curl -X PATCH https://api.oshara.ai/api/ai-characters/support-bot/ \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "widget_appearance": { "forms": [ { "id": "book-demo", "title": "Book a demo", "submit_url": "https://yoursite.com/api/demo-requests", "submit_label": "Confirm booking", "success_message": "We will be in touch within one business day.", "fields": [ { "name": "name", "label": "Your name", "type": "text", "required": true }, { "name": "email", "label": "Work email", "type": "email", "required": true }, { "name": "company", "label": "Company", "type": "text" }, { "name": "date", "label": "Preferred date","type": "date", "required": true } ] } ] } }'

The moment you save the form, the agent gains a callable tool named after the form’s id (hyphens become underscores: book-demo → tool book_demo).

Multi-step (stepper) forms

Use steps instead of fields:

{ "id": "onboarding", "title": "Get started", "submit_url": "https://yoursite.com/api/onboarding", "steps": [ { "id": "details", "title": "Your details", "fields": [ { "name": "first_name", "label": "First name", "type": "text", "required": true, "width": "half" }, { "name": "last_name", "label": "Last name", "type": "text", "required": true, "width": "half" }, { "name": "email", "label": "Work email", "type": "email", "required": true } ] }, { "id": "use-case", "title": "Your use case", "fields": [ { "name": "use_case", "label": "What are you building?", "type": "select", "required": true, "options": ["Customer support", "Sales assistant", "Internal tool", "Other"] } ] } ] }

Full FormDefinition / FormFieldDef schema: Widget → Forms.

Receive submissions at your endpoint

Set submit_url to an absolute URL on your backend. The widget (or your custom UI) POSTs a flat JSON object — field name → value:

// Express handler app.post("/api/demo-requests", express.json(), async (req, res) => { const { name, email, company, date } = req.body; await db.demoRequests.create({ name, email, company, scheduledDate: date }); await slack.notify(`New demo request from ${name} <${email}>`); // Any 2xx tells the widget the submission succeeded res.status(200).json({ ok: true }); });
BehaviourStatus code
Success → widget shows success_message, agent continuesany 2xx
Failure → widget shows error, agent notified to retryany non-2xx

submit_method

Defaults to POST. Override on the form definition:

{ "submit_url": "https://yoursite.com/api/leads", "submit_method": "PUT" }

Submit to Oshara managed storage

Set submit_url: null and the widget POSTs to:

POST /api/agents/{slug}/form-responses/

The widget calls this automatically. If you’re submitting from a custom UI, send:

{ "form_id": "book-demo", "session_id": "sess_a1b2c3", "values": { "name": "Alice Smith", "email": "alice@acme.com", "date": "2025-07-01" } }

The session_id is what POST /api/agents/agent-session/ returned at session start.

Retrieve submissions

GET /api/agents/{slug}/form-responses/ Authorization: Bearer <token>

Filter with query params:

ParamEffect
form_id=book-demoOnly this form
session_id=sess_abc123Only this call
curl "https://api.oshara.ai/api/agents/support-bot/form-responses/?form_id=book-demo" \ -H "Authorization: Bearer <token>"
[ { "id": 42, "form_id": "book-demo", "session_id": "sess_a1b2c3", "values": { "name": "Alice Smith", "email": "alice@acme.com", "date": "2025-07-01" }, "created_at": "2026-05-10T14:23:00Z" } ]

Nightly sync into your CRM:

const responses = await fetch( "https://api.oshara.ai/api/agents/support-bot/form-responses/?form_id=book-demo", { headers: { Authorization: `Bearer ${OSHARA_TOKEN}` } } ).then(r => r.json()); for (const submission of responses) { await crm.createLead({ name: submission.values.name, email: submission.values.email, source: "voice-agent", session: submission.session_id, }); }

Rendering forms in your own UI

Fetch the schema via:

GET /api/agents/{slug}/appearance/

Then walk appearance.forms[*].fields (or appearance.forms[*].steps[*].fields for steppers) and render each one with your component library. Validate before submit using required, pattern, min/max on the field schema.

async function getForms(slug) { const r = await fetch(`https://api.oshara.ai/api/agents/${slug}/appearance/`, { headers: { Origin: "https://yoursite.com" } }); return (await r.json()).forms ?? []; } const forms = await getForms("support-bot"); const demoForm = forms.find(f => f.id === "book-demo");

For the agent to open and fill the form by voice during the call, see Voice Form Control. For pre-filling values from your DB or session metadata, see Form Pre-fill.

Common errors

SymptomCauseFix
Submission not receivedsubmit_url returns non-2xx or wrong URLLog requests; confirm absolute URL
403 on appearance/ GETOrigin not whitelistedAdd domain to allowed_origins
Agent never opens formForm id not in system prompt instructionsAdd “when X happens, open form Y” to prompt
disabled: true on formForm hidden from agent on purposeSet disabled: false to expose
Last updated on