A simple Python client for the RideWithGPS API.
This project is not affiliated with or endorsed by RideWithGPS.
Note: This client is not yet feature-complete. Read the code before you use it and report any bugs you find.
API versions: RideWithGPS has a v1 API (
/api/v1/...) and a legacy API (no version prefix). This client supports both, but v1 coverage is incomplete. See API Versions for a full breakdown of what uses v1 vs legacy.
Authentication: No OAuth required.
authenticate()uses the v1 Basic auth endpoint (POST /api/v1/auth_tokens.json) with your existing API key. The returned token works for both v1 and legacy endpoints.
- Authenticates with the RideWithGPS API
- Makes any API request —
get,put,post,patch,delete— to v1 or legacy endpoints. - Built-in rate limiting, caching, and pagination.
- Use higher-level abstractions like
listto iterate collections with automatic pagination.
RideWithGPS has a v1 API (/api/v1/...) and a legacy API (no version prefix). This client supports
both. The v1 API is preferred where available, but several things are not yet in v1 and still
require the legacy API.
| Resource | Operations |
|---|---|
| Authentication | Create token (email + password + API key, no OAuth needed) |
| Trips | List, get single, delete, get polyline |
| Routes | List, get single, delete, get polyline |
| Collections | List, get single, get pinned |
| Events | List, create, get single, update, delete |
| Club Members | List, get single, update |
| Points of Interest | List, create, get single, update, delete, associate/disassociate with route |
| Sync | Get items for sync |
| Users | Get current user |
For list over a v1 endpoint, pass result_key matching the response root key (e.g. result_key="trips"). See v1 API usage for examples.
These are not available on v1 and require the legacy endpoints:
| Resource / Feature | Legacy endpoint | Reason |
|---|---|---|
| Gear | /users/{user_id}/gear.json |
No v1 gear endpoint exists |
| Create trip | /trips.json (POST) |
v1 trips is read/delete only |
| Update trip | /trips/{id}.json (PUT/PATCH) |
v1 trips is read/delete only |
| Create route | /routes.json (POST) |
v1 routes is read/delete only |
| Update route | /routes/{id}.json (PUT/PATCH) |
v1 routes is read/delete only |
v1 endpoints live under /api/v1/ and use page/page_size pagination, returning results
under a named root key (e.g. {"trips": [...], "meta": {...}}).
All of list, get, put, post, patch, and delete work with v1 paths.
For list, pass result_key matching the response root key:
# List all trips (v1)
for trip in client.list("/api/v1/trips.json", result_key="trips"):
print(trip.name, trip.id)
# List routes, up to 50 (v1)
for route in client.list("/api/v1/routes.json", result_key="routes", limit=50):
print(route.name, route.id)
# Get a single resource (v1)
route = client.get(path="/api/v1/routes/123456.json")
print(route.route.name)
# Get the authenticated user's pinned collection (v1)
pinned = client.get(path="/api/v1/collections/pinned.json")
# Create an event (v1)
event = client.post(
path="/api/v1/events.json",
params={"name": "My Gran Fondo", "start_date": "2026-06-01"},
)The package is published on PyPI.
First, install the package:
pip install pyrwgpsThen, in your Python code:
from pyrwgps import RideWithGPS
# Initialize client and authenticate, with optional in-memory GET cache enabled
client = RideWithGPS(apikey="yourapikey", cache=True)
user_info = client.authenticate(email="your@email.com", password="yourpassword")
print(user_info.id, user_info.display_name)
# Update the name of a trip (legacy API — trip update not yet in v1)
activity_id = "123456"
new_name = "Morning Ride"
response = client.put(
path=f"/trips/{activity_id}.json",
params={"name": new_name}
)
updated_name = response.trip.name if hasattr(response, "trip") else None
if updated_name == new_name:
print(f"Activity name updated to: {updated_name}")
else:
print("Failed to update activity name.")
# We changed something, so probably should clear the cache.
client.clear_cache()
# Simple GET: fetch a single trip via the v1 API
trip = client.get(path="/api/v1/trips/123456.json")
print(trip.trip.name, trip.trip.id)
# Automatically paginate: List up to 25 trips via the v1 API
for trip in client.list(
path="/api/v1/trips.json",
result_key="trips",
limit=25,
):
print(trip.name, trip.id)
# Automatically paginate: List all routes via the v1 API
for route in client.list(
path="/api/v1/routes.json",
result_key="routes",
):
print(route.name, route.id)
# Get the authenticated user's pinned collection (v1)
pinned = client.get(path="/api/v1/collections/pinned.json")
print(pinned)
# Automatically paginate: List all gear for this user (legacy API — no v1 gear endpoint yet)
gear = {}
for g in client.list(
path=f"/users/{user_info.id}/gear.json",
params={},
):
gear[g.id] = g.nickname
print(gear)Note:
- All API responses are automatically converted from JSON to Python objects with attribute access.
- You must provide your own RideWithGPS credentials and API key.
- Use v1 endpoints (
/api/v1/...) for trips, routes, collections, events, club members, points of interest, and sync. See the v1 API section for what is and isn't available. - The
list,get,put,post,patchanddeletemethods are the recommended interface for making API requests; see the code and RideWithGPS API docs for available endpoints and parameters.
If you use this as VS Dev Container, you can skip using a venv.
python3 -m venv env
source env/bin/activate
pip install .[dev]Or, for local development with editable install:
git clone https://github.com/ckdake/pyrwgps.git
cd pyrwgps
pip install -e '.[dev]'python -m pytest --cov=pyrwgps --cov-report=term-missing -vpython3 scripts/example.pyRun these tools locally to check and format your code:
-
pylint (static code analysis):
pylint pyrwgps
-
flake8 (style and lint checks):
flake8 pyrwgps
-
black (auto-formatting):
black pyrwgps
-
mypy (type checking):
mypy pyrwgps
If you need to update the VCR cassettes for integration tests:
-
Set required environment variables:
RIDEWITHGPS_EMAILRIDEWITHGPS_PASSWORDRIDEWITHGPS_KEY
Example:
export RIDEWITHGPS_EMAIL=your@email.com export RIDEWITHGPS_PASSWORD=yourpassword export RIDEWITHGPS_KEY=yourapikey
-
Delete all existing cassettes (including backups) and re-record:
rm tests/cassettes/*.yaml tests/cassettes/*.yaml.original python -m pytest --cov=pyrwgps --cov-report=term-missing -v
-
Scrub sensitive data from the cassette:
python scripts/scrub_cassettes.py
- This will back up your cassettes to
*.yaml.original(if not already present). - The sanitized cassettes will overwrite
*.yaml.
- This will back up your cassettes to
-
Re-run tests to verify:
python -m pytest --cov=pyrwgps --cov-report=term-missing -v
To publish a new version of this package:
-
Update the version number
Editpyproject.tomland increment the version. Add a new entry to CHANGELOG.md. -
Commit and push
git add pyproject.toml CHANGELOG.md git commit -m "Release vX.Y.Z" git push -
Install build tools
pip install .[dev]
-
Build the distribution
python -m build
This will create
dist/pyrwgps-<version>.tar.gzand.whlfiles. -
Check the distribution (optional but recommended)
twine check dist/* -
Upload to PyPI
twine upload dist/*You will be prompted for your PyPI API key.
-
Create a GitHub Release
Go to github.com/ckdake/pyrwgps/releases/new, create a tag (e.g.vX.Y.Z) pointing atmain, use the version as the title, and paste the relevant section from CHANGELOG.md as the description. This populates the Releases sidebar on the repo and gives users a permanent URL for the release notes. -
Open your package on PyPI (optional)
$BROWSER https://pypi.org/project/pyrwgps/
Note:
- Configure your
~/.pypircif you want to avoid entering credentials each time. - For test uploads, use
twine upload --repository testpypi dist/*and visit TestPyPI.
MIT License
This project is not affiliated with or endorsed by RideWithGPS.