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

# Forms

> One guide for form management, respondent delivery, submissions, files, scheduling and reporting.

The Forms API covers the whole life of a form: authoring and publishing it,
delivering it to a specific recipient, collecting a response from a browser or
your own application, and reading the reporting behind it.

Two different kinds of endpoint appear here, and the difference matters:

| Kind       | Path shape                               | Who calls it                | Authentication                        |
| ---------- | ---------------------------------------- | --------------------------- | ------------------------------------- |
| Management | `/api/v2/accounts/{accountId}/forms/...` | Your backend                | A scoped API key                      |
| Respondent | `/api/v2/forms/{publicCode}/...`         | The person filling the form | None; the form's own code and session |

Respondent endpoints are deliberately unauthenticated so a browser can reach
them. They are rate limited per form and per client, and a submission is bound
to the published version it was served from.

<Note>
  All Forms operations are listed together under **Forms** in the API reference.
  Start with the path shape: account-scoped routes are for your backend;
  public-code routes are for the respondent experience. If you build forms only
  inside HueChat, the app already performs both flows and you do not need to call
  these endpoints yourself.
</Note>

## Before you start

<Warning>
  Forms is a plan feature. If it is not enabled for the account, every
  management endpoint answers `402` with `{"code": "plan_disabled", "feature":
    "forms"}` rather than `403`. Ask your HueChat contact to enable **Forms &
  Surveys** on the plan before integrating.
</Warning>

Create a scoped API key in **HueChat → Account → Developer API** with the least
it needs. Key creation, rotation and usage remain inside the authenticated app:

| Scope             | Use it for                                                                                                |
| ----------------- | --------------------------------------------------------------------------------------------------------- |
| `forms:read`      | List forms, read a definition, versions, assets                                                           |
| `forms:write`     | Create, edit, publish, close, archive, restore, rotate the public link, upload assets, manage invitations |
| `contacts:read`   | Required together with `forms:write` to issue an invitation for a contact                                 |
| `reports:read`    | Required together with `forms:read` for reports, responses, invitation reporting and CSV export           |
| `ai_agents:write` | Required together with `forms:write` to generate a form through an AI agent                               |

## The lifecycle

A form is always in one of four states, and the API moves it between them:

<Steps>
  <Step title="draft">
    `POST /forms` creates it. `PATCH /forms/{formId}` edits the definition as
    often as you like. Nothing is publicly reachable yet.
  </Step>

  <Step title="published">
    `POST /forms/{formId}/publish` freezes the current definition as an
    immutable version and returns the public link. Editing afterwards creates
    the next version; responses already in flight keep the version they were
    served.
  </Step>

  <Step title="closed">
    `POST /forms/{formId}/close` stops new responses. The link still resolves
    and explains that the form is closed.
  </Step>

  <Step title="archived">
    `POST /forms/{formId}/archive` removes it from the working list.
    `POST /forms/{formId}/restore` brings it back. Responses are retained.
  </Step>
</Steps>

## Publishing and sharing

`POST /forms/{formId}/publish` returns the form's `public_code` and the URL
built from it. A published form can be shared two ways:

* **A public link**, the same for everyone: `https://app.huechat.ai/to/{publicCode}`.
* **An invitation**, issued per recipient with `POST /forms/{formId}/invitations`.

An invitation is a bearer link. It identifies the contact it was issued for, so
the response is attributed to that customer and can prefill their known fields.

<Warning>
  Anyone holding an invitation link can open it. Forwarding it passes that
  attribution along with it, so do not treat an invitation as proof of identity
  for sensitive prefill or profile updates. Use
  `POST /forms/{formId}/invitations/{invitationId}/revoke` to withdraw one, and
  `POST /forms/{formId}/rotate-link` to invalidate every existing link to a form
  at once.
</Warning>

## Collecting a response

A respondent client goes through the same three calls your own web page would:

```bash theme={null}
# 1. Load the published form. Returns the definition and a signed session.
#    Add ?invite=<token> when opening an invitation link.
curl https://app.huechat.ai/api/v2/forms/{publicCode}

# 2. Optionally record progress as the person moves through it. type is one of
#    viewed, started, question_seen, question_answered, exited.
curl -X POST https://app.huechat.ai/api/v2/forms/{publicCode}/events \
  -H "Content-Type: application/json" \
  -d '{"session_id":"...","type":"question_seen","question_id":"q1"}'

# 3. Submit. The server replays the form's own rules before accepting.
curl -X POST https://app.huechat.ai/api/v2/forms/{publicCode}/responses \
  -H "Content-Type: application/json" \
  -d '{"session_id":"...","idempotency_key":"a-key-you-generate","answers":{"q1":"Yes"}}'
```

`idempotency_key` is required. Retrying a submission with the same key returns
the original result instead of recording a second response, so a network retry
is safe.

Submission is not a blind write. The server re-evaluates the form's branching
and scoring server-side, so a client that skips a required question or posts an
answer the rules do not allow is rejected.

For an invitation, pass its token as `?invite=` when loading the form and as
`invite` in each body. `POST /forms/{publicCode}/prefill` then returns the
fields the account has consented to prefill for that contact; it requires
`session_id`, `invite` and an explicit `opt_in`.

### Respondent rate limits

Each respondent operation is limited per form, per client, per minute. The
limits are deliberately tight, because these endpoints are unauthenticated:

| Operation                  | Per minute |
| -------------------------- | ---------- |
| Open a form                | 120        |
| Record an event            | 240        |
| Fetch an asset             | 240        |
| Submit a response          | 60         |
| Request schedule slots     | 60         |
| Prefill from an invitation | 30         |
| Upload a file or AI answer | 10         |
| Book a schedule slot       | 5          |

Over the limit returns `429`. See [rate limits](/rate-limits) for the general
retry guidance.

## Reporting

| Endpoint                                   | Returns                                                            |
| ------------------------------------------ | ------------------------------------------------------------------ |
| `GET /forms/{formId}/reports`              | Performance summary, device breakdown, question reach and drop-off |
| `GET /forms/{formId}/responses`            | Individual responses, paginated                                    |
| `GET /forms/{formId}/invitations`          | Invitation lifecycle: issued, opened, responded, revoked           |
| `GET /forms/{formId}/responses/export.csv` | The same responses as CSV                                          |

All four need `forms:read` **and** `reports:read`.

## Appointment reminders

A published form can be attached to an appointment reminder rule, so each
reminder carries a link for that specific recipient. The reminder endpoints
live under [appointments](/appointments); the form-related fields appear on
`POST` and `PUT /appointments/reminders`.

Each delivery gets its own private invitation, and the reminder can stop once
the recipient has responded.

## Files and scheduling

* **Assets.** `POST /forms/{formId}/assets` uploads branding or question media.
  Respondents upload to `POST /forms/{publicCode}/assets`, and files uploaded by
  a respondent are private to the account.
* **Scheduling.** A form can include a booking step:
  `POST /forms/{publicCode}/schedule/slots` lists availability and
  `POST /forms/{publicCode}/schedule/book` books one, returning a signed receipt.

## Generating a form with AI

`POST /forms/ai/generate` drafts a form from a prompt, and
`GET /forms/ai/generation-usage` reports the monthly allowance. Generation is
capped per account per month; the usage endpoint tells you what is left before
you spend an attempt. A generated form always arrives as a **draft** for a
person to review and publish.
