Developers¶
Technical documentation for integrating with NWSL Data APIs, database, and AI services.
Quick Start¶
Choose your integration approach:
- REST API - HTTP endpoints for applications
- Direct Database - PostgreSQL access for data science
- MCP Server - Advanced AI analytics integration
- Client Libraries - Pre-built language wrappers
REST API¶
Base URL¶
Authentication¶
Currently open for research use. No API key required.
Core Endpoints¶
SQL Queries POST /sql Execute read-only SQL queries against the database.
curl -X POST "https://nwsl-database-proxy-78453984015.us-central1.run.app/sql" \
-H "Content-Type: application/json" \
-d '{"query": "SELECT COUNT(*) FROM events_2025 WHERE event_type_id = 16"}'
Natural Language Analytics POST /query AI-powered analysis using natural language questions.
curl -X POST "https://nwsl-database-proxy-78453984015.us-central1.run.app/query" \
-H "Content-Type: application/json" \
-d '{"query": "Who scored the most goals in 2025?"}'
Health Check GET /health
Response Formats¶
SQL Response
{
"results": [
{"player_name": "Sophia Smith", "goals": 16},
{"player_name": "Trinity Rodman", "goals": 14}
],
"row_count": 2,
"columns": ["player_name", "goals"]
}
Analytics Response
{
"analysis": "Based on the 2025 NWSL season data, Sophia Smith leads...",
"summary": "Sophia Smith leads with 16 goals in 2025",
"execution_time": "2.3s"
}
Client Libraries¶
Python Client¶
import requests
import pandas as pd
class NWSLClient:
def __init__(self, base_url="https://nwsl-database-proxy-78453984015.us-central1.run.app"):
self.base_url = base_url
def query(self, sql):
"""Execute SQL query and return DataFrame"""
response = requests.post(f"{self.base_url}/sql", json={"query": sql})
response.raise_for_status()
return pd.DataFrame(response.json()['results'])
def ask(self, question):
"""Ask natural language question"""
response = requests.post(f"{self.base_url}/query", json={"query": question})
response.raise_for_status()
return response.json()
# Usage
client = NWSLClient()
df = client.query("SELECT player_name, COUNT(*) as goals FROM events_2025 e JOIN players p ON e.player_id = p.id WHERE e.event_type_id = 16 GROUP BY player_name ORDER BY goals DESC LIMIT 10")
JavaScript/Node.js Client¶
class NWSLClient {
constructor(baseURL = 'https://nwsl-database-proxy-78453984015.us-central1.run.app') {
this.baseURL = baseURL;
}
async query(sql) {
const response = await fetch(`${this.baseURL}/sql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: sql })
});
return response.json();
}
async ask(question) {
const response = await fetch(`${this.baseURL}/query`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: question })
});
return response.json();
}
}
// Usage
const client = new NWSLClient();
const data = await client.query("SELECT COUNT(*) FROM events_2025");
Direct Database Access¶
Connection Details¶
- Host: Contact for database host details
- Port:
5432 - Database:
nwsl - SSL Mode:
prefer
Python Connection¶
import psycopg2
import pandas as pd
conn = psycopg2.connect(
host="[contact for host]",
port="5432",
database="nwsl",
user="notebookuser", # Contact for credentials
password="[password]",
sslmode="prefer"
)
# Query with pandas
df = pd.read_sql("SELECT * FROM players LIMIT 10", conn)
Connection Security¶
- Read-only database user with limited permissions
- SSL encryption for all connections
- Connection pooling recommended for applications
- Use Cloud SQL Proxy for enhanced security in production
Database Schema¶
Core Tables¶
matches - Match metadata
SELECT id, season, match_date, home_contestant_id, away_contestant_id
FROM matches
WHERE season = 2025;
events_YYYY - Match events (partitioned by year)
-- Available partitions: events_2022, events_2023, events_2024, events_2025
SELECT player_id, minute, x, y, event_type_id, team_id
FROM events_2025
WHERE match_id = 12345;
players - Player registry (auto-discovered from events)
SELECT id, player_name, first_known_name, last_known_name
FROM players
WHERE player_name ILIKE '%smith%';
contestants - Teams
Analytics Tables¶
shot_xg_analysis - Expected Goals calculations
SELECT event_id, xg_value, shot_angle, distance_to_goal
FROM shot_xg_analysis
WHERE match_id = 12345;
player_action_values - VAEP metrics
SELECT player_id, action_id, vaep_offensive, vaep_defensive
FROM player_action_values
WHERE match_id = 12345;
Event Types (Key IDs)¶
1- Pass13- Miss (shot off target)14- Post (shot hit post/crossbar)15- Save (shot saved by keeper)16- Goal
MCP Server Integration¶
For advanced AI analytics capabilities, integrate with the NWSL MCP server.
Direct MCP Connection¶
Available Tools¶
get_nwsl_player_rankings- Player stats and rankingsanalyze_spatial_patterns- Shot patterns, positioning analysisanalyze_player_intelligence- Player performance in contextanalyze_tactical_effectiveness- Formation and tactical analysiscompare_player_performance- Multi-player comparisons
MCP Request Example¶
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "analyze_spatial_patterns",
"arguments": {"team": "Portland Thorns", "season": "2025"}
},
"id": 1
}
Common Query Patterns¶
Player Statistics¶
-- Top goal scorers
SELECT
p.player_name,
COUNT(*) as goals,
AVG(e.x) as avg_shot_x,
AVG(e.y) as avg_shot_y
FROM events_2025 e
JOIN players p ON e.player_id = p.id
WHERE e.event_type_id = 16
GROUP BY p.player_name
ORDER BY goals DESC
LIMIT 10;
Team Performance¶
-- Team win/loss records
SELECT
c.name as team_name,
COUNT(*) as matches_played,
SUM(CASE WHEN md.winner_id = c.id THEN 1 ELSE 0 END) as wins
FROM matches m
JOIN match_details md ON m.id = md.match_id
JOIN match_contestants mc ON m.id = mc.match_id
JOIN contestants c ON mc.contestant_id = c.id
WHERE m.season = 2025
GROUP BY c.name
ORDER BY wins DESC;
Event Analysis with Qualifiers¶
-- Pass completion with end coordinates
SELECT
e.id,
e.minute,
e.x as start_x,
e.y as start_y,
eq_end_x.value as end_x,
eq_end_y.value as end_y,
eq_success.value as successful
FROM events_2025 e
LEFT JOIN event_qualifiers_2025 eq_end_x ON e.id = eq_end_x.event_id AND eq_end_x.qualifier_id = 140
LEFT JOIN event_qualifiers_2025 eq_end_y ON e.id = eq_end_y.event_id AND eq_end_y.qualifier_id = 141
LEFT JOIN event_qualifiers_2025 eq_success ON e.id = eq_success.event_id AND eq_success.qualifier_id = 1
WHERE e.event_type_id = 1 -- Passes
AND e.match_id = 12345
ORDER BY e.minute;
Performance Optimization¶
Query Best Practices¶
- Use year-partitioned event tables (
events_2025vsevents_2024) - Include
LIMITclauses for large result sets - Filter by
match_idorteam_idwhen possible - Use indexes on
player_id,event_type_id, andminute
Rate Limits¶
- SQL Endpoint: 100 requests/hour per IP
- Analytics Endpoint: 50 requests/hour per IP
- MCP Server: 100 requests/hour per token
Error Handling¶
import time
def robust_query(client, sql, max_retries=3):
for attempt in range(max_retries):
try:
return client.query(sql)
except requests.HTTPError as e:
if e.response.status_code == 429: # Rate limit
time.sleep(60)
continue
elif e.response.status_code == 408: # Timeout
# Simplify query or add more filters
raise
else:
raise
Integration Examples¶
React Application¶
import React, { useState, useEffect } from 'react';
function PlayerStats({ playerName }) {
const [stats, setStats] = useState(null);
useEffect(() => {
fetch('https://nwsl-database-proxy-78453984015.us-central1.run.app/query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `Show me ${playerName}'s 2025 season statistics`
})
})
.then(res => res.json())
.then(setStats);
}, [playerName]);
return stats ? <div>{stats.summary}</div> : <div>Loading...</div>;
}
Jupyter Notebook¶
# Install dependencies
# !pip install pandas matplotlib seaborn requests
import pandas as pd
import matplotlib.pyplot as plt
# Fetch and visualize data
client = NWSLClient()
goals = client.query("""
SELECT c.name as team, COUNT(*) as goals
FROM events_2025 e
JOIN match_contestants mc ON e.match_id = mc.match_id AND e.team_id = mc.contestant_id
JOIN contestants c ON mc.contestant_id = c.id
WHERE e.event_type_id = 16
GROUP BY c.name
ORDER BY goals DESC
""")
plt.figure(figsize=(12, 6))
plt.bar(goals['team'], goals['goals'])
plt.xticks(rotation=45)
plt.title('Goals by Team - 2025 Season')
plt.tight_layout()
plt.show()
Testing & Development¶
Health Checks¶
Always verify API connectivity before starting development:
Sample Queries for Testing¶
-- Quick connectivity test
SELECT COUNT(*) FROM matches;
-- Data availability check
SELECT season, COUNT(*) FROM matches GROUP BY season ORDER BY season;
-- Recent events sample
SELECT * FROM events_2025 ORDER BY id DESC LIMIT 5;
Support & Resources¶
Documentation¶
- Database schema details in the Architecture section
- Usage examples in Use Cases
- Access methods in Getting Started
Getting Help¶
- Check API health endpoint first
- Verify query syntax with simple test queries
- Review rate limits if getting 429 errors
- Use natural language endpoint for complex analysis questions
Contributing¶
The NWSL Data platform is built across multiple repositories: - nwsl-loader - Data pipeline and processing - nwsl-mcp - MCP server and AI analytics - nwsl-api - Gateway API service - nwsl-nextjs-web - Web application frontend - nwsl-documentation - This documentation site
Ready to build with NWSL Data? Start with the REST API for simple integration, or dive into direct database access for advanced analytics capabilities.