Files
pypretix-device/README.md
2026-06-30 19:52:07 +02:00

11 KiB

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 answering
  • reason: Error code string (e.g. "invalid", "already_redeemed", "unpaid", "blocked")
  • position: Order position data (attendee name, item info, checkin history)
  • require_attention: True if the item/order has the checkin_attention flag set
  • checkin_texts: Additional display strings for the user
  • questions: Required question list if status == "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 with checkins history (list of {datetime, type, gate})
  • require_attention: True if 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)

  1. Log into pretix web UI as organizer admin
  2. Go to Organizer Settings → Devices → New Device
  3. Name the device (e.g. "Example-Scanner")
  4. Note the Initialization Token (24 characters)
  5. Store it as env var: PRETIX_DEVICE_INIT_TOKEN=xxxxx
  6. 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