Automated bot for Spanish administrative appointment (cita previa) booking with anti-bot evasion.
Disclaimer: This project is provided for educational and non-commercial use only. The author is not responsible for any damage, data loss, account bans, legal implications, or any other consequences arising from the use of this software. Use this repository entirely at your own risk and ensure compliance with the terms of service of any website you interact with.
- Installation
- Configuration
- Usage
- Anti-Bot Evasion Techniques
- Fingerprint Protection with Chromixer
- Actors Configuration
- Adapting to Your Own Cita
- Home Assistant Notifications
- Directories
- Known Limitations and Gotchas
- Features
- Disclaimer
- Clone the repository
npm installnpx patchright install chromium- Copy
config.example.jsontoconfig.jsonand fill in your values - Optionally merge actors from
config.actors.example.jsoninto your config
All inputs come from config.json. See config.example.json for structure.
| Field | Description |
|---|---|
browserProfile |
Chrome profile directory name under profiles/ (default: test) |
baseUrl |
Override cita site URL (e.g. http://localhost:3999 for mock server) |
location |
ICP location code (e.g. 3 for Alicante) |
tramiteLabel |
Full tramite/procedure label (exact text as shown on the site) |
offices |
Array of office names (priority order); first match is selected |
minCitaDate |
Minimum cita date YYYY-MM-DD |
personalData |
NIE, nombre, telefono, email |
proxy |
Single proxy (server, username, password) |
proxies |
Array of proxies for rotation (on 429 or 5 bot detections) |
actors |
Human-behavior actor profiles (see Actors Configuration) |
keepBrowserOpenOnFailure |
Keep browser open when error at cita/confirm step (default: true) |
notifications |
failureCountThreshold, criticalAfterSeconds, homeassistant url+token |
npm run build– compile TypeScriptnpm start– run the bot
Set baseUrl: "http://localhost:3999" in config.json, then either:
- Single command:
npm run mock:bot– starts mock server and bot together - Separate terminals:
npm run mockin one,npm startin another
Env vars: MOCK_PORT (default 3999), MOCK_NO_CITA=1 to serve the "no citas" page at step 6.
CitaBot implements multiple layers of human-behavior simulation to avoid detection:
Mouse movements are powered by ghost-cursor-playwright, which generates natural Bezier-curve trajectories with overshoot and micro-corrections. All movements include randomized pre-movement and pre-click delays. Between pipeline steps, random cursor movements (moveRandom) are injected probabilistically to simulate idle user behavior.
Scrolling uses incremental mouse wheel events instead of instant jumps. Each scroll is split into small steps with random sizes (70–100% of maxScrollStep), and random delays are inserted between steps (scrollDelay). The target scroll position is randomized within a 30–70% viewport window to avoid predictable element-centering patterns.
Keystrokes are typed character by character with per-character delays derived from baseDelay ± delayVariance. Additional pauses are added after punctuation and spaces to replicate natural inter-word rhythm. The mistakeChance parameter enables deliberate typos (selecting an adjacent QWERTY key), immediately followed by a Backspace correction — mimicking realistic human typing.
The most effective protection is browser fingerprint spoofing via the Chromixer extension (see below). The bot uses native Chrome (channel: 'chrome') through patchright, a Playwright fork designed for stealth automation that patches several Playwright-specific automation fingerprints.
Every pipeline action waits a random delay drawn from configurable ranges: delayBetweenActions, delayBeforeFirstAction, delayBeforeLastAction. On bot detection, randomMoveProbability is increased (from 2% to 15%) to inject more idle cursor movement. Viewport dimensions are also randomized from a set of common presets on each new browser session.
For stronger fingerprint evasion, install the Chromixer Chrome extension into your bot's browser profile. Chromixer adds session-based noise to 12 fingerprinting vectors — Canvas, WebGL, Audio Context, fonts, hardware concurrency, screen dimensions, Battery API, plugins, media devices, WebRTC, Client Rects, and Gamepad API — making each session appear as a new unique visitor to fingerprinting scripts.
Why it matters: Most advanced bot detection systems rely primarily on browser fingerprinting. Human-like mouse movement and typing alone are not sufficient if the browser presents an automation fingerprint. Chromixer addresses this layer directly.
See profiles/README.md for step-by-step instructions on how to install Chromixer into the bot's Chrome profile.
Actors are human-behavior profiles that define the timing and variance of all interactions. Using multiple actors prevents the bot from developing a detectable behavioral fingerprint.
Add actors directly to config.json under the actors key, or copy them from config.actors.example.json. If no actors are configured, a built-in reference actor is used.
| Field | Description |
|---|---|
id |
Unique identifier (used in stats) |
typing.baseDelay |
Base delay between keystrokes (ms) |
typing.delayVariance |
± variance around baseDelay (ms) |
typing.mistakeChance |
Probability of typo per character (0–1) |
typingPressDelay |
[min, max] hold duration for key press (ms) |
cursor.overshootSpread |
Ghost-cursor overshoot spread factor |
cursor.overshootRadius |
Ghost-cursor overshoot radius |
cursor.waitBeforeMove |
[min, max] delay before cursor moves (ms) |
cursor.waitBeforeClick |
[min, max] delay before click (ms) |
cursor.waitBetweenClick |
[min, max] delay between double-click events (ms) |
cursor.scrollDelay |
[min, max] delay between scroll steps (ms) |
cursor.maxScrollStep |
Maximum pixels per scroll step |
cursor.moveRandomRange |
[min, max] distance for random cursor moves |
pipeline.delayBetweenActions |
[min, max] delay between pipeline actions (ms) |
pipeline.delayBeforeFirstAction |
[min, max] delay before first action in a step (ms) |
pipeline.delayBeforeLastAction |
[min, max] delay before last action in a step (ms) |
- Actors rotate between bot runs
- Per-actor run/detection/success statistics are saved to
stats/actors/ - When all actors have been tried and detection rate is high, the worst-performing actor is replaced with a freshly generated random actor
- Use more actors with varied timing ranges to widen the behavioral spread
CitaBot is designed as a framework that can be adapted to any Spanish cita previa flow with minimal effort.
The booking flow is modelled as a sequence of Step objects defined in src/steps/index.ts. Each step represents one page in the multi-step form. The BotWalker class executes them in order, passing a shared StepContext (page, cursor, keyboard, pipeline, config, shared data).
A Step has:
id— identifier from theStepIdenumwaitFor— CSS selector to wait for before the step runsbefore— optional async hook (cookie banners, office matching, etc.)actions— returns an array ofActionfunctions that the pipeline executes with human-like delaysafter— optional async hook for post-action logic (e.g. parsing cita dates)
All actions are composable functions from src/steps/actions.ts:
| Action | Description |
|---|---|
move(selector) |
Move cursor to element |
click(selector) |
Click element with human-like movement |
scroll(selector) |
Scroll element into view naturally |
select(selector, value) |
Select dropdown option |
typeChars(text) |
Type text with human-like delays |
press(key) |
Press a key with hold delay |
label(fn, name) |
Wrap any function as a named action |
moveRandom() |
Move cursor to random page position |
- Set
tramiteLabelinconfig.jsonto match the exact procedure text shown on the site - Set
location,offices,minCitaDate, andpersonalDataaccordingly - If the page flow differs, add, remove, or modify steps in
src/steps/and update thedefaultStepsarray insrc/steps/index.ts - Adjust selectors in each step's
actionsfunction to match the target page - Use
baseUrlpointing to the mock server (http://localhost:3999) during development to test without hitting the real site
Set notifications.homeassistant.url and notifications.homeassistant.token (long-lived token) to receive:
- Failure alert after N consecutive failures (
failureCountThreshold, default 5) - Cita found notification
- Critical notification if no cita response within N seconds (
criticalAfterSeconds, default 60)
| Directory | Description |
|---|---|
profiles/ |
Chrome profile data (gitignored; see profiles/README.md) |
logs/ |
HTML snapshots saved when no suitable cita is found |
stats/actors/ |
Per-actor JSON statistics (runs, detections, successes) |
After too many failed or suspicious attempts, the website can silently block your identity document (NIE or passport number) for approximately 24–74 hours. The ban is not obvious — the site will let you fill in all forms as normal, but will then fail with an internal error code at the final step, with no clear message in the UI.
If you suspect this has happened:
- Stop running the bot immediately
- Wait at least 24–74 hours before trying again
- Consider using a different NIE/passport if you have one available
- Rotate to a fresh proxy and browser profile when you resume
- Config-driven office selection by name (priority array)
- Cita date selection (earliest >=
minCitaDate) - Saves page HTML when no suitable dates; restarts from step 1
- Proxy rotation on 429 or after 5 bot detections
- Actor rotation with human-like behavior variants
- Statistics per actor for behavioral tuning
- Actor regeneration when all actors tried (replaces worst performer)
- Browser stays open on failure at cita/confirm step for manual recovery
- Home Assistant notifications (failure, cita found, critical)
- Mock server for local integration testing without hitting the real site
This software is provided for educational and non-commercial use only.
The author makes no warranties, express or implied, regarding the correctness, reliability, or fitness of this software for any particular purpose. The author is not liable for any damage, data loss, account suspension, legal consequences, or any other direct or indirect harm arising from the use or misuse of this code.
By using this repository you acknowledge that:
- You use it entirely at your own risk
- You are responsible for complying with all applicable laws and regulations
- You are responsible for respecting the terms of service of any website or service you interact with
- The author assumes no responsibility for any consequences of your use
This project is provided "AS IS" without warranty of any kind.