pypretix-device (0.0.1)
Installation
pip install --index-url --extra-index-url https://pypi.org/simple pypretix-deviceAbout this package
A minimal API client for pretix device authentication and ticket scanning
pypretix-device
A minimal Python API client for pretix device authentication and ticket scanning.
Designed for kiosk/scan-station devices that need to check volunteers or attendees in and out via QR/barcode scanning.
Installation
pip install pypretix-device
Editable install for development:
pip install -e pypretix-device
Quick Start
from pypretix_device import auth, PretixDeviceClient
from pypretix_device.models import CheckinResult
# --- On first use: register device ---------------------------------
# Go to Organizer Settings → Devices in the pretix web UI to get a
# 24-character initialization token, then:
device = auth.get_or_register_device(
base_url="http://localhost",
init_token="xxxxxxxxxxxxxxxxxxxxxxxx", # from pretix web UI
device_name="example-scanner",
)
# Config is saved to ~/.config/pypretix/config.json automatically
# On subsequent runs it loads the stored token:
device = auth.get_or_register_device(
base_url="http://localhost",
init_token=None, # no token needed anymore
)
# --- Create the client ---------------------------------------------
client = PretixDeviceClient(
base_url="http://localhost",
api_token=device.api_token,
organizer_slug="FL",
event_slug="PB26",
checkin_list_id=1,
)
# --- Check a ticket in (entry) -------------------------------------
result = client.checkin("qrCodeSecretFromBarcode")
if result.status == "ok":
print(f"Welcome: {result.position['attendee_name']}")
else:
print(f"Error: {result.reason}") # e.g. "invalid", "already_redeemed"
# --- Check a ticket out (exit) -------------------------------------
result = client.checkout("qrCodeSecretFromBarcode")
if result.status == "ok":
print(f"Checked out: {result.position['attendee_name']}")
# --- Look up ticket status (Infodesk) ------------------------------
result = client.search("qrCodeSecretFromBarcode")
if result.position:
print(f"Found: {result.position['attendee_name']}")
print(f"Checkins: {len(result.position.get('checkins', []))}")
for ci in result.position['checkins']:
print(f" {ci['datetime'][:19]} - {ci['type']}")
else:
print("Token not found")
# --- List events ---------------------------------------------------
events = client.list_events()
for e in events:
print(f"{e.slug} — {e.name}")
# --- List check-in lists for the current event ---------------------
lists = client.list_checkin_lists()
for cl in lists:
print(f"id={cl.id}: {cl.name} ({cl.checkin_count}/{cl.position_count})")
# --- Device info ---------------------------------------------------
info = client.device_info()
print(f"Device ID: {info.device_id}")
print(f"Server: {info.server_version}")
API Reference
pypretix_device.auth — Device Registration & Token Management
get_or_register_device(base_url, init_token=None, device_name="example-scanner")
Load stored device config, or register a new device if no config exists.
| Parameter | Description |
|---|---|
base_url |
pretix instance URL, e.g. "http://localhost" or "https://events.example.com" |
init_token |
24-char initialization token from pretix web UI (Organizer Settings → Devices). Required only on first run. |
device_name |
Human-readable name for the device. |
Returns: DeviceInfo object with api_token, device_id, organizer, etc.
Raises: RegistrationFailedError if no config exists and no init_token provided.
register_device(base_url, init_token, device_name="helferscanner")
Register a new device. Same parameters as above. The device must already exist in the pretix web UI (with an initialization token created). The init_token is consumed and cannot be reused.
Returns: DeviceInfo with the new device API token.
revoke_device()
Permanently invalidate the device's API token and remove the config file. The device must be deleted from the pretix web UI as well.
PretixDeviceClient — Ticket Scanning Operations
Create with:
client = PretixDeviceClient(
base_url: str, # pretix instance URL
api_token: str, # Device API token (from registration)
organizer_slug: str, # Organizer slug, e.g. "FL"
event_slug: str, # Event slug, e.g. "PB26"
checkin_list_id: int | None, # Check-in list ID to use (e.g. 1)
)
checkin(barcode, lists=None) -> CheckinResult
Check a ticket in (entry). Sets type="entry" on the checkinrpc/redeem endpoint. Uses force=true to handle pending/unpaid orders gracefully.
| Parameter | Description |
|---|---|
barcode |
The QR code secret string scanned from the ticket |
lists |
Optional override for check-in list IDs. Falls back to client default. |
Returns: CheckinResult
status:"ok"on success,"error"on failure,"incomplete"if questions need answeringreason: Error code string (e.g."invalid","already_redeemed","unpaid","blocked")position: Order position data (attendee name, item info, checkin history)require_attention:Trueif the item/order has the checkin_attention flag setcheckin_texts: Additional display strings for the userquestions: Required question list ifstatus == "incomplete"
checkout(barcode, lists=None) -> CheckinResult
Check a ticket out (exit). Sets type="exit" on the checkinrpc/redeem endpoint. Same parameters and return type as checkin().
search(barcode, lists=None) -> SearchResult
Read-only ticket lookup (Infodesk mode). Queries checkinrpc/search for the ticket secret.
| Parameter | Description |
|---|---|
barcode |
QR code secret or attendee name to search |
lists |
Optional check-in list IDs to search within |
Returns: SearchResult
position: Full order position dict withcheckinshistory (list of{datetime, type, gate})require_attention:Trueif the item/order has checkin_attention flag set
list_events() -> list[Event]
List all events for the configured organizer.
Returns: list[Event] with fields: slug, name, testmode, date_from, date_to.
list_checkin_lists(event_slug=None) -> list[CheckinList]
List check-in lists for the current (or specified) event.
Returns: list[CheckinList] with fields: id, name, all_products, checkin_count, position_count, allow_entry_after_exit.
select_event(event_slug) -> bool
Switch the client to a different event. Returns True on success.
device_info() -> DeviceInfo \| None
Fetch device information from the pretix server (device ID, server version, gate assignment).
device_update(software_version) -> DeviceInfo \| None
Notify the server about a software version update.
Connection status properties
| Property | Description |
|---|---|
client.is_connected |
True if the last API request succeeded |
client.last_error |
Error message string, or None |
client.last_response_time |
Timestamp (float) of last successful response |
Data Models
See pypretix_device.models:
| Class | Description |
|---|---|
CheckinResult |
Result of checkin/checkout operations |
SearchResult |
Result of search (read-only) operations |
Event |
Event data (slug, name, dates) |
CheckinList |
Check-in list data (id, name, counts) |
DeviceInfo |
Device metadata (id, organizer, server version) |
AttendeeInfo |
Personalized ticket attendee data |
Configuration
Config is stored in ~/.config/pypretix/config.json:
{
"base_url": "http://localhost",
"api_token": "a1b2c3d4e5f6...",
"device_id": 5,
"organizer": "TEST",
"device_name": "example-scanner",
"software_version": "0.0.1"
}
To use a custom config directory:
import pypretix_device.models
pypretix_device.models.CONFIG_DIR = "/path/to/custom/config"
Environment Variables (for running the app)
export PRETIX_BASE_URL="http://localhost" # pretix instance URL
export PRETIX_ORGANIZER="TEST" # organizer slug
export PRETIX_EVENT="PB26" # event slug
export PRETIX_CHECKIN_LIST="1" # check-in list ID
export PRETIX_DEVICE_INIT_TOKEN="xxx" # only needed on first run
Error Codes
The reason field in CheckinResult contains one of these codes:
| Code | Meaning |
|---|---|
invalid |
Ticket barcode not known |
already_redeemed |
Ticket already checked in |
unpaid |
Order not paid for |
blocked |
Ticket has been blocked |
invalid_time |
Ticket outside valid time window |
canceled |
Ticket has been canceled |
ambiguous |
Multiple tickets match — can't resolve |
revoked |
Ticket secret has been revoked |
unapproved |
Order not yet approved by organizer |
product |
Ticket product not allowed on this check-in list |
rules |
Check-in prevented by organizer-defined rules |
incomplete |
Required questions need answers (only with questions_supported: True) |
error |
Internal server error |
Lifecycle Example
1. Initial device setup (one-time)
- Log into pretix web UI as organizer admin
- Go to Organizer Settings → Devices → New Device
- Name the device (e.g. "Example-Scanner")
- Note the Initialization Token (24 characters)
- Store it as env var:
PRETIX_DEVICE_INIT_TOKEN=xxxxx - On first run,
get_or_register_device()uses this token and saves the device API token to config
2. Normal operation (daily)
On every subsequent run, config is loaded automatically — no token needed:
device = get_or_register_device(base_url, init_token=None)
client = PretixDeviceClient(..., api_token=device.api_token)
result = client.checkin(barcode)
3. Ticket check-in flow
Scanner reads QR code → barcode string → client.checkin(barcode)
→ status="ok" → show OK + name
→ status="error" → show error code (e.g. "Bereits gescannt")
4. Ticket check-out flow
Scanner reads QR code → barcode string → client.checkout(barcode)
→ status="ok" → show OK + name
→ status="error" → show error code
5. Info desk lookup
Scanner reads QR code → barcode string → client.search(barcode)
→ position exists → show attendee name + checkin history
→ position None → show "Token nicht gefunden"
6. Deprovisioning
When the device is no longer needed:
from pypretix_device.auth import revoke_device
revoke_device() # invalidates token + removes config
Also delete the device from the pretix web UI (Organizer Settings → Devices).
License
GNUGPLv3