Ottawa recreation schedule data
This page provides an unofficial dataset of the City of Ottawa drop-in recreation schedules, updated daily.
If you encounter any problems or inaccurate data, please open an issue or email me.
The data was last updated on 2026-07-14.
Usage
This section covers some common questions and pitfalls for using the data. Also see the schema further down this page for more detailed information about the available fields.
Data updates
I recommend polling for dataset updates every 10-60 minutes to ensure you catch updates soon after they occur. You can use standard ETag-based caching to avoid redownloading the same dataset. You should use a descriptive user-agent when fetching.
Facility keys
Facilities are keyed by their URL. Obviously, this may change, but within a single dataset, you should use this for cross-referencing. If you need a stable identifier for a facility over time, you'll need to figure that out yourself. I personally use either the first part of the address, or a an accent-stripped and sluggified facility name.
Accuracy
This dataset is scraped and parsed from the city website in a deterministic manner using a carefully designed hand-written script to ensure all times on the website are included and valid, emitting errors for any potential issues. The city has made typos and mistakes in the website on a semi-regular basis. You should display the text in the "errors" field in the dataset, if any, to avoid silently missing relevant content. I check these daily and report them to the city when necessary.
In some cases, the StartTime/EndTime may be empty due to the time being unparseable. If this is the case, you should fall back to displaying the RawTime.
The city cancels drop-in sessions on a regular basis. Usually, they update the site at least a day before (occasionally, they don't post them at all), which will cause them to appear in the facility special-hours and/or the schedule group schedule-changes. If these are present, display them to the user.
Holiday schedules may overlap with time ranges of regular schedules. It is not possible to disambiguate them unambiguously, so do not try. Showing the schedule-changes or facility notifications/special-hours if present is the best way to notify users.
The ReservationRequired field errs on the side of being true if the city website is ambiguous. Keep this in mind if designing user interfaces around it.
I'm working on an extended dataset which labels portions of the special-hours/notifications/schedule-changes on a best-effort basis so ones not relevant to a specific instance of a session can be hidden to reduce the amount of warnings which would end up needing to be displayed. I'm already using this on the main ottrec website, but I still need to design a stable schema to expose this in a usable, non-error-prone, and future-proof way.
Raw fields
The raw fields in the dataset contain the raw text from the city website most of the other fields are derived from. They have minimal whitespace and unicode normalization applied, but are otherwise unchanged.
You may find it useful to use the raw fields internally for grouping or for displaying to the user. This is okay, but be warned that they may change (or be removed entirely) if the city changes the website structure, and do not attempt to parse them (any parseable information from them is already exposed in the other stable fields).
Other stuff
In addition to the required attribution text, I'd appreciate a link to https://ottrec.ca if or a mention on social media you use this data in an app or website.
If you make something, I'm also always happy to hear about it and take a look, so feel free to send me an email.
Preview
You can view all schedules on a single webpage.
If you're not a developer and just looking for recreation schedules, see ottrec.ca.
Simplified dataset
This format contains drop-in recreation facilities and activity times, with fully-parsed dates/times, cleaned titles, and inferred reservation requirements. It is available as JSON or CSV, along with a fully-documented stable schema.
- JSON json schema.json
- CSV csv.zip schema.csv
| Facility facility facility | facility information |
|---|---|
| URL url facility_url | city of ottawa facility page url |
| ScrapedAt scrapedAt facility_scraped_at | date (YYYY-MM-DD) the date for the facility was scraped at |
| Name name facility_name | name of the facility |
| Address address facility_address | the address of the facility |
| Longitude longitude facility_longitude | facility longitude (may not be set if geocoding failed) |
| Latitude latitude facility_latitude | facility latitude (may not be set if geocoding failed) |
| Region region facility_region | facility location region (may not be set if geocoding failed) |
| Sector sector facility_sector | facility location sector (may not be set if geocoding failed) |
| SpecialHoursHTML specialHoursHtmlId facility_special_hours_html_id | html for special hours |
| NotificationsHTML notificationsHtmlId facility_notifications_html_id | html for notifications |
| Activity activity activity | activity information |
| FacilityURL facilityUrl facility_url | facility url for the activity |
| StartDate startDate activity_date_start | start date (YYYY-MM-DD), inclusive (may not be set if parsing failed or there's no range) |
| EndDate endDate activity_date_end | end date (YYYY-MM-DD), inclusive (may not be set if parsing failed or there's no range) |
| Weekday weekday activity_weekday | weekday (lowercase, long-form) or single date (YYYY-MM-DD) (may not be set if parsing failed) |
| StartTime startTime activity_time_start | start time (HH:MM), inclusive (may not be set if parsing failed) |
| EndTime endTime activity_time_end | end time (HH:MM), exclusive (may not be set if parsing failed) |
| Name name activity_name | activity name, normalized |
| ReservationRequired reservationRequired activity_reservation_required | whether reservation is required, best-effort |
| ReservationLinks reservationLinks activity_reservation_links | reservation urls (comma-separated for csv) |
| ExceptionsHTML exceptionsHtmlId activity_exceptions_html_id | html for schedule exceptions |
| RawScheduleGroup rawScheduleGroup activity_raw_group | raw schedule group text (this field is not stable) |
| RawSchedule rawSchedule activity_raw_schedule | raw schedule caption text (this field is not stable) |
| RawDay rawDay activity_raw_day | raw schedule activity day (this field is not stable) |
| RawActivity rawActivity activity_raw_activity | raw schedule activity label (this field is not stable) |
| RawTime rawTime activity_raw_time | raw schedule activity time (this field is not stable) |
| Error error error | errors which occured while scraping the facility pages |
| FacilityURL facilityUrl facility_url | facility url the error occured while scraping |
| Error error error | error message |
| HTML html html | longer snippets of html referenced from facility/activity |
| ID id id | index for cross-referencing, not stable |
| HTML html html | raw html |
| Attribution attribution attribution | attribution |
| Text text text | text |
Raw dataset
This format is designed to match the structure of the website as closely as possible with minimal processing and no assumptions. This make it more complex to use correctly, but preserves all information from the website.
API
Version specs
- latest
- Newest available data.
- latest-N
- N versions before the newest available data.
- YYYY-MM
- YYYY-MM-DD
- Newest available data at the end of the specified date.
- ID
- Canonical reference to a specific revision of the data.
Export
- /export/schema.json
- /export/schema.csv
- The current schema for the simplified dataset.
- /export/:spec.json
- /export/:spec.csv.zip
- Download a simplified dataset. Historical data may not be available beyond a cut-off date if the underlying data format changes too much.
The API is stable, but the data schema is subject to change if required.
Cross-origin requests are allowed.
Raw (v1)
- /v1/?limit=N&after=ID&revisions=true|false
- A JSON array of available data, in descending order by date/revision. If
revisionsis not set to true, only the most recent revision for each date will be listed. The default and maximum per-page limit is subject to change. Each one is uniquely identified by the ID. The revision is incremented for every additional update to the data for a specific date. You can call this endpoint repeatedly with the last ID on the previous page until an empty array is returned.[{"id": string, "revision": integer,"updated": date-rfc3339}] - /v1/:spec
- /v1/:spec/:format
- Download a raw dataset in the specified format. Currently, the valid formats are proto, pb, textpb, or json.
If the protobuf schema changes in a way which breaks backwards/forwards-compatible decoding, a new /v2/ api will be introduced for data beyond that point.
Cross-origin requests are allowed.
Historical data
Showing the last 50 versions. Use the API to access older data.
License
This data has been scraped and redistributed with permission from the City of Ottawa, and can be used freely as long as the attribution text in the provided files is displayed where the data is used.