AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Open DFA passport appointment dates at every office in the Philippines, checked every 5 minutes, with free email alerts. Unofficial.
Developer toolsTypeScript · PythonGitHub @alpharomercomaOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Read-only, rate-limited checker for DFA Philippine passport appointment availability, for Node.js and Python, and a free alerts website.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Penge ng slot? Tingnan muna natin.
Read-only, rate-limited DFA passport appointment availability for the Philippines.
A library and CLI for passport.gov.ph , for Node.js and Python. It looks; it never books.
Unofficial. Not affiliated with, endorsed by or connected to the Department of Foreign Affairs of the
Philippines. Passport appointments are free and are booked only at passport.gov.ph.Read the full README ↗[!IMPORTANT] Unofficial. Not affiliated with, endorsed by or connected to the Department of Foreign Affairs of the Philippines. Passport appointments are free and are booked only at passport.gov.ph.
Penge is Filipino for "give me (some)". The name is the wish; the tool is the reality check. It can't give you a slot, and nobody should sell you one. It tells you where one is open, so you can book it yourself for free.
PengePassportPH tells you which dates, and which hourly time slots, still have room at a DFA consular site. It reads the same data the booking page shows, through the same endpoints. It paces itself so it cannot overload the site, and it never selects a time slot: selecting one holds it for 30 minutes and takes it away from real applicants.
npm install penge-passport-ph # Node.js library
npx penge-passport-ph --help # CLI, no install
npm install -g penge-passport-ph # CLI as `penge-passport-ph`, or just `penge`
pip install penge-passport-ph # Python library (or: uv add penge-passport-ph)
pipx install penge-passport-ph # the same CLI, from Python
Both packages install the same penge-passport-ph and penge commands, with the same flags and output;
use whichever runtime you have.
$ npx penge-passport-ph sites --search antipolo
486 Antipolo (SM Center, Antipolo City, Rizal)
$ npx penge-passport-ph check --site antipolo --times
Antipolo (SM Center, Antipolo City, Rizal) (site 486)
Checked 2026-09-26 to 2027-03-31 for 1 applicant(s)
Earliest: 2026-10-14
Available (11): 2026-10-14, 2026-10-15, 2026-10-16, ...
Time slots on 2026-10-14:
08:30-09:30 Available Slots: 1
09:30-10:30 Fully Booked
...
$ npx penge-passport-ph watch --site 486 --site angeles --interval 10m
2026-09-26T14:05:11.020Z Antipolo (SM Center, Antipolo City, Rizal): earliest 2026-10-14
2026-09-26T14:15:40.311Z Antipolo (SM Center, Antipolo City, Rizal): opened 2026-10-02
| Command | What it does |
|---|---|
check --site <id|name> | Open dates at one site. --times adds the time slots on the earliest open date. |
watch --site <id|name> [--site …] | Polls and prints dates as they open or close. --interval (minimum 60 s, default 5 min). |
sites [--search <text>] | Consular sites. Philippines by default; --region and --country for posts abroad. |
countries --region <id> | Countries in a region. |
regions | The five regions. |
Every command takes --json. --applicants <n> checks room for a group booking of 2 to 5 (the most the
DFA's group form takes; applicants in the API has the same range), --from/--to narrow
the date range, and --contact <email-or-url> adds a way to reach you to the User-Agent. Exit codes:
0 ok, 2 usage error, 3 site, session or rate-limit error, 1 anything unexpected.
import { PengePassportPH } from 'penge-passport-ph';
const penge = new PengePassportPH({ contact: '[email protected]' });
const [site] = await penge.findSites('antipolo');
const availability = await penge.availability({ siteId: site.id });
console.log(availability.earliest); // '2026-10-14', or null when fully booked
console.log(availability.availableDates); // ['2026-10-14', '2026-10-15', …]
if (availability.earliest) {
const times = await penge.timeSlots({ siteId: site.id, date: availability.earliest });
// [{ start: '08:30', end: '09:30', available: true, remaining: 1, status: 'Available Slots: 1', note: null }, …]
}
for await (const event of penge.watch({ siteIds: [486, 10], intervalMs: 10 * 60_000 })) {
if (event.type === 'availability' && event.opened.length) console.log(event.siteId, 'opened', event.opened);
}
| Method | Returns |
|---|---|
regions() | The five regions (static). |
countries(regionId) | Country[] |
sites({ regionId?, countryId? }) | Site[], the Philippines by default |
findSites(text, { regionId?, countryId? }) | Site[] whose name or description contains text |
availability({ siteId, applicants?, from?, to? }) | Availability: earliest, availableDates, and every published days entry |
timeSlots({ siteId, date, applicants? }) | TimeSlot[] for one date |
watch({ siteIds, intervalMs?, applicants?, signal? }) | Async iterator of availability and error events |
stats() | Promise of the rate limiter's state: requests in the last hour, failures, pauses |
applicants is 1, or 2 to 5 for a group (MAX_APPLICANTS, the most the DFA's group form takes). from and
to default to the site's own "today" and booking horizon. days lists only the dates the site
has published (working days in its release window); a date that is absent is not published yet, which is
different from fully booked. Every method that makes a request accepts a signal to cancel it.
Errors all extend PengePassportPHError: UpstreamError (unexpected status or body, with .status),
SessionError, RateLimitError (with .retryAfterMs) and its subclass CircuitOpenError. Invalid
arguments throw RangeError. Results are copies: changing one never changes what the cache holds.
The Python package is a native port with the same behaviour: standard library only, Python 3.11+, synchronous and thread-safe, durations in seconds, snake_case names.
from penge_passport_ph import PengePassportPH
penge = PengePassportPH(contact="[email protected]")
availability = penge.availability(penge.find_sites("antipolo")[0].id)
print(availability.earliest, availability.available_dates)
Its guide is in packages/python/README.md. Both packages parse through rules pinned by the same golden files, and share one rate-limit state file, so they can run side by side.
apps/ holds a website built on this package, live at https://alphaexperiments.com/pengepassportph/
(short link /p3h): every office's open dates at a glance, in the Philippines and at the embassies and
consulates abroad, each one's calendar and hourly times for one person or a group, and an email, a push
notification or both when a date opens where you can get to.
| Path | What it is |
|---|---|
packages/penge-passport-ph/ | This package (npm) |
packages/python/ | The Python port (PyPI) |
packages/contracts/ | Validation and shapes shared by the website and the server |
apps/server/ | The checker, the mailer and the website's API |
apps/web/ | The website (React), installable as an app |
android/ | The Android app for Google Play: the website in a Trusted Web Activity (how) |
deploy/ | Provisioning, releases, mail setup and the runbook |
passport.gov.ph is a public service that people need on bad days. The limits are enforced inside the client and cannot be switched off:
| Limit | Default | Allowed |
|---|---|---|
| Gap between requests | 3 s, plus up to 25% jitter | 2 s or more |
| Requests per rolling hour | 300 | 1 to 1200 |
| Concurrency | 1 | fixed |
| Reuse of availability and slot answers | 60 s | 30 s or more |
| Reuse of country and site lists | 6 h | any |
watch interval | 5 min | 60 s or more, within the hourly budget |
~/.local/state/penge-passport-ph/, so two terminals running watch, in Node.js or Python, share one budget
instead of doubling the load.Retry-After up to an hour. After 5 failures in a row, or one
Retry-After longer than an hour, it stops for 15 minutes, or as long as Retry-After asks, up to an hour.maxWaitMs (default 60 s) in the queue fails with
RateLimitError. Identical concurrent calls share one request.penge-passport-ph/<version> (+https://alphaexperiments.com/pengepassportph/; read-only availability checker).
Pass contact to add your email.Please don't run it on many machines, don't use it to resell or broker appointments, and don't build booking on top of it. The DFA warns applicants against fixers; this project exists so people can check for themselves.
docs/legal sets out what Philippine law, the DFA and the DICT say about automated access, how other governments have treated appointment scanners, and why the alerts website uses the limits it does. It is research, not legal advice.
The site can change without notice, and a checker that silently misreads it is worse than none. The
Canary workflow runs every 6 hours and on changes to main:
Any ❌ fails the job and opens a ❌ PengePassportPH canary: passport.gov.ph changed issue with the diff and
screenshots. The issue closes itself when the canary passes again. What to do when it fires:
docs/canary.md.
| Setting | Where | Default |
|---|---|---|
contact | option, --contact | none |
minIntervalMs, maxRequestsPerHour, maxWaitMs | options | 3000, 300, 60000 |
availabilityTtlMs, directoryTtlMs, timeoutMs | options | 60 s, 6 h, 20 s |
stateDir | option, PENGE_PASSPORT_PH_STATE_DIR | $XDG_STATE_HOME/penge-passport-ph, else ~/.local/state/penge-passport-ph |
baseUrl, fetch | options | https://www.passport.gov.ph, global fetch |
Since October 2026 the site sits behind Cloudflare's bot check, which refuses Node's own fetch from any
address. A refused request fails with "was stopped by Cloudflare's bot check (HTTP 403)". To get through,
pass a fetch that looks like a browser on the wire, from an address Cloudflare lets in: the website uses
impit through a residential proxy, with whichever of its browser profiles
Cloudflare lets through at the time: that changes.
The canary has its own PENGE_PASSPORT_PH_* variables, listed in docs/canary.md.
MIT © Alpha Romer Coma