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

# Public form API

> Read and submit a Hive workflow public form over HTTP: endpoints, request and response shapes, idempotency, rate limits and error codes, with curl and Node examples.

A workflow with a **Public form** trigger can be submitted without a Hive account. The Hive-hosted
form page uses two unauthenticated endpoints, and you can call them yourself — from a server, a
script or an automation tool — to start a workflow run from another system.

<Note>
  This is the supported way to start a Hive workflow from outside Hive. Workflows have no webhook
  trigger, and Hive has no customer API keys. To send events into Signals instead, use an
  [incoming webhook](/integrations/incoming-webhooks).
</Note>

## Before you start

1. Build a workflow with a **Public form** trigger and [deploy it](/workflows/test-and-deploy#deploy-a-release).
2. Select **Form link**, then **Create form link**, and copy the link. It looks like
   `https://<your Hive app>/forms?token=<token>`.
3. The value of `token` (20-200 characters) is the credential. Anyone who holds it can submit the
   form, so treat it like a password and keep it out of client-side code.

The token stops working when a new link is created, the workflow is redeployed, re-activated,
paused, returned to draft or deleted, or its owner can no longer run it. See
[when a link stops working](/workflows/schedules-and-forms#when-a-link-stops-working).

The endpoints below live on your Hive API host, under `/public/workflow-forms`. In these examples
it is written `https://<your Hive API host>`; ask Hive support for the address if you do not have it.

<Warning>
  The API only accepts cross-origin browser requests from Hive's own app. Call it from a server, not
  from JavaScript in a visitor's browser on your site.
</Warning>

## Read a form

`GET /public/workflow-forms/forms?token=<token>`

Returns only what a visitor needs to fill the form in — never the workflow behind it.

```bash theme={null}
curl "https://<your Hive API host>/public/workflow-forms/forms?token=$FORM_TOKEN"
```

```json 200 OK theme={null}
{
  "title": "Request a callback",
  "description": "We will call you within one working day.",
  "confirmationMessage": "Thanks — we have your request.",
  "fields": [
    { "key": "full_name", "label": "Full name", "type": "text", "required": true },
    { "key": "email", "label": "Email", "type": "email", "required": true },
    { "key": "details", "label": "What do you need?", "type": "textarea", "required": false },
    { "key": "consent", "label": "I agree to be contacted", "type": "checkbox", "required": true }
  ]
}
```

<ResponseField name="title" type="string" required>Form title, up to 120 characters.</ResponseField>
<ResponseField name="description" type="string">Up to 500 characters.</ResponseField>
<ResponseField name="confirmationMessage" type="string">Shown after a submission. Absent means the default.</ResponseField>

<ResponseField name="fields" type="array" required>
  1-20 fields, each with `key`, `label`, `type` (`text`, `textarea`, `number`, `email`,
  `checkbox`), `required` and optional `placeholder`.
</ResponseField>

## Submit a form

`POST /public/workflow-forms/forms/submit` with a JSON body.

<ParamField body="token" type="string" required>The form token, 20-200 characters.</ParamField>

<ParamField body="idempotencyKey" type="string" required>
  1-120 characters, unique per submission. Resending the same key returns the same response and
  never starts a second run, so retries after a network error are safe.
</ParamField>

<ParamField body="payload" type="object" required>
  One entry per form field, keyed by the field's `key`. Values are strings (up to 5,000
  characters), numbers or booleans.
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "https://<your Hive API host>/public/workflow-forms/forms/submit" \
    -H "Content-Type: application/json" \
    -d '{
      "token": "'"$FORM_TOKEN"'",
      "idempotencyKey": "crm-lead-8841",
      "payload": {
        "full_name": "Ada Lovelace",
        "email": "ada@example.com",
        "details": "Quarterly close support",
        "consent": true
      }
    }'
  ```

  ```javascript Node.js theme={null}
  import { randomUUID } from 'node:crypto';

  const response = await fetch(
    'https://<your Hive API host>/public/workflow-forms/forms/submit',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        token: process.env.HIVE_FORM_TOKEN,
        idempotencyKey: randomUUID(), // store it and reuse it if you retry
        payload: { full_name: 'Ada Lovelace', email: 'ada@example.com', consent: true },
      }),
    },
  );
  if (!response.ok) {
    const { error } = await response.json();
    throw new Error(`${error.code}: ${error.message}`);
  }
  ```
</CodeGroup>

```json 200 OK theme={null}
{ "status": "accepted" }
```

`accepted` means the submission was recorded. It says nothing about the run: whether it started,
succeeded or was refused (for example because the workspace has used its run allowance) is never
disclosed to the submitter. Follow runs in **Past runs** inside Hive.

### Validation

The payload is checked against the live form:

* every required field must be present; a required text field must not be blank, and a required
  checkbox must be `true`;
* `text` up to 500 characters, `textarea` up to 5,000, `email` a valid address up to 200 characters,
  `number` a finite number;
* keys the form does not declare are rejected.

## Limits

| Limit | Value |
| - | - |
| Requests per form token | 20 per minute |
| Requests per client IP | 60 per minute |
| Request body | 64 KiB |
| Fields per form | 20 |

Both rate limits apply to reads and submissions alike.

## Errors

Errors use one envelope:

```json theme={null}
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded" } }
```

| Status | Code | When |
| - | - | - |
| 400 | `invalid_input` | The body is not valid JSON, is missing a field, or the payload fails the form's validation. The message names the problem. |
| 404 | `workflow_public_form_unavailable` | The token is unknown, rotated or no longer live. On a read, also when the token is missing or not 20-200 characters; on a submit, a missing or wrong-length token is a 400 `invalid_input`. Message: "This form is not available". |
| 413 | `payload_too_large` | The body is over 64 KiB. |
| 429 | `rate_limited` | A rate limit was hit. Back off and retry with the same idempotency key. |

## What the run can do

Each accepted submission starts one production run of the live release, as the workflow's owner and
through the owner's connected accounts. Its actions go through the workspace's approval and safety
policy. Treat submitted text as untrusted: it can steer an AI condition or agent.

## Related

<CardGroup cols={2}>
  <Card title="Schedules and public forms" icon="calendar" href="/workflows/schedules-and-forms">
    Publishing and managing form links.
  </Card>

  <Card title="Incoming webhooks" icon="webhook" href="/integrations/incoming-webhooks">
    Send signed events into Hive.
  </Card>

  <Card title="Developer overview" icon="code" href="/developers/overview">
    Every way to build on Hive.
  </Card>

  <Card title="Limits reference" icon="gauge" href="/developers/limits-reference">
    All limits in one table.
  </Card>
</CardGroup>
