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

# Forms API

> Send applications from your own form to Olympus

Keep the form you designed on your own site and send each application to Olympus from your server. Every application lands in Connect with all of its answers, and your automations run on it the same way they do for an Olympus form.

## Before you start

1. Build and publish the form in **Build > Forms**. Its id is the last part of the editor URL, `/build/forms/<form id>`.
2. In **Settings > API keys**, create a key just for the website and set **Access** to **Forms only**. A Forms only key can send form submissions and nothing else. Revoking it later cuts off only the website.
3. Save the key on your server as `OLYMPUS_API_TOKEN`.

<Warning>
  Keep the key on the server. A Standard key can act as you in the workspace, which is why the website should get a Forms only key. Never put it in browser code, and never commit it. Browsers can't send it to this endpoint anyway: Olympus doesn't allow the header from web pages.
</Warning>

## Get the field map

```bash theme={null}
curl https://app.etho.net/api/forms/<form id>/submit \
  -H "Authorization: Bearer $OLYMPUS_API_TOKEN"
```

```json theme={null}
{
  "formId": "…",
  "versionId": "…",
  "questions": [
    { "id": "name", "label": "Name", "kind": "short_answer", "required": false, "options": [], "contact": "name" },
    { "id": "email", "label": "Email", "kind": "email", "required": true, "options": [], "contact": "email" },
    {
      "id": "revenue",
      "label": "Monthly revenue",
      "kind": "dropdown",
      "required": true,
      "options": [{ "value": "$10k+", "label": "$10k+" }],
      "contact": null
    }
  ]
}
```

Map each input on your form to a question `id`. `contact` shows which questions fill the contact's email, phone, and name in Connect. If you change the form and publish it again, fetch the map again.

## Send an application

```bash theme={null}
curl -X POST https://app.etho.net/api/forms/<form id>/submit \
  -H "Authorization: Bearer $OLYMPUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": { "name": "Jane Doe", "email": "jane@example.com", "revenue": "$10k+" },
    "funnel_uid": "<funnel id>",
    "session_id": "<32 lowercase hex characters>"
  }'
```

* `fields` holds one answer per question id. Choice questions take an option `value`; checkboxes and multi-select take an array of values. This endpoint doesn't take file uploads.
* `funnel_uid` is optional. It credits the application to a funnel in the same workspace. The funnel id is the last part of its URL in **Build > Funnels**.
* `session_id` is optional but makes retries safe. Make a new one per application. Sending the same one again returns the first result instead of saving a second application.

A saved application returns `200` with `submission_id` and `qualification_outcome` (`qualified`, `needs_triage`, or `disqualified`). Treat any other status as not saved.

## Errors

| Status | Meaning |
| - | - |
| `401` | The key is missing, revoked, or not a valid Olympus key. |
| `403` | The `funnel_uid` belongs to another workspace, or the form only accepts submissions from its own page. |
| `404` | The form isn't published, or the key belongs to a different workspace. |
| `410` | The form is closed. |
| `422` | An answer was rejected. `errors` lists each one as `{ "fieldId", "code" }`. `unknown_field` means the id isn't on the form, `required` means a required answer is missing, and `invalid_value` or `not_an_option` means the answer doesn't fit the question. |
| `429` | Too many applications from this key: 60 a minute or 600 an hour. Wait for the seconds in `Retry-After`. |
| `503` | Olympus couldn't check the key or save right now. Retry, with the same `session_id` if you sent one. |

## What happens next

Each application creates or updates the contact in Connect, with the opportunity the form's Connect settings call for. Then the form's rules pick one automation trigger:

* `app_completed` for a qualified application
* `needs_triage` when the rules send it to review
* `disqualified` when the rules rule it out

`call_booked` fires only when the lead books on an Olympus calendar, so send qualified leads to your Olympus booking page. Your team sets `no_show` from Connect.

Don't also create the opportunity yourself, for example with the `olympus_connect_opportunities_create` MCP tool. The submission already does it, and a second one duplicates the lead.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.