Skip to main content
GET
/
games
/
h2h
Head-to-Head
curl --request GET \
  --url https://v1.basketball.sportsapipro.com/games/h2h \
  --header 'x-api-key: <api-key>'

Endpoint

GET https://v1.football.sportsapipro.com/games/h2h

Description

Returns comprehensive head-to-head data for a specific match, including:
  • Direct H2H matches (h2hGames) - All historical meetings between the two teams
  • Recent form (homeCompetitor.recentGames / awayCompetitor.recentGames) - Each team’s recent matches against any opponent
  • Betting odds - Available bookmakers and odds for the match
  • Metadata - Competition, country, and sport information

Response Data Structure

Understanding the data structure is crucial for correctly displaying H2H information.

Key Data Locations

Data TypeLocationDescription
Direct H2H Matchesgame.h2hGames[]All matches where these two teams faced each other (past + scheduled)
Home Team Recent Formgame.homeCompetitor.recentGames[]Home team’s recent matches vs any opponent
Away Team Recent Formgame.awayCompetitor.recentGames[]Away team’s recent matches vs any opponent
Bookmakers & Oddsbookmakers[]Available betting options
Competition Infocompetitions[]Competition metadata
Country Infocountries[]Country metadata
Common Mistake: Do NOT filter recentGames to find H2H matches. Use h2hGames directly - it contains all direct meetings between the two teams.

Parameters

ParameterTypeRequiredDescription
gameIdnumberYesThe game identifier from game.id in fixtures/results
matchupIdstringYesMatchup identifier from game.matchupId in fixtures/results
timezoneNamestringNoTimezone for date/time values (auto-resolved, or specify e.g., America/New_York)
All startTime fields use ISO 8601 format with a dynamic timezone offset based on the resolved or specified timezone.

matchupId Format

The matchupId uses hyphens to separate IDs:
  • homeCompetitorId-awayCompetitorId (e.g., "131-132")
  • homeCompetitorId-awayCompetitorId-competitionId (e.g., "131-132-7")
Team order does NOT matter. The API returns the same data for 14-106 and 106-14.
Best practice: Always use matchupId exactly as returned from fixtures/results endpoints.

Request Examples

curl -X GET "https://v1.football.sportsapipro.com/games/h2h?gameId=4477087&matchupId=224-245-23" \
  -H "x-api-key: YOUR_API_KEY"

Response Structure

{
  "game": {
    "id": 4477087,
    "sportId": 1,
    "competitionId": 23,
    "seasonNum": 11,
    "stageNum": 1,
    "groupNum": 0,
    "roundName": "Round",
    "stageName": "Semi Finals",
    "competitionDisplayName": "Supercoppa Italiana - Semi Finals",
    "startTime": "2025-12-19T19:00:00+00:00",
    "statusGroup": 4,
    "statusText": "After Penalties",
    "shortStatusText": "AP",
    "gameTimeAndStatusDisplayType": 1,
    "justEnded": false,
    "gameTime": 120,
    "gameTimeDisplay": "120'",
    "hasLineups": true,
    "hasMissingPlayers": false,
    "hasFieldPositions": true,
    "lineupsStatus": 2,
    "lineupsStatusText": "Confirmed",
    "hasTVNetworks": true,
    "winDescription": "Bologna won after penalties",
    "homeCompetitor": { /* Competitor Object */ },
    "awayCompetitor": { /* Competitor Object */ },
    "h2hGames": [ /* Array of H2H Game Objects */ ],
    "isHomeAwayInverted": false,
    "hasStats": true,
    "hasStandings": true,
    "hasBrackets": true,
    "hasPreviousMeetings": true,
    "hasRecentMatches": true,
    "winner": 1,
    "homeAwayTeamOrder": 1,
    "hasNews": true,
    "hasPointByPoint": false,
    "hasVideo": true
  },
  "bookmakers": [ /* Array of Bookmaker Objects */ ],
  "sports": [ /* Array of Sport Objects */ ],
  "countries": [ /* Array of Country Objects */ ],
  "competitions": [ /* Array of Competition Objects */ ]
}

Complete Response Fields Reference

Root Level Fields

FieldTypeDescription
gameObjectMain game object containing all game details
bookmakersArrayList of available bookmakers with odds
sportsArraySports information
countriesArrayCountry information
competitionsArrayCompetition information

Game Object Fields

FieldTypeDescription
idNumberUnique game identifier
sportIdNumberSport type ID (1 = Football)
competitionIdNumberCompetition ID
seasonNumNumberSeason number
stageNumNumberStage number
groupNumNumberGroup number
roundNameStringRound name (e.g., “Round”)
stageNameStringStage name (e.g., “Semi Finals”)
competitionDisplayNameStringFull competition name for display
startTimeStringGame start time (ISO 8601 format with timezone offset based on auto-resolved or specified timezone)
statusGroupNumberStatus group code (1 = Not Started, 2 = Scheduled, 3 = Live / In Progress, 4 = Ended, 5 = Cancelled, 6 = Postponed, 8 = Abandoned)
statusTextStringHuman-readable status (e.g., “After Penalties”)
shortStatusTextStringShort status text (e.g., “AP”)
gameTimeAndStatusDisplayTypeNumberDisplay type indicator
justEndedBooleanWhether game just ended
gameTimeNumberGame time in minutes
gameTimeDisplayStringFormatted game time for display
hasLineupsBooleanWhether lineups are available
hasMissingPlayersBooleanWhether there are missing players
hasFieldPositionsBooleanWhether field positions are available
lineupsStatusNumberLineups status code
lineupsStatusTextStringLineups status text
hasTVNetworksBooleanWhether TV networks are available
winDescriptionStringDescription of the winner
homeCompetitorObjectHome team/competitor details
awayCompetitorObjectAway team/competitor details
h2hGamesArrayHead-to-head historical games between the two teams
isHomeAwayInvertedBooleanWhether home/away is inverted
hasStatsBooleanWhether stats are available
hasStandingsBooleanWhether standings are available
hasBracketsBooleanWhether brackets are available
hasPreviousMeetingsBooleanWhether previous meetings are available
hasRecentMatchesBooleanWhether recent matches are available
winnerNumberWinner indicator (0 = draw/scheduled, 1 = home, 2 = away)
homeAwayTeamOrderNumberTeam order indicator
hasNewsBooleanWhether news is available
hasPointByPointBooleanWhether point-by-point data is available
hasVideoBooleanWhether video is available

Competitor Object Fields

Used for both homeCompetitor and awayCompetitor.
FieldTypeDescription
idNumberCompetitor ID
countryIdNumberCountry ID
sportIdNumberSport ID
nameStringCompetitor name
symbolicNameStringShort symbolic name (e.g., “BOL”)
nameForURLStringURL-friendly name
typeNumberCompetitor type
popularityRankNumberPopularity ranking
imageVersionNumberImage version number
scoreNumberCurrent score
isWinnerBooleanWhether competitor is winner
outcomeNumberOutcome code
colorStringPrimary color (hex)
awayColorStringAway color (hex)
hasSquadBooleanWhether squad info available
hasTransfersBooleanWhether transfer info available
competitorNumNumberCompetitor number
hideOnSearchBooleanHide on search flag
hideOnCatalogBooleanHide on catalog flag
mainCompetitionIdNumberMain competition ID
recentGamesArrayArray of recent games (against any opponent)

Recent Games / H2H Games Object Fields

Used for both h2hGames[] and recentGames[] arrays.
FieldTypeDescription
idNumberGame ID
sportIdNumberSport ID
competitionIdNumberCompetition ID
seasonNumNumberSeason number
stageNumNumberStage number
roundNumNumberRound number
roundNameStringRound name
competitionDisplayNameStringCompetition display name
startTimeStringStart time (ISO 8601 with timezone offset)
statusGroupNumberStatus group (1 = Not Started, 2 = Scheduled, 3 = Live / In Progress, 4 = Ended, 5 = Cancelled, 6 = Postponed, 8 = Abandoned)
statusTextStringStatus text
shortStatusTextStringShort status
gameTimeAndStatusDisplayTypeNumberDisplay type
oddsObjectOdds information
homeCompetitorObjectHome competitor (nested)
awayCompetitorObjectAway competitor (nested)
outcomeNumberGame outcome
extraDataArrayExtra data (scores, etc.)
winnerNumberWinner indicator (1 = home, 2 = away, -1 = draw, 0 = scheduled)
scoresArrayScore array [homeScore, awayScore]
homeAwayTeamOrderNumberTeam order
hasPointByPointBooleanPoint-by-point available
hasVideoBooleanVideo available

Odds Object Fields

FieldTypeDescription
lineIdNumberOdds line ID
gameIdNumberGame ID
bookmakerIdNumberBookmaker ID
lineTypeIdNumberLine type ID
lineTypeObjectLine type details
linkStringBookmaker link
bookmakerObjectBookmaker details
optionsArrayBetting options
outcomeOptionNumNumberOutcome option number
isConcludedBooleanWhether odds concluded

Odds Option Fields

FieldTypeDescription
numNumberOption number
nameStringOption name (1, X, 2)
rateObjectOdds rates
bookmakerIdNumberBookmaker ID
originalRateObjectOriginal odds rates
linkStringBetting link
trendNumberTrend indicator
isWonBooleanWhether this option won

Rate Object Fields

FieldTypeDescription
decimalNumberDecimal odds format
fractionalStringFractional odds format
americanStringAmerican odds format

Bookmaker Object Fields

FieldTypeDescription
idNumberBookmaker ID
nameStringBookmaker name
linkStringBookmaker link
nameForURLStringURL-friendly name
actionButtonObjectAction button details
colorStringBrand color (hex)
imageVersionNumberImage version
promotionTextStringPromotion text

Competition Object Fields

FieldTypeDescription
idNumberCompetition ID
countryIdNumberCountry ID
sportIdNumberSport ID
nameStringCompetition name
longNameStringLong competition name
hasStandingsBooleanHas standings
hasBracketsBooleanHas brackets
hasStatsBooleanHas statistics
nameForURLStringURL-friendly name
popularityRankNumberPopularity rank
imageVersionNumberImage version
currentStageTypeNumberCurrent stage type
colorStringCompetition color
competitorsTypeNumberCompetitors type
currentPhaseNumNumberCurrent phase
currentSeasonNumNumberCurrent season
currentStageNumNumberCurrent stage
isInternationalBooleanIs international
hasHistoryBooleanHas history

Complete Workflow Example

Step 1: Fetch Fixtures to Get Game and Matchup IDs

const fixturesResponse = await fetch(
  "https://v1.football.sportsapipro.com/games/fixtures?competitions=9",
  { headers: { "x-api-key": "YOUR_API_KEY" } }
);
const fixtures = await fixturesResponse.json();

// Each game contains the IDs you need
const game = fixtures.games[0];
console.log({
  gameId: game.id,                    // e.g., 4609057
  matchupId: game.matchupId,          // e.g., "14-106-9"
  homeTeamId: game.homeCompetitor.id, // e.g., 14
  awayTeamId: game.awayCompetitor.id  // e.g., 106
});

Step 2: Call H2H Endpoint

const h2hResponse = await fetch(
  `https://v1.football.sportsapipro.com/games/h2h?gameId=${game.id}&matchupId=${game.matchupId}`,
  { headers: { "x-api-key": "YOUR_API_KEY" } }
);
const h2hData = await h2hResponse.json();

Step 3: Access Direct H2H Matches

// Direct H2H matches are in h2hGames array
const directH2HMatches = h2hData.game.h2hGames;
console.log(`Found ${directH2HMatches.length} direct H2H matches`);

// Filter for completed matches only (statusGroup 4 = ended)
const completedH2H = directH2HMatches.filter(match => match.statusGroup === 4);
console.log(`${completedH2H.length} completed matches`);

Step 4: Access Recent Form for Each Team

// Recent matches for each team (against any opponent)
const homeTeamForm = h2hData.game.homeCompetitor.recentGames;
const awayTeamForm = h2hData.game.awayCompetitor.recentGames;

console.log(`Home team has ${homeTeamForm.length} recent matches`);
console.log(`Away team has ${awayTeamForm.length} recent matches`);

Common Use Cases

1. Calculate H2H Win/Draw/Loss Record

function calculateH2HRecord(h2hGames, homeTeamId, awayTeamId) {
  // Filter for completed matches only
  const completedMatches = h2hGames.filter(match => 
    match.statusGroup === 4 && match.scores?.length >= 2
  );
  
  let homeWins = 0, awayWins = 0, draws = 0;
  
  completedMatches.forEach(match => {
    if (match.winner === 1) {
      // Home team of THIS match won
      if (match.homeCompetitor.id === homeTeamId) homeWins++;
      else awayWins++;
    } else if (match.winner === 2) {
      // Away team of THIS match won
      if (match.awayCompetitor.id === homeTeamId) homeWins++;
      else awayWins++;
    } else {
      draws++;
    }
  });
  
  return { homeWins, awayWins, draws, total: completedMatches.length };
}

// Usage
const record = calculateH2HRecord(
  h2hData.game.h2hGames,
  h2hData.game.homeCompetitor.id,
  h2hData.game.awayCompetitor.id
);
console.log(`${record.homeWins} - ${record.draws} - ${record.awayWins}`);

2. Calculate Team Form (W/D/L String)

function calculateForm(recentGames, teamId, matchCount = 5) {
  return recentGames.slice(0, matchCount).map(game => {
    const isHome = game.homeCompetitor?.id === teamId;
    const teamScore = isHome ? game.homeCompetitor?.score : game.awayCompetitor?.score;
    const oppScore = isHome ? game.awayCompetitor?.score : game.homeCompetitor?.score;
    
    if (teamScore > oppScore) return 'W';
    if (teamScore === oppScore) return 'D';
    return 'L';
  });
}

// Usage
const homeForm = calculateForm(
  h2hData.game.homeCompetitor.recentGames,
  h2hData.game.homeCompetitor.id
);
console.log(`Form: ${homeForm.join('')}`); // e.g., "WWDLW"

3. Separate Scheduled vs Completed H2H Matches

const h2hGames = h2hData.game.h2hGames;

// Upcoming matches between these teams (statusGroup 1 = Scheduled)
const upcomingH2H = h2hGames.filter(match => match.statusGroup === 1);

// Live matches between these teams (statusGroup 2 or 3 = In progress)
const liveH2H = h2hGames.filter(match => match.statusGroup === 2 || match.statusGroup === 3);

// Completed matches between these teams (statusGroup 4 = Ended)
const completedH2H = h2hGames.filter(match => match.statusGroup === 4);

console.log(`${upcomingH2H.length} upcoming, ${liveH2H.length} live, ${completedH2H.length} completed`);

4. Calculate Goals Scored in H2H

function calculateH2HGoals(h2hGames, teamId) {
  const completedMatches = h2hGames.filter(m => m.statusGroup === 4 && m.scores);
  
  let goalsFor = 0, goalsAgainst = 0;
  
  completedMatches.forEach(match => {
    const isHome = match.homeCompetitor.id === teamId;
    goalsFor += isHome ? (match.scores[0] || 0) : (match.scores[1] || 0);
    goalsAgainst += isHome ? (match.scores[1] || 0) : (match.scores[0] || 0);
  });
  
  return { goalsFor, goalsAgainst, matches: completedMatches.length };
}

5. Get Most Recent N H2H Games

function getRecentH2H(h2hGames, count = 10) {
  // Filter completed games, sort by startTime descending
  return h2hGames
    .filter(match => match.statusGroup === 4)
    .sort((a, b) => new Date(b.startTime) - new Date(a.startTime))
    .slice(0, count);
}

// Get last 10 H2H meetings
const recentH2H = getRecentH2H(h2hData.game.h2hGames, 10);

6. Filter H2H by Competition

function getH2HByCompetition(h2hGames, competitionId) {
  return h2hGames.filter(match => match.competitionId === competitionId);
}

// Get only Serie A H2H matches
const serieAH2H = getH2HByCompetition(h2hData.game.h2hGames, 9);

Empty Response Scenarios

ScenarioCauseWhat to Display
Empty h2hGamesTeams have never played each other”No previous meetings”
Empty recentGamesNew team or limited data”No recent match history”
Only scheduled in h2hGamesNo completed H2H yetShow upcoming match, “First meeting”
const h2hGames = h2hData.game.h2hGames || [];
const completedH2H = h2hGames.filter(m => m.statusGroup === 4);

if (h2hGames.length === 0) {
  showMessage("These teams have never met");
} else if (completedH2H.length === 0) {
  showMessage("First competitive meeting between these teams");
} else {
  displayH2HHistory(completedH2H);
}

Rate Limiting & Caching

Each fixture requires a separate H2H call. Cache results to avoid hitting rate limits.
const h2hCache = new Map();
const CACHE_TTL = 60 * 60 * 1000; // 1 hour

async function getCachedH2H(gameId, matchupId, apiKey) {
  const cacheKey = `${gameId}-${matchupId}`;
  
  if (h2hCache.has(cacheKey)) {
    const { data, timestamp } = h2hCache.get(cacheKey);
    if (Date.now() - timestamp < CACHE_TTL) return data;
  }
  
  const response = await fetch(
    `https://v1.football.sportsapipro.com/games/h2h?gameId=${gameId}&matchupId=${matchupId}`,
    { headers: { "x-api-key": apiKey } }
  );
  const data = await response.json();
  
  h2hCache.set(cacheKey, { data, timestamp: Date.now() });
  return data;
}

Common Issues & Solutions

IssueCauseSolution
400 Bad RequestMissing parametersInclude both gameId and matchupId
401 UnauthorizedInvalid API keyCheck x-api-key header
404 Not FoundInvalid game IDVerify gameId from fixtures
Empty h2hGamesNo historical meetingsDisplay “First meeting” message
Wrong scoresUsing recentGames for H2HUse h2hGames for direct meetings
Only 10 matches shownDefault paginationAll matches returned; handle in frontend
Common Mistakes to Avoid:
  1. Don’t filter recentGames for H2H - Use h2hGames directly
  2. Don’t assume all h2hGames are completed - Check statusGroup === 4
  3. Don’t construct matchupId manually - Use value from fixtures
  4. Don’t ignore winner values - 0 means scheduled, -1 means draw

Complete Implementation Example

class H2HService {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://v1.football.sportsapipro.com';
  }

  async getH2H(gameId, matchupId) {
    const response = await fetch(
      `${this.baseUrl}/games/h2h?gameId=${gameId}&matchupId=${matchupId}`,
      { headers: { 'x-api-key': this.apiKey } }
    );
    return response.json();
  }

  analyzeH2H(h2hData) {
    const homeTeam = h2hData.game.homeCompetitor;
    const awayTeam = h2hData.game.awayCompetitor;
    const h2hGames = h2hData.game.h2hGames || [];
    
    // Completed H2H matches
    const completedH2H = h2hGames.filter(m => m.statusGroup === 4);
    
    // Upcoming H2H matches
    const upcomingH2H = h2hGames.filter(m => m.statusGroup === 2);
    
    // Calculate H2H record
    const record = this.calculateRecord(completedH2H, homeTeam.id);
    
    // Calculate goals
    const homeGoals = this.calculateGoals(completedH2H, homeTeam.id);
    const awayGoals = this.calculateGoals(completedH2H, awayTeam.id);
    
    // Calculate form for each team
    const homeForm = this.calculateForm(homeTeam.recentGames, homeTeam.id);
    const awayForm = this.calculateForm(awayTeam.recentGames, awayTeam.id);
    
    return {
      homeTeam: homeTeam.name,
      awayTeam: awayTeam.name,
      h2hRecord: record,
      totalMeetings: completedH2H.length,
      upcomingMeetings: upcomingH2H.length,
      homeGoals,
      awayGoals,
      homeForm,
      awayForm,
      recentH2H: completedH2H.slice(0, 5),
      allH2H: h2hGames
    };
  }

  calculateRecord(matches, homeTeamId) {
    let homeWins = 0, awayWins = 0, draws = 0;
    
    matches.forEach(match => {
      if (match.winner === 1) {
        match.homeCompetitor.id === homeTeamId ? homeWins++ : awayWins++;
      } else if (match.winner === 2) {
        match.awayCompetitor.id === homeTeamId ? homeWins++ : awayWins++;
      } else {
        draws++;
      }
    });
    
    return { homeWins, awayWins, draws };
  }

  calculateGoals(matches, teamId) {
    let goalsFor = 0, goalsAgainst = 0;
    
    matches.forEach(match => {
      if (!match.scores) return;
      const isHome = match.homeCompetitor.id === teamId;
      goalsFor += isHome ? match.scores[0] : match.scores[1];
      goalsAgainst += isHome ? match.scores[1] : match.scores[0];
    });
    
    return { goalsFor, goalsAgainst };
  }

  calculateForm(recentGames, teamId) {
    return recentGames.slice(0, 5).map(game => {
      const isHome = game.homeCompetitor?.id === teamId;
      const teamScore = isHome ? game.homeCompetitor?.score : game.awayCompetitor?.score;
      const oppScore = isHome ? game.awayCompetitor?.score : game.homeCompetitor?.score;
      
      if (teamScore > oppScore) return 'W';
      if (teamScore === oppScore) return 'D';
      return 'L';
    }).join('');
  }
}

// Usage
const h2hService = new H2HService('YOUR_API_KEY');
const h2hData = await h2hService.getH2H(4477087, '224-245-23');
const analysis = h2hService.analyzeH2H(h2hData);

console.log(analysis);
// {
//   homeTeam: "Bologna",
//   awayTeam: "Inter Milan",
//   h2hRecord: { homeWins: 5, awayWins: 12, draws: 3 },
//   totalMeetings: 20,
//   upcomingMeetings: 1,
//   homeGoals: { goalsFor: 18, goalsAgainst: 34 },
//   awayGoals: { goalsFor: 34, goalsAgainst: 18 },
//   homeForm: "LWDWW",
//   awayForm: "WWWLW",
//   recentH2H: [...],
//   allH2H: [...]
// }

Authorizations

x-api-key
string
header
required

Your SportsAPI Pro API key

Query Parameters

competitor1
integer
required

First team ID

Example:

678

competitor2
integer
required

Second team ID

Example:

679

numOfGames
integer
default:10

Number of historical games to return

Example:

10

Response

200

Head-to-head history retrieved successfully