WelcomeKit¶
WelcomeKit, Welcome to the Jungle's ATS (sold as Welcome to the Jungle Solutions), observed on live postings, last on 2026-10-29. Its forms need no account. The fixture pages are tests/pages/welcomekit/: old.html, served as welcomekit.co, and new.html with its job's new.json, served as ats.welcometothejungle.com and its API in tests/test_welcomekit.py: one example of each rendering below.
Where the form is¶
Two forms, on two hosts, chosen per employer:
- The older form:
www.welcomekit.co/candidates/new?o=<organization>&j=<job>, both references required; a missing or unknown one answers 200 with onlyERROR, missing organization or job:posting_gone. The double-send key is that URL with onlyoandj. Freehire lists no WelcomeKit source: the postings are on job boards and career sites WelcomeKit hosts, whose Postuler button carries both references (.btn-job-apply[data-organization-reference][data-job-reference]) and opens the form in a frame. The postings surveyed came from one such board. - The newer form:
ats.welcometothejungle.com/board/<organization>/job/<id>/apply, the job's page without/applyshowing its description instead; the adapter goes to/apply. The double-send key is the/applyURL without its query. Freehire lists these postings under itswttjsource, whose apply URL is this one.
The older form¶
form#new_candidate, a Rails simple_form rendered on the server. request_read fetches it over HTTP, browser_read takes the page's HTML, and one parser lists the fields: parity holds by construction. A question's text is posted with its answer from a hidden input; hidden inputs and a .hidden group are not listed.
A field's id is its name without candidate[ and profile_attributes: firstname, country_code, resume, media_linkedin, cover_letter, consent; a question's answers.<n>. Its label is its label[for], without the *; an address part has none, so it is Adresse: and its placeholder, the country list Adresse: Country. A control's required attribute makes it required.
Each rendering, and in how many of 29 postings (29 employers) it shows (2026-10-28):
| Rendering | Field type | Postings |
|---|---|---|
| text input: first and last name, current position, street, postal code, town, LinkedIn, website, Twitter, Behance, Dribbble; email input | text |
29 (website 26, Twitter 24, Behance and Dribbble 1) |
type=tel |
phone |
29 |
| native select, the country, France selected, countries in English | choice, default France |
29 |
file input: CV (accept PDF), profile photo (no accept) |
file; the photo accepts PNG, JPEG and GIF |
29; photo 23 |
| textarea: cover letter, each question | text |
26; questions 12 |
| consent checkbox, drawn over by an iCheck box | choice, Yes/No, never required |
29 |
Required varies per employer: the CV in 28, LinkedIn in 8, the cover letter in 7, every question in 12 of 12; the names, the position, the email and the phone in all.
The newer form¶
A React app. Its job comes from GET api.ats.welcometothejungle.com/api/v1/board/organizations/<organization>/jobs/<id>: a job whose status is not published, or a 404, is posting_gone. The form is the same personal fields on every posting, then the job's questions, sorted by order, each with a label, a type and required. Both reads list the fields from that JSON, the request read over HTTP and the browser read by a fetch from the page; the browser read then waits for each question's control.
| Rendering | Field | Field type | Postings |
|---|---|---|---|
text input: firstname, lastname, email (required) |
its name | text |
19 |
type=url: linkedin_url, personal_website_url |
its name | url |
19 |
type=tel: phone_number |
its name | phone |
19 |
upload widget: cv, PDF, required |
cv |
file |
19 |
question text: textarea custom_questions.<n>.answer |
custom_questions.<n> |
text |
4 (7 questions) |
question boolean: Yes and No buttons, data-selected on the chosen one |
custom_questions.<n> |
choice, Yes/No |
4 (8 questions) |
question document: an upload widget like cv |
custom_questions.<n> |
file |
0, from the app's code |
A question of another type is unhandled_rendering; the app draws any type but boolean and document as a textarea.
Filling¶
- Older form: a text input or textarea is filled, the select set by its option's text, a file attached to its input (it travels with the post). The consent box is under its iCheck box: its label is clicked.
- Newer form: an input is filled. A file is uploaded as soon as it is attached (a presigned URL from
/api/v1/assets/presigned-url, then storage); the widget then empties its input and shows the name and size in a ticked badge, which the fill waits for. A boolean question's Yes or No button is clicked unless already chosen; a click on the chosen one clears it.
Read-back and submit¶
- The read-back is each control's value, a select's option text, the box as
Yes/No, the older form's attached file's name, the newer form's badge's name without its size, and a boolean question's chosen button. - Older form: a reCAPTCHA v2 box sits above the submit button.
submitticks it, as BambooHR's does,Blockedon an image challenge, then clicks Je valide ma candidature, which posts the form to/candidates?j=<job>&o=<organization>. The evidence is the page it lands on; the form shown again is a failure, with its messages. The page after a send is unobserved. - Newer form: the page warns that a job takes one application. Submit application runs an invisible reCAPTCHA Enterprise check, then posts the answers as JSON to
/api/v1/board/organizations/<organization>/jobs/<id>/apply; the evidence is that POST's URL and status, a status other than 2xx a failure. Its answer is unobserved. - No send has been made through this adapter.
Known gaps¶
None met.