A comprehensive REST API for managing athletes, coaches, workouts, and fitness data integration with Garmin Connect and is tailor made for the GooseNet Platform. Built with .NET 8.0 and Firebase.
- Overview
- Features
- Technology Stack
- Prerequisites
- Configuration
- Authentication
- API Endpoints
- Data Models
- Error Handling
- Development
- Deployment
GooseAPI is a fitness coaching platform API that enables:
- Coaches to manage athletes, create workout plans, and track performance
- Athletes to connect with coaches, log workouts, and view training data
- Garmin Integration for automatic workout and sleep data synchronization
- Google OAuth for seamless authentication
- 🔐 JWT-based authentication with Google OAuth support
- 👥 Coach-athlete relationship management
- 🏃 Workout planning and tracking (running & strength)
- 📊 Training summaries and analytics
- 😴 Sleep data tracking via Garmin
- 🦆 Flocks (group management for athletes)
- 📱 Garmin Connect API integration
- 🖼️ Profile picture management
- .NET 8.0 - Web API framework
- Firebase Realtime Database - Data storage
- JWT Bearer Authentication - Token-based auth
- Swagger/OpenAPI - API documentation (development)
- Garmin Connect API - Fitness data integration
- Google OAuth 2.0 - Social authentication
Microsoft.AspNetCore.Authentication.JwtBearer(8.0.0)Google.Apis.Auth(1.73.0)FireSharp.Serialization.JsonNet(1.1.0)Swashbuckle.AspNetCore(6.4.0)DotNetEnv(3.1.1)System.Drawing.Common(9.0.6)
- .NET 8.0 SDK
- Firebase project with Realtime Database
- Garmin Connect API credentials (Consumer Key & Secret)
- Google OAuth credentials (Client ID & Secret)
- Environment variables configured (see Configuration)
Create a .env file in the project root with the following variables:
# JWT Configuration
Jwt__Secret=your-jwt-secret-key-here
Jwt__Issuer=your-issuer-name
Jwt__Audience=your-audience-name
# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=https://your-domain.com/auth/google/callback
# Firebase Configuration (set in FireBaseConfig.cs)
# Firebase credentials are configured in codeConfigure Firebase credentials in FireBaseConfig.cs:
- Firebase Base Path
- Firebase Secret
Store Garmin API credentials in Firebase at /GarminAPICredentials:
{
"ConsumerKey": "your-consumer-key",
"ConsumerSecret": "your-consumer-secret"
}Most endpoints require an apiKey query parameter. Each user receives a unique API key upon registration.
For protected endpoints, include the JWT token in the Authorization header:
Authorization: Bearer <your-jwt-token>
- Login:
POST /api/userAuth- Returns JWT token and API key - Google OAuth:
GET /auth/google?role=athlete|coach- Redirects to Google, returns JWT via callback
POST /api/registration
Content-Type: application/json
{
"userName": "string",
"email": "string",
"password": "string",
"fullName": "string",
"role": "athlete" | "coach"
}Response:
{
"message": "User Registered Successfully"
}POST /api/userAuth
Content-Type: application/json
{
"userName": "string",
"hashedPassword": "string"
}Response:
{
"message": "Valid Credentials, Authorized",
"authorized": true,
"apiKey": "string",
"token": "jwt-token-here"
}GET /api/userAuth/me
Authorization: Bearer <jwt-token>Response: User object (password excluded)
GET /auth/google?role=athlete|coachRedirects to Google OAuth, then to /auth/google/callback which redirects to:
/login/google/success?jwt=<jwt-token>
GET /api/getRole?apiKey=<api-key>Response:
{
"role": "athlete" | "coach"
}POST /api/coachConnection/connect
Content-Type: application/json
{
"apiKey": "string",
"coachId": "string"
}Response:
{
"athleteUserName": "string",
"coachUserName": "string"
}GET /api/coachConnection/getCoachName?coachId=<coach-id>Response:
{
"coachUsername": "string"
}GET /api/coachConnection/getCoachId?coachName=<coach-name>Response:
{
"coachId": "string"
}GET /api/athletes?apiKey=<api-key>Response:
{
"athletesData": [
{
"athleteName": "string",
"imageData": "base64-image-string"
}
]
}GET /api/flocks/getFlocks?apiKey=<api-key>Response:
{
"flocks": ["flock-name-1", "flock-name-2"]
}POST /api/flocks/createFlock?apiKey=<api-key>&flockName=<flock-name>Response:
{
"message": "Flock created successfully"
}GET /api/flocks/flockAthletes?apiKey=<api-key>&flockName=<flock-name>Response: Array of athlete usernames
POST /api/flocks/addToFlock?apiKey=<api-key>
Content-Type: application/json
{
"athleteUserName": "string",
"flockName": "string"
}Response:
{
"message": "athlete added to flock successfully"
}POST /api/flocks/removeAthlete?apiKey=<api-key>
Content-Type: application/json
{
"flockName": "string",
"athleteName": "string"
}Response:
{
"message": "Athlete was removed from the FLOCK successfully!"
}GET /api/flocks/getPotentialFlocks?apikey=<api-key>&athleteName=<athlete-name>Response:
{
"potentialFlocks": ["flock-name-1", "flock-name-2"]
}GET /api/workoutSummary?athleteName=<name>&apiKey=<key>&date=<MM/dd/yyyy>Response:
{
"runningWorkouts": [...],
"strengthWorkouts": [...]
}GET /api/workoutSummary/getWorkout?userName=<name>&id=<workout-id>Response: Complete workout object
GET /api/workoutSummary/data?workoutId=<id>&userName=<name>Response:
{
"dataSamples": [...],
"workoutLaps": [...]
}GET /api/workoutSummary/feed?apiKey=<key>&athleteName=<name>&runningCursor=<MM/dd/yyyy>&strengthCursor=<MM/dd/yyyy>Response:
{
"runningWorkouts": [...],
"strengthWorkouts": [...],
"runningNextCursor": "MM/dd/yyyy",
"strengthNextCursor": "MM/dd/yyyy"
}GET /api/workoutLaps?userName=<name>&id=<workout-id>Response: Array of lap objects
GET /api/plannedWorkout/byDate?apiKey=<key>&athleteName=<name>&date=<MM/dd/yyyy>Response:
{
"runningWorkouts": [...],
"StrengthWorkouts": [...]
}GET /api/plannedWorkout/byId?id=<workout-id>Response:
{
"worokutObject": {...},
"plannedWorkoutJson": "formatted-workout-string"
}GET /api/planned/feed?apiKey=<key>&athleteName=<name>&runningCursor=<MM/dd/yyyy>&strengthCursor=<MM/dd/yyyy>Response:
{
"runningWorkouts": [...],
"strengthWorkouts": [...],
"runningNextCursor": "MM/dd/yyyy",
"strengthNextCursor": "MM/dd/yyyy"
}POST /api/addWorkout?apikey=<api-key>
Content-Type: application/json
{
"jsonBody": "{...garmin-workout-json...}",
"date": "yyyy-MM-dd",
"targetName": "athlete-username",
"isFlock": false
}Response:
{
"message": "workout pushed successfully"
}Note: Workouts are automatically pushed to Garmin Connect if the athlete has connected their account.
GET /api/strength/workout?id=<workout-id>Response: Strength workout object
POST /api/strength/addWorkout?apiKey=<api-key>
Content-Type: application/json
{
"jsonBody": "{...strength-workout-json...}",
"targetName": "athlete-username-or-flock-name",
"isFlock": false
}Response:
{
"message": "workout pushed successfully",
"workoutId": "string"
}GET /api/strength/reviews?apiKey=<key>&workoutId=<id>Response: Dictionary of athlete reviews
POST /api/strength/reviews?apiKey=<key>&workoutId=<id>
Content-Type: application/json
{
"athleteName": "string",
"drills": [...]
}Response:
{
"message": "workout review inserted successfully!"
}GET /api/sleep/byDate?apiKey=<key>&athleteName=<name>&date=<yyyy-MM-dd>Response: Sleep data object
GET /api/sleep/feed?apiKey=<key>&athleteName=<name>&cursor=<yyyy-MM-dd>Response:
{
"items": [...],
"nextCursor": "yyyy-MM-dd"
}Note: Sleep data requires Garmin connection.
GET /api/trainingSummary?apiKey=<key>&athleteName=<name>&startDate=<M/d/yyyy>&endDate=<M/d/yyyy>Response:
{
"startDate": "M/d/yyyy",
"endDate": "M/d/yyyy",
"distanceInKilometers": 0.0,
"averageDailyInKilometers": 0.0,
"timeInSeconds": 0,
"averageDailyInSeconds": 0.0,
"allWorkouts": [...]
}POST /api/editProfile/changePassword?apiKey=<key>
Content-Type: application/json
{
"NewPassword": "string"
}Response:
{
"message": "password changed successfully"
}POST /api/editProfile/changePic?apiKey=<key>&isRevert=false
Content-Type: application/json
{
"PicString": "base64-image-string"
}Response:
{
"message": "Profile Picture Updated Successfully"
}POST /api/editProfile/changePic?apiKey=<key>&isRevert=trueGET /api/profilePic?userName=<name>Response: Base64 image string
GET /api/request-token?apiKey=<key>Response:
{
"stateToken": "string",
"oauth_token": "string",
"oauth_token_secret": "string"
}GET /api/access-token?oauth_token=<token>&oauth_verifier=<verifier>&token_secret=<secret>&apiKey=<key>Response: 200 OK (stores Garmin credentials)
GET /api/auth/stateToken?token=<state-token>Response:
{
"token": "jwt-token"
}GET /api/ValidateGarminConnection?apiKey=<key>Response:
{
"isConnected": true
}POST /api/webhook/workoutData
Content-Type: application/json
{...garmin-activity-json...}Response:
{
"message": "Workout Stored Successfully"
}POST /api/webhook/sleepData
Content-Type: application/json
{...garmin-sleep-json...}Response: 200 OK
{
"userName": "string",
"fullName": "string",
"role": "athlete" | "coach",
"email": "string",
"password": "string",
"profilePicString": "base64-string",
"defualtPicString": "base64-string",
"apiKey": "string",
"googleSubject": "string"
}{
"workoutId": "string",
"workoutDate": "M/d/yyyy",
"workoutName": "string",
"workoutDistanceInMeters": 0,
"workoutDurationInSeconds": 0,
"workoutAvgHR": 0,
"workoutAvgPaceInMinKm": "string",
"workoutCoordsJsonStr": "string",
"dataSamples": [...],
"workoutLaps": [...],
"userAccessToken": "string"
}{
"workoutId": "string",
"workoutName": "string",
"description": "string",
"date": "M/d/yyyy",
"coachName": "string",
"athleteNames": ["string"],
"intervals": [...]
}{
"workoutId": "string",
"workoutName": "string",
"workoutDate": "M/d/yyyy",
"coachName": "string",
"athleteNames": ["string"],
"workoutReviews": {
"athleteName": {
"athleteName": "string",
"drills": [...]
}
}
}{
"flockName": "string",
"athletesUserNames": ["string"]
}{
"sleepDate": "yyyy-MM-dd",
"sleepDurationInSeconds": 0,
"deepSleepDurationInSeconds": 0,
"lightSleepDurationInSeconds": 0,
"remSleepInSeconds": 0,
"awakeDurationInSeconds": 0,
"overallSleepScore": 0,
"sleepScores": {...},
"sleepStartTimeInSeconds": 0,
"sleepTimeOffsetInSeconds": 0
}The API returns standard HTTP status codes:
- 200 OK - Success
- 400 Bad Request - Invalid request data
- 401 Unauthorized - Authentication failed or insufficient permissions
- 404 Not Found - Resource not found
Error responses follow this format:
{
"message": "Error description"
}- Clone the repository
- Install .NET 8.0 SDK
- Configure
.envfile with required variables - Set up Firebase configuration in
FireBaseConfig.cs - Run the application:
dotnet run
- Access Swagger UI at
https://localhost:<port>/swagger(development only)
GooseAPI/
├── Controllers/ # API endpoints
├── Program.cs # Application entry point
├── GooseAPIUtils.cs # Utility functions
├── FireBaseService.cs # Firebase integration
├── FireBaseConfig.cs # Firebase configuration
└── Models/ # Data models
The API is configured for deployment to Azure App Service. Publish profiles are available in Properties/PublishProfiles/.
Ensure all environment variables are set in your deployment environment:
- JWT configuration
- Google OAuth credentials
- Firebase credentials
The API is configured with CORS policy "AllowAll" for development. Update this for production to restrict origins.
[Add your license here]
[Add contribution guidelines here]
[Add support information here]