Phil's Data
API v1

API Reference

REST endpoints for accessing player/team data and ML feature vectors. Designed for direct integration with ML pipelines, analytics dashboards, and automated workflows.

Quick Start

Base URL

https://philsdata.com

Response Format

All endpoints return JSON. Successful responses use 200 with the result body. Errors use 4xx with { "error": "..." }.

Authentication

Vector endpoints require an authenticated session (cookie-based, 30-day persistence). Sign up via /signup or log in via /login. The browser session cookie is automatically attached to API requests from the same domain.

Public preview: The example UUIDs below are placeholders. Sign in to see your live database UUIDs auto-filled into the examples.
Core Concepts

Feature Vectors

Position-specific ML-ready vectors. QBs get 13 dimensions, RBs get 12, WR/TE get 11, teams get 10. Each vector includes both normalized and raw values plus metadata.

Normalization

Values are normalized using sensible per-feature scales (e.g., passer_rating / 158.3 gives a 0–1 score). Most values fall in [0, 1] but exceptional outliers can exceed 1.

Sample Size

Always check meta.sampleSize (games played in season). Vectors built from <3 games are high-variance — consider downweighting in your model.

Cosine Similarity

Two players' vectors can be compared via cosine similarity. The in-app Compare view computes this automatically for selected players.

POST/api/signup

Create Account

Register a new user with email and password. Password is hashed with bcrypt (12 rounds) before storage.

Request Body

NameTypeDescription
emailrequiredstringUnique email address. Used as login identifier.
passwordrequiredstringMinimum 8 characters. Hashed before storage.
namestringDisplay name (optional).

Response 200

json
{
  "user": {
    "id": "01H8X2Z...",
    "email": "you@example.com"
  }
}

Error Responses

400Missing email/password or password < 8 chars
409Account with this email already exists
500Server error during account creation

Code Examples

bash
curl -X POST https://philsdata.com/api/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "you@example.com",
    "password": "your-secure-password",
    "name": "Your Name"
  }'
javascript
const res = await fetch("https://philsdata.com/api/signup", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    email: "you@example.com",
    password: "your-secure-password",
    name: "Your Name",
  }),
});
const data = await res.json();
POST/api/auth/callback/credentials

Sign In (Credentials)

Authenticate via email/password. Returns a JWT session cookie valid for 30 days. Managed by Auth.js v5.

Request Body

NameTypeDescription
emailrequiredstringAccount email
passwordrequiredstringAccount password
csrfTokenrequiredstringObtain via GET /api/auth/csrf

Response 200

json
// Sets HttpOnly cookie: __Secure-authjs.session-token
// Redirects to callback URL on success
{
  "url": "/app"
}
Most clients should use the signIn() helper from next-auth/react rather than calling this endpoint directly. See the Auth.js docs for details.
GET/api/vectors/players/{playerId}Auth required

Player Feature Vector

Returns a position-specific ML-ready feature vector for a player in a given season. Vectors are normalized to roughly [0, 1] and include raw values + metadata.

Path Parameters

NameTypeDescription
playerIdrequireduuidThe player's UUID. Find these in the URL when viewing a player's detail page.

Query Parameters

NameTypeDescription
seasonintegerSeason year (1950-2026)
Default: 2026

Response 200

json
{
  "player": {
    "id": "abc12345-...",
    "name": "Patrick Mahomes",
    "position": "QB",
    "team": "..."
  },
  "vector": {
    "features": [
      "pass_ypg", "pass_td_rate", "int_rate", "rush_ypg",
      "td_int_ratio", "passer_rating", "pass_consistency",
      "pass_trend_slope", "best_yards", "worst_yards",
      "fumble_rate", "sack_rate", "games_played"
    ],
    "values": [
      0.815, 0.733, 0.433, 0.512, 0.692, 0.658, 0.834,
      0.012, 0.978, 0.234, 0.176, 0.211, 1.000
    ],
    "raw": [
      285.3, 2.2, 0.65, 25.0, 3.38, 104.2, 0.834,
      0.4, 489, 117, 0.18, 1.06, 17
    ],
    "meta": {
      "subject": "Patrick Mahomes",
      "subjectId": "abc12345-...",
      "season": 2026,
      "position": "QB",
      "sampleSize": 17
    }
  }
}

Response Fields

NameTypeDescription
vector.featuresstring[]Position-specific feature names. 13 for QB, 12 for RB, 11 for WR/TE.
vector.valuesnumber[]Normalized values (typically [0, 1] using sensible per-feature scales).
vector.rawnumber[]Pre-normalization values in original units (yards, TDs/game, etc.).
vector.meta.sampleSizeintegerNumber of games played in this season (use to weight reliability).

Error Responses

401Not authenticated
404Player not found OR no data for given season

Code Examples

bash
curl https://philsdata.com/api/vectors/players/abc12345-...?season=2026
javascript
const res = await fetch(
  "https://philsdata.com/api/vectors/players/abc12345-...?season=2026"
);
const { player, vector } = await res.json();
// vector.values is your ML model input
console.log(vector.values);
python
import requests

resp = requests.get(
    "https://philsdata.com/api/vectors/players/abc12345-...",
    params={"season": 2026},
)
data = resp.json()
features = data["vector"]["features"]
values = data["vector"]["values"]
# Feed into your model
import numpy as np
X = np.array(values).reshape(1, -1)
GET/api/vectors/teams/{teamId}Auth required

Team Feature Vector

Returns a 10-dimensional feature vector for a team in a given season. Includes win%, points/game, point differential, yards splits, turnover rate, third-down/red-zone efficiency.

Path Parameters

NameTypeDescription
teamIdrequireduuidThe team's UUID. Find these in the URL when viewing a team's detail page.

Query Parameters

NameTypeDescription
seasonintegerSeason year (1950-2026)
Default: 2026

Response 200

json
{
  "team": {
    "id": "def67890-...",
    "name": "Kansas City Chiefs",
    "abbreviation": "KC"
  },
  "vector": {
    "features": [
      "win_pct", "ppg", "papg", "point_diff_pg",
      "ypg", "pass_yards_pct", "rush_yards_pct",
      "turnovers_pg", "3rd_down_pct", "red_zone_pct"
    ],
    "values": [
      0.882, 0.834, 0.501, 0.580, 0.812,
      0.602, 0.310, 0.350, 0.452, 0.621
    ],
    "raw": [
      0.882, 29.2, 17.5, 11.7, 365.4,
      0.602, 0.310, 1.05, 45.2, 62.1
    ],
    "meta": {
      "teamId": "def67890-...",
      "teamAbbr": "KC",
      "season": 2026
    }
  }
}

Error Responses

401Not authenticated
404Team not found

Code Examples

bash
curl https://philsdata.com/api/vectors/teams/def67890-...?season=2026
javascript
const res = await fetch(
  "https://philsdata.com/api/vectors/teams/def67890-...?season=2026"
);
const { team, vector } = await res.json();
python
import requests

resp = requests.get(
    "https://philsdata.com/api/vectors/teams/def67890-...",
    params={"season": 2026},
)
data = resp.json()
print(data["team"]["name"], data["vector"]["values"])
POST/api/sync

Database Sync (Admin)

Reseed the database with the latest historical data. Idempotent by default — pass force=true to wipe and regenerate everything. Used internally for data refreshes.

Query Parameters

NameTypeDescription
forcebooleanIf true, wipe all data first. Otherwise, only inserts missing rows.
Default: false
seasonsstringComma-separated list of seasons to process. Defaults to all 1950-2026.
standingsOnlybooleanSkip per-week stats generation. Faster for standings-only refresh.
Default: false

Response 200

json
{
  "success": true,
  "message": "Database wiped and reseeded",
  "results": {
    "cleared": true,
    "teams": 32,
    "players": 462,
    "standings": 2464,
    "seasonsProcessed": 77
  }
}

Error Responses

500Server error during sync

Code Examples

bash
curl -X POST "https://philsdata.com/api/sync?force=true"
Heads up: sync operations can take 20-30 seconds when processing all 77 seasons. The endpoint has a 60s function timeout configured. For local testing, use npm run seed:force to bypass the timeout entirely.
In-App Tools

Most users won't need to hit the API directly. The same data powers these in-app views:

  • Dashboard— power rankings, top performers, weekly league averages
  • Analytics— league-wide trends, leaderboards, year-over-year deltas
  • Compare— side-by-side comparison + cosine similarity matrix
  • Players— filterable roster with percentile rank bars
  • Teams— all 32 teams, real logos, season-aware records
Database Schema

Backed by PostgreSQL via Neon. 8 tables (4 auth + 4 sports data).

sql
-- Auth tables (managed by Auth.js Drizzle adapter)
users               (id, name, email, hashed_password, created_at)
accounts            (user_id, type, provider, provider_account_id)
sessions            (session_token, user_id, expires)
verification_tokens (identifier, token, expires)

-- Sports data
teams               (id, name, abbreviation, conference, division,
                     primary_color, secondary_color, logo_url)
players             (id, team_id, name, position, jersey_number,
                     height, weight, college, status, birth_year,
                     rookie_year, retired_year, image_url, bio, era)
games               (id, season, week, home_team_id, away_team_id,
                     home_score, away_score, date, venue, status)
player_stats        (id, player_id, game_id, passing_yards,
                     passing_tds, interceptions, rushing_yards,
                     rushing_tds, receiving_yards, receiving_tds,
                     receptions, targets, fumbles, sacks, tackles)
team_stats          (id, team_id, game_id, total_yards, passing_yards,
                     rushing_yards, turnovers, penalties, penalty_yards,
                     third_down_conv, red_zone_conv, time_of_possession)
standings           (id, team_id, season, wins, losses, ties,
                     points_for, points_against, division_rank,
                     conference_rank)
Limits & Conventions
Rate limitNo hard limits during preview. Reasonable use expected.
Auth methodCookie-based JWT session (30-day persistence)
API versioningNo version prefix. Breaking changes will be communicated.
CORSSame-origin only. No cross-domain access.
Response timeVector endpoints typically respond < 200ms
Function timeout60s for /api/sync, 30s for vector endpoints
Questions? Email support@philsdata.com