> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mavera.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Form Submission Analysis

> Batch-categorize HubSpot form free-text submissions by pain point, urgency, and use case with Mavera Chat

## Scenario

Your HubSpot forms have 500+ free-text submissions. You batch them into Mavera Chat with a customer persona and get structured categorization by pain point, urgency, and use case.

## Architecture

```mermaid theme={"dark"}
flowchart LR
    A[HubSpot Forms API] --> B["Batch free-text (50/chunk)"] --> C["POST /responses (persona + structured)"] --> D[Categorized results]
```

## Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, json, requests
  from openai import OpenAI

  HS = os.environ["HUBSPOT_ACCESS_TOKEN"]
  MV = os.environ["MAVERA_API_KEY"]

  # 1. Pull form submissions (paginated)
  FORM_ID = "your-form-guid"
  subs, after = [], ""
  while len(subs) < 200:
      params = {"limit": 50}
      if after: params["after"] = after
      r = requests.get(f"https://api.hubapi.com/form-integrations/v1/submissions/forms/{FORM_ID}",
          headers={"Authorization": f"Bearer {HS}"}, params=params)
      r.raise_for_status()
      data = r.json()
      subs.extend(data.get("results", []))
      after = data.get("paging", {}).get("next", {}).get("after", "")
      if not after: break

  # 2. Extract free-text
  texts = []
  for s in subs:
      for f in s.get("values", []):
          if f.get("name") == "biggest_challenge" and f.get("value", "").strip():
              texts.append(f["value"].strip())

  # 3. Categorize in chunks
  mavera = OpenAI(api_key=MV, base_url="https://app.mavera.io/api/v1")
  schema = {"type": "json_schema", "json_schema": {"name": "cats", "strict": True, "schema": {
      "type": "object", "required": ["categories"],
      "properties": {"categories": {"type": "array", "items": {"type": "object",
          "required": ["index", "pain_point", "urgency", "use_case"],
          "properties": {
              "index": {"type": "number"}, "pain_point": {"type": "string"},
              "urgency": {"type": "string", "enum": ["high", "medium", "low"]},
              "use_case": {"type": "string"},
          }}}}}}}

  all_cats = []
  for i in range(0, len(texts), 50):
      chunk = texts[i:i+50]
      numbered = "\n".join(f"{j+1}. {t[:200]}" for j, t in enumerate(chunk))
      result = mavera.responses.create(model="mavera-1",
          input=[{"role": "user", "content": f"Categorize these {len(chunk)} form responses by pain point, urgency, use case.\n\n{numbered}"}],
          extra_body={"persona_id": os.environ.get("CUSTOMER_PERSONA_ID",""), "response_format": schema})
      parsed = json.loads(result.output[0].content[0].text)
      all_cats.extend(parsed.get("categories", []))

  # 4. Summary
  points = {}
  for c in all_cats:
      points[c["pain_point"]] = points.get(c["pain_point"], 0) + 1
  print(f"Categorized: {len(all_cats)} | Unique pain points: {len(points)}")
  for pp, n in sorted(points.items(), key=lambda x: -x[1])[:10]:
      print(f"  {pp}: {n}")
  print(f"High urgency: {sum(1 for c in all_cats if c['urgency']=='high')}/{len(all_cats)}")
  ```

  ```javascript JavaScript theme={"dark"}
  const OpenAI = require("openai").default;
  const HS = process.env.HUBSPOT_ACCESS_TOKEN;
  const MV = process.env.MAVERA_API_KEY;

  // 1. Pull form submissions
  const FORM_ID = "your-form-guid";
  const subs = [];
  let after = "";
  while (subs.length < 200) {
    const params = new URLSearchParams({ limit: "50" });
    if (after) params.set("after", after);
    const data = await fetch(
      `https://api.hubapi.com/form-integrations/v1/submissions/forms/${FORM_ID}?${params}`,
      { headers: { Authorization: `Bearer ${HS}` } }
    ).then(r => r.json());
    subs.push(...(data.results || []));
    after = data.paging?.next?.after || "";
    if (!after) break;
  }

  // 2. Extract free-text
  const texts = subs.flatMap(s =>
    (s.values || []).filter(f => f.name === "biggest_challenge" && f.value?.trim()).map(f => f.value.trim())
  );

  // 3. Categorize
  const mavera = new OpenAI({ apiKey: MV, baseURL: "https://app.mavera.io/api/v1" });
  const allCats = [];
  for (let i = 0; i < texts.length; i += 50) {
    const chunk = texts.slice(i, i + 50);
    const numbered = chunk.map((t, j) => `${j+1}. ${t.slice(0, 200)}`).join("\n");
    const result = await mavera.responses.create({
      model: "mavera-1",
      input: [{ role: "user", content: `Categorize these ${chunk.length} form responses by pain point, urgency, use case.\n\n${numbered}` }],
      extra_body: { persona_id: process.env.CUSTOMER_PERSONA_ID || "", response_format: {
        type: "json_schema", json_schema: { name: "cats", strict: true, schema: {
          type: "object", required: ["categories"],
          properties: { categories: { type: "array", items: { type: "object",
            required: ["index", "pain_point", "urgency", "use_case"],
            properties: { index: { type: "number" }, pain_point: { type: "string" },
              urgency: { type: "string", enum: ["high", "medium", "low"] }, use_case: { type: "string" } }
          }}}
        }}
      }},
    });
    allCats.push(...(JSON.parse(result.output[0].content[0].text).categories || []));
  }

  // 4. Summary
  const points = {};
  allCats.forEach(c => { points[c.pain_point] = (points[c.pain_point] || 0) + 1; });
  console.log(`Categorized: ${allCats.length} | Pain points: ${Object.keys(points).length}`);
  Object.entries(points).sort(([,a],[,b]) => b-a).slice(0,10).forEach(([p,n]) => console.log(`  ${p}: ${n}`));
  ```
</CodeGroup>

## Example Output

```json theme={"dark"}
{ "categorized": 487,
  "top_pain_points": [
    { "pain_point": "Manual reporting takes too long", "count": 89 },
    { "pain_point": "No single source of truth", "count": 72 },
    { "pain_point": "Data scattered across tools", "count": 64 }
  ], "urgency": { "high": 142, "medium": 231, "low": 114 } }
```

## Error Handling

<AccordionGroup>
  <Accordion title="Form GUID">Forms API uses a GUID. Find it in HubSpot → Marketing → Forms → URL, or `GET /marketing/v3/forms`.</Accordion>
  <Accordion title="Short responses add noise">Filter out responses under 10 characters before sending to Mavera.</Accordion>
</AccordionGroup>

***

<CardGroup cols={2}>
  <Card title="HubSpot Integration" icon="arrow-left" href="/integrations/hubspot" />

  <Card title="Chat API" icon="comments" href="/features/chat" />
</CardGroup>
