AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
필리핀 전 사무소의 DFA 여권 예약일을 5분마다 확인하고 무료 이메일 알림을 제공합니다(비공식)
Open DFA passport appointment dates at every office in the Philippines, checked every 5 minutes, with free email alerts. Unofficial.
개발 도구TypeScript · PythonGitHub @alpharomercoma온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Read-only, rate-limited checker for DFA Philippine passport appointment availability, for Node.js and Python, and a free alerts website.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
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.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