atsfill 0.0.4.dev133__tar.gz → 0.0.4.dev134__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/PKG-INFO +2 -2
  2. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/README.md +1 -1
  3. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/pyproject.toml +1 -1
  4. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/pyproject.toml.orig +1 -1
  5. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/successfactors.js +3 -0
  6. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/successfactors.py +67 -14
  7. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/registry.toml +1 -1
  8. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/LICENSE +0 -0
  9. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/__init__.py +0 -0
  10. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/__init__.py +0 -0
  11. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/ashby.py +0 -0
  12. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/bamboohr.py +0 -0
  13. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/breezy.py +0 -0
  14. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/dayforce.py +0 -0
  15. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/eightfold.py +0 -0
  16. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/greenhouse.py +0 -0
  17. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/gupy.py +0 -0
  18. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/hireology.py +0 -0
  19. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/hiringthing.py +0 -0
  20. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/hrmdirect.py +0 -0
  21. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/isolvedhire.py +0 -0
  22. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/jazzhr.py +0 -0
  23. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/join.py +0 -0
  24. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/lever.py +0 -0
  25. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/manatal.py +0 -0
  26. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/oracle.py +0 -0
  27. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/paycor.py +0 -0
  28. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/paylocity.py +0 -0
  29. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/personio.py +0 -0
  30. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/phenom.py +0 -0
  31. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/pinpoint.py +0 -0
  32. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/recruitee.py +0 -0
  33. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/rippling.py +0 -0
  34. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/smartrecruiters.py +0 -0
  35. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/taleo.py +0 -0
  36. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/teamtailor.py +0 -0
  37. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/ukg.py +0 -0
  38. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/workable.py +0 -0
  39. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/workday.py +0 -0
  40. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/wttj.py +0 -0
  41. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/adapters/zohorecruit.py +0 -0
  42. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/cli.py +0 -0
  43. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/__init__.py +0 -0
  44. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/accounts.py +0 -0
  45. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/adapter.py +0 -0
  46. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/attempts.py +0 -0
  47. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/browser.py +0 -0
  48. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/commands.py +0 -0
  49. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/conditions.py +0 -0
  50. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/html.py +0 -0
  51. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/matching.py +0 -0
  52. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/passwords.py +0 -0
  53. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/paths.py +0 -0
  54. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/registry.py +0 -0
  55. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/core/results.py +0 -0
  56. {atsfill-0.0.4.dev133 → atsfill-0.0.4.dev134}/src/atsfill/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atsfill
3
- Version: 0.0.4.dev133
3
+ Version: 0.0.4.dev134
4
4
  Summary: Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle.
5
5
  Keywords: job-applications,ats,ashby,greenhouse,teamtailor,ai-agents,cli
6
6
  Author: shbernal
@@ -57,7 +57,7 @@ It is built for agents with a shell: every command prints one JSON document and
57
57
  ## Supported hiring systems
58
58
 
59
59
  <!-- badges -->
60
- ![oracle: dry run verified 2026-10-20](https://img.shields.io/badge/oracle-dry%20run%20verified%202026--10--20-7aa2f7) ![smartrecruiters: dry run verified 2026-10-20](https://img.shields.io/badge/smartrecruiters-dry%20run%20verified%202026--10--20-7aa2f7) ![greenhouse: dry run verified 2026-10-19](https://img.shields.io/badge/greenhouse-dry%20run%20verified%202026--10--19-7aa2f7) ![manatal: dry run verified 2026-10-20](https://img.shields.io/badge/manatal-dry%20run%20verified%202026--10--20-7aa2f7) ![paylocity: dry run verified 2026-10-20](https://img.shields.io/badge/paylocity-dry%20run%20verified%202026--10--20-7aa2f7) ![dayforce: dry run verified 2026-10-20](https://img.shields.io/badge/dayforce-dry%20run%20verified%202026--10--20-7aa2f7) ![workable: dry run verified 2026-10-20](https://img.shields.io/badge/workable-dry%20run%20verified%202026--10--20-7aa2f7) ![ashby: send verified 2026-10-19](https://img.shields.io/badge/ashby-send%20verified%202026--10--19-9ece6a) ![bamboohr: dry run verified 2026-10-21](https://img.shields.io/badge/bamboohr-dry%20run%20verified%202026--10--21-7aa2f7) ![teamtailor: send verified 2026-10-20](https://img.shields.io/badge/teamtailor-send%20verified%202026--10--20-9ece6a) ![jazzhr: dry run verified 2026-10-20](https://img.shields.io/badge/jazzhr-dry%20run%20verified%202026--10--20-7aa2f7) ![lever: dry run verified 2026-10-20](https://img.shields.io/badge/lever-dry%20run%20verified%202026--10--20-7aa2f7) ![hireology: dry run verified 2026-10-20](https://img.shields.io/badge/hireology-dry%20run%20verified%202026--10--20-7aa2f7) ![paycor: dry run verified 2026-10-20](https://img.shields.io/badge/paycor-dry%20run%20verified%202026--10--20-7aa2f7) ![zohorecruit: dry run verified 2026-10-22](https://img.shields.io/badge/zohorecruit-dry%20run%20verified%202026--10--22-7aa2f7) ![phenom: dry run verified 2026-10-21](https://img.shields.io/badge/phenom-dry%20run%20verified%202026--10--21-7aa2f7) ![breezy: dry run verified 2026-10-21](https://img.shields.io/badge/breezy-dry%20run%20verified%202026--10--21-7aa2f7) ![recruitee: dry run verified 2026-10-25](https://img.shields.io/badge/recruitee-dry%20run%20verified%202026--10--25-7aa2f7) ![rippling: dry run verified 2026-10-22](https://img.shields.io/badge/rippling-dry%20run%20verified%202026--10--22-7aa2f7) ![personio: dry run verified 2026-10-22](https://img.shields.io/badge/personio-dry%20run%20verified%202026--10--22-7aa2f7) ![eightfold: dry run verified 2026-10-22](https://img.shields.io/badge/eightfold-dry%20run%20verified%202026--10--22-7aa2f7) ![pinpoint: dry run verified 2026-10-22](https://img.shields.io/badge/pinpoint-dry%20run%20verified%202026--10--22-7aa2f7) ![hiringthing: dry run verified 2026-10-22](https://img.shields.io/badge/hiringthing-dry%20run%20verified%202026--10--22-7aa2f7) ![join: dry run verified 2026-10-26](https://img.shields.io/badge/join-dry%20run%20verified%202026--10--26-7aa2f7) ![wttj: send verified 2026-10-20](https://img.shields.io/badge/wttj-send%20verified%202026--10--20-9ece6a) ![workday: dry run verified 2026-10-24](https://img.shields.io/badge/workday-dry%20run%20verified%202026--10--24-7aa2f7) ![isolvedhire: dry run verified 2026-10-26](https://img.shields.io/badge/isolvedhire-dry%20run%20verified%202026--10--26-7aa2f7) ![gupy: dry run verified 2026-10-26](https://img.shields.io/badge/gupy-dry%20run%20verified%202026--10--26-7aa2f7) ![successfactors: unsupported 2026-10-26](https://img.shields.io/badge/successfactors-unsupported%202026--10--26-e0af68) ![ukg: unsupported 2026-10-26](https://img.shields.io/badge/ukg-unsupported%202026--10--26-e0af68)
60
+ ![oracle: dry run verified 2026-10-20](https://img.shields.io/badge/oracle-dry%20run%20verified%202026--10--20-7aa2f7) ![smartrecruiters: dry run verified 2026-10-20](https://img.shields.io/badge/smartrecruiters-dry%20run%20verified%202026--10--20-7aa2f7) ![greenhouse: dry run verified 2026-10-19](https://img.shields.io/badge/greenhouse-dry%20run%20verified%202026--10--19-7aa2f7) ![manatal: dry run verified 2026-10-20](https://img.shields.io/badge/manatal-dry%20run%20verified%202026--10--20-7aa2f7) ![paylocity: dry run verified 2026-10-20](https://img.shields.io/badge/paylocity-dry%20run%20verified%202026--10--20-7aa2f7) ![dayforce: dry run verified 2026-10-20](https://img.shields.io/badge/dayforce-dry%20run%20verified%202026--10--20-7aa2f7) ![workable: dry run verified 2026-10-20](https://img.shields.io/badge/workable-dry%20run%20verified%202026--10--20-7aa2f7) ![ashby: send verified 2026-10-19](https://img.shields.io/badge/ashby-send%20verified%202026--10--19-9ece6a) ![bamboohr: dry run verified 2026-10-21](https://img.shields.io/badge/bamboohr-dry%20run%20verified%202026--10--21-7aa2f7) ![teamtailor: send verified 2026-10-20](https://img.shields.io/badge/teamtailor-send%20verified%202026--10--20-9ece6a) ![jazzhr: dry run verified 2026-10-20](https://img.shields.io/badge/jazzhr-dry%20run%20verified%202026--10--20-7aa2f7) ![lever: dry run verified 2026-10-20](https://img.shields.io/badge/lever-dry%20run%20verified%202026--10--20-7aa2f7) ![hireology: dry run verified 2026-10-20](https://img.shields.io/badge/hireology-dry%20run%20verified%202026--10--20-7aa2f7) ![paycor: dry run verified 2026-10-20](https://img.shields.io/badge/paycor-dry%20run%20verified%202026--10--20-7aa2f7) ![zohorecruit: dry run verified 2026-10-22](https://img.shields.io/badge/zohorecruit-dry%20run%20verified%202026--10--22-7aa2f7) ![phenom: dry run verified 2026-10-21](https://img.shields.io/badge/phenom-dry%20run%20verified%202026--10--21-7aa2f7) ![breezy: dry run verified 2026-10-21](https://img.shields.io/badge/breezy-dry%20run%20verified%202026--10--21-7aa2f7) ![recruitee: dry run verified 2026-10-25](https://img.shields.io/badge/recruitee-dry%20run%20verified%202026--10--25-7aa2f7) ![rippling: dry run verified 2026-10-22](https://img.shields.io/badge/rippling-dry%20run%20verified%202026--10--22-7aa2f7) ![personio: dry run verified 2026-10-22](https://img.shields.io/badge/personio-dry%20run%20verified%202026--10--22-7aa2f7) ![eightfold: dry run verified 2026-10-22](https://img.shields.io/badge/eightfold-dry%20run%20verified%202026--10--22-7aa2f7) ![pinpoint: dry run verified 2026-10-22](https://img.shields.io/badge/pinpoint-dry%20run%20verified%202026--10--22-7aa2f7) ![hiringthing: dry run verified 2026-10-22](https://img.shields.io/badge/hiringthing-dry%20run%20verified%202026--10--22-7aa2f7) ![join: dry run verified 2026-10-26](https://img.shields.io/badge/join-dry%20run%20verified%202026--10--26-7aa2f7) ![wttj: send verified 2026-10-20](https://img.shields.io/badge/wttj-send%20verified%202026--10--20-9ece6a) ![workday: dry run verified 2026-10-24](https://img.shields.io/badge/workday-dry%20run%20verified%202026--10--24-7aa2f7) ![successfactors: dry run verified 2026-10-26](https://img.shields.io/badge/successfactors-dry%20run%20verified%202026--10--26-7aa2f7) ![isolvedhire: dry run verified 2026-10-26](https://img.shields.io/badge/isolvedhire-dry%20run%20verified%202026--10--26-7aa2f7) ![gupy: dry run verified 2026-10-26](https://img.shields.io/badge/gupy-dry%20run%20verified%202026--10--26-7aa2f7) ![ukg: unsupported 2026-10-26](https://img.shields.io/badge/ukg-unsupported%202026--10--26-e0af68)
61
61
  <!-- /badges -->
62
62
 
63
63
  | System | Account needed | `read` without a browser |
@@ -29,7 +29,7 @@ It is built for agents with a shell: every command prints one JSON document and
29
29
  ## Supported hiring systems
30
30
 
31
31
  <!-- badges -->
32
- ![oracle: dry run verified 2026-10-20](https://img.shields.io/badge/oracle-dry%20run%20verified%202026--10--20-7aa2f7) ![smartrecruiters: dry run verified 2026-10-20](https://img.shields.io/badge/smartrecruiters-dry%20run%20verified%202026--10--20-7aa2f7) ![greenhouse: dry run verified 2026-10-19](https://img.shields.io/badge/greenhouse-dry%20run%20verified%202026--10--19-7aa2f7) ![manatal: dry run verified 2026-10-20](https://img.shields.io/badge/manatal-dry%20run%20verified%202026--10--20-7aa2f7) ![paylocity: dry run verified 2026-10-20](https://img.shields.io/badge/paylocity-dry%20run%20verified%202026--10--20-7aa2f7) ![dayforce: dry run verified 2026-10-20](https://img.shields.io/badge/dayforce-dry%20run%20verified%202026--10--20-7aa2f7) ![workable: dry run verified 2026-10-20](https://img.shields.io/badge/workable-dry%20run%20verified%202026--10--20-7aa2f7) ![ashby: send verified 2026-10-19](https://img.shields.io/badge/ashby-send%20verified%202026--10--19-9ece6a) ![bamboohr: dry run verified 2026-10-21](https://img.shields.io/badge/bamboohr-dry%20run%20verified%202026--10--21-7aa2f7) ![teamtailor: send verified 2026-10-20](https://img.shields.io/badge/teamtailor-send%20verified%202026--10--20-9ece6a) ![jazzhr: dry run verified 2026-10-20](https://img.shields.io/badge/jazzhr-dry%20run%20verified%202026--10--20-7aa2f7) ![lever: dry run verified 2026-10-20](https://img.shields.io/badge/lever-dry%20run%20verified%202026--10--20-7aa2f7) ![hireology: dry run verified 2026-10-20](https://img.shields.io/badge/hireology-dry%20run%20verified%202026--10--20-7aa2f7) ![paycor: dry run verified 2026-10-20](https://img.shields.io/badge/paycor-dry%20run%20verified%202026--10--20-7aa2f7) ![zohorecruit: dry run verified 2026-10-22](https://img.shields.io/badge/zohorecruit-dry%20run%20verified%202026--10--22-7aa2f7) ![phenom: dry run verified 2026-10-21](https://img.shields.io/badge/phenom-dry%20run%20verified%202026--10--21-7aa2f7) ![breezy: dry run verified 2026-10-21](https://img.shields.io/badge/breezy-dry%20run%20verified%202026--10--21-7aa2f7) ![recruitee: dry run verified 2026-10-25](https://img.shields.io/badge/recruitee-dry%20run%20verified%202026--10--25-7aa2f7) ![rippling: dry run verified 2026-10-22](https://img.shields.io/badge/rippling-dry%20run%20verified%202026--10--22-7aa2f7) ![personio: dry run verified 2026-10-22](https://img.shields.io/badge/personio-dry%20run%20verified%202026--10--22-7aa2f7) ![eightfold: dry run verified 2026-10-22](https://img.shields.io/badge/eightfold-dry%20run%20verified%202026--10--22-7aa2f7) ![pinpoint: dry run verified 2026-10-22](https://img.shields.io/badge/pinpoint-dry%20run%20verified%202026--10--22-7aa2f7) ![hiringthing: dry run verified 2026-10-22](https://img.shields.io/badge/hiringthing-dry%20run%20verified%202026--10--22-7aa2f7) ![join: dry run verified 2026-10-26](https://img.shields.io/badge/join-dry%20run%20verified%202026--10--26-7aa2f7) ![wttj: send verified 2026-10-20](https://img.shields.io/badge/wttj-send%20verified%202026--10--20-9ece6a) ![workday: dry run verified 2026-10-24](https://img.shields.io/badge/workday-dry%20run%20verified%202026--10--24-7aa2f7) ![isolvedhire: dry run verified 2026-10-26](https://img.shields.io/badge/isolvedhire-dry%20run%20verified%202026--10--26-7aa2f7) ![gupy: dry run verified 2026-10-26](https://img.shields.io/badge/gupy-dry%20run%20verified%202026--10--26-7aa2f7) ![successfactors: unsupported 2026-10-26](https://img.shields.io/badge/successfactors-unsupported%202026--10--26-e0af68) ![ukg: unsupported 2026-10-26](https://img.shields.io/badge/ukg-unsupported%202026--10--26-e0af68)
32
+ ![oracle: dry run verified 2026-10-20](https://img.shields.io/badge/oracle-dry%20run%20verified%202026--10--20-7aa2f7) ![smartrecruiters: dry run verified 2026-10-20](https://img.shields.io/badge/smartrecruiters-dry%20run%20verified%202026--10--20-7aa2f7) ![greenhouse: dry run verified 2026-10-19](https://img.shields.io/badge/greenhouse-dry%20run%20verified%202026--10--19-7aa2f7) ![manatal: dry run verified 2026-10-20](https://img.shields.io/badge/manatal-dry%20run%20verified%202026--10--20-7aa2f7) ![paylocity: dry run verified 2026-10-20](https://img.shields.io/badge/paylocity-dry%20run%20verified%202026--10--20-7aa2f7) ![dayforce: dry run verified 2026-10-20](https://img.shields.io/badge/dayforce-dry%20run%20verified%202026--10--20-7aa2f7) ![workable: dry run verified 2026-10-20](https://img.shields.io/badge/workable-dry%20run%20verified%202026--10--20-7aa2f7) ![ashby: send verified 2026-10-19](https://img.shields.io/badge/ashby-send%20verified%202026--10--19-9ece6a) ![bamboohr: dry run verified 2026-10-21](https://img.shields.io/badge/bamboohr-dry%20run%20verified%202026--10--21-7aa2f7) ![teamtailor: send verified 2026-10-20](https://img.shields.io/badge/teamtailor-send%20verified%202026--10--20-9ece6a) ![jazzhr: dry run verified 2026-10-20](https://img.shields.io/badge/jazzhr-dry%20run%20verified%202026--10--20-7aa2f7) ![lever: dry run verified 2026-10-20](https://img.shields.io/badge/lever-dry%20run%20verified%202026--10--20-7aa2f7) ![hireology: dry run verified 2026-10-20](https://img.shields.io/badge/hireology-dry%20run%20verified%202026--10--20-7aa2f7) ![paycor: dry run verified 2026-10-20](https://img.shields.io/badge/paycor-dry%20run%20verified%202026--10--20-7aa2f7) ![zohorecruit: dry run verified 2026-10-22](https://img.shields.io/badge/zohorecruit-dry%20run%20verified%202026--10--22-7aa2f7) ![phenom: dry run verified 2026-10-21](https://img.shields.io/badge/phenom-dry%20run%20verified%202026--10--21-7aa2f7) ![breezy: dry run verified 2026-10-21](https://img.shields.io/badge/breezy-dry%20run%20verified%202026--10--21-7aa2f7) ![recruitee: dry run verified 2026-10-25](https://img.shields.io/badge/recruitee-dry%20run%20verified%202026--10--25-7aa2f7) ![rippling: dry run verified 2026-10-22](https://img.shields.io/badge/rippling-dry%20run%20verified%202026--10--22-7aa2f7) ![personio: dry run verified 2026-10-22](https://img.shields.io/badge/personio-dry%20run%20verified%202026--10--22-7aa2f7) ![eightfold: dry run verified 2026-10-22](https://img.shields.io/badge/eightfold-dry%20run%20verified%202026--10--22-7aa2f7) ![pinpoint: dry run verified 2026-10-22](https://img.shields.io/badge/pinpoint-dry%20run%20verified%202026--10--22-7aa2f7) ![hiringthing: dry run verified 2026-10-22](https://img.shields.io/badge/hiringthing-dry%20run%20verified%202026--10--22-7aa2f7) ![join: dry run verified 2026-10-26](https://img.shields.io/badge/join-dry%20run%20verified%202026--10--26-7aa2f7) ![wttj: send verified 2026-10-20](https://img.shields.io/badge/wttj-send%20verified%202026--10--20-9ece6a) ![workday: dry run verified 2026-10-24](https://img.shields.io/badge/workday-dry%20run%20verified%202026--10--24-7aa2f7) ![successfactors: dry run verified 2026-10-26](https://img.shields.io/badge/successfactors-dry%20run%20verified%202026--10--26-7aa2f7) ![isolvedhire: dry run verified 2026-10-26](https://img.shields.io/badge/isolvedhire-dry%20run%20verified%202026--10--26-7aa2f7) ![gupy: dry run verified 2026-10-26](https://img.shields.io/badge/gupy-dry%20run%20verified%202026--10--26-7aa2f7) ![ukg: unsupported 2026-10-26](https://img.shields.io/badge/ukg-unsupported%202026--10--26-e0af68)
33
33
  <!-- /badges -->
34
34
 
35
35
  | System | Account needed | `read` without a browser |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.4.dev133"
3
+ version = "0.0.4.dev134"
4
4
  description = "Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.4.dev133"
3
+ version = "0.0.4.dev134"
4
4
  description = "Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -26,6 +26,9 @@
26
26
  d.kind = 'picklist';
27
27
  d.id = picklist.id;
28
28
  d.owns = picklist.getAttribute('aria-owns');
29
+ d.placeholder = picklist.placeholder; // its empty item's text, in the page's language
30
+ const opener = box.querySelector('button.rcmpaginatedselectbutton');
31
+ d.disabled = !!opener && opener.disabled; // a cascading list before its parent is chosen
29
32
  d.value = picklist.value === picklist.placeholder ? '' : picklist.value;
30
33
  d.required = d.required || picklist.getAttribute('aria-required') === 'true';
31
34
  } else if (radios.length) {
@@ -11,7 +11,7 @@ import re
11
11
  import time
12
12
  from pathlib import Path
13
13
  from typing import Any
14
- from urllib.parse import urlsplit
14
+ from urllib.parse import parse_qs, urlsplit
15
15
 
16
16
  from atsfill.core.adapter import (
17
17
  Blocked,
@@ -22,7 +22,7 @@ from atsfill.core.adapter import (
22
22
  Unsupported,
23
23
  known_gap,
24
24
  )
25
- from atsfill.core.browser import Locator, Page, Request, Timeout
25
+ from atsfill.core.browser import Locator, Page, PageRequest, Request, Timeout
26
26
  from atsfill.core.results import Answer, Field, FieldType, SideEffect
27
27
 
28
28
  JOB = re.compile(r"^(?:/[^/]+)?/job/[^/]+/(?P<id>\d{5,})/?$")
@@ -30,13 +30,19 @@ DC_HOST = re.compile(r"^career\d*\.(?:sapsf|successfactors)\.(?:eu|com|cn)$")
30
30
  APPLY = "a[href*='/talentcommunity/apply/']"
31
31
  COMPANY_COOKIE = "careerSiteCompanyId"
32
32
  PROFILE = "/career?company={company}&career_ns=job_listing_summary&navBarLevel=MY_PROFILE"
33
- SIGNED_IN = re.compile(r"\bsignOut|\blogout\b|_logout", re.IGNORECASE)
33
+ JOB_REQ = "career_job_req_id" # the job the apply link posts, the candidate pages' own id
34
+ APPLICATION = "/career?company={company}&career_ns=job_application&career_job_req_id={req}"
35
+ GUIDED = "body.cxApplyEnabled" # a site whose application is a step-by-step one, not sections
36
+ HOME = "body.careerUserLoggedIn" # any candidate page signed in, the candidate home's too
37
+ # The body's class for an account; a signed-out page also holds a log-out link and its label
38
+ SIGNED_IN = re.compile(r"<body[^>]*\bclass=[\"'][^\"']*\bcareerUserLoggedIn\b")
34
39
  FIELDS = (Path(__file__).with_suffix(".js")).read_text()
35
40
  # What the candidate pages show once loaded
36
41
  SIGN_IN = "#username"
37
42
  COUNTRY = "fbclc_country" # its options' values are ISO 3166 alpha-2 codes
38
43
  PHONE, CALLING_CODE = "#fbclc_phoneNumber", "#fbclc_ituCode" # `CA`: `Canada (+1)`
39
44
  SIGN_UP = "#fbclc_userName"
45
+ PASSCODE = "#passcode" # an OTP some sign-ups ask before the account exists
40
46
  QUICK_APPLY = "#fbqa_apply"
41
47
  FORM = ".RCMFormField"
42
48
  LANDED = f"{SIGN_IN}, {SIGN_UP}, {FORM}"
@@ -47,6 +53,8 @@ SIGN_IN_MODAL = "a[onclick*=openSignInModal]" # a Quick Apply page's
47
53
  REFUSAL = re.compile(r"required|exist|already|invalid|bereits|erforderlich|ungültig", re.I)
48
54
  ACTIVATE = re.compile(r"activat|verify your|verification e-?mail|bestätig", re.I)
49
55
  PRIVACY = "#dataPrivacyId"
56
+ VISIBILITY = "input[name=fbclc_searPref]" # who may see the profile
57
+ APPLIED_ONLY = "2" # its option for only the recruiters of the jobs applied to
50
58
  EXPAND = "[id$=_expandAllSections]"
51
59
  SUBMIT = "[id$=_submitBtn]"
52
60
  OVERLAY = "#sfOverlayMgr" # a confirmation dialog
@@ -88,6 +96,15 @@ def _company(request: Request) -> list[tuple[str, str]]:
88
96
  )
89
97
 
90
98
 
99
+ def _posts_job(request: PageRequest) -> bool:
100
+ """Whether the apply link's request posts a job to the candidate pages."""
101
+ return (
102
+ request.method == "POST"
103
+ and bool(DC_HOST.match(urlsplit(request.url).hostname or ""))
104
+ and JOB_REQ in (request.post_data or "")
105
+ )
106
+
107
+
91
108
  def _calling_code(select: Locator, details: SignUpDetails) -> tuple[str, str]:
92
109
  """The calling code's option for the E.164 phone, the one of `country` among the countries
93
110
  sharing it, and the national number that follows the code."""
@@ -108,7 +125,10 @@ class SuccessFactors:
108
125
  name = "successfactors"
109
126
  per_employer = True
110
127
  asks = {"name"}
111
- known_gaps = {"quick_apply_sign_up": Gap.FLOW, "sign_up_fields": Gap.FLOW}
128
+ known_gaps = {
129
+ "quick_apply_sign_up": Gap.FLOW,
130
+ "guided_application": Gap.FLOW,
131
+ }
112
132
  side_effects = [
113
133
  SideEffect("sign_up", "employer", "the account is created in the employer's tenant"),
114
134
  SideEffect("fill", "hiring_system", "an attachment is uploaded to the account's profile"),
@@ -140,7 +160,7 @@ class SuccessFactors:
140
160
  return self._postings.get(scope or "", f"https://{scope}/")
141
161
 
142
162
  def signed_in(self, request: Request, scope: str | None) -> bool:
143
- """The profile page of the session's company shows a sign-out for an account."""
163
+ """The profile page of the session's company marks its body for an account."""
144
164
  for host, company in _company(request):
145
165
  response = request.get(f"https://{host}{PROFILE.format(company=company)}")
146
166
  if response.ok and SIGNED_IN.search(response.text()):
@@ -165,8 +185,9 @@ class SuccessFactors:
165
185
 
166
186
  def sign_up(self, page: Page, email: str, password: str, details: SignUpDetails) -> None:
167
187
  """Creates the account from the career site's sign-up: the name, the country of
168
- residence from `country` (the list's first without it), and the phone where the site
169
- requires one, its calling code picked from the number; a missing phone is refused
188
+ residence from `country` (the list's first without it), the phone where the site
189
+ requires one, its calling code picked from the number, and the profile visible only to
190
+ the recruiters of the jobs applied to where the site asks; a missing phone is refused
170
191
  before anything is sent."""
171
192
  self._candidate(page)
172
193
  if page.locator(SIGN_IN).count():
@@ -206,6 +227,11 @@ class SuccessFactors:
206
227
  if wanted and wanted not in values:
207
228
  raise Unsupported(f"unhandled_rendering: the sign-up's countries lack {wanted}")
208
229
  select.select_option(wanted or values[0])
230
+ if page.locator(f"{VISIBILITY}:visible").count():
231
+ least = page.locator(f"{VISIBILITY}[value='{APPLIED_ONLY}']")
232
+ if not least.count():
233
+ raise Unsupported("unhandled_rendering: the profile's visibility has no jobs only")
234
+ least.check()
209
235
  if page.locator(PRIVACY).count():
210
236
  page.locator(PRIVACY).click()
211
237
  dialog = page.locator("[role=dialog]:visible").last
@@ -216,6 +242,8 @@ class SuccessFactors:
216
242
  page.wait_for_timeout(POLL_MS)
217
243
  if page.locator(FORM).count():
218
244
  return
245
+ if page.locator(f"{PASSCODE}:visible").count():
246
+ raise Blocked("email_otp: the sign-up sent a passcode to the email")
219
247
  if not page.locator(f"{SIGN_UP}:visible").count():
220
248
  break
221
249
  if self._message(page, REFUSAL):
@@ -226,7 +254,7 @@ class SuccessFactors:
226
254
  if re.search(r"password|kennwort", message, re.IGNORECASE):
227
255
  raise Refused(f"password_rule: {message}")
228
256
  if message:
229
- raise Unsupported(known_gap(self, "sign_up_fields", message))
257
+ raise Unsupported(f"unhandled_rendering: the sign-up refused: {message}")
230
258
  text = page.locator("body").inner_text()
231
259
  if ACTIVATE.search(text):
232
260
  raise Blocked("email_link: the sign-up sent an activation email")
@@ -249,8 +277,8 @@ class SuccessFactors:
249
277
  page.wait_for_timeout(SETTLE_MS)
250
278
  fields = []
251
279
  for d in page.evaluate(FIELDS):
252
- if d["kind"] == "picklist":
253
- d["options"] = self._options(page, d)
280
+ if d["kind"] == "picklist": # a disabled one waits for its parent: no items yet
281
+ d["options"] = [] if d["disabled"] else self._options(page, d)
254
282
  self._fields[d["key"]] = d
255
283
  fields.append(self._field(d))
256
284
  return fields
@@ -288,7 +316,7 @@ class SuccessFactors:
288
316
  items = self._open(page, d)
289
317
  options = [t for t in items.evaluate_all("as => as.map(a => a.title.trim())") if t]
290
318
  self._close(page, d)
291
- return [o for o in options if o != NO_SELECTION]
319
+ return [o for o in options if o not in (NO_SELECTION, d["placeholder"])]
292
320
 
293
321
  def _open(self, page: Page, d: dict[str, Any]) -> Locator:
294
322
  """Opens a picklist; a click can go unheard, or close a list another left open."""
@@ -343,6 +371,8 @@ class SuccessFactors:
343
371
  self._upload(page, d, value)
344
372
 
345
373
  def _pick(self, page: Page, d: dict[str, Any], value: str) -> None:
374
+ if d["disabled"]:
375
+ raise NotTaken(f"{d['key']} stays disabled: its parent's answer offers no items")
346
376
  items = self._open(page, d)
347
377
  titles = [t.strip() for t in items.evaluate_all("as => as.map(a => a.title)")]
348
378
  wanted = next((i for i, t in enumerate(titles) if t == value), None)
@@ -364,8 +394,8 @@ class SuccessFactors:
364
394
  confirm.first.wait_for(timeout=LIST_TIMEOUT_MS)
365
395
  confirm.first.click()
366
396
  deadline = time.monotonic() + UPLOAD_TIMEOUT_S
367
- while self._current(page, d["key"])["multiple"]:
368
- if time.monotonic() > deadline:
397
+ while (now := self._current(page, d["key"]))["multiple"] or now["id"] is None:
398
+ if time.monotonic() > deadline: # deleting, it shows neither button
369
399
  raise NotTaken(f"{d['key']} kept its file")
370
400
  page.wait_for_timeout(POLL_MS)
371
401
  source = page.locator("[role=dialog]:visible input[type=file]")
@@ -407,16 +437,39 @@ class SuccessFactors:
407
437
  return page.url
408
438
 
409
439
  def _candidate(self, page: Page) -> None:
410
- """From the posting, follows its apply link to the candidate pages."""
440
+ """From the posting, follows its apply link to the candidate pages. The link posts the
441
+ job there; signed in, some sites drop it and land on the candidate home, so the
442
+ application is opened from the job the link posted."""
411
443
  host = urlsplit(page.url).hostname or ""
444
+ posted: list[PageRequest] = []
412
445
  if not DC_HOST.match(host):
413
446
  apply = page.locator(APPLY)
414
447
  if not apply.count():
415
448
  raise Unsupported("posting_gone: the posting has no apply link")
449
+
450
+ def keep(request: PageRequest) -> None:
451
+ if _posts_job(request):
452
+ posted.append(request)
453
+
454
+ page.on("request", keep)
416
455
  apply.first.evaluate("a => a.click()") # some sites open a dialog on a real click
417
456
  try:
457
+ page.locator(f"{LANDED}, {HOME}").first.wait_for(
458
+ state="attached", timeout=LANDING_TIMEOUT_MS
459
+ )
460
+ page.wait_for_load_state(timeout=LANDING_TIMEOUT_MS)
461
+ if posted and JOB_REQ not in page.url and not page.locator(LANDED).count():
462
+ post = posted[0]
463
+ parts = urlsplit(post.url)
464
+ job = parse_qs(parts.query) | parse_qs(post.post_data or "")
465
+ application = APPLICATION.format(company=job["company"][0], req=job[JOB_REQ][0])
466
+ page.goto(f"https://{parts.hostname}{application}")
418
467
  page.locator(LANDED).first.wait_for(state="attached", timeout=LANDING_TIMEOUT_MS)
419
468
  except Timeout:
469
+ if page.locator(GUIDED).count():
470
+ raise Unsupported(
471
+ known_gap(self, "guided_application", "the application is a step-by-step one")
472
+ ) from None
420
473
  raise Unsupported(
421
474
  f"unhandled_rendering: the apply link led to {urlsplit(page.url).path}"
422
475
  ) from None
@@ -96,8 +96,8 @@ share = 0.0350
96
96
  account = "to_read"
97
97
  hosting = ["hosted"]
98
98
  adapter = "successfactors"
99
- unsupported = { date = 2026-10-26, version = "0.0.3", commit = "0d8baea", by = "agent", reason = "quick_apply_sign_up: a Quick Apply page creates the account and sends the application in one submit, its sign-in offering no sign-up; sign_up_fields: a sign-up asking who may see the profile" }
100
99
  mapped = { date = 2026-10-20, version = "0.0.2", by = "agent", commit = "7934b25" }
100
+ dry_run_verified = { date = 2026-10-26, version = "0.0.3", by = "adapter_gate", commit = "79c8327" }
101
101
 
102
102
  [ukg]
103
103
  kind = "ats"
File without changes