> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/igorek05m/daily-geogame/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /api/progress

> Save user game progress with distance and bearing calculations

Saves the user's guesses for a specific date, calculates geographic relationships, and manages session state. This endpoint handles both creating new sessions and updating existing progress.

## Endpoint

```http theme={null}
POST /api/progress
```

## Request body

<ParamField body="date" type="string" required>
  The game date in YYYY-MM-DD format
</ParamField>

<ParamField body="guesses" type="Country[]" required>
  Array of country guesses. Each guess should include basic country data that will be enriched with calculated fields.

  <Expandable title="Guess object properties">
    <ParamField body="name" type="string" required>
      Country name
    </ParamField>

    <ParamField body="alpha2Code" type="string" required>
      ISO 3166-1 alpha-2 code
    </ParamField>

    <ParamField body="alpha3Code" type="string" optional>
      ISO 3166-1 alpha-3 code (used for relationship calculation)
    </ParamField>

    <ParamField body="flag" type="string" required>
      Flag image URL
    </ParamField>

    <ParamField body="latlng" type="number[]" required>
      Coordinates as `[latitude, longitude]`
    </ParamField>

    <ParamField body="region" type="string" optional>
      Geographic region
    </ParamField>

    <ParamField body="subregion" type="string" optional>
      Geographic subregion
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="success" type="boolean" required>
  Indicates if the progress was saved successfully
</ResponseField>

<ResponseField name="sessionId" type="string" required>
  The user's session ID (UUID v4 format)
</ResponseField>

<ResponseField name="won" type="boolean" required>
  Whether the user has won the game (guessed correctly)
</ResponseField>

<ResponseField name="guesses" type="Country[]" required>
  The enriched array of guesses with calculated fields

  <Expandable title="Enriched guess properties">
    <ResponseField name="name" type="string">
      Country name
    </ResponseField>

    <ResponseField name="alpha2Code" type="string">
      ISO 3166-1 alpha-2 code
    </ResponseField>

    <ResponseField name="flag" type="string">
      Flag image URL
    </ResponseField>

    <ResponseField name="latlng" type="number[]">
      Coordinates as `[latitude, longitude]`
    </ResponseField>

    <ResponseField name="distance" type="number">
      Distance in kilometers from the guess to the target country (rounded)
    </ResponseField>

    <ResponseField name="bearing" type="number">
      Bearing angle in degrees from the guess to the target country (0-360)
    </ResponseField>

    <ResponseField name="connection" type="string">
      Relationship to target country. One of:

      * `"guess"` - Exact match (correct answer)
      * `"neighbor"` - Shares a border with target
      * `"subregion"` - Same subregion as target
      * `"region"` - Same region as target
      * `"none"` - No special relationship
    </ResponseField>
  </Expandable>
</ResponseField>

## Geographic calculations

The endpoint enriches each guess with calculated geographic data:

**Distance calculation** uses the Haversine formula:

```typescript theme={null}
const distance = Math.round(
  getDistanceFromLatLonInKm(lat, lng, targetLat, targetLng)
);
```

From `app/api/progress/route.ts:39`

**Bearing calculation** computes the angle from guess to target:

```typescript theme={null}
const bearing = Math.round(
  getBearingAngle(lat, lng, targetLat, targetLng)
);
```

From `app/api/progress/route.ts:40`

**Connection logic** determines geographic relationships:

```typescript theme={null}
if (dailyGame.targetCountry.alpha3Code === g.alpha3Code) {
  connection = "guess"; // Exact match
} else if (dailyGame.targetCountry.borders?.includes(g.alpha3Code)) {
  connection = "neighbor"; // Shares border
} else if (dailyGame.targetCountry.subregion === g.subregion) {
  connection = "subregion"; // Same subregion
} else if (dailyGame.targetCountry.region === g.region) {
  connection = "region"; // Same region
}
```

From `app/api/progress/route.ts:44-49`

## Session management

The endpoint automatically creates and manages user sessions:

1. Checks for existing `geo_session` cookie
2. Generates new UUID v4 session ID if not present
3. Sets HTTP-only cookie with 10-year expiration
4. Associates all progress with the session ID

```typescript theme={null}
if (!sessionId) {
    sessionId = uuidv4();
}
```

From `app/api/progress/route.ts:20-22`

## Win detection

The game is marked as won when the newest guess matches the target country name (case-insensitive):

```typescript theme={null}
const newestGuess = guesses[0];
if (newestGuess.name?.toLowerCase() === dailyGame.targetCountry.name.toLowerCase()) {
    isWon = true;
}
```

From `app/api/progress/route.ts:65-68`

## Example request

```bash theme={null}
curl -X POST https://yourdomain.com/api/progress \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2026-03-02",
    "guesses": [
      {
        "name": "Germany",
        "alpha2Code": "DE",
        "alpha3Code": "DEU",
        "flag": "https://flagcdn.com/de.svg",
        "latlng": [51, 9],
        "region": "Europe",
        "subregion": "Western Europe"
      }
    ]
  }'
```

## Example response

```json theme={null}
{
  "success": true,
  "sessionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "won": false,
  "guesses": [
    {
      "name": "Germany",
      "alpha2Code": "DE",
      "flag": "https://flagcdn.com/de.svg",
      "latlng": [51, 9],
      "distance": 516,
      "bearing": 78,
      "connection": "neighbor"
    }
  ]
}
```

<Note>
  The `guesses` array in the response is enriched with `distance`, `bearing`, and `connection` fields calculated on the server.
</Note>

## Error responses

**Invalid payload:**

```json theme={null}
{
  "error": "Invalid payload"
}
```

Status: `400`

This occurs when:

* `date` is missing
* `guesses` is not an array

**Failed to save:**

```json theme={null}
{
  "error": "Failed to save"
}
```

Status: `500`

This occurs when the database operation fails.

## Code example

```typescript theme={null}
// Save progress after a guess
const saveProgress = async (date: string, guesses: Country[]) => {
  const response = await fetch('/api/progress', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ date, guesses })
  });
  
  const result = await response.json();
  
  if (result.won) {
    console.log('Congratulations! You won!');
  }
  
  return result.guesses; // Now includes distance, bearing, connection
};
```

***

# GET /api/progress

Retrieves the user's progress for a specific date, including guess history and personal statistics.

## Endpoint

```http theme={null}
GET /api/progress
```

## Query parameters

<ParamField query="date" type="string" required>
  The game date in YYYY-MM-DD format.

  **Validation**: Must match the pattern `^\d{4}-\d{2}-\d{2}$`
</ParamField>

## Authentication

Requires `geo_session` cookie. If no session exists, returns empty progress.

## Response

<ResponseField name="guesses" type="Country[]" required>
  Array of the user's guesses for this date (enriched with distance/bearing/connection)
</ResponseField>

<ResponseField name="won" type="boolean" required>
  Whether the user won this game
</ResponseField>

<ResponseField name="stats" type="object" required>
  User's overall statistics across all games

  <Expandable title="Stats properties">
    <ResponseField name="gamesPlayed" type="number">
      Total number of games played by this user
    </ResponseField>

    <ResponseField name="wins" type="number">
      Total number of games won
    </ResponseField>

    <ResponseField name="winRate" type="number">
      Win percentage (0-100), rounded to nearest integer
    </ResponseField>
  </Expandable>
</ResponseField>

## Example request

```bash theme={null}
curl https://yourdomain.com/api/progress?date=2026-03-02 \
  -H "Cookie: geo_session=a1b2c3d4-e5f6-7890-abcd-ef1234567890"
```

## Example response

**With existing progress:**

```json theme={null}
{
  "guesses": [
    {
      "name": "Germany",
      "alpha2Code": "DE",
      "flag": "https://flagcdn.com/de.svg",
      "latlng": [51, 9],
      "distance": 516,
      "bearing": 78,
      "connection": "neighbor"
    }
  ],
  "won": false,
  "stats": {
    "gamesPlayed": 5,
    "wins": 3,
    "winRate": 60
  }
}
```

**No session or no progress:**

```json theme={null}
{
  "guesses": [],
  "winStreak": 0,
  "gamesPlayed": 0
}
```

## Error responses

**Invalid date:**

```json theme={null}
{
  "error": "Valid date required"
}
```

Status: `400`

**Server error:**

```json theme={null}
{
  "error": "Failed to load"
}
```

Status: `500`

## Code example

```typescript theme={null}
// Load user progress for today
const loadProgress = async (date: string) => {
  const response = await fetch(`/api/progress?date=${date}`);
  const progress = await response.json();
  
  console.log(`Played ${progress.stats.gamesPlayed} games`);
  console.log(`Win rate: ${progress.stats.winRate}%`);
  
  return progress;
};
```
