{
    "openapi": "3.1.1",
    "info": {
        "title": "capper.win public consumer API",
        "version": "2.0.0",
        "description": "Fixtures, prediction markets, leagues, and session-authenticated favorites. Forecasts are informational and no outcome is guaranteed. This describes the consumer feed and existing-account session flow; administrative, billing, account registration and statistics interfaces are outside this contract. Send Accept: application/json. API errors also return JSON without that header. No API key or delegated OAuth token is offered. Public feed access does not grant premium access. A signed-in session whose email is not verified receives 403 with email_unverified: true on every /api/v2 operation; public reads should send no session cookie."
    },
    "servers": [
        {
            "url": "https://capper.win"
        }
    ],
    "externalDocs": {
        "url": "https://capper.win/developers"
    },
    "security": [],
    "paths": {
        "/api/v2/list": {
            "get": {
                "operationId": "listFixtures",
                "summary": "List fixtures for a calendar day",
                "description": "Public. Existing session cookies personalize premium access. A day spans local midnight inclusive to the next midnight exclusive. No pagination. Reading may trigger a lock-protected live-score refresh. Free model picks are public; premium upcoming/live picks omit pick, line, label, odd and result. Finished picks are public. Hidden providers are excluded. Tipster identities use public codenames for non-admins.",
                "security": [],
                "responses": {
                    "200": {
                        "description": "List fixtures for a calendar day",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/DayFeed"
                                }
                            }
                        },
                        "headers": {
                            "X-RateLimit-Limit": {
                                "schema": {
                                    "type": "integer"
                                },
                                "description": "Requests allowed per minute (120, shared across feed-throttled routes for this IP)."
                            },
                            "X-RateLimit-Remaining": {
                                "schema": {
                                    "type": "integer"
                                },
                                "description": "Remaining requests in this window."
                            }
                        }
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    },
                    "422": {
                        "description": "Query parameters must be strings. Arrays or objects return field validation errors.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ValidationError"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "date",
                        "in": "query",
                        "description": "Calendar date in tz. Omit for today. Unparseable strings fall back to today; always send a valid YYYY-MM-DD date.",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "tz",
                        "in": "query",
                        "description": "IANA timezone used for the calendar day and display time. Invalid or omitted values fall back to Europe/Kyiv.",
                        "schema": {
                            "type": "string",
                            "default": "Europe/Kyiv",
                            "examples": [
                                "Europe/Kyiv",
                                "America/Chicago",
                                "UTC"
                            ]
                        }
                    },
                    {
                        "name": "market",
                        "in": "query",
                        "description": "Unknown or omitted values fall back to total_goals. Non-total markets omit fixtures without predictions.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "total_goals",
                                "corners",
                                "match_winner",
                                "double_chance",
                                "handicap"
                            ],
                            "default": "total_goals"
                        }
                    }
                ]
            }
        },
        "/api/v2/leagues": {
            "get": {
                "operationId": "listLeagues",
                "summary": "List active leagues",
                "description": "",
                "security": [],
                "responses": {
                    "200": {
                        "description": "List active leagues",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/League"
                                            }
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        },
                        "headers": {
                            "X-RateLimit-Limit": {
                                "schema": {
                                    "type": "integer"
                                },
                                "description": "Requests allowed per minute (120, shared across feed-throttled routes for this IP)."
                            },
                            "X-RateLimit-Remaining": {
                                "schema": {
                                    "type": "integer"
                                },
                                "description": "Remaining requests in this window."
                            }
                        }
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/v2/favorites": {
            "get": {
                "operationId": "listFavorites",
                "summary": "List your favorite fixture IDs",
                "description": "",
                "security": [
                    {
                        "sessionCookie": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "List your favorite fixture IDs",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "type": "integer"
                                            }
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    }
                }
            }
        },
        "/api/v2/favorites/feed": {
            "get": {
                "operationId": "favoriteFixtures",
                "summary": "Read your favorite fixtures across all dates",
                "description": "Same prediction entitlement rules as the daily feed. No pagination.",
                "security": [
                    {
                        "sessionCookie": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Read your favorite fixtures across all dates",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/FavoritesFeed"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "422": {
                        "description": "Query parameters must be strings. Arrays or objects return field validation errors.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ValidationError"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "tz",
                        "in": "query",
                        "description": "IANA timezone used for the calendar day and display time. Invalid or omitted values fall back to Europe/Kyiv.",
                        "schema": {
                            "type": "string",
                            "default": "Europe/Kyiv",
                            "examples": [
                                "Europe/Kyiv",
                                "America/Chicago",
                                "UTC"
                            ]
                        }
                    },
                    {
                        "name": "market",
                        "in": "query",
                        "description": "Unknown or omitted values fall back to total_goals. Non-total markets omit fixtures without predictions.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "total_goals",
                                "corners",
                                "match_winner",
                                "double_chance",
                                "handicap"
                            ],
                            "default": "total_goals"
                        }
                    }
                ]
            }
        },
        "/api/v2/favorites/{fixture}": {
            "parameters": [
                {
                    "name": "fixture",
                    "in": "path",
                    "required": true,
                    "description": "An internal fixture ID obtained from listFixtures.",
                    "schema": {
                        "type": "integer",
                        "minimum": 1
                    }
                }
            ],
            "post": {
                "operationId": "addFavorite",
                "summary": "Add a fixture to your favorites",
                "description": "Idempotent: adding an existing favorite still returns ok. Send no request body. Use an existing fixture ID from the feed.",
                "security": [
                    {
                        "sessionCookie": [],
                        "csrfToken": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Add a fixture to your favorites",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Ok"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "419": {
                        "$ref": "#/components/responses/CsrfMismatch"
                    }
                }
            },
            "delete": {
                "operationId": "removeFavorite",
                "summary": "Remove a fixture from your favorites",
                "description": "Idempotent: removing an absent favorite still returns ok. Send no request body.",
                "security": [
                    {
                        "sessionCookie": [],
                        "csrfToken": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Remove a fixture from your favorites",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Ok"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "419": {
                        "$ref": "#/components/responses/CsrfMismatch"
                    }
                }
            }
        },
        "/api/auth/csrf-cookie": {
            "get": {
                "operationId": "bootstrapSession",
                "summary": "Initialize the session and CSRF cookies",
                "description": "Retain all Set-Cookie values. Before POST/DELETE requests, URL-decode the current XSRF-TOKEN cookie and send it as X-XSRF-TOKEN. Cookies may rotate on login/logout; keep the cookie jar current.",
                "security": [],
                "responses": {
                    "204": {
                        "description": "Initialize the session and CSRF cookies"
                    }
                }
            }
        },
        "/api/auth/login": {
            "post": {
                "operationId": "login",
                "summary": "Sign in with an existing account",
                "description": "Bootstrap cookies first. Send JSON credentials. This endpoint is for guests. A successful login rotates session/CSRF cookies. Validation failures, invalid credentials and the limit of five failed attempts per email/IP per minute return 422.",
                "security": [
                    {
                        "sessionCookie": [],
                        "csrfToken": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Sign in with an existing account",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserResponse"
                                }
                            }
                        }
                    },
                    "419": {
                        "$ref": "#/components/responses/CsrfMismatch"
                    },
                    "422": {
                        "description": "Invalid credentials, fields, or too many failed login attempts.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ValidationError"
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email"
                                    },
                                    "password": {
                                        "type": "string",
                                        "writeOnly": true
                                    },
                                    "remember": {
                                        "type": "boolean",
                                        "default": false
                                    }
                                },
                                "required": [
                                    "email",
                                    "password"
                                ]
                            }
                        }
                    }
                }
            }
        },
        "/api/auth/user": {
            "get": {
                "operationId": "currentUser",
                "summary": "Read the signed-in user",
                "description": "",
                "security": [
                    {
                        "sessionCookie": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Read the signed-in user",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserResponse"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    }
                }
            }
        },
        "/api/auth/logout": {
            "post": {
                "operationId": "logout",
                "summary": "Sign out and invalidate the session",
                "description": "",
                "security": [
                    {
                        "sessionCookie": [],
                        "csrfToken": []
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Sign out and invalidate the session"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "419": {
                        "$ref": "#/components/responses/CsrfMismatch"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "Provider": {
                "anyOf": [
                    {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string"
                            },
                            "icon_url": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        },
                        "required": [
                            "name",
                            "icon_url"
                        ]
                    },
                    {
                        "type": "null"
                    }
                ]
            },
            "LockedPrediction": {
                "type": "object",
                "properties": {
                    "locked": {
                        "const": true
                    },
                    "capper": {
                        "type": "boolean",
                        "description": "Highlighted model for this fixture."
                    },
                    "alien": {
                        "type": "boolean",
                        "description": "Consensus flag on the highlighted prediction; does not reveal a locked pick."
                    },
                    "provider": {
                        "$ref": "#/components/schemas/Provider"
                    }
                },
                "required": [
                    "locked",
                    "capper",
                    "alien",
                    "provider"
                ],
                "additionalProperties": false
            },
            "Prediction": {
                "type": "object",
                "properties": {
                    "locked": {
                        "const": false
                    },
                    "capper": {
                        "type": "boolean",
                        "description": "Highlighted model for this fixture."
                    },
                    "alien": {
                        "type": "boolean",
                        "description": "Consensus flag on the highlighted prediction; does not reveal a locked pick."
                    },
                    "provider": {
                        "$ref": "#/components/schemas/Provider"
                    },
                    "pick": {
                        "type": "string",
                        "enum": [
                            "over",
                            "under",
                            "home",
                            "draw",
                            "away",
                            "home_draw",
                            "home_away",
                            "draw_away"
                        ]
                    },
                    "line": {
                        "type": [
                            "number",
                            "null"
                        ]
                    },
                    "label": {
                        "type": "string"
                    },
                    "odd": {
                        "type": [
                            "number",
                            "null"
                        ],
                        "description": "Bookmaker odds; null for non-admin viewers."
                    },
                    "result": {
                        "type": "string",
                        "enum": [
                            "hit",
                            "miss",
                            "push",
                            "pending"
                        ],
                        "description": "hit = won, miss = lost, push = stake returned, pending = not settled."
                    }
                },
                "required": [
                    "locked",
                    "capper",
                    "alien",
                    "provider",
                    "pick",
                    "line",
                    "label",
                    "odd",
                    "result"
                ]
            },
            "Team": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "logo": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "goals": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "goals_ft": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "corners": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    }
                },
                "required": [
                    "name",
                    "logo",
                    "goals",
                    "goals_ft",
                    "corners"
                ]
            },
            "Fixture": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Internal fixture ID. Use this ID for favorites, not an upstream provider ID."
                    },
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "Canonical HTML match page, readable without JavaScript."
                    },
                    "kickoff": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "time": {
                        "type": "string",
                        "pattern": "^[0-2][0-9]:[0-5][0-9]$"
                    },
                    "status_short": {
                        "type": "string"
                    },
                    "status_long": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "elapsed": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "finished": {
                        "type": "boolean"
                    },
                    "live": {
                        "type": "boolean"
                    },
                    "winner": {
                        "enum": [
                            "home",
                            "away",
                            "draw",
                            null
                        ]
                    },
                    "home": {
                        "$ref": "#/components/schemas/Team"
                    },
                    "away": {
                        "$ref": "#/components/schemas/Team"
                    },
                    "predictions": {
                        "type": "array",
                        "items": {
                            "oneOf": [
                                {
                                    "$ref": "#/components/schemas/LockedPrediction"
                                },
                                {
                                    "$ref": "#/components/schemas/Prediction"
                                }
                            ]
                        }
                    },
                    "predictions_eta": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Estimated next scheduled prediction time, when known; not a guarantee."
                    }
                },
                "required": [
                    "id",
                    "url",
                    "kickoff",
                    "time",
                    "status_short",
                    "status_long",
                    "elapsed",
                    "finished",
                    "live",
                    "winner",
                    "home",
                    "away",
                    "predictions",
                    "predictions_eta"
                ]
            },
            "LeagueGroup": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "logo": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "fixtures": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/Fixture"
                        }
                    }
                },
                "required": [
                    "id",
                    "name",
                    "logo",
                    "fixtures"
                ]
            },
            "Country": {
                "type": "object",
                "properties": {
                    "country": {
                        "type": "string"
                    },
                    "flag": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "leagues": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/LeagueGroup"
                        }
                    }
                },
                "required": [
                    "country",
                    "flag",
                    "leagues"
                ]
            },
            "MarketCounts": {
                "type": "object",
                "properties": {
                    "total_goals": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "corners": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "match_winner": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "double_chance": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "handicap": {
                        "type": "integer",
                        "minimum": 0
                    }
                },
                "required": [
                    "total_goals",
                    "corners",
                    "match_winner",
                    "double_chance",
                    "handicap"
                ]
            },
            "League": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "api_league_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "name": {
                        "type": "string"
                    },
                    "country_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "flag_url": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "logo_url": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "api_league_id",
                    "name",
                    "country_name",
                    "flag_url",
                    "logo_url"
                ]
            },
            "Error": {
                "type": "object",
                "properties": {
                    "message": {
                        "type": "string"
                    }
                },
                "required": [
                    "message"
                ],
                "additionalProperties": true
            },
            "ValidationError": {
                "type": "object",
                "properties": {
                    "message": {
                        "type": "string"
                    },
                    "errors": {
                        "type": "object",
                        "additionalProperties": {
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    }
                },
                "required": [
                    "message",
                    "errors"
                ]
            },
            "UserResponse": {
                "type": "object",
                "properties": {
                    "user": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "email": {
                                "type": "string",
                                "format": "email"
                            },
                            "premium": {
                                "type": "boolean"
                            }
                        },
                        "required": [
                            "id",
                            "name",
                            "email",
                            "premium"
                        ],
                        "additionalProperties": true
                    }
                },
                "required": [
                    "user"
                ]
            },
            "Ok": {
                "type": "object",
                "properties": {
                    "ok": {
                        "const": true
                    }
                },
                "required": [
                    "ok"
                ]
            },
            "DayFeed": {
                "type": "object",
                "properties": {
                    "date": {
                        "type": "string",
                        "format": "date"
                    },
                    "market": {
                        "type": "string",
                        "enum": [
                            "total_goals",
                            "corners",
                            "match_winner",
                            "double_chance",
                            "handicap"
                        ]
                    },
                    "market_counts": {
                        "$ref": "#/components/schemas/MarketCounts"
                    },
                    "countries": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/Country"
                        }
                    }
                },
                "required": [
                    "date",
                    "market",
                    "market_counts",
                    "countries"
                ]
            },
            "FavoritesFeed": {
                "type": "object",
                "properties": {
                    "market": {
                        "type": "string",
                        "enum": [
                            "total_goals",
                            "corners",
                            "match_winner",
                            "double_chance",
                            "handicap"
                        ]
                    },
                    "market_counts": {
                        "$ref": "#/components/schemas/MarketCounts"
                    },
                    "countries": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/Country"
                        }
                    }
                },
                "required": [
                    "market",
                    "market_counts",
                    "countries"
                ]
            }
        },
        "responses": {
            "Unauthenticated": {
                "description": "Authentication required. Sign in and retain the session cookies.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "CsrfMismatch": {
                "description": "CSRF token is missing or expired. Refresh csrf-cookie, update the cookie jar and X-XSRF-TOKEN header, then retry.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "RateLimited": {
                "description": "Too many requests. Wait for Retry-After seconds before retrying.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                },
                "headers": {
                    "X-RateLimit-Limit": {
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Requests allowed per minute (120, shared across feed-throttled routes for this IP)."
                    },
                    "X-RateLimit-Remaining": {
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Remaining requests in this window."
                    },
                    "Retry-After": {
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Seconds to wait before retrying."
                    }
                }
            }
        },
        "securitySchemes": {
            "sessionCookie": {
                "type": "apiKey",
                "in": "cookie",
                "name": "c_a_p_p_e_r_session",
                "description": "Session cookie issued by the server. Retain the actual Set-Cookie values; do not construct a cookie."
            },
            "csrfToken": {
                "type": "apiKey",
                "in": "header",
                "name": "X-XSRF-TOKEN",
                "description": "URL-decoded current XSRF-TOKEN cookie. Required with the session cookie for POST and DELETE, including login."
            }
        }
    }
}