{
    "openapi": "3.0.3",
    "info": {
        "title": "Sara777 API",
        "version": "1.0.0",
        "description": "Token-based REST API for the mobile app. Every response is JSON and uses the same envelope. Authentication is a Sanctum bearer token returned by register and login."
    },
    "servers": [
        {
            "url": "https://dpbossapp.io"
        }
    ],
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer"
            }
        }
    },
    "tags": [
        {
            "name": "App Config",
            "description": "Call this first, on every app launch. It tells the client which auth flows are live."
        },
        {
            "name": "Markets & Games",
            "description": "The game catalogue: markets, their timings, results, payout rates and the panna lookups. All public, so the app can render its home screen before the user signs in."
        },
        {
            "name": "Bidding",
            "description": "Placing bets and reading them back. One submission is all-or-nothing: every bid in it is validated first, then the whole lot is stored and charged as a single wallet debit. A bad bid anywhere in the list means nothing is stored and the wallet is untouched."
        },
        {
            "name": "Wallet",
            "description": "Balance, history, and the two ways money moves. Deposits are claims until an admin confirms them. A withdrawal leaves the wallet the moment it is raised, not when it is approved, so the same balance cannot also be staked while a payout is pending."
        },
        {
            "name": "Bank Accounts",
            "description": "The accounts a player can be paid into. Up to five; the first one saved becomes the primary automatically. Account numbers are always returned masked -- only the last four digits."
        },
        {
            "name": "Content",
            "description": "Banners, notices and the information screens. All public, so the home screen can be drawn before anyone signs in. The text comes from Settings in the admin panel, so it changes without an app release."
        },
        {
            "name": "Support Messages",
            "description": "Messages a player sends from the contact screen, and the replies an admin writes back. A player only ever sees their own."
        },
        {
            "name": "Live Chat",
            "description": "One-to-one chat with support. The messages never pass through this API: they live in Firestore, so they arrive on both sides without polling. All this API does is vouch for who the player is, by handing out a short-lived Firebase custom token. See FIREBASE-CHAT.md for the Firestore shape and the security rules."
        },
        {
            "name": "Registration",
            "description": "Account creation. One step or two, depending on the OTP switch."
        },
        {
            "name": "Login & Session",
            "description": "Password login and token lifecycle. Unaffected by the OTP switch."
        },
        {
            "name": "Password Reset",
            "description": "OTP-only recovery. Entirely gated by the admin OTP switch \u2014 check /app-config first."
        },
        {
            "name": "Profile & Devices",
            "description": "Authenticated account management. Send Authorization: Bearer {token} on every call."
        },
        {
            "name": "Notifications",
            "description": "The in-app inbox. Mirrors what the admin panel pushed, so a missed push is still readable."
        }
    ],
    "paths": {
        "/api/v1/app-config": {
            "get": {
                "tags": [
                    "App Config"
                ],
                "summary": "Get App Config",
                "description": "Returns the OTP switch state, the flow the client should use, Firebase client keys, version gating and the maintenance flag. This endpoint stays reachable during maintenance mode.\n\nNotes:\n- flows.registration is \"otp_verification\" or \"direct\".\n- flows.forgot_password is \"otp_verification\" or \"disabled\".\n\nRate limit: 60 / minute",
                "operationId": "appConfig",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "App configuration fetched successfully.",
                                    "data": {
                                        "app": {
                                            "name": "Sara777",
                                            "tagline": "Admin Panel",
                                            "support_email": "support@sara777.test",
                                            "support_mobile": ""
                                        },
                                        "otp": {
                                            "enabled": true,
                                            "length": 6,
                                            "expiry_minutes": 10,
                                            "resend_cooldown_seconds": 60,
                                            "max_resend": 3,
                                            "max_verify_attempts": 5,
                                            "test_mode": false
                                        },
                                        "flows": {
                                            "registration": "otp_verification",
                                            "forgot_password": "otp_verification"
                                        },
                                        "firebase": {
                                            "enabled": true,
                                            "apiKey": "AIza...",
                                            "authDomain": "your-app.firebaseapp.com",
                                            "projectId": "your-app",
                                            "storageBucket": "your-app.appspot.com",
                                            "messagingSenderId": "1234567890",
                                            "appId": "1:1234567890:android:abcdef",
                                            "measurementId": "",
                                            "vapidKey": ""
                                        },
                                        "version": {
                                            "android": "1.0.0",
                                            "ios": "1.0.0",
                                            "force_update": false
                                        },
                                        "maintenance": {
                                            "enabled": false,
                                            "message": ""
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/markets": {
            "get": {
                "tags": [
                    "Markets & Games"
                ],
                "summary": "List Markets",
                "description": "Every active market for one engine, with its open and close times, whether each session is still taking bids, and the result for the current gaming day. One endpoint covers all three engines -- pass `engine` to pick which.\n\nParameters:\n- engine (string, optional) \u2014 Which game engine to list. Defaults to main. Rules: nullable, in:main,starline,gali_disawar\n\nNotes:\n- status is one of: open (both sessions), close_only (open time has passed), finished (close time has passed), holiday (market shut today), suspended (admin paused it), closed (betting switched off globally).\n- Drive the bid buttons from accepts_open_bids / accepts_close_bids, not from the times -- they already account for the holiday, pause and global switches.\n- gaming_date is the betting day, which only rolls over at the configured time (06:00 by default). Before that the previous day results are still shown.\n- Undeclared results come back as *** and *, ready to display as-is.\n\nRate limit: 120 / minute",
                "operationId": "marketsIndex",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Markets fetched successfully.",
                                    "data": {
                                        "engine": "main",
                                        "gaming_date": "2026-10-13",
                                        "betting_enabled": true,
                                        "markets": [
                                            {
                                                "id": 4,
                                                "name": "Kalyan",
                                                "slug": "kalyan",
                                                "engine": "main",
                                                "open_time": "04:00 PM",
                                                "close_time": "06:00 PM",
                                                "status": "open",
                                                "accepts_open_bids": true,
                                                "accepts_close_bids": true,
                                                "closed_reason": null,
                                                "result": {
                                                    "date": "2026-10-13",
                                                    "open_panna": "128",
                                                    "open_digit": "1",
                                                    "close_panna": "***",
                                                    "close_digit": "*",
                                                    "display": "128-1*-***"
                                                }
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "engine",
                        "in": "query",
                        "required": false,
                        "description": "optional. Which game engine to list. Defaults to main. Rules: nullable, in:main,starline,gali_disawar",
                        "schema": {
                            "type": "string",
                            "example": "main"
                        }
                    }
                ]
            }
        },
        "/api/v1/markets/{slug}": {
            "get": {
                "tags": [
                    "Markets & Games"
                ],
                "summary": "Get One Market",
                "description": "A single market in the same shape as the list entry. Answers 404 if the market is inactive.\n\nParameters:\n- slug (string, required) \u2014 Path parameter -- the market slug from the list. Rules: required, must be an active market\n\nRate limit: 120 / minute",
                "operationId": "marketsShow",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Market fetched successfully.",
                                    "data": {
                                        "market": {
                                            "id": 4,
                                            "name": "Kalyan",
                                            "slug": "kalyan",
                                            "status": "open"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "slug",
                        "in": "path",
                        "required": true,
                        "description": "required. Path parameter -- the market slug from the list. Rules: required, must be an active market",
                        "schema": {
                            "type": "string",
                            "example": "kalyan"
                        }
                    }
                ]
            }
        },
        "/api/v1/markets/{slug}/chart": {
            "get": {
                "tags": [
                    "Markets & Games"
                ],
                "summary": "Market Result Chart",
                "description": "Declared results for a market, newest first, for the chart screen.\n\nParameters:\n- slug (string, required) \u2014 Path parameter -- the market slug. Rules: required\n- days (integer, optional) \u2014 How far back to read. Defaults to 60. Query string. Rules: nullable, integer, min:1, max:365\n\nRate limit: 120 / minute",
                "operationId": "marketsChart",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Chart fetched successfully.",
                                    "data": {
                                        "market": {
                                            "id": 4,
                                            "name": "Kalyan",
                                            "slug": "kalyan",
                                            "engine": "main"
                                        },
                                        "results": [
                                            {
                                                "date": "2026-10-13",
                                                "open_panna": "128",
                                                "open_digit": 1,
                                                "close_panna": "460",
                                                "close_digit": 0,
                                                "jodi": "10",
                                                "display": "128-10-460"
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "slug",
                        "in": "path",
                        "required": true,
                        "description": "required. Path parameter -- the market slug. Rules: required",
                        "schema": {
                            "type": "string",
                            "example": "kalyan"
                        }
                    },
                    {
                        "name": "days",
                        "in": "query",
                        "required": false,
                        "description": "optional. How far back to read. Defaults to 60. Query string. Rules: nullable, integer, min:1, max:365",
                        "schema": {
                            "type": "integer",
                            "example": 60
                        }
                    }
                ]
            }
        },
        "/api/v1/game-rates": {
            "get": {
                "tags": [
                    "Markets & Games"
                ],
                "summary": "Game Rates",
                "description": "Bid types and their payouts, plus the stake limits the bid screen must enforce.\n\nParameters:\n- engine (string, optional) \u2014 Limit to one engine. Omit for all of them. Rules: nullable, in:main,starline,gali_disawar\n\nNotes:\n- requires_session marks the bets placed against the open or close side. Jodi and the sangam bets need both results, so they carry no session.\n- Payout scales with the stake: at \"10 ka 95\", a 50 stake returns 475.\n\nRate limit: 120 / minute",
                "operationId": "gameRates",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Game rates fetched successfully.",
                                    "data": {
                                        "rates": [
                                            {
                                                "code": "single",
                                                "name": "Single Digit",
                                                "engine": "main",
                                                "requires_session": true,
                                                "bid_amount": 10,
                                                "win_amount": 95,
                                                "label": "10 ka 95"
                                            }
                                        ],
                                        "limits": {
                                            "min_bid_amount": 10,
                                            "max_bid_amount": 10000,
                                            "max_bids_per_submission": 100
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "engine",
                        "in": "query",
                        "required": false,
                        "description": "optional. Limit to one engine. Omit for all of them. Rules: nullable, in:main,starline,gali_disawar",
                        "schema": {
                            "type": "string",
                            "example": "main"
                        }
                    }
                ]
            }
        },
        "/api/v1/panna": {
            "get": {
                "tags": [
                    "Markets & Games"
                ],
                "summary": "Panna Lookup",
                "description": "The panna reference list, grouped by type. Used by the bulk bid screens to expand a shorthand selection into the actual numbers. With no filters it returns all 220.\n\nParameters:\n- types[] (string, optional) \u2014 Repeat the key to pass several, e.g. types[]=sp&types[]=dp. Defaults to all three. Rules: nullable, array, each in:sp,dp,tp\n- digits[] (integer, optional) \u2014 Keep only pannas reducing to these digits. Rules: nullable, array, each integer 0-9\n\nNotes:\n- There are 220 pannas in total: 120 single, 90 double and 10 triple.\n- Panna digits ascend, with 0 ranking last because it stands for 10. So 128 and 100 are valid pannas and 821 is not.\n- The digit is the last digit of the sum: 1 + 2 + 8 = 11, so 128 reduces to 1.\n\nRate limit: 120 / minute",
                "operationId": "pannaIndex",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Panna numbers fetched successfully.",
                                    "data": {
                                        "total": 120,
                                        "pannas": {
                                            "sp": [
                                                {
                                                    "panna": "128",
                                                    "digit": 1
                                                },
                                                {
                                                    "panna": "137",
                                                    "digit": 1
                                                }
                                            ]
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "types[]",
                        "in": "query",
                        "required": false,
                        "description": "optional. Repeat the key to pass several, e.g. types[]=sp&types[]=dp. Defaults to all three. Rules: nullable, array, each in:sp,dp,tp",
                        "schema": {
                            "type": "string",
                            "example": "sp"
                        }
                    },
                    {
                        "name": "digits[]",
                        "in": "query",
                        "required": false,
                        "description": "optional. Keep only pannas reducing to these digits. Rules: nullable, array, each integer 0-9",
                        "schema": {
                            "type": "integer",
                            "example": 1
                        }
                    }
                ]
            }
        },
        "/api/v1/panna/digits": {
            "get": {
                "tags": [
                    "Markets & Games"
                ],
                "summary": "Digit Helper",
                "description": "The digits 0-9 filtered to odd, even or all, for the odd/even quick-pick.\n\nParameters:\n- option (string, optional) \u2014 Defaults to all. Rules: nullable, in:odd,even,all\n\nRate limit: 120 / minute",
                "operationId": "pannaDigits",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Digits fetched successfully.",
                                    "data": {
                                        "option": "odd",
                                        "digits": [
                                            1,
                                            3,
                                            5,
                                            7,
                                            9
                                        ]
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "option",
                        "in": "query",
                        "required": false,
                        "description": "optional. Defaults to all. Rules: nullable, in:odd,even,all",
                        "schema": {
                            "type": "string",
                            "example": "odd"
                        }
                    }
                ]
            }
        },
        "/api/v1/bids": {
            "post": {
                "tags": [
                    "Bidding"
                ],
                "summary": "Place Bids",
                "description": "Takes one or more bids for a single market. Send the whole screenful in one call rather than one request per bid. `session` is required for games that are played per session (single digit and the pannas) and must be omitted for jodi and the sangams, which are decided by both results and therefore close when the open session closes.\n\nParameters:\n- market (string, required) \u2014 The market slug, from GET /markets. Rules: required, must be an existing market slug\n- bids (array, required) \u2014 The bids to place. All of them must be for the market named above. Rules: required, array, min:1, max per the Max Bids per Submission setting (100 by default)\n- bids[].game_type (string, required) \u2014 The game code from GET /game-rates, for example single, jodi, single_panna, half_sangam. Rules: required, must be an active game on this market engine\n- bids[].bid_number (string, required) \u2014 The number staked. Sent as a string so leading zeros survive. Rules: required, shape depends on the game -- see the notes below\n- bids[].amount (number, required) \u2014 The stake for this one bid. Read the current limits from GET /app-config rather than hard-coding them. Rules: required, numeric, between the Minimum Bid and Maximum Bid settings\n- bids[].session (string, optional / sometimes) \u2014 Which side of the market this bid is for. Omit it for jodi, half sangam and full sangam. Rules: nullable, in:open,close -- required for single digit and the pannas on a main market\n\nNotes:\n- single -- one digit, 0 to 9. Example: 5\n- jodi -- two digits, 00 to 99. Example: 57\n- single_panna / double_panna / triple_panna -- a three digit panna that really exists and is of that type. 123 is a single panna and is refused as a double. Use GET /panna to list the valid ones.\n- half_sangam -- a panna and a digit, either way round: \"123-7\" is open panna with close digit, \"3-457\" is open digit with close panna.\n- full_sangam -- open panna and close panna: \"123-457\".\n\nRate limit: 60 / minute",
                "operationId": "bidsStore",
                "responses": {
                    "201": {
                        "description": "Placed",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "2 bids placed successfully.",
                                    "data": {
                                        "bids": [
                                            {
                                                "id": 811,
                                                "engine": "main",
                                                "market": {
                                                    "slug": "kalyan",
                                                    "name": "Kalyan"
                                                },
                                                "game_type": "single",
                                                "game_name": "Single Digit",
                                                "session": "open",
                                                "bid_number": "5",
                                                "amount": 100,
                                                "status": "pending",
                                                "win_amount": 0,
                                                "result_date": "2026-10-19",
                                                "settled_at": null,
                                                "created_at": "2026-10-19T06:32:10+00:00"
                                            }
                                        ],
                                        "total_amount": 150,
                                        "balance": 850
                                    }
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "One of the bids was rejected",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": false,
                                    "message": "Bid 2: 133 is a double panna, not a single panna.",
                                    "error_code": "BID_NUMBER_INVALID",
                                    "bid_index": 1
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "market",
                                    "bids",
                                    "bids[].game_type",
                                    "bids[].bid_number",
                                    "bids[].amount"
                                ],
                                "properties": {
                                    "market": {
                                        "type": "string",
                                        "description": "The market slug, from GET /markets. Validation: required, must be an existing market slug",
                                        "example": "kalyan"
                                    },
                                    "bids": {
                                        "type": "string",
                                        "description": "The bids to place. All of them must be for the market named above. Validation: required, array, min:1, max per the Max Bids per Submission setting (100 by default)",
                                        "example": "[ { ... } ]"
                                    },
                                    "bids[].game_type": {
                                        "type": "string",
                                        "description": "The game code from GET /game-rates, for example single, jodi, single_panna, half_sangam. Validation: required, must be an active game on this market engine",
                                        "example": "single_panna"
                                    },
                                    "bids[].bid_number": {
                                        "type": "string",
                                        "description": "The number staked. Sent as a string so leading zeros survive. Validation: required, shape depends on the game -- see the notes below",
                                        "example": "123"
                                    },
                                    "bids[].amount": {
                                        "type": "string",
                                        "description": "The stake for this one bid. Read the current limits from GET /app-config rather than hard-coding them. Validation: required, numeric, between the Minimum Bid and Maximum Bid settings",
                                        "example": 50
                                    },
                                    "bids[].session": {
                                        "type": "string",
                                        "description": "Which side of the market this bid is for. Omit it for jodi, half sangam and full sangam. Validation: nullable, in:open,close -- required for single digit and the pannas on a main market",
                                        "example": "open"
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "get": {
                "tags": [
                    "Bidding"
                ],
                "summary": "My Bids",
                "description": "The signed-in player's own bids, newest first, with a summary over whatever the filters select. A bid stays `pending` until the result it depends on is declared, then becomes `won` or `lost` by itself.\n\nParameters:\n- engine (string, optional) \u2014 Limit to one engine. Rules: nullable, in:main,starline,gali_disawar\n- market (string, optional) \u2014 A market slug. Rules: nullable, max:60\n- status (string, optional) \u2014 Limit to one status. Rules: nullable, in:pending,won,lost,refunded\n- date (string, optional) \u2014 One gaming day. Rules: nullable, date Y-m-d\n- from (string, optional) \u2014 Start of a range of gaming days. Rules: nullable, date Y-m-d\n- to (string, optional) \u2014 End of that range. Rules: nullable, date Y-m-d\n- per_page (integer, optional) \u2014 Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100\n\nRate limit: 120 / minute",
                "operationId": "bidsIndex",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Bids fetched successfully.",
                                    "data": {
                                        "summary": {
                                            "total_bids": 24,
                                            "total_staked": 2400,
                                            "total_won": 1900,
                                            "pending": 3
                                        },
                                        "bids": [
                                            {
                                                "id": 811,
                                                "bid_number": "5",
                                                "amount": 100,
                                                "status": "won",
                                                "win_amount": 950
                                            }
                                        ],
                                        "pagination": {
                                            "current_page": 1,
                                            "last_page": 1,
                                            "per_page": 25,
                                            "total": 24
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "engine",
                        "in": "query",
                        "required": false,
                        "description": "optional. Limit to one engine. Rules: nullable, in:main,starline,gali_disawar",
                        "schema": {
                            "type": "string",
                            "example": "main"
                        }
                    },
                    {
                        "name": "market",
                        "in": "query",
                        "required": false,
                        "description": "optional. A market slug. Rules: nullable, max:60",
                        "schema": {
                            "type": "string",
                            "example": "kalyan"
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "description": "optional. Limit to one status. Rules: nullable, in:pending,won,lost,refunded",
                        "schema": {
                            "type": "string",
                            "example": "won"
                        }
                    },
                    {
                        "name": "date",
                        "in": "query",
                        "required": false,
                        "description": "optional. One gaming day. Rules: nullable, date Y-m-d",
                        "schema": {
                            "type": "string",
                            "example": "2026-10-19"
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "required": false,
                        "description": "optional. Start of a range of gaming days. Rules: nullable, date Y-m-d",
                        "schema": {
                            "type": "string",
                            "example": "2026-10-01"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": false,
                        "description": "optional. End of that range. Rules: nullable, date Y-m-d",
                        "schema": {
                            "type": "string",
                            "example": "2026-10-19"
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "optional. Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100",
                        "schema": {
                            "type": "integer",
                            "example": 25
                        }
                    }
                ]
            }
        },
        "/api/v1/wallet": {
            "get": {
                "tags": [
                    "Wallet"
                ],
                "summary": "Wallet Balance",
                "description": "The current balance, the two feature flags, and how many requests are still waiting.\n\nRate limit: 120 / minute",
                "operationId": "walletShow",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Wallet fetched successfully.",
                                    "data": {
                                        "balance": 850,
                                        "betting_enabled": true,
                                        "pending_deposits": 1,
                                        "pending_withdrawals": 0
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            }
        },
        "/api/v1/wallet/transactions": {
            "get": {
                "tags": [
                    "Wallet"
                ],
                "summary": "Wallet History",
                "description": "Every movement, newest first. Each row carries `signed_amount`, negative for a debit, and `balance_after`, so a list can be rendered without branching on the type and a running balance never has to be worked out in the app.\n\nParameters:\n- type (string, optional) \u2014 Money in or money out. Rules: nullable, in:credit,debit\n- category (string, optional) \u2014 One of: signup_bonus, deposit, withdrawal, withdrawal_reversal, bid_placed, bid_won, bid_refund, admin_credit, admin_debit. Rules: nullable, max:30\n- per_page (integer, optional) \u2014 Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100\n\nRate limit: 120 / minute",
                "operationId": "walletTransactions",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Wallet history fetched successfully.",
                                    "data": {
                                        "balance": 850,
                                        "transactions": [
                                            {
                                                "id": 44,
                                                "type": "debit",
                                                "category": "bid_placed",
                                                "label": "Bid placed",
                                                "amount": 150,
                                                "signed_amount": -150,
                                                "balance_after": 850,
                                                "remark": "2 bids on Kalyan",
                                                "created_at": "2026-10-19T06:32:10+00:00"
                                            }
                                        ],
                                        "pagination": {
                                            "current_page": 1,
                                            "last_page": 1,
                                            "per_page": 25,
                                            "total": 1
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "type",
                        "in": "query",
                        "required": false,
                        "description": "optional. Money in or money out. Rules: nullable, in:credit,debit",
                        "schema": {
                            "type": "string",
                            "example": "debit"
                        }
                    },
                    {
                        "name": "category",
                        "in": "query",
                        "required": false,
                        "description": "optional. One of: signup_bonus, deposit, withdrawal, withdrawal_reversal, bid_placed, bid_won, bid_refund, admin_credit, admin_debit. Rules: nullable, max:30",
                        "schema": {
                            "type": "string",
                            "example": "bid_placed"
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "optional. Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100",
                        "schema": {
                            "type": "integer",
                            "example": 25
                        }
                    }
                ]
            }
        },
        "/api/v1/wallet/deposit-info": {
            "get": {
                "tags": [
                    "Wallet"
                ],
                "summary": "Deposit Details",
                "description": "What the pay-in screen needs. `upi_intent` is a complete upi:// string -- encode it as a QR in the app rather than shipping an image, and the code can never show a VPA the admin has since changed. The same string also deep-links straight into GPay or PhonePe.\n\nRate limit: 120 / minute",
                "operationId": "walletDepositInfo",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Deposit details fetched successfully.",
                                    "data": {
                                        "upi_id": "sara777@ybl",
                                        "payee_name": "Sara777",
                                        "upi_intent": "upi://pay?pa=sara777%40ybl&pn=Sara777&cu=INR",
                                        "min_amount": 100,
                                        "max_amount": 100000,
                                        "auto_approved": false
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            }
        },
        "/api/v1/wallet/deposit": {
            "post": {
                "tags": [
                    "Wallet"
                ],
                "summary": "Add Fund",
                "description": "Raise an add-fund request after paying through UPI. Send it as multipart/form-data when a screenshot is attached. Read `status` on the way back rather than assuming: normally it is `pending` and the balance does not move until an admin approves, but if auto-approval is switched on it comes back `approved` with the balance already updated.\n\nParameters:\n- amount (number, required) \u2014 How much was paid in. Rules: required, numeric, between the Minimum and Maximum Deposit settings\n- transaction_reference (string, optional) \u2014 The UTR or reference from the payment app. Rules: nullable, max:100\n- screenshot (file, optional) \u2014 Proof of payment. Stored privately and only ever read by an admin. Rules: nullable, image, jpg/jpeg/png/webp, max 5 MB\n\nRate limit: 10 / minute",
                "operationId": "walletDeposit",
                "responses": {
                    "201": {
                        "description": "Submitted",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Request submitted. Your wallet is credited once it is approved.",
                                    "data": {
                                        "request": {
                                            "id": 12,
                                            "amount": 500,
                                            "method": "upi",
                                            "transaction_reference": "UPI2410166789",
                                            "status": "pending",
                                            "admin_remark": null,
                                            "processed_at": null,
                                            "created_at": "2026-10-19T06:32:10+00:00"
                                        },
                                        "balance": 850
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "amount"
                                ],
                                "properties": {
                                    "amount": {
                                        "type": "string",
                                        "description": "How much was paid in. Validation: required, numeric, between the Minimum and Maximum Deposit settings",
                                        "example": 500
                                    },
                                    "transaction_reference": {
                                        "type": "string",
                                        "description": "The UTR or reference from the payment app. Validation: nullable, max:100",
                                        "example": "UPI2410166789"
                                    },
                                    "screenshot": {
                                        "type": "string",
                                        "description": "Proof of payment. Stored privately and only ever read by an admin. Validation: nullable, image, jpg/jpeg/png/webp, max 5 MB",
                                        "example": "(file)"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/wallet/deposits": {
            "get": {
                "tags": [
                    "Wallet"
                ],
                "summary": "My Add-Fund Requests",
                "description": "The player's own add-fund requests, newest first.\n\nParameters:\n- status (string, optional) \u2014 Limit to one status. Rules: nullable, in:pending,approved,rejected\n- per_page (integer, optional) \u2014 Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100\n\nRate limit: 120 / minute",
                "operationId": "walletDeposits",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Add-fund requests fetched successfully.",
                                    "data": {
                                        "requests": [],
                                        "pagination": {
                                            "current_page": 1,
                                            "last_page": 1,
                                            "per_page": 25,
                                            "total": 0
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "description": "optional. Limit to one status. Rules: nullable, in:pending,approved,rejected",
                        "schema": {
                            "type": "string",
                            "example": "pending"
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "optional. Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100",
                        "schema": {
                            "type": "integer",
                            "example": 25
                        }
                    }
                ]
            }
        },
        "/api/v1/wallet/withdraw-info": {
            "get": {
                "tags": [
                    "Wallet"
                ],
                "summary": "Withdrawal Rules",
                "description": "Call this before showing the withdrawal form. If `is_open_now` is false, disable the button and show `days`, `opens_at` and `closes_at` instead of letting the request fail. `days` are ISO weekday numbers, 1 = Monday.\n\nRate limit: 120 / minute",
                "operationId": "walletWithdrawInfo",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Withdrawal details fetched successfully.",
                                    "data": {
                                        "min_amount": 1000,
                                        "max_amount_per_day": 50000,
                                        "max_requests_per_day": 0,
                                        "charge_percent": 0,
                                        "days": [
                                            1,
                                            2,
                                            3,
                                            4,
                                            5
                                        ],
                                        "opens_at": "09:00",
                                        "closes_at": "14:00",
                                        "all_day": false,
                                        "is_open_now": true,
                                        "requested_today": {
                                            "amount": 0,
                                            "count": 0
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            }
        },
        "/api/v1/wallet/withdraw": {
            "post": {
                "tags": [
                    "Wallet"
                ],
                "summary": "Withdraw",
                "description": "Raise a withdrawal. The amount leaves the wallet immediately and is held until an admin pays it; the balance in the response is already reduced. If the admin rejects it, the money comes back by itself and shows in the history as a withdrawal refund.\n\nParameters:\n- amount (number, required) \u2014 How much to withdraw. Rules: required, numeric, at least the Minimum Withdrawal setting, within the daily cap\n- method (string, required) \u2014 How to be paid. Rules: required, in:bank,upi,phonepe,googlepay,paytm\n- bank_account_id (integer, optional / sometimes) \u2014 Which saved bank account to pay into. Rules: required when method is bank, must be one of the player's own saved accounts\n- payout_handle (string, optional / sometimes) \u2014 The number or UPI ID to pay on. Rules: required for every method other than bank, max:50\n- user_remark (string, optional) \u2014 A note for the admin. Rules: nullable, max:255\n\nRate limit: 10 / minute",
                "operationId": "walletWithdraw",
                "responses": {
                    "201": {
                        "description": "Requested",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Withdrawal requested. The amount is held until it is paid.",
                                    "data": {
                                        "request": {
                                            "id": 7,
                                            "amount": 2000,
                                            "method": "phonepe",
                                            "payout_handle": "9876543210",
                                            "status": "pending",
                                            "created_at": "2026-10-19T06:32:10+00:00"
                                        },
                                        "balance": 3000
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "amount",
                                    "method"
                                ],
                                "properties": {
                                    "amount": {
                                        "type": "string",
                                        "description": "How much to withdraw. Validation: required, numeric, at least the Minimum Withdrawal setting, within the daily cap",
                                        "example": 2000
                                    },
                                    "method": {
                                        "type": "string",
                                        "description": "How to be paid. Validation: required, in:bank,upi,phonepe,googlepay,paytm",
                                        "example": "phonepe"
                                    },
                                    "bank_account_id": {
                                        "type": "integer",
                                        "description": "Which saved bank account to pay into. Validation: required when method is bank, must be one of the player's own saved accounts",
                                        "example": 3
                                    },
                                    "payout_handle": {
                                        "type": "string",
                                        "description": "The number or UPI ID to pay on. Validation: required for every method other than bank, max:50",
                                        "example": "9876543210"
                                    },
                                    "user_remark": {
                                        "type": "string",
                                        "description": "A note for the admin. Validation: nullable, max:255",
                                        "example": "Please pay by evening"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/wallet/withdrawals": {
            "get": {
                "tags": [
                    "Wallet"
                ],
                "summary": "My Withdrawals",
                "description": "The player's own withdrawal requests, newest first. `paid` means the admin has sent the money.\n\nParameters:\n- status (string, optional) \u2014 Limit to one status. Rules: nullable, in:pending,approved,rejected,paid\n- per_page (integer, optional) \u2014 Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100\n\nRate limit: 120 / minute",
                "operationId": "walletWithdrawals",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Withdrawal requests fetched successfully.",
                                    "data": {
                                        "requests": [],
                                        "pagination": {
                                            "current_page": 1,
                                            "last_page": 1,
                                            "per_page": 25,
                                            "total": 0
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "description": "optional. Limit to one status. Rules: nullable, in:pending,approved,rejected,paid",
                        "schema": {
                            "type": "string",
                            "example": "pending"
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "optional. Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100",
                        "schema": {
                            "type": "integer",
                            "example": 25
                        }
                    }
                ]
            }
        },
        "/api/v1/bank-accounts": {
            "get": {
                "tags": [
                    "Bank Accounts"
                ],
                "summary": "List Bank Accounts",
                "description": "All saved accounts, the primary one first.\n\nRate limit: 120 / minute",
                "operationId": "bankAccountsIndex",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Bank accounts fetched successfully.",
                                    "data": {
                                        "accounts": [
                                            {
                                                "id": 3,
                                                "account_holder": "Ramesh Patel",
                                                "account_number_masked": "XXXXXXXX9012",
                                                "ifsc_code": "HDFC0001234",
                                                "bank_name": "HDFC Bank",
                                                "branch": null,
                                                "is_primary": true,
                                                "created_at": "2026-10-19T06:32:10+00:00"
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            },
            "post": {
                "tags": [
                    "Bank Accounts"
                ],
                "summary": "Add Bank Account",
                "description": "Save an account. The same account number cannot be saved twice on one profile.\n\nParameters:\n- account_holder (string, required) \u2014 Name as it appears on the account. Rules: required, max:100\n- account_number (string, required) \u2014 The account number. Rules: required, digits only, min:6, max:30, unique per player\n- ifsc_code (string, required) \u2014 IFSC of the branch. Upper-cased automatically. Rules: required, 4 letters + 0 + 6 alphanumerics\n- bank_name (string, required) \u2014 Name of the bank. Rules: required, max:100\n- branch (string, optional) \u2014 Branch name. Rules: nullable, max:100\n- is_primary (boolean, optional) \u2014 Make this the default for payouts. The first account saved becomes primary whatever this says. Rules: nullable, boolean\n\nRate limit: 120 / minute",
                "operationId": "bankAccountsStore",
                "responses": {
                    "201": {
                        "description": "Saved",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Bank account saved successfully.",
                                    "data": {
                                        "account": {
                                            "id": 3,
                                            "account_number_masked": "XXXXXXXX9012",
                                            "is_primary": true
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "account_holder",
                                    "account_number",
                                    "ifsc_code",
                                    "bank_name"
                                ],
                                "properties": {
                                    "account_holder": {
                                        "type": "string",
                                        "description": "Name as it appears on the account. Validation: required, max:100",
                                        "example": "Ramesh Patel"
                                    },
                                    "account_number": {
                                        "type": "string",
                                        "description": "The account number. Validation: required, digits only, min:6, max:30, unique per player",
                                        "example": "123456789012"
                                    },
                                    "ifsc_code": {
                                        "type": "string",
                                        "description": "IFSC of the branch. Upper-cased automatically. Validation: required, 4 letters + 0 + 6 alphanumerics",
                                        "example": "HDFC0001234"
                                    },
                                    "bank_name": {
                                        "type": "string",
                                        "description": "Name of the bank. Validation: required, max:100",
                                        "example": "HDFC Bank"
                                    },
                                    "branch": {
                                        "type": "string",
                                        "description": "Branch name. Validation: nullable, max:100",
                                        "example": "Navrangpura"
                                    },
                                    "is_primary": {
                                        "type": "boolean",
                                        "description": "Make this the default for payouts. The first account saved becomes primary whatever this says. Validation: nullable, boolean",
                                        "example": true
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/bank-accounts/{id}": {
            "put": {
                "tags": [
                    "Bank Accounts"
                ],
                "summary": "Update Bank Account",
                "description": "Change a saved account. Takes the same fields as adding one.\n\nParameters:\n- id (integer, required) \u2014 Path parameter -- the account id. Rules: required, must belong to the signed-in player\n\nRate limit: 120 / minute",
                "operationId": "bankAccountsUpdate",
                "responses": {
                    "200": {
                        "description": "Updated",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Bank account updated successfully.",
                                    "data": {
                                        "account": {
                                            "id": 3
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "required. Path parameter -- the account id. Rules: required, must belong to the signed-in player",
                        "schema": {
                            "type": "integer",
                            "example": 3
                        }
                    }
                ]
            },
            "delete": {
                "tags": [
                    "Bank Accounts"
                ],
                "summary": "Remove Bank Account",
                "description": "Remove an account. Refused while a pending withdrawal still points at it. If the primary one is removed, the next account becomes primary so the player is never left with accounts but no default.\n\nParameters:\n- id (integer, required) \u2014 Path parameter -- the account id. Rules: required, must belong to the signed-in player\n\nRate limit: 120 / minute",
                "operationId": "bankAccountsDestroy",
                "responses": {
                    "200": {
                        "description": "Removed",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Bank account removed successfully."
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "required. Path parameter -- the account id. Rules: required, must belong to the signed-in player",
                        "schema": {
                            "type": "integer",
                            "example": 3
                        }
                    }
                ]
            }
        },
        "/api/v1/bank-accounts/{id}/primary": {
            "post": {
                "tags": [
                    "Bank Accounts"
                ],
                "summary": "Set Primary Account",
                "description": "Make this the account payouts default to. Whichever was primary stops being so.\n\nParameters:\n- id (integer, required) \u2014 Path parameter -- the account id. Rules: required, must belong to the signed-in player\n\nRate limit: 120 / minute",
                "operationId": "bankAccountsPrimary",
                "responses": {
                    "200": {
                        "description": "Updated",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Primary bank account updated.",
                                    "data": {
                                        "account": {
                                            "id": 3,
                                            "is_primary": true
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "required. Path parameter -- the account id. Rules: required, must belong to the signed-in player",
                        "schema": {
                            "type": "integer",
                            "example": 3
                        }
                    }
                ]
            }
        },
        "/api/v1/content": {
            "get": {
                "tags": [
                    "Content"
                ],
                "summary": "Home Content",
                "description": "Banners, the notices for one placement, and the support channels, in one call so the home screen does not have to make three.\n\nParameters:\n- placement (string, optional) \u2014 Which screen the notices are for. Defaults to home. Rules: nullable, in:home,app,add_fund,withdraw\n\nRate limit: 120 / minute",
                "operationId": "contentIndex",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Content fetched successfully.",
                                    "data": {
                                        "sliders": [
                                            {
                                                "id": 2,
                                                "image_url": "https://dpbossapp.io/api/v1/content/sliders/2/image",
                                                "link_url": null,
                                                "caption": "Welcome"
                                            }
                                        ],
                                        "notices": [
                                            {
                                                "id": 5,
                                                "placement": "home",
                                                "title": null,
                                                "body": "Markets are closed on Sunday.",
                                                "updated_at": "2026-10-19T06:32:10+00:00"
                                            }
                                        ],
                                        "contact": {
                                            "whatsapp": "919876543210",
                                            "telegram": null,
                                            "mobile": null,
                                            "email": null
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "placement",
                        "in": "query",
                        "required": false,
                        "description": "optional. Which screen the notices are for. Defaults to home. Rules: nullable, in:home,app,add_fund,withdraw",
                        "schema": {
                            "type": "string",
                            "example": "home"
                        }
                    }
                ]
            }
        },
        "/api/v1/content/notices": {
            "get": {
                "tags": [
                    "Content"
                ],
                "summary": "Notices",
                "description": "The live notices, lowest sort order first. Omit `placement` to get them all; pass one to get only that screen's.\n\nParameters:\n- placement (string, optional) \u2014 Which screen the notice belongs above. Rules: nullable, in:home,app,add_fund,withdraw\n\nRate limit: 120 / minute",
                "operationId": "contentNotices",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Notices fetched successfully.",
                                    "data": {
                                        "notices": []
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "placement",
                        "in": "query",
                        "required": false,
                        "description": "optional. Which screen the notice belongs above. Rules: nullable, in:home,app,add_fund,withdraw",
                        "schema": {
                            "type": "string",
                            "example": "add_fund"
                        }
                    }
                ]
            }
        },
        "/api/v1/content/sliders": {
            "get": {
                "tags": [
                    "Content"
                ],
                "summary": "Banners",
                "description": "The home screen banners, in order. Each carries a ready-made `image_url`.\n\nRate limit: 120 / minute",
                "operationId": "contentSliders",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Sliders fetched successfully.",
                                    "data": {
                                        "sliders": []
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/content/sliders/{slider}/image": {
            "get": {
                "tags": [
                    "Content"
                ],
                "summary": "Banner Image",
                "description": "The banner image itself. Use the `image_url` from the list rather than building this path by hand. Answers the image bytes, not JSON, and is cached for a day. A banner the admin has switched off answers 404.\n\nParameters:\n- slider (integer, required) \u2014 Path parameter -- the banner id. Rules: required, must be a live banner\n\nRate limit: 120 / minute",
                "operationId": "contentSliderImage",
                "responses": {
                    "200": {
                        "description": "The image bytes",
                        "content": {
                            "application/json": {
                                "example": "(binary image data)"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "slider",
                        "in": "path",
                        "required": true,
                        "description": "required. Path parameter -- the banner id. Rules: required, must be a live banner",
                        "schema": {
                            "type": "integer",
                            "example": 2
                        }
                    }
                ]
            }
        },
        "/api/v1/content/pages/{page}": {
            "get": {
                "tags": [
                    "Content"
                ],
                "summary": "Information Page",
                "description": "The text for one information screen. Plain text, with blank lines between paragraphs. Edited in the admin panel under Settings, so it changes without an app release.\n\nParameters:\n- page (string, required) \u2014 Path parameter -- which screen. Rules: required, one of: terms, how-to-play, about\n\nRate limit: 120 / minute",
                "operationId": "contentPage",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Page fetched successfully.",
                                    "data": {
                                        "key": "terms",
                                        "title": "Terms",
                                        "body": "By using this app you agree ..."
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "page",
                        "in": "path",
                        "required": true,
                        "description": "required. Path parameter -- which screen. Rules: required, one of: terms, how-to-play, about",
                        "schema": {
                            "type": "string",
                            "example": "terms"
                        }
                    }
                ]
            }
        },
        "/api/v1/content/contact": {
            "get": {
                "tags": [
                    "Content"
                ],
                "summary": "Contact Details",
                "description": "How players reach support. Any of these may be null -- hide the button when it is.\n\nRate limit: 120 / minute",
                "operationId": "contentContact",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Contact details fetched successfully.",
                                    "data": {
                                        "whatsapp": "919876543210",
                                        "telegram": null,
                                        "mobile": "9876543210",
                                        "email": "support@sara777.test"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/enquiries": {
            "post": {
                "tags": [
                    "Support Messages"
                ],
                "summary": "Send a Message",
                "description": "Send support a message. Name and mobile fall back to the signed-in account, so normally only `message` needs sending.\n\nParameters:\n- message (string, required) \u2014 What the player wants to say. Rules: required, min:5, max:2000\n- name (string, optional) \u2014 Defaults to the name on the account. Rules: nullable, max:100\n- mobile (string, optional) \u2014 Defaults to the mobile on the account. Rules: nullable, max:15\n\nRate limit: 5 / minute",
                "operationId": "enquiriesStore",
                "responses": {
                    "201": {
                        "description": "Sent",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Thanks -- we have your message and will reply soon.",
                                    "data": {
                                        "enquiry": {
                                            "id": 9,
                                            "message": "My withdrawal is still pending.",
                                            "status": "open",
                                            "admin_reply": null,
                                            "replied_at": null,
                                            "created_at": "2026-10-19T06:32:10+00:00"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "message"
                                ],
                                "properties": {
                                    "message": {
                                        "type": "string",
                                        "description": "What the player wants to say. Validation: required, min:5, max:2000",
                                        "example": "My withdrawal is still pending."
                                    },
                                    "name": {
                                        "type": "string",
                                        "description": "Defaults to the name on the account. Validation: nullable, max:100",
                                        "example": "Ramesh Patel"
                                    },
                                    "mobile": {
                                        "type": "string",
                                        "description": "Defaults to the mobile on the account. Validation: nullable, max:15",
                                        "example": "9876543210"
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "get": {
                "tags": [
                    "Support Messages"
                ],
                "summary": "My Messages",
                "description": "The player's own messages, newest first. `status` moves from `open` to `replied` once an admin answers, and `admin_reply` carries what they wrote.\n\nRate limit: 120 / minute",
                "operationId": "enquiriesIndex",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Your messages fetched successfully.",
                                    "data": {
                                        "enquiries": [
                                            {
                                                "id": 9,
                                                "message": "My withdrawal is still pending.",
                                                "status": "replied",
                                                "admin_reply": "Paid this afternoon.",
                                                "replied_at": "2026-10-19T08:00:00+00:00",
                                                "created_at": "2026-10-19T06:32:10+00:00"
                                            }
                                        ],
                                        "pagination": {
                                            "current_page": 1,
                                            "last_page": 1,
                                            "per_page": 25,
                                            "total": 1
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            }
        },
        "/api/v1/chat/config": {
            "get": {
                "tags": [
                    "Live Chat"
                ],
                "summary": "Chat Availability",
                "description": "Whether to show the chat screen at all. Check this before asking for a token, so a switched-off chat is a hidden button rather than a failed request.\n\nRate limit: 120 / minute",
                "operationId": "chatConfig",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Chat configuration fetched successfully.",
                                    "data": {
                                        "enabled": true,
                                        "welcome_message": "Hi! How can we help you today?"
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            }
        },
        "/api/v1/chat/token": {
            "post": {
                "tags": [
                    "Live Chat"
                ],
                "summary": "Chat Token",
                "description": "A Firebase custom token to sign in with, plus the Firestore paths this player may use. Sign in with signInWithCustomToken(token), then listen to messages_path ordered by created_at. The token lasts an hour -- ask for another when Firebase reports the session has gone, rather than holding one indefinitely.\n\nNotes:\n- The uid is the player's account id, and the thread is keyed by the same id. The security rules match one against the other, so a player can only ever reach their own conversation.\n- Writing a message means two writes: the message itself, and the thread summary (last_message, last_message_at, unread_for_admin). Without the second, the admin list will not show it.\n- created_at must be serverTimestamp(). The rules reject anything else, so neither side can backdate a message.\n- Messages cannot be edited or deleted by either side.\n\nRate limit: 30 / minute",
                "operationId": "chatToken",
                "responses": {
                    "200": {
                        "description": "Issued",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Chat token issued successfully.",
                                    "data": {
                                        "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
                                        "uid": "42",
                                        "thread_path": "chat_threads/42",
                                        "messages_path": "chat_threads/42/messages",
                                        "expires_in": 3600,
                                        "firebase": {
                                            "apiKey": "AIza...",
                                            "authDomain": "sara777.firebaseapp.com",
                                            "projectId": "sara777",
                                            "appId": "1:1234:web:abcd",
                                            "messagingSenderId": "1234"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Chat is switched off",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": false,
                                    "message": "Live chat is currently switched off.",
                                    "error_code": "CHAT_DISABLED"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            }
        },
        "/api/v1/auth/register": {
            "post": {
                "tags": [
                    "Registration"
                ],
                "summary": "Register",
                "description": "Starts registration. Validation runs the same way in both modes, so a duplicate mobile number or a weak password is rejected before any SMS is sent.\n\nOTP ON: Nothing is written to the users table yet. An OTP is sent and the submitted data is held, encrypted, against the returned verification_id. Call /auth/register/verify-otp to finish.\nOTP OFF: The account is created immediately and the response contains the access token. otp_required is false and no verification_id is returned.\n\nParameters:\n- name (string, required) \u2014 Full name. Digits and other symbols are rejected. Rules: required, min:2, max:100, letters / spaces / . ' - only\n- mobile (string, required) \u2014 Indian mobile number and the login identifier. A leading +91, 91 or 0 is stripped automatically, so \"+91 98765 43210\" is accepted. Rules: required, 10 digits starting 6-9, unique:users\n- email (string, optional) \u2014 Optional. Send null or omit the key entirely. Rules: nullable, valid email, max:150, unique:users\n- password (string, required) \u2014 In production also requires letters + numbers and is checked against the HaveIBeenPwned breach list. Rules: required, min:8, confirmed\n- password_confirmation (string, required) \u2014 Required because of the `confirmed` rule on password. Rules: required, must equal password\n- fcm_token (string, optional) \u2014 Firebase Cloud Messaging registration token for this device. Stored against the account so the admin panel can push notifications to it. Rules: max:500\n- device_type (string, optional) \u2014 Platform the request came from. Rules: in:android,ios,web\n- device_id (string, optional) \u2014 Stable per-install identifier. Send the same value every time so one handset keeps one device record instead of creating duplicates. Rules: max:150\n- device_name (string, optional) \u2014 Human-readable device label. Also used as the access-token name. Rules: max:100\n- app_version (string, optional) \u2014 Installed app version. Rules: max:20\n\nNotes:\n- Branch on data.otp_required, not on the HTTP status.\n- With OTP off, is_mobile_verified is false \u2014 there was no verification step. An admin can tick it manually.\n- In Test Mode the response also carries data.otp and data.test_mode so you can develop without real SMS.\n\nRate limit: 10 / minute per IP",
                "operationId": "register",
                "responses": {
                    "200": {
                        "description": "OTP ON \u2014 verification required",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "An OTP has been sent to your mobile number.",
                                    "data": {
                                        "otp_required": true,
                                        "verification_id": "9f2c1a84-7b3e-4d51-9c8a-1e2f3a4b5c6d",
                                        "mobile": "9876543210",
                                        "purpose": "registration",
                                        "otp_length": 6,
                                        "expires_at": "2026-10-04T12:40:00+00:00",
                                        "expires_in_seconds": 600,
                                        "resend_available_in_seconds": 60,
                                        "resend_attempts_left": 3
                                    }
                                }
                            }
                        }
                    },
                    "201": {
                        "description": "OTP OFF \u2014 account created",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Registration successful.",
                                    "data": {
                                        "otp_required": false,
                                        "token_type": "Bearer",
                                        "token": "1|xZq8Lm2PwKd93nTyRv4bCsE7HgJ1aF6uOi0pYlXz",
                                        "user": {
                                            "id": 1,
                                            "name": "Maharshi Suhagiya",
                                            "mobile": "9876543210",
                                            "email": "user@example.com",
                                            "is_active": true,
                                            "is_mobile_verified": false,
                                            "mobile_verified_at": null,
                                            "device_type": "android",
                                            "app_version": "1.0.0",
                                            "last_login_at": "2026-10-04T12:30:00+00:00",
                                            "created_at": "2026-10-04T12:30:00+00:00"
                                        },
                                        "note": "OTP verification is disabled, so the account is active immediately."
                                    }
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "name",
                                    "mobile",
                                    "password",
                                    "password_confirmation"
                                ],
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "Full name. Digits and other symbols are rejected. Validation: required, min:2, max:100, letters / spaces / . ' - only",
                                        "example": "Maharshi Suhagiya"
                                    },
                                    "mobile": {
                                        "type": "string",
                                        "description": "Indian mobile number and the login identifier. A leading +91, 91 or 0 is stripped automatically, so \"+91 98765 43210\" is accepted. Validation: required, 10 digits starting 6-9, unique:users",
                                        "example": "9876543210"
                                    },
                                    "email": {
                                        "type": "string",
                                        "description": "Optional. Send null or omit the key entirely. Validation: nullable, valid email, max:150, unique:users",
                                        "example": "user@example.com"
                                    },
                                    "password": {
                                        "type": "string",
                                        "description": "In production also requires letters + numbers and is checked against the HaveIBeenPwned breach list. Validation: required, min:8, confirmed",
                                        "example": "Secret@123"
                                    },
                                    "password_confirmation": {
                                        "type": "string",
                                        "description": "Required because of the `confirmed` rule on password. Validation: required, must equal password",
                                        "example": "Secret@123"
                                    },
                                    "fcm_token": {
                                        "type": "string",
                                        "description": "Firebase Cloud Messaging registration token for this device. Stored against the account so the admin panel can push notifications to it. Validation: max:500",
                                        "example": "fZx9K2...A1b"
                                    },
                                    "device_type": {
                                        "type": "string",
                                        "description": "Platform the request came from. Validation: in:android,ios,web",
                                        "example": "android"
                                    },
                                    "device_id": {
                                        "type": "string",
                                        "description": "Stable per-install identifier. Send the same value every time so one handset keeps one device record instead of creating duplicates. Validation: max:150",
                                        "example": "a1b2c3d4e5f6"
                                    },
                                    "device_name": {
                                        "type": "string",
                                        "description": "Human-readable device label. Also used as the access-token name. Validation: max:100",
                                        "example": "Redmi Note 13"
                                    },
                                    "app_version": {
                                        "type": "string",
                                        "description": "Installed app version. Validation: max:20",
                                        "example": "1.0.0"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/auth/register/verify-otp": {
            "post": {
                "tags": [
                    "Registration"
                ],
                "summary": "Verify Registration OTP",
                "description": "Confirms the OTP from /auth/register and creates the account. Only called when OTP is ON.\n\nParameters:\n- verification_id (uuid, required) \u2014 The value returned by /auth/register. Rules: required, uuid\n- otp (string, required) \u2014 The code from the SMS. Send it as a string to preserve leading zeros. Rules: required, numeric, 4-8 digits\n- fcm_token (string, optional) \u2014 Firebase Cloud Messaging registration token for this device. Stored against the account so the admin panel can push notifications to it. Rules: max:500\n- device_type (string, optional) \u2014 Platform the request came from. Rules: in:android,ios,web\n- device_id (string, optional) \u2014 Stable per-install identifier. Send the same value every time so one handset keeps one device record instead of creating duplicates. Rules: max:150\n- device_name (string, optional) \u2014 Human-readable device label. Also used as the access-token name. Rules: max:100\n- app_version (string, optional) \u2014 Installed app version. Rules: max:20\n\nNotes:\n- Device fields sent here override whatever was sent at register time, so you can skip them on register and send them once at verify.\n- After max attempts the verification is deleted \u2014 restart from /auth/register.\n\nRate limit: 20 / minute per IP",
                "operationId": "registerVerifyOtp",
                "responses": {
                    "201": {
                        "description": "Verified and registered",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Registration completed successfully.",
                                    "data": {
                                        "otp_required": false,
                                        "token_type": "Bearer",
                                        "token": "2|aB3cD4eF5gH6iJ7kL8mN9oP0qR1sT2uV3wX4yZ5",
                                        "user": {
                                            "id": 2,
                                            "name": "Maharshi Suhagiya",
                                            "mobile": "9876543210",
                                            "email": null,
                                            "is_active": true,
                                            "is_mobile_verified": true,
                                            "mobile_verified_at": "2026-10-04T12:33:00+00:00",
                                            "device_type": "android",
                                            "app_version": "1.0.0",
                                            "last_login_at": "2026-10-04T12:33:00+00:00",
                                            "created_at": "2026-10-04T12:33:00+00:00"
                                        },
                                        "note": "Mobile number verified successfully."
                                    }
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Wrong code",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": false,
                                    "message": "The OTP you entered is incorrect. 4 attempt(s) left.",
                                    "error_code": "OTP_INVALID",
                                    "attempts_remaining": 4
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "verification_id",
                                    "otp"
                                ],
                                "properties": {
                                    "verification_id": {
                                        "type": "string",
                                        "description": "The value returned by /auth/register. Validation: required, uuid",
                                        "example": "9f2c1a84-7b3e-4d51-9c8a-1e2f3a4b5c6d"
                                    },
                                    "otp": {
                                        "type": "string",
                                        "description": "The code from the SMS. Send it as a string to preserve leading zeros. Validation: required, numeric, 4-8 digits",
                                        "example": "483921"
                                    },
                                    "fcm_token": {
                                        "type": "string",
                                        "description": "Firebase Cloud Messaging registration token for this device. Stored against the account so the admin panel can push notifications to it. Validation: max:500",
                                        "example": "fZx9K2...A1b"
                                    },
                                    "device_type": {
                                        "type": "string",
                                        "description": "Platform the request came from. Validation: in:android,ios,web",
                                        "example": "android"
                                    },
                                    "device_id": {
                                        "type": "string",
                                        "description": "Stable per-install identifier. Send the same value every time so one handset keeps one device record instead of creating duplicates. Validation: max:150",
                                        "example": "a1b2c3d4e5f6"
                                    },
                                    "device_name": {
                                        "type": "string",
                                        "description": "Human-readable device label. Also used as the access-token name. Validation: max:100",
                                        "example": "Redmi Note 13"
                                    },
                                    "app_version": {
                                        "type": "string",
                                        "description": "Installed app version. Validation: max:20",
                                        "example": "1.0.0"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/auth/login": {
            "post": {
                "tags": [
                    "Login & Session"
                ],
                "summary": "Login",
                "description": "Exchanges mobile + password for a bearer token. The device payload is captured on every login, so the FCM token stays current without a separate call.\n\nParameters:\n- mobile (string, required) \u2014 A +91 / 91 / 0 prefix is stripped automatically. Rules: required, 10 digits starting 6-9\n- password (string, required) \u2014 The account password. Rules: required\n- fcm_token (string, optional) \u2014 Firebase Cloud Messaging registration token for this device. Stored against the account so the admin panel can push notifications to it. Rules: max:500\n- device_type (string, optional) \u2014 Platform the request came from. Rules: in:android,ios,web\n- device_id (string, optional) \u2014 Stable per-install identifier. Send the same value every time so one handset keeps one device record instead of creating duplicates. Rules: max:150\n- device_name (string, optional) \u2014 Human-readable device label. Also used as the access-token name. Rules: max:100\n- app_version (string, optional) \u2014 Installed app version. Rules: max:20\n\nNotes:\n- Logging in does not revoke tokens on other devices \u2014 one token per device is the intended model.\n- Store the token securely (Keychain / EncryptedSharedPreferences), not in plain preferences.\n\nRate limit: 20 / minute per IP, plus 5 failures per mobile+IP then a 5-minute lockout",
                "operationId": "login",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Logged in successfully.",
                                    "data": {
                                        "otp_required": false,
                                        "token_type": "Bearer",
                                        "token": "3|pQ8rS9tU0vW1xY2zA3bC4dE5fG6hI7jK8lM9nO0",
                                        "user": {
                                            "id": 2,
                                            "name": "Maharshi Suhagiya",
                                            "mobile": "9876543210",
                                            "email": null,
                                            "is_active": true,
                                            "is_mobile_verified": true,
                                            "mobile_verified_at": "2026-10-04T12:33:00+00:00",
                                            "device_type": "android",
                                            "app_version": "1.0.0",
                                            "last_login_at": "2026-10-04T13:00:00+00:00",
                                            "created_at": "2026-10-04T12:33:00+00:00"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Wrong credentials",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": false,
                                    "message": "The mobile number or password is incorrect.",
                                    "error_code": "INVALID_CREDENTIALS"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Deactivated by admin",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": false,
                                    "message": "Your account has been deactivated. Please contact support.",
                                    "error_code": "ACCOUNT_INACTIVE"
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "mobile",
                                    "password"
                                ],
                                "properties": {
                                    "mobile": {
                                        "type": "string",
                                        "description": "A +91 / 91 / 0 prefix is stripped automatically. Validation: required, 10 digits starting 6-9",
                                        "example": "9876543210"
                                    },
                                    "password": {
                                        "type": "string",
                                        "description": "The account password. Validation: required",
                                        "example": "Secret@123"
                                    },
                                    "fcm_token": {
                                        "type": "string",
                                        "description": "Firebase Cloud Messaging registration token for this device. Stored against the account so the admin panel can push notifications to it. Validation: max:500",
                                        "example": "fZx9K2...A1b"
                                    },
                                    "device_type": {
                                        "type": "string",
                                        "description": "Platform the request came from. Validation: in:android,ios,web",
                                        "example": "android"
                                    },
                                    "device_id": {
                                        "type": "string",
                                        "description": "Stable per-install identifier. Send the same value every time so one handset keeps one device record instead of creating duplicates. Validation: max:150",
                                        "example": "a1b2c3d4e5f6"
                                    },
                                    "device_name": {
                                        "type": "string",
                                        "description": "Human-readable device label. Also used as the access-token name. Validation: max:100",
                                        "example": "Redmi Note 13"
                                    },
                                    "app_version": {
                                        "type": "string",
                                        "description": "Installed app version. Validation: max:20",
                                        "example": "1.0.0"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/auth/resend-otp": {
            "post": {
                "tags": [
                    "Login & Session"
                ],
                "summary": "Resend OTP",
                "description": "Issues a brand-new code for an existing verification. Works for both the registration and forgot-password flows \u2014 the purpose is taken from the verification_id. The wrong-attempt counter resets and the expiry window restarts.\n\nParameters:\n- verification_id (uuid, required) \u2014 The verification to re-send for. Rules: required, uuid\n\nNotes:\n- Drive the resend button countdown from data.resend_available_in_seconds, and from retry_after on a 429.\n- When resend_attempts_left hits 0 the user must restart the flow.\n\nRate limit: 10 / minute per IP, plus the configured cooldown per verification",
                "operationId": "resendOtp",
                "responses": {
                    "200": {
                        "description": "Resent",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "A new OTP has been sent to your mobile number.",
                                    "data": {
                                        "verification_id": "9f2c1a84-7b3e-4d51-9c8a-1e2f3a4b5c6d",
                                        "mobile": "9876543210",
                                        "purpose": "registration",
                                        "otp_length": 6,
                                        "expires_at": "2026-10-04T12:50:00+00:00",
                                        "expires_in_seconds": 600,
                                        "resend_available_in_seconds": 60,
                                        "resend_attempts_left": 2
                                    }
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too soon",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": false,
                                    "message": "Please wait 43 second(s) before requesting another OTP.",
                                    "error_code": "OTP_COOLDOWN",
                                    "retry_after": 43
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "verification_id"
                                ],
                                "properties": {
                                    "verification_id": {
                                        "type": "string",
                                        "description": "The verification to re-send for. Validation: required, uuid",
                                        "example": "9f2c1a84-7b3e-4d51-9c8a-1e2f3a4b5c6d"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/auth/logout": {
            "post": {
                "tags": [
                    "Login & Session"
                ],
                "summary": "Logout",
                "description": "Revokes only the token used to make the call. Other devices stay signed in.\n\nParameters:\n- device_id (string, optional) \u2014 Send the same device_id you used at login to also clear that device's FCM token, so it stops receiving push notifications. Rules: nullable, max:150\n\nRate limit: 120 / minute",
                "operationId": "logout",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Logged out successfully."
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "device_id": {
                                        "type": "string",
                                        "description": "Send the same device_id you used at login to also clear that device's FCM token, so it stops receiving push notifications. Validation: nullable, max:150",
                                        "example": "a1b2c3d4e5f6"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/auth/logout-all": {
            "post": {
                "tags": [
                    "Login & Session"
                ],
                "summary": "Logout All Devices",
                "description": "Revokes every token for the account and clears all stored FCM tokens.\n\nRate limit: 120 / minute",
                "operationId": "logoutAll",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Logged out from all devices."
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            }
        },
        "/api/v1/auth/forgot-password/availability": {
            "get": {
                "tags": [
                    "Password Reset"
                ],
                "summary": "Forgot Password Availability",
                "description": "A tiny endpoint for deciding whether to render the \u201cForgot password?\u201d link, if you would rather not cache the whole /app-config payload. Always answers 200.\n\nRate limit: 60 / minute",
                "operationId": "forgotAvailability",
                "responses": {
                    "200": {
                        "description": "OTP OFF",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Password reset is disabled.",
                                    "data": {
                                        "available": false,
                                        "reason": "OTP verification is disabled by the administrator."
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/auth/forgot-password": {
            "post": {
                "tags": [
                    "Password Reset"
                ],
                "summary": "Forgot Password \u2014 Send OTP",
                "description": "Step 1 of 3. Sends a reset OTP to a registered mobile number.\n\nOTP ON: An OTP is sent and a verification_id is returned.\nOTP OFF: Returns 403 OTP_DISABLED without sending anything. There is no alternative recovery channel \u2014 an admin must reset the password from the panel.\n\nParameters:\n- mobile (string, required) \u2014 Must belong to an existing, active account. Rules: required, 10 digits starting 6-9\n\nNotes:\n- This endpoint does confirm whether a number is registered, which the OTP flow unavoidably reveals. It is rate limited to 10 requests per minute per IP to blunt enumeration.\n\nRate limit: 10 / minute per IP",
                "operationId": "forgotPassword",
                "responses": {
                    "200": {
                        "description": "OTP sent",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "An OTP has been sent to your registered mobile number.",
                                    "data": {
                                        "otp_required": true,
                                        "verification_id": "4c7d2e91-8a5b-4f63-b2d1-7e8f9a0b1c2d",
                                        "mobile": "9876543210",
                                        "purpose": "forgot_password",
                                        "otp_length": 6,
                                        "expires_at": "2026-10-04T13:10:00+00:00",
                                        "expires_in_seconds": 600,
                                        "resend_available_in_seconds": 60,
                                        "resend_attempts_left": 3
                                    }
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "OTP switched off",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": false,
                                    "message": "Password reset is currently unavailable. Please contact support for help.",
                                    "error_code": "OTP_DISABLED"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not registered",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": false,
                                    "message": "No account is registered with this mobile number.",
                                    "error_code": "USER_NOT_FOUND"
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "mobile"
                                ],
                                "properties": {
                                    "mobile": {
                                        "type": "string",
                                        "description": "Must belong to an existing, active account. Validation: required, 10 digits starting 6-9",
                                        "example": "9876543210"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/auth/forgot-password/verify-otp": {
            "post": {
                "tags": [
                    "Password Reset"
                ],
                "summary": "Forgot Password \u2014 Verify OTP",
                "description": "Step 2 of 3. On success returns a single-use reset_token valid for 15 minutes. The password is not changed yet.\n\nParameters:\n- verification_id (uuid, required) \u2014 From step 1. Rules: required, uuid\n- otp (string, required) \u2014 The code from the SMS. Rules: required, numeric, 4-8 digits\n\nNotes:\n- reset_token is always 64 characters. Keep it in memory only \u2014 do not persist it.\n\nRate limit: 20 / minute per IP",
                "operationId": "forgotVerifyOtp",
                "responses": {
                    "200": {
                        "description": "Verified",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "OTP verified. Use the reset_token to set a new password.",
                                    "data": {
                                        "verified": true,
                                        "reset_token": "KxP9mQ2rT5vY8zA1bC4dE7fG0hJ3kL6nO9pR2sU5wX8yZ1aB4cD7eF0gH3iJ6kM9",
                                        "reset_token_expires_in_seconds": 900
                                    }
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "verification_id",
                                    "otp"
                                ],
                                "properties": {
                                    "verification_id": {
                                        "type": "string",
                                        "description": "From step 1. Validation: required, uuid",
                                        "example": "4c7d2e91-8a5b-4f63-b2d1-7e8f9a0b1c2d"
                                    },
                                    "otp": {
                                        "type": "string",
                                        "description": "The code from the SMS. Validation: required, numeric, 4-8 digits",
                                        "example": "728140"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/auth/reset-password": {
            "post": {
                "tags": [
                    "Password Reset"
                ],
                "summary": "Reset Password",
                "description": "Step 3 of 3. Consumes the reset_token, sets the new password and revokes every existing token for the account, so all devices are signed out. The user then logs in again.\n\nParameters:\n- reset_token (string, required) \u2014 From step 2. Single use. Rules: required, exactly 64 characters\n- password (string, required) \u2014 The new password. Rules: required, min:8, confirmed\n- password_confirmation (string, required) \u2014 Confirmation field. Rules: required, must equal password\n\nRate limit: 10 / minute per IP",
                "operationId": "resetPassword",
                "responses": {
                    "200": {
                        "description": "Password reset",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Your password has been reset successfully. Please log in again."
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Token already used",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": false,
                                    "message": "This reset session is invalid or has already been used.",
                                    "error_code": "RESET_TOKEN_INVALID"
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "reset_token",
                                    "password",
                                    "password_confirmation"
                                ],
                                "properties": {
                                    "reset_token": {
                                        "type": "string",
                                        "description": "From step 2. Single use. Validation: required, exactly 64 characters",
                                        "example": "KxP9mQ2rT5vY8zA1bC4dE7fG0hJ3kL6nO9pR2sU5wX8yZ1aB4cD7eF0gH3iJ6kM9"
                                    },
                                    "password": {
                                        "type": "string",
                                        "description": "The new password. Validation: required, min:8, confirmed",
                                        "example": "NewSecret@456"
                                    },
                                    "password_confirmation": {
                                        "type": "string",
                                        "description": "Confirmation field. Validation: required, must equal password",
                                        "example": "NewSecret@456"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/auth/change-password": {
            "post": {
                "tags": [
                    "Password Reset"
                ],
                "summary": "Change Password",
                "description": "For a signed-in user who knows their current password. Available even when OTP is switched off. Other devices are signed out; the calling token keeps working.\n\nParameters:\n- current_password (string, required) \u2014 Verified against the authenticated account. Rules: required, must match the stored password\n- password (string, required) \u2014 The new password. Rules: required, min:8, confirmed, different from current_password\n- password_confirmation (string, required) \u2014 Confirmation field. Rules: required, must equal password\n\nRate limit: 120 / minute",
                "operationId": "changePassword",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Password changed successfully."
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "current_password",
                                    "password",
                                    "password_confirmation"
                                ],
                                "properties": {
                                    "current_password": {
                                        "type": "string",
                                        "description": "Verified against the authenticated account. Validation: required, must match the stored password",
                                        "example": "Secret@123"
                                    },
                                    "password": {
                                        "type": "string",
                                        "description": "The new password. Validation: required, min:8, confirmed, different from current_password",
                                        "example": "NewSecret@456"
                                    },
                                    "password_confirmation": {
                                        "type": "string",
                                        "description": "Confirmation field. Validation: required, must equal password",
                                        "example": "NewSecret@456"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/profile": {
            "get": {
                "tags": [
                    "Profile & Devices"
                ],
                "summary": "Get Profile",
                "description": "Returns the authenticated user. Also the cheapest way to check a stored token is still valid.\n\nRate limit: 120 / minute",
                "operationId": "profileShow",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Profile fetched successfully.",
                                    "data": {
                                        "user": {
                                            "id": 2,
                                            "name": "Maharshi Suhagiya",
                                            "mobile": "9876543210",
                                            "email": "user@example.com",
                                            "is_active": true,
                                            "is_mobile_verified": true,
                                            "mobile_verified_at": "2026-10-04T12:33:00+00:00",
                                            "device_type": "android",
                                            "app_version": "1.0.0",
                                            "last_login_at": "2026-10-04T13:00:00+00:00",
                                            "created_at": "2026-10-04T12:33:00+00:00"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            },
            "put": {
                "tags": [
                    "Profile & Devices"
                ],
                "summary": "Update Profile",
                "description": "Updates name and/or email. Both fields use `sometimes`, so send only what changed. The mobile number cannot be changed here \u2014 it is the account identity.\n\nParameters:\n- name (string, optional / sometimes) \u2014 Omit the key to leave the name unchanged. Rules: sometimes required, min:2, max:100, letters / spaces / . ' - only\n- email (string, optional / sometimes) \u2014 Send null to clear it. Changing it resets email_verified_at. Rules: sometimes, nullable, valid email, max:150, unique:users\n\nRate limit: 120 / minute",
                "operationId": "profileUpdate",
                "responses": {
                    "200": {
                        "description": "Updated",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Profile updated successfully.",
                                    "data": {
                                        "user": {
                                            "id": 2,
                                            "name": "Maharshi S.",
                                            "mobile": "9876543210",
                                            "email": "new@example.com",
                                            "is_active": true,
                                            "is_mobile_verified": true
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "Omit the key to leave the name unchanged. Validation: sometimes required, min:2, max:100, letters / spaces / . ' - only",
                                        "example": "Maharshi S."
                                    },
                                    "email": {
                                        "type": "string",
                                        "description": "Send null to clear it. Changing it resets email_verified_at. Validation: sometimes, nullable, valid email, max:150, unique:users",
                                        "example": "new@example.com"
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "delete": {
                "tags": [
                    "Profile & Devices"
                ],
                "summary": "Delete Account",
                "description": "Soft-deletes the account, revokes all tokens and removes every device record. An admin can still see and restore it, and the mobile number stays reserved.\n\nParameters:\n- password (string, required) \u2014 Confirms intent. Rules: required, must match the stored password\n\nRate limit: 120 / minute",
                "operationId": "profileDelete",
                "responses": {
                    "200": {
                        "description": "Deleted",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Your account has been deleted."
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "password"
                                ],
                                "properties": {
                                    "password": {
                                        "type": "string",
                                        "description": "Confirms intent. Validation: required, must match the stored password",
                                        "example": "Secret@123"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/profile/fcm-token": {
            "post": {
                "tags": [
                    "Profile & Devices"
                ],
                "summary": "Update FCM Token",
                "description": "Call this from Firebase's onNewToken / onTokenRefresh callback. Tokens are keyed by device_id, so one handset keeps one record. If the same token turns up on another account it is detached from the old one, which handles a device changing hands.\n\nParameters:\n- fcm_token (string, required) \u2014 The fresh registration token. Rules: required, max:500\n- device_type (string, optional) \u2014 Platform. Rules: nullable, in:android,ios,web\n- device_id (string, optional) \u2014 Strongly recommended \u2014 without it every refresh creates a new device row. Rules: nullable, max:150\n- device_name (string, optional) \u2014 Device label. Rules: nullable, max:100\n- app_version (string, optional) \u2014 App version. Rules: nullable, max:20\n- notifications_enabled (boolean, optional) \u2014 Set false when the user turns notifications off in-app; the server then skips this device. Rules: nullable, boolean\n\nRate limit: 120 / minute",
                "operationId": "fcmToken",
                "responses": {
                    "200": {
                        "description": "Saved",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "FCM token saved successfully.",
                                    "data": {
                                        "device_id": "a1b2c3d4e5f6",
                                        "notifications_enabled": true
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "fcm_token"
                                ],
                                "properties": {
                                    "fcm_token": {
                                        "type": "string",
                                        "description": "The fresh registration token. Validation: required, max:500",
                                        "example": "fZx9K2...A1b"
                                    },
                                    "device_type": {
                                        "type": "string",
                                        "description": "Platform. Validation: nullable, in:android,ios,web",
                                        "example": "android"
                                    },
                                    "device_id": {
                                        "type": "string",
                                        "description": "Strongly recommended \u2014 without it every refresh creates a new device row. Validation: nullable, max:150",
                                        "example": "a1b2c3d4e5f6"
                                    },
                                    "device_name": {
                                        "type": "string",
                                        "description": "Device label. Validation: nullable, max:100",
                                        "example": "Redmi Note 13"
                                    },
                                    "app_version": {
                                        "type": "string",
                                        "description": "App version. Validation: nullable, max:20",
                                        "example": "1.0.1"
                                    },
                                    "notifications_enabled": {
                                        "type": "boolean",
                                        "description": "Set false when the user turns notifications off in-app; the server then skips this device. Validation: nullable, boolean",
                                        "example": true
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/notifications": {
            "get": {
                "tags": [
                    "Notifications"
                ],
                "summary": "List Notifications",
                "description": "Paginated inbox, newest first.\n\nParameters:\n- page (integer, optional) \u2014 Page number. Query string. Rules: nullable, integer, min:1\n- per_page (integer, optional) \u2014 Items per page. Defaults to 20. Query string. Rules: nullable, integer, min:1, max:100\n- unread_only (boolean, optional) \u2014 Pass 1 to return only unread items. Query string. Rules: nullable, boolean\n\nRate limit: 120 / minute",
                "operationId": "notificationsIndex",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Notifications fetched successfully.",
                                    "data": {
                                        "notifications": [
                                            {
                                                "id": 12,
                                                "title": "Result declared",
                                                "body": "Today's result is now available in the app.",
                                                "image_url": null,
                                                "data": {
                                                    "screen": "results"
                                                },
                                                "is_read": false,
                                                "read_at": null,
                                                "created_at": "2026-10-04T13:20:00+00:00"
                                            }
                                        ],
                                        "unread_count": 3,
                                        "pagination": {
                                            "current_page": 1,
                                            "per_page": 20,
                                            "total": 23,
                                            "last_page": 2,
                                            "has_more": true
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "description": "optional. Page number. Query string. Rules: nullable, integer, min:1",
                        "schema": {
                            "type": "integer",
                            "example": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "optional. Items per page. Defaults to 20. Query string. Rules: nullable, integer, min:1, max:100",
                        "schema": {
                            "type": "integer",
                            "example": 20
                        }
                    },
                    {
                        "name": "unread_only",
                        "in": "query",
                        "required": false,
                        "description": "optional. Pass 1 to return only unread items. Query string. Rules: nullable, boolean",
                        "schema": {
                            "type": "boolean",
                            "example": 0
                        }
                    }
                ]
            }
        },
        "/api/v1/notifications/unread-count": {
            "get": {
                "tags": [
                    "Notifications"
                ],
                "summary": "Unread Count",
                "description": "Just the badge number \u2014 cheap enough to poll.\n\nRate limit: 120 / minute",
                "operationId": "notificationsUnreadCount",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Unread count fetched successfully.",
                                    "data": {
                                        "unread_count": 3
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            }
        },
        "/api/v1/notifications/{id}/read": {
            "post": {
                "tags": [
                    "Notifications"
                ],
                "summary": "Mark One As Read",
                "description": "Marks a single notification read. 404 if the id belongs to another user.\n\nParameters:\n- id (integer, required) \u2014 Path parameter \u2014 the notification id. Rules: required, integer, must belong to the authenticated user\n\nRate limit: 120 / minute",
                "operationId": "notificationsRead",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Notification marked as read.",
                                    "data": {
                                        "notification": {
                                            "id": 12,
                                            "title": "Result declared",
                                            "is_read": true,
                                            "read_at": "2026-10-04T13:25:00+00:00"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "required. Path parameter \u2014 the notification id. Rules: required, integer, must belong to the authenticated user",
                        "schema": {
                            "type": "integer",
                            "example": 12
                        }
                    }
                ]
            }
        },
        "/api/v1/notifications/read-all": {
            "post": {
                "tags": [
                    "Notifications"
                ],
                "summary": "Mark All As Read",
                "description": "Clears the whole unread badge in one call.\n\nRate limit: 120 / minute",
                "operationId": "notificationsReadAll",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "All notifications marked as read.",
                                    "data": {
                                        "marked_count": 3
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ]
            }
        },
        "/api/v1/notifications/{id}": {
            "delete": {
                "tags": [
                    "Notifications"
                ],
                "summary": "Delete Notification",
                "description": "Removes one notification from this user's inbox.\n\nParameters:\n- id (integer, required) \u2014 Path parameter \u2014 the notification id. Rules: required, integer, must belong to the authenticated user\n\nRate limit: 120 / minute",
                "operationId": "notificationsDelete",
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "success": true,
                                    "message": "Notification deleted successfully."
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": {}
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "required. Path parameter \u2014 the notification id. Rules: required, integer, must belong to the authenticated user",
                        "schema": {
                            "type": "integer",
                            "example": 12
                        }
                    }
                ]
            }
        }
    }
}