API Reference¶
Complete reference for the Tempo REST API.
Base URL¶
- Development:
http://localhost:5001 - Production: Configured per deployment
Authentication¶
Tempo uses JWT-based authentication with httpOnly cookies. Most endpoints require authentication.
Register¶
Register the first user (only available when no users exist):
POST /auth/register
Content-Type: application/json
{
"username": "your-username",
"password": "your-password",
"confirmPassword": "your-password"
}
Login¶
Authenticate and receive JWT token:
POST /auth/login
Content-Type: application/json
{
"username": "your-username",
"password": "your-password"
}
The JWT token is stored in an httpOnly cookie.
Get Current User¶
Requires authentication.
Logout¶
Clears the authentication cookie.
Check Registration Availability¶
Returns whether registration is currently available.
Workouts¶
Import Workout¶
Import a single workout file (GPX, FIT, or CSV):
Bulk Import¶
Import multiple workouts from a Strava export ZIP:
Supports files up to 500MB.
Export All Data¶
Export all user data in a portable ZIP format:
Authentication: Required
Response:
- Content-Type: application/zip
- Content-Disposition: attachment; filename="tempo-export-{timestamp}.zip"
- Body: ZIP file stream
The export includes: - All workouts with complete data (stats, metadata, JSONB fields) - Workout routes (GeoJSON) - Workout splits and time series data - Media files (photos and videos) as binary files - Raw workout files (GPX, FIT, CSV) as binary files - Shoes with GUIDs and relationships - User settings (heart rate zones, unit preferences, default shoe) - Best efforts
Export Format Structure:
tempo-export-{timestamp}.zip
├── manifest.json # Export metadata and version info
├── data/ # JSON files with all database records
│ ├── settings.json
│ ├── shoes.json
│ ├── workouts.json
│ ├── routes.json
│ ├── splits.json
│ ├── time-series.json
│ ├── media-metadata.json
│ └── best-efforts.json
├── workouts/ # Binary files organized by workout
│ ├── {workoutId}/
│ │ ├── raw/ # Original workout files
│ │ └── media/ # Photos and videos
│ │ └── {mediaId}/
│ │ └── {filename}
└── README.txt # Export format documentation
Manifest File (manifest.json):
{
"version": "1.0.0",
"tempoVersion": "2.0.0",
"exportDate": "2024-01-15T10:30:00Z",
"exportedBy": "username",
"statistics": {
"workouts": 150,
"shoes": 5,
"mediaFiles": 45,
"totalSizeBytes": 1234567890,
"settings": 1,
"routes": 150,
"splits": 1500,
"timeSeries": 45000,
"bestEfforts": 10
},
"dataFormat": {
"settings": "data/settings.json",
"shoes": "data/shoes.json",
"workouts": "data/workouts.json",
"routes": "data/routes.json",
"splits": "data/splits.json",
"timeSeries": "data/time-series.json",
"mediaMetadata": "data/media-metadata.json",
"bestEfforts": "data/best-efforts.json"
}
}
Notes: - Export may take several minutes for large datasets - Missing media files are logged as warnings but don't fail the export - GUIDs are preserved to maintain relationships - Export format version is 1.0.0 (compatible with future import feature)
List Workouts¶
Get all workouts with filtering and pagination:
Query parameters:
- startDate - Filter by start date (ISO 8601)
- endDate - Filter by end date (ISO 8601)
- page - Page number (default: 1)
- pageSize - Items per page (default: 20)
Get Workout¶
Get detailed workout information:
Update Workout¶
Update workout details (e.g., activity name, shoe assignment):
PATCH /workouts/{id}
Content-Type: application/json
{
"activityName": "New Activity Name",
"shoeId": "guid-here"
}
Fields:
- activityName (string, optional) - New activity name
- shoeId (Guid, optional, nullable) - Shoe ID to assign to this workout. Set to null to remove shoe assignment.
Delete Workout¶
Permanently deletes the workout and all associated data.
Crop Workout¶
Remove time from the start and/or end:
POST /workouts/{id}/crop
Content-Type: application/json
{
"removeFromStartSeconds": 60,
"removeFromEndSeconds": 30
}
Recalculate Relative Effort (Single Workout)¶
Recalculate Splits (Single Workout)¶
Get Recalculation Count (Relative Effort)¶
Get count of workouts eligible for relative effort recalculation:
Recalculate All Relative Effort¶
Recalculate relative effort for all qualifying workouts:
Get Recalculation Count (Splits)¶
Get count of workouts eligible for split recalculation:
Recalculate All Splits¶
Recalculate splits for all workouts:
Statistics¶
Weekly Statistics¶
Yearly Statistics¶
Relative Effort Statistics¶
Combined Yearly and Weekly Stats¶
Available Periods¶
Available Years¶
Best Efforts¶
Get your fastest times for standard distances:
Returns your best effort times for all supported distances (400m, 1/2 mile, 1K, 1 mile, 2 mile, 5K, 10K, 15K, 10 mile, 20K, Half-Marathon, 30K, Marathon). Best efforts are calculated from any segment within any workout, not just workouts of that exact distance.
Response format:
{
"distances": [
{
"distance": "5K",
"distanceM": 5000,
"timeS": 1200,
"workoutId": "guid-here",
"workoutDate": "2025-01-15T10:30:00Z"
}
]
}
Recalculate Best Efforts¶
Recalculate all best efforts across all workouts:
Performs a full recalculation of all best efforts. This may take some time depending on the number of workouts and time series data.
Response format:
Media¶
Upload Media¶
Maximum file size: 50MB (configurable).
List Media¶
Get Media¶
Returns the media file.
Delete Media¶
Settings¶
Get Heart Rate Zones¶
Update Heart Rate Zones¶
PUT /settings/heart-rate-zones
Content-Type: application/json
{
"method": "Karvonen",
"age": 30,
"restingHeartRate": 60,
"zones": [...]
}
Update Zones with Recalculation¶
POST /settings/heart-rate-zones/update-with-recalc
Content-Type: application/json
{
"method": "Karvonen",
"age": 30,
"restingHeartRate": 60,
"recalculateAll": true
}
Get Unit Preference¶
Update Unit Preference¶
Get Default Shoe¶
Get the currently set default shoe:
Returns the default shoe object or null if no default is set.
Set Default Shoe¶
Set a shoe as the default for automatic assignment to new workouts:
Set shoeId to null to remove the default shoe.
Shoes¶
List Shoes¶
Get all shoes with calculated mileage:
Returns a list of all shoes with their current total mileage (calculated from assigned workouts plus initial mileage).
Create Shoe¶
Create a new shoe:
POST /shoes
Content-Type: application/json
{
"brand": "Nike",
"model": "Pegasus 40",
"initialMileageM": 0.0
}
Fields:
- brand (string, required, max 100 chars) - Shoe manufacturer
- model (string, required, max 100 chars) - Shoe model name
- initialMileageM (double, optional) - Initial mileage in meters when adding the shoe
Update Shoe¶
Update shoe details:
PATCH /shoes/{id}
Content-Type: application/json
{
"brand": "Nike",
"model": "Pegasus 41",
"initialMileageM": 50.0
}
All fields are optional. Only provided fields will be updated.
Delete Shoe¶
Delete a shoe:
When a shoe is deleted, all workouts assigned to that shoe will have their shoeId set to null. The workouts themselves are not deleted.
Get Shoe Mileage¶
Get calculated total mileage for a specific shoe:
Query parameters:
- unitPreference (string, optional) - "metric" or "imperial" (defaults to user's preference)
Returns the total mileage in the requested units (sum of all assigned workout distances plus initial mileage).
System¶
Version¶
Returns application version, build date, and git commit.
Health Check¶
Public endpoint (no authentication required).
Error Responses¶
All endpoints may return standard HTTP error codes:
400 Bad Request- Invalid request data401 Unauthorized- Authentication required403 Forbidden- Insufficient permissions404 Not Found- Resource not found500 Internal Server Error- Server error
Error response format:
Interactive Documentation¶
In development mode, interactive API documentation is available at /swagger. This provides:
- Complete endpoint documentation
- Request/response examples
- Interactive testing interface
- XML documentation comments
API Testing¶
A Bruno API testing collection is available in api/bruno/Tempo.Api/ with test requests for all endpoints.