About

About

Built by competitors. Used at the line.

Snapshot Scoring exists because we got tired of paper score sheets, lost timer tapes, and Sunday-night Excel marathons just to publish results from a Saturday match.

The story

This started where most useful software starts — someone shooting an action match got fed up with the way it was being scored.

The first version was a side project to digitize a single Nebraska club’s SASS scoring workflow. It worked. Other clubs noticed. Other disciplines noticed — “can you make it work for USPSA?” became “can you make it work for IDPA?” became “what about 3-Gun?” The codebase grew. The product grew with it.

That product is Snapshot Scoring: a mobile scoring app, a Django REST backend, and a free WordPress publishing plugin — built for the people who actually run action shooting matches in the dirt. Five sports, five scoring engines (Time Plus, Hit Factor, Points Down, Raw Time, Time Plus per-firearm) composed from eight swappable modules, one match-day workflow, and a publishing plugin (snapshot-results v2.6.1) that lands every result on your club site the second a match locks.

Every feature we ship has to pass one test: does it save the director ten minutes, make the scorekeeper faster, or give the competitor a reason to open the app on a non-match-day? If not, it doesn’t ship.

Principles

What we won’t do.

Building a scoring app for a niche, opinionated sport requires saying no to a lot of things. Here’s what we’ve already said no to.

No spreadsheet exports as the source of truth

The match lives in the app and the database. CSV is for sharing — never for re-entering data later. If the workflow needs Excel, the workflow is broken.

No setup required for official rules

SASS, USPSA, IDPA, 3-Gun, Steel Challenge ship seeded with their official categories, divisions, and penalty weights. Clubs shouldn’t have to set up rules somebody else wrote. If you run a house variant or a local league year, email us — we add it to the shared catalog and your mobile app picks it up on next sync.

No “Premium” tier paywalling core scoring

The scoring engine is the product. We’re not going to ship a free version that misses penalties and ask you to upgrade for accuracy.

No bright UI in direct sun

High-contrast, oversized targets. Every screen optimized for outdoor range conditions — not a dim conference room.

No ad-supported anything

The app will never serve ads, sponsored content, or promoted firearms. Real shooters use this to score real matches; we’re not turning their attention into an asset.

No locking your match data

Your match data is yours. Export to CSV, JSON, or PractiScore .psc any time. We’re not building a moat with your data.

No mystery state

Every mutation — a score edit, an ownership transfer, a grant revocation, a staff admin action — lands in an append-only audit log with actor, timestamp, IP, and before/after deltas.

No vendor lock on your site

The WordPress plugin is GPL-licensed and free. Pair your own site via OAuth 2.0 PKCE — we never host your club site. Styled templates ship out of the box; override them via the standard WP template hierarchy when you want to.

Tech

How it’s built.

For the engineers reading: the stack, in case it matters to your evaluation.

React Native

iOS & Android from a single TypeScript codebase. Native build, not a wrapped web view. BLE timer adapters in TypeScript, NFC pairing where available, QR scanner via react-native-camera-kit. Hub-and-spoke HomeScreen scales with shooters who hold multiple roles (Compete / Score / Manage).

Django REST + PostgreSQL

Five scoring engines composed from eight modular primitives (time_plus, hit_factor, points_down, raw_time, drop_worst, power_factor, bonus_targets, detailed_misses). Organization → League → RuleSet hierarchy in the database, versioned per year. JWT auth for mobile; OAuth 2.0 provider for WP pairing; scope-gated ServiceAccount bearer tokens for admin/support.

Equipment validation engine

Per-shooter equipment slots (firearm, holster, ammo, optics) on the profile validate against per-match equipment constraints at registration. Match directors set requirements per match — minimum age (computed from DOB), valid membership, alias required, equipment compliance — and the app blocks ineligible registrations before they hit the line.

WordPress plugin v2.6.1

Free GPL-licensed plugin for any club’s WordPress site. OAuth 2.0 PKCE pairing, encrypted access + refresh tokens, custom post types with styled out-of-box templates. Multi-site management — one director account pairs many sites. Results auto-publish on match lock; templates overridable via the normal WP hierarchy.

Append-only audit log + CSV export

Every destructive operation — score edits, unlocks, ownership transfers, staff actions — captured with actor, timestamp, IP, and before/after deltas. The Score Audit screen surfaces every score edit per match. Full audit log CSV export for season records.

Maestro E2E

Real device tests run against the production API on every release. We catch breakage before you do. Marketing screenshots are captured through the same pipeline — so every image on this site was produced by the real app against the real backend.

Offline-first sync stack

Six independent mechanisms compose the offline story. (1) 14-day local mutation queue, 5,000-operation capacity, payloads obfuscated at rest with a key in the device Keychain / Keystore, mutex-protected against concurrent writes. (2) Background sync — auto-sync on foreground, configurable interval, WiFi-only mode, low-battery skip, pause/resume, exponential retry up to 5 minutes. (3) Delta sync — ETag-based HTTP caching plus per-entity modifiedSince timestamps across nine entity types. (4) Local-network HTTP peer sync on port 8847 with QR-bootstrap session, 30-minute timeout. (5) NFC tap-to-transfer with NDEF tags up to 8 KB carrying shooter / match / stage / club / scores payloads. (6) QR transfer codec across 12 payload types, chunked for larger data.

Embedded scoring defaults

Sport rules cached on device at install time. A freshly-installed phone can score offline immediately — no first-load round-trip, no “please connect to download rulesets.” Volunteer permission grants cache on device too, so volunteers can score remote bays with no signal.

Server-driven onboarding

First-run flow lives on the backend, so improvements ship without an app update. Walks new shooters through profile, membership numbers, follow-a-club, and notification preferences. Update the wizard once on the server; every install picks it up on next launch.

Multi-org schema

One shooter profile holds SASS, USPSA, and IDPA membership numbers and per-org aliases. Scores from any sport auto-link to the right ID at the right organization. Travel shooters across three disciplines stop showing up as three duplicates with three classifiers.

Director analytics layer

Match Analytics, Operations Dashboard, and Score Audit screens give directors everything from segmented standings and DQ analysis to match participant demographics. Performance trends + career stats roll up across every match a club has run.

Direct PractiScore .psc interop

The only non-PractiScore app that reads and writes the PractiScore .psc SQLite format directly. POST /api/matches/import/practiscore/ ingests a .psc file and maps it onto our match / entry / stage / score models; GET /api/matches/{id}/export/practiscore writes a fresh SQLite database in PractiScore’s schema and returns it as a download.

Infrastructure under our control

Django + Postgres on our own metal (no serverless black box). TLS everywhere. Database encrypted at rest. Deploys go through review; production writes route through audit-logged admin paths; there’s no “quick fix in the shell.”

57+ backend test cases

Admin API tested green across 57+ pytest cases covering every CRUD surface, scope check, and audit hook. Mobile-app contract has separate coverage. CI runs both before every deploy.

Get in touch

Questions, ideas, or a bug to report?

We read every email. The team is small enough that you’ll get a response from someone who can actually do something about it.