REST API to validate conference attendees using multiple ticketing systems through a modular backend architecture.
- Tito - Default backend with native activity support
- Pretix - Full integration with category-based attribute mapping
The following attributes are built-in and default to False:
- is_onsite
- is_remote
- online_access
- is_speaker
- is_sponsor
- is_volunteer
- is_organizer
- is_guest
It's important to follow a base structure when setting up the tickets in Pretix already.
Roles can be assigned via:
"Product categories" in Pretix
The category defines on-site and remote access baseline (e.g. attributes: is_remote, is_onsite, online_access)
"Products" in Pretix
This will update the attributes set in 1.
A usual use case is to add is_speaker for speaker tickets for example.
'ABCDE-1', "Orders" in Pretix, note one order can have multiple items 'ABCDE-1, 'ABCDE-2',…
This will update the attributes set in 1. and 2.
This is mostly used to handle multiple roles if a person is:
- organizer_and_speaker: The person is an organizer and gives a talk.
- organizer_and_sponsor: The person is an organizer and the employer is also sponsor.
- speaker_and_sponsor: The person is a speaker and the employer is also sponsor.
- speaker_add_keynote: The person is a keynote speaker.
- add_speaker: The person is a speaker but has a non-speaker ticket for some reason.
This is a direct assignment for that one ticket for that one person
Access rights are assigned in the following order:
- Ticket category
- Ticket ID
- Order ID
Any step might change attributes. The best practice is to only add access, i,e. setting attributes to True. A mix of adding and removing access will be confusing.
You grant all ticket holders access to remote attendance online_access: True. But there are also
social event tickets available for a +1, social event tickets does not include online_access
- put event tickets in own category, e.g. Social Event
pretix_mapping:
categories:
by_id:
999: # ID of social event category
online_access: False
# OR exclude one or multiple ticket IDs
by_ticket_id:
8888: # ID of social event ticket
online_access: FalseThe category can very easily be changed in the Pretix backend. Other people might do that to:
- get a nicer look on the stats
- improve the ticket shop order.
uv syncOption A: Tito (Default)
# Create .env file
TITO_TOKEN="your_secret_token"
ACCOUNT_SLUG="account_slug_from_tito"
EVENT_SLUG="event_slug_from_tito"Option B: Pretix
# Create .env file
PRETIX_TOKEN="your_pretix_api_token"
PRETIX_BASE_URL="https://pretix.eu/api/v1"
PRETIX_ORGANIZER_SLUG="your_organizer_slug"
PRETIX_EVENT_SLUG="your_event_slug"
# Set backend (environment variable takes precedence over config file)
TICKETING_BACKEND=pretixThe API supports OAuth2/OIDC authentication via Keycloak (or any OIDC-compliant provider). All
/tickets/ endpoints require a valid JWT Bearer token when authentication is enabled. Healthcheck
endpoints remain public.
Add to your .env file:
OIDC_ISSUER_URL="https://keycloak.example.com/realms/your-realm"
OIDC_AUDIENCE="your-keycloak-client-id"
# Optional: override signing algorithm (default: RS256)
# OIDC_ALGORITHMS="RS256"When OIDC_ISSUER_URL is not set, authentication is disabled and all endpoints are open. This is
the default for local development.
Edit event_config.yml to map your event's ticket categories and special roles:
# Set backend
TICKETING_BACKEND: pretix
pretix_mapping:
categories:
by_id:
227668: # Your category ID
is_onsite: true
online_access: true
by_ticket_id:
819314: # Speaker ticket ID
is_speaker: true
# Special multi-role assignments
speaker_and_sponsor:
- "C3UAP-1" # Ticket codeOption A: Direct Run
# IMPORTANT: Use single worker only!
uvicorn app.main:app --port 9898 --host "0.0.0.0"Option B: Docker (Development)
Running docker compose up loads compose.override.yaml automatically, which starts a local
Keycloak instance alongside the API for OAuth2 development.
# Starts fact_check_in + Keycloak (dev mode with hot-reload)
docker compose upKeycloak quick setup for machine-to-machine access:
- Open
http://localhost:8080and login withadmin / admin. - Create realm
fact-check-in. - Create API client (the token audience):
- Client ID:
fact-check-in-api - Protocol:
OpenID Connect - Client authentication:
Off
- Client ID:
- Create a caller client for each service that will call the API. Example:
- Client ID:
fact-check-in-caller - Protocol:
OpenID Connect - Client authentication:
On - Enable
Service account roles - Copy client secret from
Credentials
- Client ID:
- Add audience mapper so caller tokens include API audience:
- Create client scope
fact-check-in-api-access - Add mapper type
Audiencewith included audiencefact-check-in-api - Assign that client scope to
fact-check-in-caller
- Create client scope
Set API env vars in .env:
# Docker: http://keycloak:8080 | Prod: https://auth.yourdomain.com | Local tools: add 127.0.0.1 keycloak to /etc/hosts
OIDC_ISSUER_URL=http://keycloak:8080/realms/fact-check-in
OIDC_AUDIENCE="fact-check-in-api"Option C: Docker (Production)
In production, Keycloak should already be running (dedicated host or managed service). Use only
compose.yaml to skip the dev override, and point your host nginx to port 9898.
# Build the image
# Set DOCKER_DEFAULT_PLATFORM to match server arch if building cross-platform.
# Configure image name/tag with IMAGE_NAME and IMAGE_TAG env vars.
# Example: `DOCKER_DEFAULT_PLATFORM=linux/amd64 IMAGE_NAME=validation.api IMAGE_TAG=latest docker compose build`
docker compose build
# Start without the dev Keycloak override
docker compose -f compose.yaml up -dNote: Startup takes ~30 seconds while loading ticket data.
API Documentation: Once running, visit:
- Interactive API docs: http://localhost:9898/docs
- ReDoc: http://localhost:9898/redoc
- OAuth2 Authentication: JWT validation via Keycloak or any OIDC provider, with OIDC auto-discovery, JWKS caching, and typed token claims
- Modular Backend Architecture: Easily switch between ticketing systems or add new ones
- Dynamic Configuration: Backend selection via environment variables or config files
- Smart Validation:
- Validate attendees by ticket code + name (with fuzzy matching)
- Validate attendees by email
- Configurable name matching thresholds
- Flexible Attribute Mapping:
- Tito: Native activity support
- Pretix: Category and product name-based mapping
- Special Attendee Types: Automatic detection of speakers, sponsors, volunteers, organizers
- Access Level Detection: Distinguish between on-site, remote, and online attendees
- Day Pass Support: Handle day-specific access (Monday, Tuesday, etc.)
The application uses a modular backend system that allows seamless switching between different ticketing platforms:
- Abstract Interface:
TicketingBackendbase class defines the contract - Dynamic Loading: Backends are loaded at runtime based on configuration
- Consistent API: Same REST endpoints work with any backend
- Easy Extension: Add new backends by implementing the interface
All /tickets/ endpoints require a valid Bearer token when authentication is enabled.
POST /tickets/validate_name/- Validate by ticket ID and namePOST /tickets/validate_email/- Validate by emailPOST /tickets/validate_attendee/- Validate by order ID and name (Pretix)GET /tickets/ticket_types/- List available ticket typesGET /tickets/ticket_count/- Count of tickets in cacheGET /tickets/refresh_all/- Force reload ticket dataGET /healthcheck/alive- Health check (public, no auth required)
# Install with dev dependencies
uv sync
# Set up pre-commit hooks
prek install --hook-type pre-commit --hook-type pre-push
# Run tests
pytest
# Run linting/formatting
ruff check . --fix
ruff format .This project was partially updated with Claude CLI. Instructions for Claude are in CLAUDE.md
- Developer Guide - Development setup and guidelines
- API Documentation - Interactive API docs (when running)
There are issues of the library that created the social cards on macOS, the cairo svg library is
required: brew install cairo.
Even if installed cairo might not be found. Fixes:
export DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib- Add a symlink in project root:
ln -s /opt/homebrew/opt/cairo/lib/libcairo.2.dylib