Skip to main content

PengePassportPH

Penge ng slot? Tingnan muna natin.
Read-only, rate-limited DFA passport appointment availability for the Philippines, in 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.

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.

This is the Python package. It is a native port of the Node.js package with the same behaviour, the same CLI and one shared rate limit: Python and Node.js processes on the same machine count against a single budget.

  • Standard library only. No runtime dependencies. Python 3.11+, fully typed (py.typed).
  • Synchronous and thread-safe. Share one client between threads; identical concurrent calls share one request.
  • Polite by default, and it can't be made faster. At least 2 s between requests, a capped hourly budget, backoff when the site struggles.

Install

pip install penge-passport-ph      # or: uv add penge-passport-ph
pipx install penge-passport-ph     # just the CLI: penge-passport-ph, or penge

CLI

Same commands, flags, output and exit codes as the Node.js CLI:

$ penge sites --search antipolo
  486  Antipolo (SM Center, Antipolo City, Rizal)

$ penge 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, ...

$ penge watch --site 486 --site angeles --interval 10m

penge --help lists everything; python -m penge_passport_ph works too.

Library

from penge_passport_ph import PengePassportPH

penge = PengePassportPH(contact="you@example.com")

site = penge.find_sites("antipolo")[0]
availability = penge.availability(site.id)

print(availability.earliest)  # '2026-10-14', or None when fully booked
print(availability.available_dates)  # ('2026-10-14', '2026-10-15', …)

if availability.earliest:
    for slot in penge.time_slots(site.id, availability.earliest):
        print(slot.start, slot.end, slot.status)  # 08:30 09:30 Available Slots: 1

for event in penge.watch([486, 10], interval=600):
    if event.type == "availability" and event.opened:
        print(event.site_id, "opened", event.opened)
Method Returns
regions() The five regions.
countries(region_id) list[Country]
sites(region_id=1, country_id=1) list[Site], the Philippines by default
find_sites(text, region_id=1, country_id=1) Sites whose name or description contains text
availability(site_id, *, applicants=1, from_date=None, to_date=None) Availability: earliest, available_dates, and every published days entry
time_slots(site_id, date, *, applicants=1) list[TimeSlot] for one date
watch(site_ids, *, interval=300, applicants=None, stop=None) Iterator of AvailabilityEvent and ErrorEvent; set the threading.Event stop, or break, to end it
stats() The rate limiter's state: requests in the last hour, failures, pauses in seconds

Results are frozen dataclasses; to_dict() gives the same JSON as the Node.js package. Dates are YYYY-MM-DD strings. applicants is 1, or 2 to 5 for a group (MAX_APPLICANTS, the most the DFA's group form takes). from_date and to_date default to the site's own booking window. A date absent from days is not published yet, which is different from fully booked.

Errors all derive from PengePassportPHError: UpstreamError (unexpected status or body, or the site unreachable, with .status), SessionError, RateLimitError (with .retry_after in seconds) and its subclass CircuitOpenError. Invalid arguments raise ValueError.

Rate limits

Limit Default Allowed
Gap between requests (min_interval) 3 s, plus up to 25% jitter 2 s or more
Requests per rolling hour (max_requests_per_hour) 300 1 to 1200
Concurrency 1 fixed
Reuse of availability and slot answers (availability_ttl) 60 s 30 s or more
watch interval 5 min 60 s or more, within the hourly budget

The limiter's state lives in ~/.local/state/penge-passport-ph/ (override with state_dir or PENGE_PASSPORT_PH_STATE_DIR), in the same file and format as the Node.js package, behind the same lock. Any non-2xx response, unreadable body, timeout or network error pauses the client, doubling up to 10 minutes; it honours Retry-After, and after 5 failures in a row it rests for 15 minutes. If the state file can't be written, it issues a StateSharingWarning and limits the current process only.

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.

Configuration

PengePassportPH(
    base_url="https://passport.gov.ph",
    contact=None,  # added to the User-Agent
    min_interval=3.0,  # seconds, at least 2
    max_requests_per_hour=300,  # at most 1200
    max_wait=60.0,  # refuse, rather than queue, past this
    availability_ttl=60.0,  # at least 30
    directory_ttl=6 * 3600,
    timeout=20.0,
    state_dir=None,  # default: default_state_dir()
    transport=None,  # custom HTTP transport (proxies, tests)
)

More

  • How it works: the endpoints, the site's quirks, and the shared state-file protocol.
  • Canary runbook: the scheduled check that fails loudly when passport.gov.ph changes. It covers this package too.
  • Law and responsible use: the DFA's rules, Philippine law, and why the limits are what they are.
  • Changelog

MIT © Alpha Romer Coma

Release files for penge-passport-ph 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for penge-passport-ph 0.1.0
File Size Uploaded
penge_passport_ph-0.1.0.tar.gz 32.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for penge-passport-ph 0.1.0
File Interpreter ABI Platform
penge_passport_ph-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 70.3 kB

Release files / penge_passport_ph-0.1.0.tar.gz

Download URL penge_passport_ph-0.1.0.tar.gz
Size 32.1 kB
Tags Source
SHA-256 checksum
How to use checksums
71445b43e02a896d6e5dd33989b7eb50fbb732bd6456d994938ef6dae97af8e0
BLAKE2b-256 checksum
How to use checksums
7e2d11711eaed3fb8d5080ddcfae449c701621ccc60a7740578494fd5c231400
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / penge_passport_ph-0.1.0-py3-none-any.whl

Download URL penge_passport_ph-0.1.0-py3-none-any.whl
Size 38.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a4237c9d309398f936b2e3215d604a381c1a8684fa8bed15d21762e569064339
BLAKE2b-256 checksum
How to use checksums
738b46b494f224f39bc9928092106d2ab27c102b2e4a37546e37c7798ae4d882
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page