{
    "schemes": ["http"],
    "swagger": "2.0",
    "info": {
        "description": "## Welcome\nMarmaradar helps drivers in Turkey stay aware of speed cameras, average-speed corridors, and community road reports. This reference documents the REST API used by the mobile app and future integrations.\n\n## Base URL\n**This site (`api.marmaradar.com`) hosts documentation only.** To call the API, send requests to the Marmaradar **gateway**:\n\n| Environment | Base URL |\n|-------------|----------|\n| Production gateway | `http://35.239.129.237:8081` |\n| Local development | `http://127.0.0.1:8081` |\n\nAll paths below are relative to that gateway base (e.g. `GET /v1/users/me` → `http://35.239.129.237:8081/v1/users/me`).\n\n## Authentication\nMost endpoints require a JWT **access token** obtained via `/v1/auth/login`, `/v1/auth/register`, or `/v1/auth/oauth`. Pass it on every protected request:\n\n```\nAuthorization: Bearer <access_token>\n```\n\nWhen the access token expires, refresh it with `POST /v1/auth/refresh` and the `refresh_token` from the login response.\n\n## Conventions\n- Request and response bodies are JSON unless noted (e.g. profile photo upload uses `multipart/form-data`).\n- Errors return `{\"error\": \"<human-readable message>\"}` with an appropriate HTTP status code.\n- UUIDs are lowercase strings (e.g. `550e8400-e29b-41d4-a716-446655440000`).",
        "title": "Marmaradar API",
        "termsOfService": "https://www.marmaradar.com/terms",
        "contact": {
            "name": "Marmaradar Support",
            "url": "https://www.marmaradar.com"
        },
        "license": {
            "name": "Proprietary"
        },
        "version": "1.0"
    },
    "host": "35.239.129.237:8081",
    "basePath": "/",
    "paths": {
        "/v1/leaderboard": {
            "get": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "## What this does\nReturns the **top 100** users for a lifetime category, plus the **caller's own rank** even when they are outside the top 100.\n\n## Categories\n| `category` | Metric (`value`) |\n|------------|------------------|\n| `distance` | Total distance driven in **meters** (`user_stats.total_distance_m`) |\n| `reports`  | Verified contributions: non-removed mobile radar reports + confirmed live reports |\n\n## Enrichment\nEach entry includes `username` (custom name, or a masked email local-part like `ozc***`), `profile_picture_url`, `vehicle_type`, and `vehicle_color` for rich mobile UI.\n\n## Ranking\nOrdered by metric descending, then `user_id` ascending for stable ties. `me.in_top` is true when the caller appears in `entries`.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Leaderboard"
                ],
                "summary": "Get distance or community-contribution leaderboard",
                "parameters": [
                    {
                        "enum": [
                            "distance",
                            "reports"
                        ],
                        "type": "string",
                        "description": "Leaderboard category",
                        "name": "category",
                        "in": "query",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Top 100 plus caller rank",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.LeaderboardResponse"
                        }
                    },
                    "400": {
                        "description": "Missing or invalid category",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "401": {
                        "description": "Missing or expired access token",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "404": {
                        "description": "User record no longer exists",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "500": {
                        "description": "Unexpected server error",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/v1/uploads/avatars/{file}": {
            "get": {
                "description": "## What this does\nServes a publicly cached profile photo previously uploaded via `POST /v1/users/me/profile-picture`.\n\n## Path format\nThe `{file}` segment is the stored filename: `{user-uuid}.{jpg|png|webp}` — exactly as returned in `profile_picture_url` after the `/v1/uploads/avatars/` prefix.\n\n**Example:** `GET /v1/uploads/avatars/550e8400-e29b-41d4-a716-446655440000.jpg`\n\n## Caching\nResponses include `Cache-Control: public, max-age=86400` (24 hours). No authentication required.",
                "produces": [
                    "image/jpeg",
                    "image/png"
                ],
                "tags": [
                    "Profile Photo"
                ],
                "summary": "Download a user's avatar image",
                "parameters": [
                    {
                        "type": "string",
                        "description": "Avatar filename, e.g. 550e8400-e29b-41d4-a716-446655440000.jpg",
                        "name": "file",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Binary image body"
                    },
                    "404": {
                        "description": "Avatar not found"
                    }
                }
            }
        },
        "/v1/users/me": {
            "get": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "## What this does\nReturns everything Marmaradar knows about the **currently signed-in user**: email, profile photo URL, and vehicle customization used on the map and in drive videos.\n\n## When to call\n- After login, to hydrate the profile screen.\n- On app launch, to sync vehicle icon settings from the server.\n\n## Response notes\n- `profile_picture_url` is a **relative path** on the gateway (e.g. `/v1/uploads/avatars/{user-id}.jpg`). Prepend your gateway base URL to display the image.\n- If the user has never uploaded a photo, `profile_picture_url` is `null`.\n- `username` is `null` until set via `PATCH /v1/users/me`. On public leaderboards, unset usernames are shown as a masked email local-part.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Account \u0026 Profile"
                ],
                "summary": "View my account and saved preferences",
                "responses": {
                    "200": {
                        "description": "Current profile",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.UserProfileResponse"
                        }
                    },
                    "401": {
                        "description": "Missing or expired access token",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "404": {
                        "description": "User record no longer exists",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "500": {
                        "description": "Unexpected server error",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    }
                }
            },
            "delete": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "## What this does\nDeletes the **currently signed-in user** and all associated personal data\n(profile, sessions, drives, reports, stats, OAuth identities, avatar file).\nThis cannot be undone.\n\n## When to call\nAfter an explicit confirmation step in the app or website.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Account \u0026 Profile"
                ],
                "summary": "Permanently delete my account",
                "responses": {
                    "200": {
                        "description": "Account deleted (`ok`: true)",
                        "schema": {
                            "type": "object",
                            "additionalProperties": {
                                "type": "boolean"
                            }
                        }
                    },
                    "401": {
                        "description": "Missing or expired access token",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "404": {
                        "description": "User record no longer exists",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "500": {
                        "description": "Unexpected server error",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    }
                }
            },
            "patch": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "## What this does\nUpdates the **username**, **vehicle type**, and/or **color**. Vehicle settings are used as your position marker during live tracking and in exported drive videos. Username appears on public leaderboards.\n\n## Partial updates\nSend only the fields you want to change. Omitted keys are left as-is on the server.\n\n```json\n{ \"username\": \"ozcan_driver\", \"vehicle_type\": \"hatchback\", \"vehicle_color\": \"#E8262D\" }\n```\n\n## Allowed values\n| Field | Constraints |\n|-------|-------------|\n| `username` | lowercase `a-z`, `0-9`, `_`; length 3–20; unique |\n| `vehicle_type` | `sedan`, `hatchback`, `station_wagon`, `kamyon`, or `tir` |\n| `vehicle_color` | `#` followed by six hex digits, e.g. `#1E88E5` |\n\nUnknown JSON keys are rejected with **400 Bad Request**.",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Vehicle Customization"
                ],
                "summary": "Change my username or vehicle icon on the map",
                "parameters": [
                    {
                        "description": "Fields to update (partial OK)",
                        "name": "body",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/internal_handler.UpdatePreferencesRequest"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Profile after update",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.UserProfileResponse"
                        }
                    },
                    "400": {
                        "description": "Invalid username, vehicle_type, vehicle_color, or JSON",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "401": {
                        "description": "Missing or expired access token",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "404": {
                        "description": "User record no longer exists",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "409": {
                        "description": "Username already taken",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "500": {
                        "description": "Unexpected server error",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/v1/users/me/profile-picture": {
            "post": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "## What this does\nStores a new avatar for the signed-in user. The previous image file is deleted automatically when replaced.\n\n## How to send\nUse `multipart/form-data` with a single field named **`file`** containing the image bytes.\n\n## File rules\n| Rule | Value |\n|------|-------|\n| Formats | JPEG, PNG, or WebP |\n| Max size | 2 MB |\n| Field name | `file` (required) |\n\n## Response\nReturns the updated profile. The new `profile_picture_url` points to `GET /v1/uploads/avatars/{user-id}.{ext}` on the gateway — no auth required to view avatars.",
                "consumes": [
                    "multipart/form-data"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Profile Photo"
                ],
                "summary": "Upload or replace my profile photo",
                "parameters": [
                    {
                        "type": "file",
                        "description": "Avatar image (JPEG, PNG, or WebP, max 2 MB)",
                        "name": "file",
                        "in": "formData",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Profile with new profile_picture_url",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.UserProfileResponse"
                        }
                    },
                    "400": {
                        "description": "Missing file, unsupported type, or file too large",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "401": {
                        "description": "Missing or expired access token",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    },
                    "500": {
                        "description": "Unexpected server error",
                        "schema": {
                            "$ref": "#/definitions/internal_handler.ErrorResponse"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "internal_handler.ErrorResponse": {
            "type": "object",
            "properties": {
                "error": {
                    "description": "Human-readable explanation suitable to show in a client UI.",
                    "type": "string",
                    "example": "invalid JSON body"
                }
            }
        },
        "internal_handler.LeaderboardEntry": {
            "type": "object",
            "properties": {
                "profile_picture_url": {
                    "type": "string",
                    "example": "/v1/uploads/avatars/550e8400-e29b-41d4-a716-446655440000.jpg"
                },
                "rank": {
                    "type": "integer",
                    "example": 1
                },
                "user_id": {
                    "type": "string",
                    "example": "550e8400-e29b-41d4-a716-446655440000"
                },
                "username": {
                    "type": "string",
                    "example": "ozc***"
                },
                "value": {
                    "description": "Meters for category=distance; contribution count for category=reports.",
                    "type": "number",
                    "example": 152340.5
                },
                "vehicle_color": {
                    "type": "string",
                    "example": "#E8262D"
                },
                "vehicle_type": {
                    "type": "string",
                    "enum": [
                        "sedan",
                        "hatchback",
                        "station_wagon",
                        "kamyon",
                        "tir"
                    ],
                    "example": "sedan"
                }
            }
        },
        "internal_handler.LeaderboardMeEntry": {
            "type": "object",
            "properties": {
                "in_top": {
                    "description": "True when the caller appears in entries.",
                    "type": "boolean",
                    "example": false
                },
                "profile_picture_url": {
                    "type": "string"
                },
                "rank": {
                    "type": "integer",
                    "example": 142
                },
                "user_id": {
                    "type": "string",
                    "example": "550e8400-e29b-41d4-a716-446655440000"
                },
                "username": {
                    "type": "string",
                    "example": "driver_one"
                },
                "value": {
                    "type": "number",
                    "example": 8200
                },
                "vehicle_color": {
                    "type": "string",
                    "example": "#1E88E5"
                },
                "vehicle_type": {
                    "type": "string",
                    "example": "hatchback"
                }
            }
        },
        "internal_handler.LeaderboardResponse": {
            "type": "object",
            "properties": {
                "category": {
                    "type": "string",
                    "enum": [
                        "distance",
                        "reports"
                    ],
                    "example": "distance"
                },
                "entries": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/internal_handler.LeaderboardEntry"
                    }
                },
                "me": {
                    "$ref": "#/definitions/internal_handler.LeaderboardMeEntry"
                }
            }
        },
        "internal_handler.UpdatePreferencesRequest": {
            "type": "object",
            "properties": {
                "username": {
                    "description": "Unique public username: lowercase letters, digits, underscore; 3–20 chars.",
                    "type": "string",
                    "example": "ozcan_driver"
                },
                "vehicle_color": {
                    "description": "Six-digit hex color with leading `#`, e.g. `#E8262D`.",
                    "type": "string",
                    "example": "#1E88E5"
                },
                "vehicle_type": {
                    "description": "One of: `sedan`, `hatchback`, `station_wagon`, `kamyon`, `tir`.",
                    "type": "string",
                    "example": "sedan"
                }
            }
        },
        "internal_handler.UserProfileResponse": {
            "type": "object",
            "properties": {
                "email": {
                    "description": "Account email address.",
                    "type": "string",
                    "example": "driver@example.com"
                },
                "id": {
                    "description": "Marmaradar user UUID.",
                    "type": "string",
                    "example": "550e8400-e29b-41d4-a716-446655440000"
                },
                "profile_picture_url": {
                    "description": "Public path to the avatar on the gateway, e.g. `/v1/uploads/avatars/{id}.jpg`. Null if no photo uploaded.",
                    "type": "string",
                    "example": "/v1/uploads/avatars/550e8400-e29b-41d4-a716-446655440000.jpg"
                },
                "username": {
                    "description": "Public display name for leaderboards. Null until the user sets one.",
                    "type": "string",
                    "example": "ozcan_driver"
                },
                "vehicle_color": {
                    "description": "Hex color for the vehicle icon (`#RRGGBB`).",
                    "type": "string",
                    "example": "#1E88E5"
                },
                "vehicle_type": {
                    "description": "Vehicle body style used for the map marker and drive video export.",
                    "type": "string",
                    "enum": [
                        "sedan",
                        "hatchback",
                        "station_wagon",
                        "kamyon",
                        "tir"
                    ],
                    "example": "sedan"
                }
            }
        }
    },
    "securityDefinitions": {
        "BearerAuth": {
            "description": "Short-lived JWT from login/register/oauth. Example: `Bearer eyJhbGciOiJIUzI1NiIs...`",
            "type": "apiKey",
            "name": "Authorization",
            "in": "header"
        }
    }
}