smartposti-tracker
An unofficial Python library and CLI for tracking SmartPosti (Estonia, Latvia, Lithuania) and Posti (Finland) parcels. It also watches parcels and tells you when new tracking events appear.
$ smartposti-track watch JJFI12345678901234567 --interval 300
NEW JJFI12345678901234567 2026-10-29 06:46 The item is in sorting
NEW JJFI12345678901234567 2026-10-29 11:02 The item has arrived at the parcel locker
Disclaimer. This project is not affiliated with or endorsed by Posti Group. It uses the same public endpoints as the tracking page on smartposti.ee, and they are not documented. They may change or stop working without notice. Please poll responsibly (every few minutes, not every second).
Features
- No browser needed. Plain HTTP via
requests, with no Selenium or Playwright. - Library first. Typed
ShipmentandEventmodels, and a small exception hierarchy. - Watcher with pluggable parts. You choose where seen events are stored (memory, JSON file, or your own store) and what happens when one arrives (print, Telegram, email, webhook, and so on).
- CLI for quick lookups, and a
--oncemode for cron or Windows Task Scheduler. - Localized events in
en,et,fi,lv,lt.
Installation
pip install smartposti-tracker
For development:
git clone https://github.com/kuttim/smartposti-tracker.git
cd smartposti-tracker
pip install -e ".[dev]"
Requires Python 3.9+.
Command line
# Current status and full history
smartposti-track status JJFI12345678901234567
# Raw API response as JSON
smartposti-track status JJFI12345678901234567 --json
# Poll every 5 minutes and print only new events (Ctrl+C to stop)
smartposti-track watch CODE1 CODE2 --interval 300 --bell
# Check once and exit. Good for cron or Task Scheduler
smartposti-track watch CODE --once --state ~/.smartposti_state.json
# Events in Estonian
smartposti-track --locale et status CODE
python -m smartposti_tracker ... also works.
On the first watch check, existing events are only recorded, not reported.
Pass --notify-existing to print them too.
Library usage
Look up a shipment
from smartposti_tracker import TrackingClient, ShipmentNotFound
client = TrackingClient(locale="en")
try:
shipment = client.get_shipment("JJFI12345678901234567")
except ShipmentNotFound:
print("No such parcel")
else:
print(shipment.status) # "IN_TRANSPORT"
for event in shipment.events: # oldest first
print(event.timestamp, event.description)
print(shipment.latest_event)
shipment.raw and event.raw hold the original API data.
Watch for new events
from smartposti_tracker import JsonFileStore, Watcher
watcher = Watcher(store=JsonFileStore("state.json"))
@watcher.add_handler
def on_new_events(shipment, events):
for e in events:
print(f"{shipment.tracking_code}: {e.description}")
watcher.run(["JJFI12345678901234567"], interval=600) # blocks
# or: watcher.check("CODE") -> list of new events, for your own scheduler
Extending
The library has three small, replaceable pieces:
| Piece | What it does | How to replace it |
|---|---|---|
TrackingClient |
Talks to the API | Pass your own requests.Session (proxies, retries), or query= to request more GraphQL fields |
StateStore |
Remembers which events were already seen | Any object with get_seen(code) and set_seen(code, keys) |
| Event handlers | React to new events | Any callable handler(shipment, new_events) |
Custom notifications
A handler is just a function. Here is one that posts to a webhook:
import requests
def to_webhook(shipment, events):
requests.post("https://example.com/hook", json={
"code": shipment.tracking_code,
"status": shipment.status,
"events": [{"time": e.timestamp.isoformat(), "text": e.description} for e in events],
})
Watcher(handlers=[to_webhook]).run(["CODE"])
Custom storage
class RedisStore:
def __init__(self, redis):
self.r = redis
def get_seen(self, code):
# Return None if this code was never checked, so the first check stays silent
if not self.r.exists(f"seen:{code}"):
return None
return {k.decode() for k in self.r.smembers(f"seen:{code}")}
def set_seen(self, code, keys):
pipe = self.r.pipeline()
pipe.delete(f"seen:{code}")
pipe.sadd(f"seen:{code}", "__checked__", *keys)
pipe.execute()
Retries and proxies
import requests
from requests.adapters import HTTPAdapter, Retry
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=Retry(total=3, backoff_factor=2)))
client = TrackingClient(session=session)
Requesting more fields
The default query asks only for status and events. To get delivery details,
pickup point, and similar data, pass your own GraphQL query that still returns
consumerSearchShipments.hits, then read the extra fields from shipment.raw.
See examples/ for complete scripts, including a
Telegram notifier and a
SQLite store.
Running on a schedule
Linux/macOS (cron), every 10 minutes:
*/10 * * * * smartposti-track watch CODE --once --state $HOME/.smartposti_state.json >> $HOME/parcels.log
Windows (Task Scheduler):
schtasks /Create /SC MINUTE /MO 10 /TN "Parcel tracker" `
/TR "smartposti-track watch CODE --once --state %USERPROFILE%\smartposti_state.json"
How it works
The smartposti.ee tracking page loads its data in the browser in two steps. This library repeats those two requests:
POST https://auth-service.posti.fi/api/v1/anonymous_tokenreturns a short-lived anonymousid_tokenand arole_tokens[0].token. No account is needed.POST https://graphql.posti.fi/graphqlruns theconsumerSearchShipmentsquery, withauthorization: <role token>andx-posti-token: Bearer <id_token>.
Tokens are cached until shortly before they expire. A 401 or 403 response causes a fresh token on the next call.
If Posti changes this flow, open the tracking page with your browser's dev tools
open (Network tab, filter graphql) to see the new requests, then
open an issue.
Development
pip install -e ".[dev]"
ruff check .
pytest # offline, API is mocked
SMARTPOSTI_TEST_CODE=<code> pytest -m live # optional real-API smoke test
See CONTRIBUTING.md.
License
Metadata
Release files for smartposti-tracker 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| smartposti_tracker-0.1.0.tar.gz | 14.8 kB | With provenance | Details for smartposti_tracker-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| smartposti_tracker-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for smartposti_tracker-0.1.0-py3-none-any.whl |
Total release size: 28.4 kB
Release files / smartposti_tracker-0.1.0.tar.gz
| Download URL | smartposti_tracker-0.1.0.tar.gz |
|---|---|
| Size | 14.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5eb1262e991f53ba6510b6ddf9cf292cb426240508a121c034d81ad23113fa9e
|
|
BLAKE2b-256 checksum How to use checksums |
babbfd39db04c555453256378e39a586a9e9ea7669ae933ba53b88a37f5fe607
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logRelease files / smartposti_tracker-0.1.0-py3-none-any.whl
| Download URL | smartposti_tracker-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
70efebfb12a9eaf160988dc6b5e022eac15ee187c8aec7a4385b00ce47e0fc83
|
|
BLAKE2b-256 checksum How to use checksums |
d0acf1fe29e7fa4cee8bba3e711156603d50c445f3fe6adfa6205e24660e6315
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logSecurity for smartposti-tracker 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for smartposti_tracker-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.