API Reference

Complete documentation for the Football API.

Base URL

Environment URL
Development http://localhost:8080
Production https://api.your-domain.com

Authentication

All /api/* endpoints require an API key via the X-API-Key header.

curl -H "X-API-Key: your-api-key" https://api.your-domain.com/api/events

Public endpoints (/health, /ready, /metrics, /) do not require authentication.


Health Endpoints

GET /health

Simple liveness check. Returns 200 if the service is running.

Request:

curl http://localhost:8080/health

Response (200 OK):

{
  "status": "healthy",
  "timestamp": "2024-01-15T14:30:00Z"
}

GET /ready

Readiness check that verifies Kafka and ClickHouse connectivity.

Request:

curl http://localhost:8080/ready

Response (200 OK):

{
  "status": "ready",
  "timestamp": "2024-01-15T14:30:00Z",
  "checks": {
    "clickhouse": "healthy",
    "kafka": "healthy"
  }
}

Response (503 Service Unavailable):

{
  "status": "not ready",
  "timestamp": "2024-01-15T14:30:00Z",
  "checks": {
    "clickhouse": "unhealthy: connection refused",
    "kafka": "healthy"
  }
}

Documentation Endpoints

GET /

Swagger UI - Interactive API documentation.

GET /openapi.yaml

OpenAPI 3.0.3 specification file.

GET /metrics

Prometheus metrics endpoint.


Match Events API

POST /api/events

Ingest a match event (goal, pass, foul, etc.).

Headers: | Header | Required | Description | |——–|———-|————-| | Content-Type | Yes | application/json | | X-API-Key | Yes | Your API key |

Request Body:

{
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "matchId": "match-2024-001",
  "eventType": "goal",
  "timestamp": "2024-01-15T14:30:00Z",
  "teamId": 1,
  "playerId": "player-123",
  "metadata": {
    "minute": 45,
    "assistPlayerId": "player-456"
  }
}

Fields:

Field Type Required Description
eventId string (UUID) Yes Unique event identifier
matchId string Yes Match identifier
eventType string (enum) Yes Type of event
timestamp string (RFC3339) Yes Event timestamp
teamId integer Yes Team identifier (1 or 2)
playerId string No Player identifier
metadata object No Additional event data

Event Types:

Type Description
pass Completed pass
shot Shot attempt
goal Goal scored
foul Foul committed
yellow_card Yellow card shown
red_card Red card shown
substitution Player substitution
offside Offside call
corner Corner kick awarded
free_kick Free kick awarded
interception Ball intercepted

Response (202 Accepted):

{
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "accepted",
  "timestamp": "2024-01-15T14:30:00Z"
}

Example:

curl -X POST http://localhost:8080/api/events \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "eventId": "550e8400-e29b-41d4-a716-446655440000",
    "matchId": "match-2024-001",
    "eventType": "goal",
    "timestamp": "2024-01-15T14:30:00Z",
    "teamId": 1,
    "playerId": "player-10",
    "metadata": {
      "minute": 45,
      "assistPlayerId": "player-7"
    }
  }'

Engagement Events API

POST /api/engagements

Batch ingest viewer engagement events.

Headers: | Header | Required | Description | |——–|———-|————-| | Content-Type | Yes | application/json | | X-API-Key | Yes | Your API key |

Request Body:

{
  "events": [
    {
      "event_id": "550e8400-e29b-41d4-a716-446655440001",
      "match_id": "match-2024-001",
      "user_id": "user-456",
      "session_id": "session-789",
      "engagement_type": "reaction",
      "engagement_subtype": "emoji_goal",
      "related_game_event_id": "550e8400-e29b-41d4-a716-446655440000",
      "game_minute": 45,
      "device_type": "mobile",
      "platform": "ios",
      "country_code": "US",
      "content": "GOOOAL!",
      "metadata": {},
      "timestamp": "2024-01-15T14:30:01Z"
    }
  ]
}

Event Fields:

Field Type Required Description
event_id string (UUID) Yes Unique event identifier
match_id string Yes Match identifier
user_id string Yes User identifier
session_id string Yes Session identifier
engagement_type string (enum) Yes Type of engagement
engagement_subtype string No Subtype of engagement
related_game_event_id string (UUID) No Related match event
game_minute integer Yes Match minute (0-120)
device_type string (enum) Yes Device type
platform string No Platform (ios, android, web)
country_code string No ISO country code
content string No User-generated content
metadata object No Additional data
timestamp string (RFC3339) Yes Event timestamp

Engagement Types and Subtypes:

Type Subtypes
reaction cheer, boo, emoji_goal, emoji_fire, emoji_clap, emoji_cry, emoji_angry, emoji_heart, emoji_laugh, emoji_wow
comment match_commentary, player_discussion, team_support, trash_talk, question
video_action pause, play, rewind, replay, camera_switch, quality_change, fullscreen
share twitter, facebook, whatsapp, instagram, in_app, copy_link
prediction score_prediction, next_goal, player_rating, poll_vote, man_of_match
click stats_view, player_profile, team_info, lineup, ad_click, merchandise, ticket
session join, leave, heartbeat, reconnect

Device Types:

Type Description
mobile Mobile phone
desktop Desktop computer
tablet Tablet device
tv Smart TV or streaming device
unknown Unknown device type

Response (202 Accepted):

{
  "accepted": 1,
  "rejected": 0,
  "status": "accepted",
  "timestamp": "2024-01-15T14:30:02Z"
}

Example:

curl -X POST http://localhost:8080/api/engagements \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "events": [
      {
        "event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "match_id": "match-2024-001",
        "user_id": "user-123",
        "session_id": "sess-456",
        "engagement_type": "reaction",
        "engagement_subtype": "emoji_goal",
        "game_minute": 45,
        "device_type": "mobile",
        "platform": "ios",
        "timestamp": "2024-01-15T14:30:01Z"
      },
      {
        "event_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
        "match_id": "match-2024-001",
        "user_id": "user-789",
        "session_id": "sess-012",
        "engagement_type": "comment",
        "engagement_subtype": "match_commentary",
        "game_minute": 45,
        "device_type": "desktop",
        "content": "What a goal!",
        "timestamp": "2024-01-15T14:30:02Z"
      }
    ]
  }'

Metrics API

GET /api/matches/{matchId}/metrics

Query aggregated metrics for a match.

Path Parameters:

Parameter Type Description
matchId string Match identifier

Headers: | Header | Required | Description | |——–|———-|————-| | X-API-Key | Yes | Your API key |

Response (200 OK):

{
  "matchId": "match-2024-001",
  "totalEvents": 1250,
  "eventsByType": {
    "pass": 800,
    "shot": 150,
    "goal": 2,
    "yellow_card": 3,
    "red_card": 0
  },
  "goals": 2,
  "yellowCards": 3,
  "redCards": 0,
  "firstEventAt": "2024-01-15T14:00:00Z",
  "lastEventAt": "2024-01-15T15:45:00Z",
  "peakMinute": {
    "minute": "2024-01-15T14:45:00Z",
    "eventCount": 42
  },
  "responseTimePercentiles": {
    "p50": 15.5,
    "p95": 45.2,
    "p99": 78.9
  }
}

Example:

curl http://localhost:8080/api/matches/match-2024-001/metrics \
  -H "X-API-Key: your-api-key"

Error Responses

All errors follow a consistent format:

{
  "error": "Bad Request",
  "message": "invalid JSON body",
  "field": "eventType"
}

Status Codes:

Code Description
400 Bad Request - Invalid input data
401 Unauthorized - Missing or invalid API key
404 Not Found - Resource not found
500 Internal Server Error - Server error
503 Service Unavailable - Dependencies unhealthy

Rate Limiting

Currently, no rate limiting is enforced at the application level. For production deployments, consider:


Timeouts

Operation Timeout
Request read 10s
Request write 10s
Request processing 30s
Kafka produce 10s

SDK Examples

Python

import requests

API_URL = "http://localhost:8080"
API_KEY = "your-api-key"

headers = {
    "Content-Type": "application/json",
    "X-API-Key": API_KEY
}

# Send match event
event = {
    "eventId": "550e8400-e29b-41d4-a716-446655440000",
    "matchId": "match-001",
    "eventType": "goal",
    "timestamp": "2024-01-15T14:30:00Z",
    "teamId": 1
}
response = requests.post(f"{API_URL}/api/events", json=event, headers=headers)
print(response.json())

# Send engagement events
engagements = {
    "events": [{
        "event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "match_id": "match-001",
        "user_id": "user-123",
        "session_id": "sess-456",
        "engagement_type": "reaction",
        "game_minute": 45,
        "device_type": "mobile",
        "timestamp": "2024-01-15T14:30:01Z"
    }]
}
response = requests.post(f"{API_URL}/api/engagements", json=engagements, headers=headers)
print(response.json())

JavaScript

const API_URL = "http://localhost:8080";
const API_KEY = "your-api-key";

// Send match event
const event = {
  eventId: crypto.randomUUID(),
  matchId: "match-001",
  eventType: "goal",
  timestamp: new Date().toISOString(),
  teamId: 1
};

const response = await fetch(`${API_URL}/api/events`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": API_KEY
  },
  body: JSON.stringify(event)
});

console.log(await response.json());

Go

package main

import (
    "bytes"
    "encoding/json"
    "net/http"
)

func main() {
    event := map[string]interface{}{
        "eventId":   "550e8400-e29b-41d4-a716-446655440000",
        "matchId":   "match-001",
        "eventType": "goal",
        "timestamp": "2024-01-15T14:30:00Z",
        "teamId":    1,
    }

    body, _ := json.Marshal(event)
    req, _ := http.NewRequest("POST", "http://localhost:8080/api/events", bytes.NewBuffer(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-API-Key", "your-api-key")

    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()
}