[Intum Pomoc](https://intum.pl/pomoc.md) / [API](https://intum.pl/pomoc/formularze/api.md)

# [Formularze](https://intum.pl/pomoc/formularze/api/formularze.md)

Dokumentacja API do tworzenia formularzy przez HTTP.

**Autoryzacja:** Wszystkie requesty wymagają `Authorization: Bearer TOKEN`

---

## API Endpoints

### Forms (Formularze)

| Metoda | Ścieżka | Opis |
|--------|---------|------|
| GET | `/form/forms.json` | Lista formularzy |
| GET | `/form/forms/:id.json` | Szczegóły formularza |
| POST | `/form/forms.json` | Utworzenie formularza |
| PATCH | `/form/forms/:id.json` | Aktualizacja formularza |
| DELETE | `/form/forms/:id.json` | Usunięcie formularza |

### Results (Wyniki / Zgłoszenia)

| Metoda | Ścieżka | Opis |
|--------|---------|------|
| GET | `/form/results.json` | Lista wyników |
| POST | `/form/results/save` | Zapisanie wyniku (publiczne, bez auth) |
| POST | `/form/results/:id/close` | Zamknięcie zgłoszenia (`open` otwiera z powrotem) |
| POST | `/form/results/change_bulk_option_multiple` | Akcja masowa na paczce: `ids[]` + `close=true`/`open=true` albo `client_id`/`department_id`/`project_id` |

---

## Pola modeli

### Form

| Pole | Typ | Wymagane | Opis |
|------|-----|----------|------|
| `name` | string | tak | Nazwa formularza |
| `description` | string | nie | Opis |
| `lang` | string | nie | Język (`pl`, `en`) |
| `date_from` | date | nie | Formularz aktywny od |
| `date_to` | date | nie | Formularz aktywny do |
| `team_id` | integer | nie | ID zespołu |
| `department_id` | integer | nie | ID działu |
| `mapping` | string | nie | Mapowanie wyniku na obiekt: `client`, `task`, `deal`, `email` |
| `mapping_options` | object | nie | Opcje mapowania (JSON) |
| `mapping_mailbox_id` | integer | nie | ID skrzynki, w której powstaje e-mail (wymagane przy `mapping: "email"`; zapisywane w `fields`) |
| `fields` | object | nie | Konfiguracja formularza (JSON) |

**Pola w `fields`:**

| Klucz | Typ | Opis |
|-------|-----|------|
| `submit_text` | string | Tekst przycisku submit (domyślnie: "Wyślij") |
| `form_response` | string | Tekst po wysłaniu formularza |
| `form_redirect` | string | URL przekierowania po wysłaniu |
| `form_style` | string | Dodatkowe style CSS |
| `form_class` | string | Klasa CSS formularza |
| `button_class` | string | Klasa CSS przycisku |
| `form_anonymous` | integer | `1` = wypełnienie zapisuje się bez adresu IP, przeglądarki i adresu strony, z której przyszło (`ip`, `agent`, `referrer` zostają puste). Nie ma związku z logowaniem - zapis wyniku jest publiczny niezależnie od tej opcji |
| `use_mailbox_department` | integer | `1` = mail o nowym wypełnieniu formularza bierze nazwę, adres strony, logo i kolor z danych firmy działu przypiętego do skrzynki, z której wychodzi (`confirmation_mailbox_id`), z zejściem na główny dział konta. Czego dział nie ma, tego w mailu nie ma - marki produktu wtedy nie podstawiamy. Domyślnie `0` - mail wygląda jak marka produktu. W UI ustawiane w edycji formularza, karta pokazuje tylko stan. |
| `brand_client_email` | integer | `1` = tą samą marką opakowane jest też potwierdzenie dla wypełniającego (nagłówek z logo i stopka wokół treści z `confirmation_body`, sama treść zostaje nietknięta). Działa tylko razem z `use_mailbox_department`. Osobna opcja, bo treść tego maila pisze użytkownik i może już mieć własną oprawę. Treści będącej gotowym dokumentem (`<html>`/`<body>`, w tym `template:Nazwa`) nie opakowujemy nigdy. Domyślnie `0`. |
| `mapping_mailbox_id` | integer | ID skrzynki, w której powstaje e-mail przy `mapping: "email"` (wymagane dla tego mapowania). Można przesłać też jako atrybut `mapping_mailbox_id` obok `mapping` - zapisze się w `fields`. |

### FormField (Pole formularza)

Pola formularza dodaje się przy aktualizacji formularza przez `form_fields_attributes`:

| Pole | Typ | Wymagane | Opis |
|------|-----|----------|------|
| `name` | string | tak | Nazwa/etykieta pola |
| `kind` | string | tak | Typ pola (patrz niżej) |
| `required` | boolean | nie | Czy pole jest wymagane |
| `priority` | integer | nie | Kolejność (0 = pierwsze) |
| `description` | string | nie | Opis/podpowiedź |
| `regex` | string | nie | Walidacja: `email`, `digits`, `alphanum` |
| `map_to` | string | nie | Mapowanie na pole w CRM |
| `fields` | object | nie | Dodatkowa konfiguracja (JSON) |
| `_destroy` | boolean | nie | `true` aby usunąć pole |

**Typy pól (`kind`):**

| Typ | Opis |
|-----|------|
| `string` | Pole tekstowe (jednoliniowe) |
| `text` | Pole tekstowe (wieloliniowe) |
| `select` | Lista rozwijana |
| `radio` | Przyciski radio |
| `checkbox` | Pola checkbox |
| `title` | Nagłówek sekcji (bez inputa) |
| `placeholder` | Tekst opisowy (bez inputa) |
| `attachment` | Pole do wgrania pliku |

**Pola w `fields` dla FormField:**

| Klucz | Typ | Opis |
|-------|-----|------|
| `input_placeholder` | string | Placeholder w polu input |
| `options` | string | Opcje dla select/radio/checkbox (rozdzielone `\r\n`) |

---

## Tworzenie formularza

Tworzenie formularza odbywa się w 2 krokach:

### 1. Utwórz formularz (POST)

```
POST /form/forms.json
Content-Type: application/json

{
  "form": {
    "name": "Formularz kontaktowy",
    "lang": "pl",
    "fields": {
      "submit_text": "Wyślij",
      "form_response": "Dziękujemy za wiadomość!"
    }
  }
}
```

**Odpowiedź:** Zwraca obiekt formularza z `id` i `token`.

### 2. Dodaj pola (PATCH)

```
PATCH /form/forms/:id.json
Content-Type: application/json

{
  "form": {
    "form_fields_attributes": [
      {
        "kind": "string",
        "name": "Imię i nazwisko",
        "required": true,
        "priority": 0,
        "fields": { "input_placeholder": "Jan Kowalski" }
      },
      {
        "kind": "string",
        "name": "Email",
        "required": true,
        "priority": 1,
        "regex": "email",
        "fields": { "input_placeholder": "jan@example.com" }
      },
      {
        "kind": "text",
        "name": "Wiadomość",
        "required": true,
        "priority": 2,
        "fields": { "input_placeholder": "Twoja wiadomość..." }
      }
    ]
  }
}
```

---

## Osadzanie formularza na stronie

Po utworzeniu formularza, w odpowiedzi API dostępny jest `token`. Formularz osadza się na stronie za pomocą kodu JS:

```html
<script src="https://widgets.intum.net/widgets/app/intum-forms-widget-1.1.js"></script>
<!-- Intum Form Start -->
  <div data-form="TOKEN_FORMULARZA"></div>
<!-- Intum Form End -->
```

Widget automatycznie:
- Pobiera konfigurację formularza po tokenie
- Renderuje formularz na stronie
- Obsługuje walidację pól
- Wysyła dane przez POST do `/form/results/save`
- Wyświetla komunikat po wysłaniu lub przekierowuje

---

## Przykład: formularz z select i checkbox

```
PATCH /form/forms/:id.json
{
  "form": {
    "form_fields_attributes": [
      {
        "kind": "select",
        "name": "Temat",
        "required": true,
        "priority": 0,
        "fields": { "options": "Pytanie ogólne\r\nWspółpraca\r\nReklamacja" }
      },
      {
        "kind": "checkbox",
        "name": "Zgody",
        "priority": 1,
        "fields": { "options": "Akceptuję regulamin\r\nChcę otrzymywać newsletter" }
      }
    ]
  }
}
```

---

## Mapowanie wyniku

Po wysłaniu formularz może automatycznie utworzyć obiekt. Ustaw `mapping` na jeden z: `client` (klient CRM), `task` (zadanie), `deal` (szansa sprzedaży), `email` (wiadomość odebrana). **Mapowanie uruchomi się tylko gdy `mapping_options.auto_mapping = 1`** - bez tego wynik nie tworzy żadnego obiektu:

```
POST /form/forms.json
{
  "form": {
    "name": "Lead form",
    "mapping": "client",
    "mapping_options": {
      "auto_mapping": 1
    }
  }
}
```

**Klucze `mapping_options`:**

| Klucz | Opis |
|-------|------|
| `auto_mapping` | `1` włącza mapowanie (wymagane, inaczej nic się nie utworzy) |
| `client_force_update` | (mapping `client`) `1` = nadpisuj też wypełnione pola istniejącego klienta (domyślnie uzupełniamy tylko puste) |
| `client_sensitive_data` | (mapping `client`) `1` = oznacz klienta jako dane wrażliwe |

Następnie w polach formularza użyj `map_to`, aby przypisać odpowiedź do atrybutu obiektu:

```json
{
  "kind": "string",
  "name": "Imię",
  "map_to": "name"
}
```

Dla `mapping: "email"` trzeba dodatkowo wskazać skrzynkę mapowania (`mapping_mailbox_id`, trafia do `fields`) - patrz wymagania niżej.

### Wymagania mapowania (inaczej API zwróci 422)

Niekompletne mapowanie jest odrzucane przy zapisie formularza - **niezależnie od `auto_mapping`**. Odpowiedź `422` zawiera błąd na atrybucie `mapping`. Wymagania:

- `mapping` tylko z listy: `client`, `task`, `deal`, `email`.
- `auto_mapping: 1` bez ustawionego `mapping` - błąd.
- Wybrany `mapping` wymaga co najmniej jednego pola z `map_to`.
- Każde `map_to` musi należeć do wybranego typu (lista pól różni się per typ) i **nie może się powtarzać** w obrębie formularza.
- `mapping: "email"` wymaga: skrzynki mapowania (`mapping_mailbox_id`), nadawcy (pole z `map_to: "from"` **albo** `client_email_field_id`) oraz pola z `map_to: "subject"`.
- `mapping: "client"` wymaga pola identyfikującego klienta: `name`, `first_name`, `last_name`, `email`, `tax_no`, `register_number` lub `external_id`.

Formularz można utworzyć jednym requestem razem z polami (`form_fields_attributes`) - wtedy walidacja widzi już pola. Osobno tworzony formularz bez pól przechodzi (pola dodaje się kolejnym requestem), ale ten request musi już spełniać powyższe wymagania.

### Pola referencyjne (podawane po nazwie, nie po ID)

Osoba wypełniająca formularz nie zna wewnętrznych ID, więc pola wskazujące na inny rekord rozwiązujemy po czytelnej wartości. Kolejność prób: **surowe ID → numer biznesowy → nazwa** (bez rozróżniania wielkości liter).

| `map_to` | Dotyczy (`mapping`) | Rozwiązywane po |
|----------|---------------------|-----------------|
| `client_id` | task, deal, email | ID, `external_id`, NIP, REGON lub email klienta |
| `status_id` | client, task, deal, email | nazwie statusu (tylko statusy dozwolone w danym module) |
| `project_id` | task, client, deal, email | ID, numerze projektu (np. `1/02/2026`) lub nazwie |
| `department_id` | task, deal, client, email | nazwie działu |
| `team_id` | task | nazwie zespołu |
| `folder_id` | email | nazwie folderu poczty |

Pozostałe pola `*_id` przypisywane są wprost - trzeba podać gotowe ID.

### Pozostałe zasady mapowania

- **Pola dodatkowe (custom fields)** - `map_to` może wskazywać na klucz pola dodatkowego; wartość trafi wtedy do `fields` obiektu.
- **`tag_names`** - dokłada tagi, ale tylko do nowo utworzonych rekordów (istniejącemu klientowi tagów nie zmienia).
- **Odpowiedzi wielokrotnego wyboru** - łączone w tekst rozdzielony przecinkami.
- **Opiekun** - nie ustawia się przez mapowanie; przypisz go regułą automatyzacji (`Automation::Rule`) z triggerem na wyniku formularza.
- **Istniejący klient** - przy `mapping: "client"` szukamy klienta po unikalnych identyfikatorach i uzupełniamy tylko puste pola (chyba że `mapping_options.client_force_update = 1` - wtedy nadpisujemy też wypełnione).

---

## Zabezpieczenia antyspamowe

Widget formularza automatycznie dodaje:
- **Honeypot** — ukryte pole `_fax_number` (boty je wypełniają, ludzie nie)
- **Elapsed time** — odrzuca wypełnienia wysłane szybciej niż 3 sekundy

---

## Stylowanie formularza (CSS)

Formularz można stylować przez pole `fields.form_style`. CSS jest wstrzykiwany jako `<style>` wewnątrz formularza.

**Identyfikator formularza:** Użyj `~intum_form_id~` w CSS — zostanie zamieniony na ID formularza (np. `.intum_form_123`).

```
PATCH /form/forms/:id.json
{
  "form": {
    "fields": {
      "form_style": ".intum_form_~intum_form_id~ input { border: 2px solid #3b82f6; border-radius: 8px; padding: 10px; }\n.intum_form_~intum_form_id~ label { font-weight: 600; color: #1f2937; }\n.intum_form_~intum_form_id~ .intum_button_~intum_form_id~ { background: #2563eb; color: white; padding: 12px 24px; border-radius: 8px; border: none; cursor: pointer; }"
    }
  }
}
```

**Dostępne selektory CSS:**

| Selektor | Element |
|----------|---------|
| `.intum_form_~intum_form_id~` | Kontener formularza |
| `.intum_form_~intum_form_id~ label` | Etykiety pól |
| `.intum_form_~intum_form_id~ input` | Pola tekstowe |
| `.intum_form_~intum_form_id~ textarea` | Pola wieloliniowe |
| `.intum_form_~intum_form_id~ select` | Listy rozwijane |
| `.intum_button_~intum_form_id~` | Przycisk submit |
| `.intum_form_~intum_form_id~ .form_field` | Kontener pojedynczego pola |

**Inne pola konfiguracyjne:**

| Klucz w `fields` | Opis |
|-------------------|------|
| `form_class` | Dodatkowa klasa CSS na kontenerze (domyślnie: `intum_form_{id}`) |
| `button_class` | Klasa CSS przycisku submit (domyślnie: `intum_button_{id}`) |
| `submit_text` | Tekst przycisku submit |
| `form_response` | Tekst wyświetlany po wysłaniu (obsługuje Liquid) |
| `form_redirect` | URL przekierowania po wysłaniu |
| `custom_js` | Własny JavaScript wykonywany po załadowaniu formularza |

---

## Wskazówki

1. **Token** — po utworzeniu formularza, `token` jest generowany automatycznie i służy do osadzania
2. **Kolejność pól** — ustalana przez `priority` (0 = pierwsze)
3. **Walidacja email** — użyj `regex: "email"` na polu
4. **Opcje select/radio/checkbox** — rozdzielaj przez `\r\n` w polu `fields.options`
5. **Usuwanie pól** — wyślij `_destroy: true` z `id` pola w `form_fields_attributes`
6. **Anonimowy formularz** — `fields.form_anonymous: 1` sprawia, że przy wypełnieniu nie zapisujemy IP, przeglądarki ani adresu strony (przydatne przy ankietach z obietnicą anonimowości)
7. **CSS** — styluj przez `fields.form_style`, używaj `~intum_form_id~` jako placeholder ID
8. **PATCH fields — merge nadpisuje!** — wysyłając `{"form": {"fields": {"custom_js": "..."}}}` Rails merge'uje na poziomie klucza, ale inne klucze (np. `form_style`) zostaną **wyzerowane**. Zawsze: (1) GET aktualny formularz, (2) zmodyfikuj potrzebne klucze w `fields`, (3) wyślij PEŁNY obiekt `fields` w PATCH
9. **form_style vs custom_css** — `fields.form_style` to jedyne pole CSS trafiające do widgetu. `fields.custom_css` jest **nigdzie nie używane** — nie wysyłaj go
10. **Wygląd maila o nowym wypełnieniu formularza** — domyślnie marka produktu. Formularz z `fields.use_mailbox_department: 1` bierze ją z danych firmy działu przypiętego do skrzynki nadawczej (`confirmation_mailbox_id`), a gdy skrzynka nie ma działu — z głównego działu konta. Przy włączonej opcji puste pole działu zostaje puste, zamiast schodzić na wartość produktu: dział bez logo daje nagłówek z samą nazwą, a dział bez nazwy nie dostaje ani nagłówka, ani stopki, ani podpisu. Wyjątek: `brand_color` (tło przycisku) schodzi na kolor produktu. Samej marki nie ustawia się na formularzu: nazwa to `company_name` działu, adres `company_website`, logo `company_logo`, a kolor przycisku to `brand_color` w JSONB `fields` działu (nie ma własnej kolumny). Logotyp jest skalowany do wysokości 36px z zachowaniem proporcji.
11. **Podgląd maili** — karta formularza (`/form/forms/:id`) ma odnośniki do podglądu obu maili, każdy na osobnej stronie: `GET /form/forms/:id/notification_email_preview` (powiadomienie dla obsługi) i `GET /form/forms/:id/client_email_preview` (potwierdzenie dla wypełniającego). HTML to gotowa strona z wiadomością, `.json` zwraca `{ html }`. Zawsze stoją na odpowiedziach na pokaz, zbudowanych z pól formularza - nigdy na prawdziwym wypełnieniu, bo w treści maila widać dane osoby, która formularz wysłała. Nic nie zapisują i nic nie wysyłają. Pusta treść oznacza, że dany mail i tak by nie poszedł: brak skrzynek albo niewypełnione potwierdzenie.

## Powiązane

- [common_api](https://app.intum.pl/noe/prompt/common_api.md) — wspólne zasady API (format, autoryzacja, odpowiedzi)