Pinquest Backend API (1.0.0)

Download OpenAPI specification:

OpenAPI description of the Pinquest Backend API. The API is deployed as a set of independent AWS Lambda services (one per bounded context: auth, health, contests, league, tournaments, quests, high-scores, leaderboards, machine-reports, locations, uploads, prizes) fronted by API Gateway v2. This document unifies every route into a single surface.

Authentication

Protected routes require a Cognito access token supplied as an HTTP Bearer token (Authorization: Bearer <access_token>). A missing or invalid token is rejected by the shared verifier middleware with 401 {"error":"unauthorized"} and a WWW-Authenticate: Bearer realm="pinquest-api" header, before the handler runs. Public routes (auth, health, uploads, leaderboards, and the read-only tournament/high-score lists) do not require a token.

Conventions

  • All request and response bodies are application/json unless noted.

  • Errors share the shape {"error": "<message>"} (the sole exception is GET /healthz/db, which uses a db key).

  • List endpoints always emit their array ([], never null).

  • int64 identifiers and scores serialize as JSON numbers.

  • Dates are ISO calendar dates (YYYY-MM-DD); timestamps are RFC 3339.

Auth

Cognito-backed signup, login, password reset, and profile.

Register a new user

Creates a Cognito user (auto-confirmed in dev) and best-effort captures profile fields into the legacy users row.

Request Body schema: application/json
required
email
required
string <email>
password
required
string <password>
username
string
first_name
string
last_name
string
state
string
dob
string <date>
ifpa_number
string

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "password": "pa$$word",
  • "username": "string",
  • "first_name": "string",
  • "last_name": "string",
  • "state": "string",
  • "dob": "2019-08-24",
  • "ifpa_number": "string"
}

Response samples

Content type
application/json
{
  • "user_sub": "string",
  • "confirmed": true,
  • "confirmation_required": true,
  • "profile_captured": true
}

Log in

Authenticates via Cognito USER_PASSWORD_AUTH and returns the token bundle.

Request Body schema: application/json
required
email
required
string <email>
password
required
string <password>

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "password": "pa$$word"
}

Response samples

Content type
application/json
{
  • "access_token": "string",
  • "id_token": "string",
  • "refresh_token": "string",
  • "token_type": "string",
  • "expires_in": 0
}

Request a password-reset code

Triggers a Cognito reset code. Anti-enumeration — an unknown user still returns 202 with an empty body.

Request Body schema: application/json
required
email
required
string <email>

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com"
}

Response samples

Content type
application/json
{
  • "delivery_medium": "string",
  • "destination": "string"
}

Complete a password reset

Completes the reset via Cognito ConfirmForgotPassword (code + new password).

Request Body schema: application/json
required
email
required
string <email>
code
required
string
password
required
string <password>

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "code": "string",
  • "password": "pa$$word"
}

Response samples

Content type
application/json
{
  • "error": "string"
}

Current principal's JWT claims

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "sub": "string",
  • "username": "string",
  • "client_id": "string",
  • "scopes": [
    ],
  • "groups": [
    ],
  • "email": "string"
}

Enriched profile

The /me claims plus Cognito GetUser attributes and best-effort legacy users-row fields.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "sub": "string",
  • "username": "string",
  • "client_id": "string",
  • "scopes": [
    ],
  • "groups": [
    ],
  • "email": "string",
  • "email_verified": true,
  • "first_name": "string",
  • "last_name": "string",
  • "state": "string",
  • "dob": "2019-08-24",
  • "ifpa_number": "string"
}

Update profile

Updates legacy users-row fields. All fields optional; an omitted field is left unchanged, a present field (including empty string) sets the value. At least one field must be supplied. Requires the DB store.

Authorizations:
bearerAuth
Request Body schema: application/json
required
non-empty
username
string
first_name
string
last_name
string
state
string
dob
string <date>

Validated as YYYY-MM-DD when non-empty

ifpa_number
string

Responses

Request samples

Content type
application/json
{
  • "username": "string",
  • "first_name": "string",
  • "last_name": "string",
  • "state": "string",
  • "dob": "2019-08-24",
  • "ifpa_number": "string"
}

Response samples

Content type
application/json
{
  • "username": "string",
  • "first_name": "string",
  • "last_name": "string",
  • "state": "string",
  • "dob": "2019-08-24",
  • "ifpa_number": "string"
}

Health

Deployment and database smoke checks.

Deployment smoke test

Responses

Response samples

Content type
application/json
{
  • "status": "ok",
  • "env": "prod"
}

Database connectivity check

Note: uses a db key rather than the standard error shape.

Responses

Response samples

Content type
application/json
{
  • "db": "ok"
}

Contests

National Contests — list, detail, entry submission, find-a-game.

List running national contests

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "contests": [
    ]
}

Contest detail

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "score": 0,
  • "end_on": "2019-08-24",
  • "rules": "string",
  • "prize": "string",
  • "prize_photo": "string",
  • "at_contest_limit": true,
  • "game": {
    }
}

Submit a contest entry

Submits a proof-photo high-score entry. Runs six ordered validations (at-limit, photo freshness, score-vs-target, 24h duplicate, demographics, terms) in one transaction.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1
Request Body schema: application/json
required
score
required
integer <int64>
photo
required
string

Object key of the proof photo

agreed_to_terms
required
boolean
latitude
number <double>
longitude
number <double>
time_offset
integer

Client tz offset in whole minutes (JS getTimezoneOffset convention)

required
object

Responses

Request samples

Content type
application/json
{
  • "score": 0,
  • "photo": "string",
  • "agreed_to_terms": true,
  • "latitude": 0.1,
  • "longitude": 0.1,
  • "time_offset": 0,
  • "demographics": {
    }
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "contest_id": 0,
  • "score": 0,
  • "remaining_entries": 0,
  • "created_at": "2019-08-24T14:15:22Z"
}

Find a game for a contest

Resolves the contest's game and queries pinballmap.com for nearby venues. A pinballmap failure degrades to an empty list with source_unavailable: true (never a 500).

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "game": {
    },
  • "disclaimer": "string",
  • "source_unavailable": true,
  • "locations": [
    ]
}

League

Location leagues — seasons, standings, meets, signups, check-ins.

Location league summary

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "location_id": 0,
  • "current_seasons": [
    ],
  • "past_seasons": [
    ]
}

Season standings

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "season_id": 0,
  • "name": "string",
  • "drop_lowest": 0,
  • "rows": [
    ]
}

Meet detail

Returns the groups→rounds→players→scores tree for a non-virtual meet, or a standings table for a virtual meet. The shape is discriminated by virtual.

Authorizations:
bearerAuth
path Parameters
seasonID
required
integer <int64> >= 1
meetID
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
Example
{
  • "meet_id": 0,
  • "season_id": 0,
  • "at": "2019-08-24T14:15:22Z",
  • "checkin_open": true,
  • "complete": true,
  • "virtual": false,
  • "ungrouped": [
    ],
  • "groups": [
    ]
}

Sign up for a season

Idempotent. Returns 201 on a new signup, 200 if already signed up.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1
Request Body schema: application/json
optional
accepted_terms
boolean

Must be true to sign up

Responses

Request samples

Content type
application/json
{
  • "accepted_terms": true
}

Response samples

Content type
application/json
{
  • "season_id": 0,
  • "signup_id": 0,
  • "user_id": 0,
  • "name": "string",
  • "signed_up": true
}

Check in to a meet

Restricted to players signed up for the season. Idempotent — 201 on a new check-in, 200 if already checked in.

Authorizations:
bearerAuth
path Parameters
seasonID
required
integer <int64> >= 1
meetID
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "meet_id": 0,
  • "season_id": 0,
  • "checkin_id": 0,
  • "signup_id": 0,
  • "checked_in": true
}

Tournaments

Tournament lists, detail, and signups.

List all tournaments

Responses

Response samples

Content type
application/json
{
  • "upcoming": [
    ],
  • "past": [
    ]
}

List tournaments at a location

path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "upcoming": [
    ],
  • "past": [
    ]
}

Tournament detail

path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "at": "2019-08-24T14:15:22Z",
  • "format": {
    },
  • "full": true,
  • "finished": true,
  • "results": [
    ],
  • "signups": [
    ],
  • "rounds": [
    ],
  • "ungrouped": [
    ]
}

Sign up for a tournament

Fields default from the player's profile; the body may override them.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1
Request Body schema: application/json
optional
first_name
string
last_name
string
ifpa_number
string

Responses

Request samples

Content type
application/json
{
  • "first_name": "string",
  • "last_name": "string",
  • "ifpa_number": "string"
}

Response samples

Content type
application/json
{
  • "tournament_id": 0,
  • "signup_id": 0,
  • "user_id": 0,
  • "first_name": "string",
  • "last_name": "string",
  • "ifpa_number": "string",
  • "place": 0,
  • "signed_up": true
}

Quests

Quests, embarkments, mission (score) submission, find-a-machine.

List a location's eligible quests

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "quests": [
    ]
}

Quest detail

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "global": true,
  • "location_id": 0,
  • "instructions": "string",
  • "end_on": "2019-08-24",
  • "foreground_color": "string",
  • "background_color": "string",
  • "rewards": {
    },
  • "embarkment_state": "none",
  • "goals_with_progress": [
    ]
}

Embark on a quest

Idempotent first-or-create. 201 creates the embarkment, 200 returns the existing one.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "quest_id": 0,
  • "state": "none",
  • "id_pin": "string",
  • "wizard_points": 0
}

Embarkment detail (PIN screen)

Scoped to the owning caller — another user's embarkment reports 404.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "quest_id": 0,
  • "state": "none",
  • "id_pin": "string",
  • "rewards": {
    }
}

Submit a mission score

Submits a score against a goal with a proof photo. Runs the anti-cheat validation gate (photo freshness, GPS radius, 24h duplicate, score target) and advances the embarkment state machine in one transaction.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1
Request Body schema: application/json
required
photo_key
required
string
score
integer <int64>
latitude
number <double>
longitude
number <double>
time_offset
integer

Whole minutes (JS getTimezoneOffset convention)

user_agent
string

Accepted for parity; not persisted

Responses

Request samples

Content type
application/json
{
  • "photo_key": "string",
  • "score": 0,
  • "latitude": 0.1,
  • "longitude": 0.1,
  • "time_offset": 0,
  • "user_agent": "string"
}

Response samples

Content type
application/json
{
  • "mission_id": 0,
  • "goal_id": 0,
  • "goal_complete": true,
  • "quest_complete": true,
  • "embarkment": {
    },
  • "earned_questbux": 0,
  • "earned_wizard_points": 0,
  • "old_wizard_level": {
    },
  • "new_wizard_level": {
    }
}

Find a machine for a goal

Lists active venues carrying the goal's game. Optional lat/lng attaches per-venue distance and orders nearest-first.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1
query Parameters
lat
number <double> [ -90 .. 90 ]

Must be supplied together with lng

lng
number <double> [ -180 .. 180 ]

Must be supplied together with lat

Responses

Response samples

Content type
application/json
{
  • "locations": [
    ]
}

High Scores

Monthly per-location high-score boards and submissions.

Monthly high-score board

path Parameters
id
required
integer <int64> >= 1
query Parameters
month
string^\d{4}-\d{2}$

YYYY-MM; defaults to the current month

Responses

Response samples

Content type
application/json
{
  • "month": "2026-07",
  • "games": [
    ]
}

Submit a high score

Records a monthly high score with anti-cheat validation (proof-photo freshness, 24h duplicate, GPS radius).

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1
Request Body schema: application/json
required
game_id
required
integer <int64>
score
required
integer <int64> >= 0
photo
required
string

Object key from POST /uploads

latitude
number <double>
longitude
number <double>
time_offset
integer

Responses

Request samples

Content type
application/json
{
  • "game_id": 0,
  • "score": 0,
  • "photo": "string",
  • "latitude": 0.1,
  • "longitude": 0.1,
  • "time_offset": 0
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "game_id": 0,
  • "location_id": 0,
  • "score": 0,
  • "month": "2026-07",
  • "created_at": "2019-08-24T14:15:22Z"
}

Leaderboards

Wizard-points ranking.

Wizard-points ranking

query Parameters
page
integer >= 1
Default: 1
state
string
location_id
integer <int64> >= 1
timeframe
string
Default: "all_time"
Enum: "all_time" "month" "week"

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "pagination": {
    }
}

Machine Reports

Reporting malfunctioning machines to location staff.

Report a malfunctioning machine

Flags a machine at a location so staff are notified. The reporter is resolved from the token; any client-supplied user id is ignored. Rate-limited to 5 reports per user per location per hour.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1
Request Body schema: application/json
required
machine_type
required
string
Enum: "pinball" "other"
description
required
string
game_id
integer or null <int64>

Required for machine_type=pinball

name
string

Required for machine_type=other

Responses

Request samples

Content type
application/json
{
  • "machine_type": "pinball",
  • "description": "string",
  • "game_id": 0,
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "location_id": 0,
  • "user_id": 0,
  • "machine_type": "string",
  • "game_id": 0,
  • "name": "string",
  • "description": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Locations

Location search, detail, follow/unfollow, and games.

Search nearby questable locations

Authorizations:
bearerAuth
query Parameters
lat
required
number <double> [ -90 .. 90 ]
lng
required
number <double> [ -180 .. 180 ]
radius
number <double> ( 0 .. 500 ]
Default: 25

Miles

query
string

Case-insensitive name substring filter

Responses

Response samples

Content type
application/json
{
  • "locations": [
    ]
}

Location detail

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "address": "string",
  • "city": "string",
  • "state": "string",
  • "zip": "string",
  • "latitude": 0.1,
  • "longitude": 0.1,
  • "distance_miles": 0.1,
  • "quest_count": 0,
  • "notification": "string",
  • "notification_hash": "string",
  • "following": true,
  • "wallet_balance": 0,
  • "features": {
    }
}

Follow a location

Idempotent opt-in to a location's notifications.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "error": "string"
}

Unfollow a location

Idempotent removal of the caller's membership. Accepts both POST and DELETE.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "error": "string"
}

Unfollow a location

Idempotent removal of the caller's membership. Accepts both POST and DELETE.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "error": "string"
}

List games at a location

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "games": [
    ]
}

Uploads

Presigned upload URLs for proof photos.

Mint a presigned upload URL

Returns a short-lived presigned PUT URL and a server-generated object key for direct-to-storage image uploads.

Request Body schema: application/json
optional
content_type
string

If set, must start with image/

ext
string

File extension appended to the key

Responses

Request samples

Content type
application/json
{
  • "content_type": "string",
  • "ext": "string"
}

Response samples

Content type
application/json
{
  • "key": "string",
  • "method": "PUT",
  • "headers": {
    },
  • "expires_in": 0
}

Prizes

Prize catalog, detail, and QuestBux redemption.

Location prize catalog

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "wallet_balance": 0,
  • "prizes": [
    ]
}

Prize detail

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "cost": 0,
  • "redeem_via": "mail",
  • "image": "string",
  • "global": true,
  • "location_id": 0,
  • "variants": [
    ]
}

Redeem a prize (QuestBux)

Branches on redeem_via — on_location validates a staff PIN + signature; mail validates the shipping address + variant. The cost is debited from the caller's wallet at the location.

Authorizations:
bearerAuth
path Parameters
id
required
integer <int64> >= 1
Request Body schema: application/json
required
prize_id
required
integer <int64>
redeem_via
required
string
Enum: "on_location" "mail"
location_pin
string

Required for on_location

location_signature
string

Required for on_location

first_name
string

Required for mail

last_name
string

Required for mail

address
string

Required for mail

address2
string
city
string

Required for mail

state
string

Required for mail

zip
string

Required for mail

variant_id
integer <int64>

Required when the prize has variants (mail)

Responses

Request samples

Content type
application/json
{
  • "prize_id": 0,
  • "redeem_via": "on_location",
  • "location_pin": "string",
  • "location_signature": "string",
  • "first_name": "string",
  • "last_name": "string",
  • "address": "string",
  • "address2": "string",
  • "city": "string",
  • "state": "string",
  • "zip": "string",
  • "variant_id": 0
}

Response samples

Content type
application/json
{
  • "purchase_id": 0,
  • "remaining_balance": 0
}