Login Get API Key
Tutorials

How to Let Users Add Matches to Their Calendar (ICS Export)

Illustration of a football match being added to a calendar app

Learn how to generate .ics calendar files from match data so users can add kickoff times directly to Google Calendar, Outlook, or Apple Calendar.

On this page

"Add to calendar" is a small feature that removes real friction β€” instead of relying on your app's own push notifications (which users may have muted), you can drop a kickoff time directly into whatever calendar app someone already checks every day. This guide covers generating standard .ics files from match data.

Why .ics Instead of a Custom Reminder System

The .ics format (iCalendar) is supported natively by Google Calendar, Apple Calendar, Outlook, and virtually every calendar app. Generating one server-side means zero dependency on push notification permissions or your own reminder infrastructure β€” the user's own calendar app handles the reminder.

Step 1: Getting the Match Data You Need

Pull the match from /matches, which gives you everything required for a calendar entry β€” teams, kickoff time, and competition name:

import requests

response = requests.get(
    'https://live-football-api.com/api/v1/matches',
    params={'api_key': 'YOUR_KEY', 'date': '2026-09-05', 'lang': 'en'}
).json()

match = response['data']['matches'][0]

Step 2: Building the .ics File Content

An .ics file is plain text following a specific structure. Here's a minimal generator for a single match:

from datetime import datetime, timedelta

def build_ics(match):
    start = datetime.strptime(match['date_time'], '%Y-%m-%d %H:%M:%S')
    end = start + timedelta(hours=2)  # matches typically run under 2 hours

    summary = f"{match['home']['name']} vs {match['away']['name']}"
    description = f"{match['league']['name']} β€” kickoff at {match['kickoff']}"

    ics = f"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//YourApp//Football Matches//EN
BEGIN:VEVENT
UID:{match['id']}@yourapp.com
DTSTAMP:{datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')}
DTSTART:{start.strftime('%Y%m%dT%H%M%S')}
DTEND:{end.strftime('%Y%m%dT%H%M%S')}
SUMMARY:{summary}
DESCRIPTION:{description}
LOCATION:{match.get('venue', '')}
END:VEVENT
END:VCALENDAR"""
    return ics

Step 3: Serving the File for Download

Set the correct MIME type so browsers and mobile devices recognize it as a calendar file rather than plain text:

# Flask example
from flask import Response

@app.route('/match/<match_id>/calendar.ics')
def match_calendar(match_id):
    match = get_match_by_id(match_id)  # your own lookup
    ics_content = build_ics(match)

    return Response(
        ics_content,
        mimetype='text/calendar',
        headers={'Content-Disposition': f'attachment; filename="match-{match_id}.ics"'}
    )

Step 4: Handling Time Zones Correctly

Kickoff times from the API are typically in the competition's local time or UTC depending on the endpoint β€” always confirm which, and convert explicitly rather than assuming. A mismatch here is the most common bug in calendar exports, since a wrongly-timed calendar entry is worse than no entry at all:

import pytz

def to_utc_ics_format(date_str, source_tz='Europe/London'):
    naive = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')
    localized = pytz.timezone(source_tz).localize(naive)
    utc_time = localized.astimezone(pytz.utc)
    return utc_time.strftime('%Y%m%dT%H%M%SZ')

Using the UTC format (with a trailing Z) in DTSTART/DTEND is the safest choice β€” every calendar app converts it to the viewer's local time zone automatically, so you don't need to guess where your user actually is.

Step 5: Adding Multiple Matches (a Team's Full Fixture List)

A single .ics file can contain multiple VEVENT blocks β€” useful for a "subscribe to my team's whole season" feature built from /team_matches:

def build_season_ics(matches, team_name):
    events = '\n'.join(build_vevent_block(m) for m in matches)
    return f"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//YourApp//{team_name} Fixtures//EN
{events}
END:VCALENDAR"""

def build_vevent_block(match):
    start = parse_and_convert_to_utc(match['date_time'])
    return f"""BEGIN:VEVENT
UID:{match['id']}@yourapp.com
DTSTAMP:{datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')}
DTSTART:{start}
SUMMARY:{match['home']['name']} vs {match['away']['name']}
END:VEVENT"""

Rather than a one-time download, you can serve a stable URL that calendar apps periodically re-fetch β€” this keeps postponed or rescheduled matches automatically updated in the user's calendar without them re-downloading anything:

<a href="webcal://yourapp.com/team/arsenal/calendar.ics">
  Subscribe to Arsenal fixtures
</a>

The webcal:// scheme tells the OS to treat it as a live calendar subscription rather than a one-off file download β€” most calendar apps then re-check that URL periodically for changes.

Frequently Asked Questions

Does adding a calendar event require any special API access?

No, this is entirely built from standard /matches or /team_matches data on your own server β€” no special endpoint or extra credits are needed beyond the normal match data call.

What happens if a match gets postponed after a user has already added it to their calendar?

A one-time downloaded .ics file won't update automatically β€” this is exactly what the webcal:// subscription approach solves, since the calendar app re-fetches the source periodically.

Should I include venue information in the calendar event?

Yes if available β€” the LOCATION field lets the user's calendar app show a map link or travel time estimate, which is a nice touch for fans attending in person.

Can I generate .ics files client-side instead of on my server?

Yes, the same string-building logic works in JavaScript in the browser β€” generate a Blob with the .ics content and trigger a download via an anchor tag, without needing a server round-trip.

Share this article:
← Back to blog