Element External Integration API — dokumentacja
REST API do integracji zewnętrznych systemów (voice-boty, ATS, pipeline'y danych) z platformą Element. Interfejs jest w fazie beta — kontrakt może się jeszcze zmienić w niekompatybilny sposób w oknie ~2–3 miesięcy testów produkcyjnych; zmiany łamiące będą komunikowane z wyprzedzeniem.
POST …/apply— poleemailopcjonalne. E-mail nie jest już wymagany przy składaniu aplikacji; jeśli go podasz, nadal jest walidowany (błędny format →400 validation_failed). Aplikacja bez e-maila tworzy kandydata bez adresu. Zob. 8.11.POST …/apply— dane podstawowe w podglądzie formularza. Imię, nazwisko, e-mail i telefon przekazane w aplikacji przez API trafiają teraz do arkusza odpowiedzi i są widoczne w podglądzie formularza kandydata w Element. Bez zmian w kontrakcie żądania i odpowiedzi; dotyczy aplikacji złożonych po wdrożeniu. Zob. 8.11.
Zmiany z wersji 1.2 (rozwinięte subForms[], załączniki attachments[], odpowiedzi calendarDay) pozostają oznaczone znaczkami „v1.2" w sekcjach 8.6 i 8.11.
1O API
External Integration API udostępnia programowy dostęp tylko-do-odczytu do projektów rekrutacyjnych, kandydatów i formularzy aplikacyjnych oraz ograniczony zestaw operacji zapisu (utworzenie kandydata przez aplikację, dodanie publicznej notatki). Adresowany jest do zespołów developerskich wdrażających integracje z systemami zewnętrznymi.
Wszystkie ścieżki są wersjonowane prefiksem /api/v1-beta. Po stabilizacji interfejs przejdzie w zamrożone v1; przyszłe zmiany łamiące trafią pod nowy prefiks (np. /api/v2).
2Wymagania wstępne
- Aktywny tenant Element z włączonym modułem External Integration API.
- Konto API (login + hasło) utworzone w panelu — jeden zestaw poświadczeń na jeden system zewnętrzny.
- Klient HTTP obsługujący uwierzytelnianie HTTP Basic oraz
multipart/form-data(dla przesyłania plików).
3Pierwsze wywołanie
Najprostszy test łączności — endpoint health (bez uwierzytelniania):
curl https://<tenant>.elementapp.ai/api/v1-beta/health
# 200 OK
{ "status": "ok", "apiVersion": "v1-beta" }
Kolejny krok — uwierzytelnione pobranie listy projektów (zob. 8.2).
4Uwierzytelnianie
Schemat: HTTP Basic (RFC 7617), realm External Integration API.
- login — w formacie
api+<nazwa-systemu>@<tenant>.local(np.api+voicebot@acme.local). - hasło — generowane losowo, pokazywane jednorazowo przy utworzeniu, nieodzyskiwalne.
- nagłówek —
Authorization: Basic base64(login:hasło).
Wielotenantowość: tenant identyfikowany jest wyłącznie po subdomenie w URL (<tenant>.elementapp.ai); poświadczenia są zablokowane do jednego tenanta.
- (IP + login): 5 nieudanych prób → blokada 15 min.
- sam login: 20 nieudanych prób → blokada 15 min.
- Odpowiedź:
429z kodemtoo_many_failed_attempts.
5Format danych
- Odpowiedzi —
application/json; charset=utf-8. - Żądania —
application/x-www-form-urlencodedlubmultipart/form-data(dla plików). - Daty — ISO 8601 UTC z offsetem (
2026-05-28T12:34:56+00:00); daty-tylko jakoYYYY-MM-DD. - Identyfikatory — UUID v4 (np.
ff315ea1-c056-472d-aefe-b99f3d41e5c0). - Lokalizacja — parametr
locale(pl,en,de, …) dla schematu formularza i odpowiedzi.
Koperta odpowiedzi (sukces):
{ "data": { /* encja lub tablica */ },
"meta": { "page": 1, "perPage": 20, "total": 8 } }
Koperta odpowiedzi (błąd):
{ "error": { "code": "snake_case_identifier",
"message": "Czytelny opis (dla wybranych kodów)" } }
Paginacja: ?page=N&perPage=N (1–100, domyślnie 20); w odpowiedzi meta: { page, perPage, total }.
6Limity zapytań
- Domyślnie: 70 wywołań/min na poświadczenie (60 bazowe + 10 burst) ≈ 1,2 req/s.
- Odpowiedź przy przekroczeniu:
429z kodemrate_limit_exceeded. - Backoff: wykładniczy od 1 s, limit 30 s.
- Wyższe limity — kontakt ze wsparciem Element.
7Obsługa błędów
| HTTP | code | Znaczenie |
|---|---|---|
| 200 | — | Sukces (GET) |
| 201 | — | Utworzono (POST) |
| 400 | validation_failed | Nieprawidłowe/niekompletne żądanie |
| 400 | invalid_answers | POST /apply: błędny JSON lub kształt/wartości answers |
| 401 | — | Brak / błędne uwierzytelnianie |
| 403 | — | Brak roli lub konto dezaktywowane |
| 404 | project_not_found | Projekt nie istnieje / zarchiwizowany / spoza tenanta |
| 404 | candidate_not_found | Kandydat nie istnieje / zarchiwizowany / usunięty RODO |
| 404 | application_form_not_found | Brak formularza / niewypełniony przez kandydata |
| 409 | no_application_stage | Projekt bez etapu aplikacyjnego |
| 409 | stale_form_version | POST /apply: niezgodna wersja formularza |
| 429 | rate_limit_exceeded | Przekroczony limit zapytań |
| 429 | too_many_failed_attempts | Blokada po nieudanych logowaniach |
| 5xx | — | Błąd serwera Element; ponów z backoffem |
8Referencja endpointów
Base URL: https://<tenant>.elementapp.ai/api/v1-beta
8.1 — Health check
Kontrola łączności bez uwierzytelniania.
{ "status": "ok", "apiVersion": "v1-beta" }
8.2 — Lista projektów
Stronicowana lista aktywnych projektów.
{ "data": [
{ "id": "ff315ea1-…", "name": "Senior PHP Developer",
"positionName": null, "isRemote": true,
"statusId": "73f09ef2-…", "createdAt": "2021-10-31T11:42:24+00:00" } ],
"meta": { "page": 1, "perPage": 20, "total": 8 } }
Błędy: 401, 429.
8.3 — Szczegóły projektu
Pełne dane projektu wraz z listą etapów. Pola responsibilities, requirements, offer zawierają treść HTML. minimumSalary/maximumSalary w walucie tenanta (0 = nieustawione).
{ "data": { "id": "ff315ea1-…", "name": "Senior PHP Developer",
"minimumSalary": 12000, "maximumSalary": 18000, "numberOfVacancies": 4,
"stages": [
{ "id": "…", "name": "Aplikacja", "isApplicationType": true },
{ "id": "…", "name": "Rozmowa", "isApplicationType": false } ] } }
Błędy: 401, 404 project_not_found, 429.
8.4 — Etapy projektu
Sama tablica etapów (kształt jak data.stages z 8.3). Etap o isApplicationType: true to etap aplikacyjny (max jeden na projekt).
Błędy: 401, 404 project_not_found, 429.
8.5 — Kandydaci w projekcie
Stronicowana lista kandydatów; opcjonalny filtr stageId. Pola: id, firstName, lastName, email, stageId, isRejected, addedAt. Pole email może być puste — od v1.3 aplikację przez API można złożyć bez adresu e-mail (zob. 8.11).
Błędy: 401, 404 project_not_found, 429.
8.6 — Schemat formularza aplikacyjnego ZMIANA v1.2
Zwraca pełną strukturę formularza (metadane + zagnieżdżone sekcje → pytania → warianty odpowiedzi). Użyj do zbudowania mapy odpowiedzi i pobrania version do POST /apply.
Każda sekcja zwraca teraz tablicę subForms[] z pełną, rozwiniętą strukturą podformularza (np. calendar-days, week-days) — zagnieżdżone sekcje i pytania. Wcześniej dostępne były wyłącznie identyfikatory. Pole subFormIds pozostaje jako powiązanie sekcja→podformularz.
{ "data": {
"id": "78132d76-…", "projectId": "ff315ea1-…", "schemaId": "d6aea4da-…",
"formType": "custom", "schemaType": "default",
"schemaName": { "pl": "Formularz aplikacyjny" },
"version": "2026-04-09T12:55:57+00:00",
"isEmailRequired": true, "isPhoneNumberRequired": true, "isCvRequired": true,
"sections": [
{ "id": "1f0d3c8a-…", "name": { "pl": "Dane podstawowe" }, "order": 0,
"questions": [
{ "id": "9a2c7e44-…", "type": "oneAnswer", "subtype": null,
"label": { "pl": "Forma zatrudnienia" }, "isRequired": true, "order": 0,
"answers": [
{ "id": "b7e1d2a3-…", "content": { "pl": "Umowa o pracę" },
"isDisqualifying": false, "order": 0 } ] } ],
"subFormIds": ["30c5a856-…"],
"subForms": [ // ◆ v1.2: rozwinięte
{ "id": "30c5a856-…", "type": "week-days",
"name": { "pl": "W jakich dniach możesz się spotkać?" },
"sections": [
{ "id": "c1c4ae4c-…", "name": { "pl": "pn." }, "order": 0,
"questions": [
{ "id": "4a793138-…", "type": "yesNo", "subtype": "weekDay",
"label": { "pl": "pn." }, "isRequired": false, "order": 0 },
{ "id": "499a4fed-…", "type": "oneAnswer", "subtype": "hourFrom",
"label": { "pl": "Od godziny" } } ],
"subFormIds": [], "subForms": [] } ] } ] } ] } }
Pola sekcji: id, name, description, payload, order, questions[], subFormIds[], subForms[].
Obiekt podformularza (subForms[]): id (zgodny z pozycją w subFormIds sekcji-rodzica), type (np. calendar-days, week-days), name (mapa lokalizacji), sections[] (ta sama struktura co sekcje najwyższego poziomu — rekurencyjnie z questions, subFormIds, subForms).
Obiekt pytania: id (klucz w mapie answers dla POST /apply), type (oneAnswer, multiAnswers, scale, yesNo, open, financialRequirements, languages, file, referral, …), subtype (np. calendarDay, weekDay, hourFrom, hourTo), label, isRequired, order, answers[] (warianty: id — wartość dla oneAnswer/multiAnswers — content, isDisqualifying, order).
Błędy: 401, 404 project_not_found, 404 application_form_not_found, 429.
8.7 — Wypełniony formularz kandydata
Zwraca wypełniony formularz danego kandydata — sekcje z pytaniami i przesłanymi odpowiedziami.
{ "data": { "projectId": "ff315ea1-…", "candidateId": "87c42b09-…",
"applicationFormId": "78132d76-…", "submittedAt": "2026-05-27T17:57:48+00:00",
"formVersion": "2026-04-09T12:55:57+00:00", "locale": "pl",
"sections": [
{ "title": "Pytania aplikacyjne", "items": [
{ "questionId": "ae8a8a8a-…", "label": "Ocena PHP",
"type": "scale", "subtype": null, "answer": "1" } ] } ] } }
items[].answer zależy od type (string | int | bool | tablica | null = brak odpowiedzi). Wewnętrzny scoring (obtainedScore, isDisqualified) oraz pytania plikowe są celowo pominięte.
Błędy: 401, 404 project_not_found, 404 application_form_not_found, 429.
8.8 — Profil kandydata
Pełny profil (kontakt, doświadczenie, edukacja, umiejętności, języki, certyfikaty). Pola m.in. experience[], education[], skills[], languageSkills[] (cefrLevel A1–C2), certifications[]. Wewnętrzny description CRM jest wykluczony.
Błędy: 401, 404 candidate_not_found, 429.
8.9 — Projekty kandydata
Stronicowana lista aktywnych projektów, w których uczestniczy kandydat (malejąco po dacie dodania).
Błędy: 401, 404 candidate_not_found, 429.
8.10 — Publiczne notatki kandydata
Stronicowana lista publicznych notatek. Notatki prywatne nigdy nie są ujawniane. Opcjonalny projectId zawęża do kontekstu projektu. Pola: id, content, createdAt, createdBy, projectId (lub null).
Błędy: 401, 404 candidate_not_found, 429.
8.11 — Aplikacja (utworzenie kandydata) ZMIANA v1.3
Tworzy nowego kandydata i dodaje go do projektu na etapie aplikacyjnym.
Każde wywołanie tworzy nowego kandydata — API nie deduplikuje po e-mailu. Aby uniknąć duplikatów, sprawdź najpierw /projects/{id}/candidates. Aplikując bez e-maila (dozwolone od v1.3), tracisz też możliwość deduplikacji po adresie — rozważ własny klucz idempotencji.
Content-Type: multipart/form-data (z plikami) lub application/x-www-form-urlencoded (bez plików).
| Pole | Wym. | Typ | Limit | Opis |
|---|---|---|---|---|
| firstName | tak | string | 255 | Imię |
| lastName | tak | string | 255 | Nazwisko |
| nie | string | 255 | v1.3E-mail — od v1.3 opcjonalny; jeśli podany, jest walidowany (błędny format → 400 validation_failed). Pominięcie pola tworzy kandydata bez adresu e-mail. | |
| phoneNumber | nie | string | 32 | Telefon (dowolny format) |
| source | nie | string | 64 | Identyfikator źródła; domyślnie external-api |
| cv | nie | file | 5 MB | CV kandydata. Zapisywane jako dokument CV. |
| attachments[] | nie | file[] | 3 × 5 MB | v1.2Do 3 dodatkowych plików (poza CV), wysyłanych jako powtarzane części multipart attachments[]. Te same dozwolone typy/rozmiar co cv. Każdy zapisywany jako dokument kandydata „Inne". |
| answers | nie | string (JSON) | — | Mapa {questionId: wartość} zakodowana jako JSON. Pomiń, by aplikować bez odpowiedzi. |
| version | warunk. | string | — | Wymagane gdy podano answers. Wartość z pola version w GET …/application-form; niezgodność → 409 stale_form_version. |
Mapowanie wartości answers wg typu pytania:
| Typ pytania | Wartość w answers |
|---|---|
| open, yesNo, oneAnswer | string (dla oneAnswer — id wariantu odpowiedzi) |
| scale | integer |
| financialRequirements | number |
| multiAnswers | tablica id wariantów [id1, id2, …] |
| calendarDay (subtype; podformularz calendar-days) | v1.2data jako string YYYY-MM-DD (np. 2026-06-30) |
| languages, file, referral | niedozwolone — pomiń te pytania w answers |
Odpowiedź 201:
{ "candidateId": "4b257ea8-…", "projectId": "ff315ea1-…",
"answerSheetId": "3c9b1f7e-…" } // answerSheetId tylko gdy podano poprawne answers; inaczej null
Imię, nazwisko, e-mail i telefon przekazane w POST /apply są zapisywane także w arkuszu odpowiedzi — rekruter widzi je w podglądzie formularza kandydata w Element (wcześniej sekcja danych podstawowych była w podglądzie pusta dla aplikacji z API). Kontrakt żądania i odpowiedzi pozostaje bez zmian. Dotyczy aplikacji złożonych po wdrożeniu v1.3 — wcześniejsze arkusze nie są uzupełniane wstecznie.
Przykład (kontakt + CV + załączniki + odpowiedzi):
curl --user "api+voicebot@acme.local:<hasło>" \
-F "firstName=Anna" -F "lastName=Nowak" -F "email=anna.nowak@example.com" \
-F "source=voicebot-inbound" \
-F "cv=@./cv.pdf" \
-F "attachments[]=@./portfolio.pdf" -F "attachments[]=@./certyfikat.png" \
-F 'version=2026-04-09T12:55:57+00:00' \
-F 'answers={"9a2c7e44-…":"b7e1d2a3-…","cal-day-q-id":"2026-06-30"}' \
https://acme.elementapp.ai/api/v1-beta/projects/ff315ea1-…/apply
Przykład minimalny (bez e-maila, od v1.3):
curl --user "api+voicebot@acme.local:<hasło>" \
-F "firstName=Anna" -F "lastName=Nowak" -F "phoneNumber=+48 500 100 200" \
https://acme.elementapp.ai/api/v1-beta/projects/ff315ea1-…/apply
Błędy: 400 validation_failed, 400 invalid_answers, 401, 404 project_not_found, 409 no_application_stage, 409 stale_form_version, 429.
8.12 — Dodanie notatki do kandydata
Dodaje publiczną notatkę (autor: konto API, w UI jako API: <nazwa-systemu>). Content-Type: application/x-www-form-urlencoded. Pola: content (wymagane, do 10 000 znaków), projectId (opcjonalne). Odpowiedź 201: { "noteId": "…" }.
Błędy: 400 validation_failed, 401, 404 candidate_not_found, 429.
9Scenariusze integracji
9.1 — Voice-bot: zgłoszenie aplikacji
Bot dzwoni do kandydata, zbiera dane, składa aplikację (kontakt + CV) i dodaje transkrypcję jako notatkę.
9.2 — Partner ATS: synchronizacja statusów
ATS cyklicznie pobiera statusy kandydatów w projektach; zachowaj odstęp ≥1 s między wywołaniami (limit 70/min).
9.3 — Pipeline danych: eksport odpowiedzi
System analityczny pobiera listę kandydatów, a dla każdego wypełniony formularz (z parametrem locale); odstęp ≥1 s.
10Bezpieczeństwo i higiena
- Poświadczenia: jeden zestaw na system; hasło w sejfie sekretów; nigdy w repo/czacie; rotacja przez „Wygeneruj nowe hasło" (stare unieważniane natychmiast).
- Idempotencja (po stronie integratora): API nie deduplikuje
POST /apply. Strategie: sprawdź kandydatów po e-mailu przed wysłaniem (jeśli go zbierasz — od v1.3 e-mail jest opcjonalny), logujcandidateIdprzed oznaczeniem operacji jako zakończonej, lub użyj wzorca outbox z kluczem idempotencji. - Backoff: wykładniczy od 1 s (limit 30 s) na 429; rozkładaj operacje wsadowe w czasie; unikaj równoległych żądań na jednym poświadczeniu.
- RODO/GDPR: dane kandydatów to dane osobowe (obowiązki po Twojej stronie). Element zwraca
404 candidate_not_founddla kandydatów usuniętych RODO — usuń też swoją kopię.
11Słownik
- Tenant — klient Element (firma); identyfikowany po subdomenie; poświadczenia zablokowane do jednego tenanta.
- Projekt — proces rekrutacyjny na jedno stanowisko; ma listę etapów.
- Etap — krok rekrutacji (Aplikacja, Rozmowa, Oferta…). Etap aplikacyjny (
isApplicationType: true) — miejsce lądowania nowych zgłoszeń, max jeden na projekt. - Kandydat — osoba w CRM Element; może uczestniczyć w wielu projektach.
- Podformularz (sub-form) — zagnieżdżony blok pytań w sekcji (np.
calendar-days,week-days). Od v1.2 zwracany rozwinięty wsubForms[]. - Notatka publiczna / prywatna — publiczne widoczne dla konsultantów tenanta i dostępne przez API; prywatne nigdy nie eksponowane.
- RODO / zapomniany — kandydat zażądał usunięcia danych; zwraca 404.
12Zakres i roadmapa
Dostępne w v1-beta. Odczyt: projekty, etapy, kandydaci w projekcie, pełny schemat formularza (z rozwiniętymi podformularzami), wypełnione odpowiedzi kandydata, profil kandydata, projekty kandydata, publiczne notatki. Zapis: zgłoszenie aplikacji (nowy kandydat; wymagane tylko imię i nazwisko — e-mail, telefon, CV, załączniki i odpowiedzi opcjonalne), publiczna notatka.
Planowane (po v1-beta). Zmiana etapu kandydata, odrzucenie, aktualizacja scoringu, dodanie istniejącego kandydata do projektu, utworzenie projektu.
Celowo pominięte. Notatki prywatne (odczyt/zapis), wewnętrzny description CRM, wewnętrzne pola scoringu (obtainedScore, isDisqualified), odpowiedzi na pytania typu file/referral/languages w POST /apply.
Kontrakt może zmienić się niekompatybilnie w trakcie bety (~2–3 mies. użycia produkcyjnego); zmiany łamiące komunikowane z wyprzedzeniem. Dodatki minorowe (nowe opcjonalne pola, endpointy, kody błędów) nie są łamiące — Twój klient HTTP powinien tolerować dodatkowe pola.
13Kontakt i wsparcie
Zgłoszenia przez standardowy kanał wsparcia Element. Dołącz: subdomenę, login API, znacznik czasu UTC wywołania, metodę + ścieżkę, status HTTP, treść błędu (przynajmniej error.code i error.message) oraz własny identyfikator korelacji. Te dane pozwalają szybko odnaleźć żądanie w logu audytowym.