{
    "info": {
        "name": "Sara777 API — v1",
        "_postman_id": "c13ea42e-8c2f-4026-935b-77b62a40d7a4",
        "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.\n\n## Getting started\n1. Set the `base_url` variable (defaults to this installation's APP_URL).\n2. Send **App Config → Get App Config** and read `data.otp.enabled`.\n3. Run the matching folder top to bottom. This collection contains no scripts, so copy `verification_id`, `reset_token` and `token` out of each response and paste them into the matching collection variable before the next call.\n\n## The OTP master switch\nA single admin setting (Settings → OTP → “OTP Verification Enabled”) changes how two flows behave. Read GET /app-config at app start and branch on data.otp.enabled — never hard-code either flow.\n\n**OTP ON**\n- Register is two steps: POST /auth/register sends an SMS OTP and returns a verification_id; the account is created only by POST /auth/register/verify-otp.\n- Forgot password works: POST /auth/forgot-password → POST /auth/forgot-password/verify-otp → POST /auth/reset-password.\n- POST /auth/resend-otp is available for both flows.\n\n**OTP OFF**\n- Register is one step: POST /auth/register creates the account and returns the access token straight away. No SMS is sent and no verification_id is returned.\n- Forgot password is unavailable: every forgot-password / verify / reset endpoint answers 403 with error_code OTP_DISABLED. Hide the “Forgot password?” link.\n- POST /auth/resend-otp also answers 403 OTP_DISABLED.\n- An authenticated user can still change their password with POST /auth/change-password, since the current password is proof enough.\n\nFull documentation: https://dpbossapp.io/docs",
        "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
    },
    "auth": {
        "type": "bearer",
        "bearer": [
            {
                "key": "token",
                "value": "{{access_token}}",
                "type": "string"
            }
        ]
    },
    "variable": [
        {
            "key": "base_url",
            "value": "https://dpbossapp.io",
            "type": "string"
        },
        {
            "key": "access_token",
            "value": "",
            "type": "string"
        },
        {
            "key": "verification_id",
            "value": "",
            "type": "string"
        },
        {
            "key": "reset_token",
            "value": "",
            "type": "string"
        },
        {
            "key": "mobile",
            "value": "9876543210",
            "type": "string"
        },
        {
            "key": "password",
            "value": "Secret@123",
            "type": "string"
        },
        {
            "key": "notification_id",
            "value": "1",
            "type": "string"
        }
    ],
    "item": [
        {
            "name": "App Config",
            "description": "Call this first, on every app launch. It tells the client which auth flows are live.",
            "item": [
                {
                    "name": "Get App Config  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/app-config",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "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",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                }
            ]
        },
        {
            "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.",
            "item": [
                {
                    "name": "List Markets  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/markets?engine=main",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "markets"
                            ],
                            "query": [
                                {
                                    "key": "engine",
                                    "value": "main",
                                    "description": "optional. Which game engine to list. Defaults to main. Rules: nullable, in:main,starline,gali_disawar",
                                    "disabled": true
                                }
                            ]
                        },
                        "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) — 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",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get One Market  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/markets/{{slug}}",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "markets",
                                "{{slug}}"
                            ]
                        },
                        "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) — Path parameter -- the market slug from the list. Rules: required, must be an active market\n\nRate limit: 120 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Market Result Chart  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/markets/{{slug}}/chart?days=60",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "markets",
                                "{{slug}}",
                                "chart"
                            ],
                            "query": [
                                {
                                    "key": "days",
                                    "value": "60",
                                    "description": "optional. How far back to read. Defaults to 60. Query string. Rules: nullable, integer, min:1, max:365",
                                    "disabled": true
                                }
                            ]
                        },
                        "description": "Declared results for a market, newest first, for the chart screen.\n\nParameters:\n- slug (string, required) — Path parameter -- the market slug. Rules: required\n- days (integer, optional) — How far back to read. Defaults to 60. Query string. Rules: nullable, integer, min:1, max:365\n\nRate limit: 120 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Game Rates  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/game-rates?engine=main",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "game-rates"
                            ],
                            "query": [
                                {
                                    "key": "engine",
                                    "value": "main",
                                    "description": "optional. Limit to one engine. Omit for all of them. Rules: nullable, in:main,starline,gali_disawar",
                                    "disabled": true
                                }
                            ]
                        },
                        "description": "Bid types and their payouts, plus the stake limits the bid screen must enforce.\n\nParameters:\n- engine (string, optional) — 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",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Panna Lookup  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/panna?types[]=sp&digits[]=1",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "panna"
                            ],
                            "query": [
                                {
                                    "key": "types[]",
                                    "value": "sp",
                                    "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",
                                    "disabled": true
                                },
                                {
                                    "key": "digits[]",
                                    "value": "1",
                                    "description": "optional. Keep only pannas reducing to these digits. Rules: nullable, array, each integer 0-9",
                                    "disabled": true
                                }
                            ]
                        },
                        "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) — 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) — 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",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Digit Helper  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/panna/digits?option=odd",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "panna",
                                "digits"
                            ],
                            "query": [
                                {
                                    "key": "option",
                                    "value": "odd",
                                    "description": "optional. Defaults to all. Rules: nullable, in:odd,even,all",
                                    "disabled": true
                                }
                            ]
                        },
                        "description": "The digits 0-9 filtered to odd, even or all, for the odd/even quick-pick.\n\nParameters:\n- option (string, optional) — Defaults to all. Rules: nullable, in:odd,even,all\n\nRate limit: 120 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                }
            ]
        },
        {
            "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.",
            "item": [
                {
                    "name": "Place Bids  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/bids",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "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) — The market slug, from GET /markets. Rules: required, must be an existing market slug\n- bids (array, required) — 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) — 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) — 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) — 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) — 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",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"market\": \"kalyan\",\n    \"bids\": \"[ { ... } ]\",\n    \"bids[].game_type\": \"single_panna\",\n    \"bids[].bid_number\": \"123\",\n    \"bids[].amount\": 50,\n    \"bids[].session\": \"open\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "My Bids  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/bids?engine=main&market=kalyan&status=won&date=2026-10-19&from=2026-10-01&to=2026-10-19&per_page=25",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "bids"
                            ],
                            "query": [
                                {
                                    "key": "engine",
                                    "value": "main",
                                    "description": "optional. Limit to one engine. Rules: nullable, in:main,starline,gali_disawar",
                                    "disabled": true
                                },
                                {
                                    "key": "market",
                                    "value": "kalyan",
                                    "description": "optional. A market slug. Rules: nullable, max:60",
                                    "disabled": true
                                },
                                {
                                    "key": "status",
                                    "value": "won",
                                    "description": "optional. Limit to one status. Rules: nullable, in:pending,won,lost,refunded",
                                    "disabled": true
                                },
                                {
                                    "key": "date",
                                    "value": "2026-10-19",
                                    "description": "optional. One gaming day. Rules: nullable, date Y-m-d",
                                    "disabled": true
                                },
                                {
                                    "key": "from",
                                    "value": "2026-10-01",
                                    "description": "optional. Start of a range of gaming days. Rules: nullable, date Y-m-d",
                                    "disabled": true
                                },
                                {
                                    "key": "to",
                                    "value": "2026-10-19",
                                    "description": "optional. End of that range. Rules: nullable, date Y-m-d",
                                    "disabled": true
                                },
                                {
                                    "key": "per_page",
                                    "value": "25",
                                    "description": "optional. Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100",
                                    "disabled": true
                                }
                            ]
                        },
                        "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) — Limit to one engine. Rules: nullable, in:main,starline,gali_disawar\n- market (string, optional) — A market slug. Rules: nullable, max:60\n- status (string, optional) — Limit to one status. Rules: nullable, in:pending,won,lost,refunded\n- date (string, optional) — One gaming day. Rules: nullable, date Y-m-d\n- from (string, optional) — Start of a range of gaming days. Rules: nullable, date Y-m-d\n- to (string, optional) — End of that range. Rules: nullable, date Y-m-d\n- per_page (integer, optional) — Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100\n\nRate limit: 120 / minute"
                    },
                    "response": []
                }
            ]
        },
        {
            "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.",
            "item": [
                {
                    "name": "Wallet Balance  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/wallet",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "wallet"
                            ]
                        },
                        "description": "The current balance, the two feature flags, and how many requests are still waiting.\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Wallet History  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/wallet/transactions?type=debit&category=bid_placed&per_page=25",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "wallet",
                                "transactions"
                            ],
                            "query": [
                                {
                                    "key": "type",
                                    "value": "debit",
                                    "description": "optional. Money in or money out. Rules: nullable, in:credit,debit",
                                    "disabled": true
                                },
                                {
                                    "key": "category",
                                    "value": "bid_placed",
                                    "description": "optional. One of: signup_bonus, deposit, withdrawal, withdrawal_reversal, bid_placed, bid_won, bid_refund, admin_credit, admin_debit. Rules: nullable, max:30",
                                    "disabled": true
                                },
                                {
                                    "key": "per_page",
                                    "value": "25",
                                    "description": "optional. Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100",
                                    "disabled": true
                                }
                            ]
                        },
                        "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) — Money in or money out. Rules: nullable, in:credit,debit\n- category (string, optional) — 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) — Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Deposit Details  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/wallet/deposit-info",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "wallet",
                                "deposit-info"
                            ]
                        },
                        "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"
                    },
                    "response": []
                },
                {
                    "name": "Add Fund  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/wallet/deposit",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "wallet",
                                "deposit"
                            ]
                        },
                        "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) — How much was paid in. Rules: required, numeric, between the Minimum and Maximum Deposit settings\n- transaction_reference (string, optional) — The UTR or reference from the payment app. Rules: nullable, max:100\n- screenshot (file, optional) — 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",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"amount\": 500,\n    \"transaction_reference\": \"UPI2410166789\",\n    \"screenshot\": \"(file)\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "My Add-Fund Requests  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/wallet/deposits?status=pending&per_page=25",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "wallet",
                                "deposits"
                            ],
                            "query": [
                                {
                                    "key": "status",
                                    "value": "pending",
                                    "description": "optional. Limit to one status. Rules: nullable, in:pending,approved,rejected",
                                    "disabled": true
                                },
                                {
                                    "key": "per_page",
                                    "value": "25",
                                    "description": "optional. Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100",
                                    "disabled": true
                                }
                            ]
                        },
                        "description": "The player's own add-fund requests, newest first.\n\nParameters:\n- status (string, optional) — Limit to one status. Rules: nullable, in:pending,approved,rejected\n- per_page (integer, optional) — Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Withdrawal Rules  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/wallet/withdraw-info",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "wallet",
                                "withdraw-info"
                            ]
                        },
                        "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"
                    },
                    "response": []
                },
                {
                    "name": "Withdraw  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/wallet/withdraw",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "wallet",
                                "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) — How much to withdraw. Rules: required, numeric, at least the Minimum Withdrawal setting, within the daily cap\n- method (string, required) — How to be paid. Rules: required, in:bank,upi,phonepe,googlepay,paytm\n- bank_account_id (integer, optional / sometimes) — 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) — The number or UPI ID to pay on. Rules: required for every method other than bank, max:50\n- user_remark (string, optional) — A note for the admin. Rules: nullable, max:255\n\nRate limit: 10 / minute",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"amount\": 2000,\n    \"method\": \"phonepe\",\n    \"bank_account_id\": 3,\n    \"payout_handle\": \"9876543210\",\n    \"user_remark\": \"Please pay by evening\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "My Withdrawals  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/wallet/withdrawals?status=pending&per_page=25",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "wallet",
                                "withdrawals"
                            ],
                            "query": [
                                {
                                    "key": "status",
                                    "value": "pending",
                                    "description": "optional. Limit to one status. Rules: nullable, in:pending,approved,rejected,paid",
                                    "disabled": true
                                },
                                {
                                    "key": "per_page",
                                    "value": "25",
                                    "description": "optional. Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100",
                                    "disabled": true
                                }
                            ]
                        },
                        "description": "The player's own withdrawal requests, newest first. `paid` means the admin has sent the money.\n\nParameters:\n- status (string, optional) — Limit to one status. Rules: nullable, in:pending,approved,rejected,paid\n- per_page (integer, optional) — Page size. Defaults to 25. Rules: nullable, integer, min:1, max:100\n\nRate limit: 120 / minute"
                    },
                    "response": []
                }
            ]
        },
        {
            "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.",
            "item": [
                {
                    "name": "List Bank Accounts  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/bank-accounts",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "bank-accounts"
                            ]
                        },
                        "description": "All saved accounts, the primary one first.\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Add Bank Account  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/bank-accounts",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "bank-accounts"
                            ]
                        },
                        "description": "Save an account. The same account number cannot be saved twice on one profile.\n\nParameters:\n- account_holder (string, required) — Name as it appears on the account. Rules: required, max:100\n- account_number (string, required) — The account number. Rules: required, digits only, min:6, max:30, unique per player\n- ifsc_code (string, required) — IFSC of the branch. Upper-cased automatically. Rules: required, 4 letters + 0 + 6 alphanumerics\n- bank_name (string, required) — Name of the bank. Rules: required, max:100\n- branch (string, optional) — Branch name. Rules: nullable, max:100\n- is_primary (boolean, optional) — Make this the default for payouts. The first account saved becomes primary whatever this says. Rules: nullable, boolean\n\nRate limit: 120 / minute",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"account_holder\": \"Ramesh Patel\",\n    \"account_number\": \"123456789012\",\n    \"ifsc_code\": \"HDFC0001234\",\n    \"bank_name\": \"HDFC Bank\",\n    \"branch\": \"Navrangpura\",\n    \"is_primary\": true\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Update Bank Account  [PUT]",
                    "request": {
                        "method": "PUT",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/bank-accounts/{{notification_id}}",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "bank-accounts",
                                "{{notification_id}}"
                            ]
                        },
                        "description": "Change a saved account. Takes the same fields as adding one.\n\nParameters:\n- id (integer, required) — Path parameter -- the account id. Rules: required, must belong to the signed-in player\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Set Primary Account  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/bank-accounts/{{notification_id}}/primary",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "bank-accounts",
                                "{{notification_id}}",
                                "primary"
                            ]
                        },
                        "description": "Make this the account payouts default to. Whichever was primary stops being so.\n\nParameters:\n- id (integer, required) — Path parameter -- the account id. Rules: required, must belong to the signed-in player\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Remove Bank Account  [DELETE]",
                    "request": {
                        "method": "DELETE",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/bank-accounts/{{notification_id}}",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "bank-accounts",
                                "{{notification_id}}"
                            ]
                        },
                        "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) — Path parameter -- the account id. Rules: required, must belong to the signed-in player\n\nRate limit: 120 / minute"
                    },
                    "response": []
                }
            ]
        },
        {
            "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.",
            "item": [
                {
                    "name": "Home Content  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/content?placement=home",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "content"
                            ],
                            "query": [
                                {
                                    "key": "placement",
                                    "value": "home",
                                    "description": "optional. Which screen the notices are for. Defaults to home. Rules: nullable, in:home,app,add_fund,withdraw",
                                    "disabled": true
                                }
                            ]
                        },
                        "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) — Which screen the notices are for. Defaults to home. Rules: nullable, in:home,app,add_fund,withdraw\n\nRate limit: 120 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Notices  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/content/notices?placement=add_fund",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "content",
                                "notices"
                            ],
                            "query": [
                                {
                                    "key": "placement",
                                    "value": "add_fund",
                                    "description": "optional. Which screen the notice belongs above. Rules: nullable, in:home,app,add_fund,withdraw",
                                    "disabled": true
                                }
                            ]
                        },
                        "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) — Which screen the notice belongs above. Rules: nullable, in:home,app,add_fund,withdraw\n\nRate limit: 120 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Banners  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/content/sliders",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "content",
                                "sliders"
                            ]
                        },
                        "description": "The home screen banners, in order. Each carries a ready-made `image_url`.\n\nRate limit: 120 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Banner Image  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/content/sliders/{{slider}}/image",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "content",
                                "sliders",
                                "{{slider}}",
                                "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) — Path parameter -- the banner id. Rules: required, must be a live banner\n\nRate limit: 120 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Information Page  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/content/pages/{{page}}",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "content",
                                "pages",
                                "{{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) — Path parameter -- which screen. Rules: required, one of: terms, how-to-play, about\n\nRate limit: 120 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Contact Details  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/content/contact",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "content",
                                "contact"
                            ]
                        },
                        "description": "How players reach support. Any of these may be null -- hide the button when it is.\n\nRate limit: 120 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                }
            ]
        },
        {
            "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.",
            "item": [
                {
                    "name": "Send a Message  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/enquiries",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "enquiries"
                            ]
                        },
                        "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) — What the player wants to say. Rules: required, min:5, max:2000\n- name (string, optional) — Defaults to the name on the account. Rules: nullable, max:100\n- mobile (string, optional) — Defaults to the mobile on the account. Rules: nullable, max:15\n\nRate limit: 5 / minute",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"message\": \"My withdrawal is still pending.\",\n    \"name\": \"Ramesh Patel\",\n    \"mobile\": \"{{mobile}}\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "My Messages  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/enquiries",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "enquiries"
                            ]
                        },
                        "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"
                    },
                    "response": []
                }
            ]
        },
        {
            "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.",
            "item": [
                {
                    "name": "Chat Availability  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/chat/config",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "chat",
                                "config"
                            ]
                        },
                        "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"
                    },
                    "response": []
                },
                {
                    "name": "Chat Token  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/chat/token",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "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"
                    },
                    "response": []
                }
            ]
        },
        {
            "name": "Registration",
            "description": "Account creation. One step or two, depending on the OTP switch.",
            "item": [
                {
                    "name": "Register  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/register",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "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) — Full name. Digits and other symbols are rejected. Rules: required, min:2, max:100, letters / spaces / . ' - only\n- mobile (string, required) — 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) — Optional. Send null or omit the key entirely. Rules: nullable, valid email, max:150, unique:users\n- password (string, required) — In production also requires letters + numbers and is checked against the HaveIBeenPwned breach list. Rules: required, min:8, confirmed\n- password_confirmation (string, required) — Required because of the `confirmed` rule on password. Rules: required, must equal password\n- fcm_token (string, optional) — 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) — Platform the request came from. Rules: in:android,ios,web\n- device_id (string, optional) — 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) — Human-readable device label. Also used as the access-token name. Rules: max:100\n- app_version (string, optional) — 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 — 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",
                        "auth": {
                            "type": "noauth"
                        },
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"name\": \"Maharshi Suhagiya\",\n    \"mobile\": \"{{mobile}}\",\n    \"email\": \"user@example.com\",\n    \"password\": \"{{password}}\",\n    \"password_confirmation\": \"{{password}}\",\n    \"fcm_token\": \"fZx9K2...A1b\",\n    \"device_type\": \"android\",\n    \"device_id\": \"a1b2c3d4e5f6\",\n    \"device_name\": \"Redmi Note 13\",\n    \"app_version\": \"1.0.0\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Verify Registration OTP  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/register/verify-otp",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "register",
                                "verify-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) — The value returned by /auth/register. Rules: required, uuid\n- otp (string, required) — 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) — 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) — Platform the request came from. Rules: in:android,ios,web\n- device_id (string, optional) — 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) — Human-readable device label. Also used as the access-token name. Rules: max:100\n- app_version (string, optional) — 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 — restart from /auth/register.\n\nRate limit: 20 / minute per IP",
                        "auth": {
                            "type": "noauth"
                        },
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"verification_id\": \"{{verification_id}}\",\n    \"otp\": \"483921\",\n    \"fcm_token\": \"fZx9K2...A1b\",\n    \"device_type\": \"android\",\n    \"device_id\": \"a1b2c3d4e5f6\",\n    \"device_name\": \"Redmi Note 13\",\n    \"app_version\": \"1.0.0\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                }
            ]
        },
        {
            "name": "Login & Session",
            "description": "Password login and token lifecycle. Unaffected by the OTP switch.",
            "item": [
                {
                    "name": "Login  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/login",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "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) — A +91 / 91 / 0 prefix is stripped automatically. Rules: required, 10 digits starting 6-9\n- password (string, required) — The account password. Rules: required\n- fcm_token (string, optional) — 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) — Platform the request came from. Rules: in:android,ios,web\n- device_id (string, optional) — 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) — Human-readable device label. Also used as the access-token name. Rules: max:100\n- app_version (string, optional) — Installed app version. Rules: max:20\n\nNotes:\n- Logging in does not revoke tokens on other devices — 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",
                        "auth": {
                            "type": "noauth"
                        },
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"mobile\": \"{{mobile}}\",\n    \"password\": \"{{password}}\",\n    \"fcm_token\": \"fZx9K2...A1b\",\n    \"device_type\": \"android\",\n    \"device_id\": \"a1b2c3d4e5f6\",\n    \"device_name\": \"Redmi Note 13\",\n    \"app_version\": \"1.0.0\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Resend OTP  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/resend-otp",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "resend-otp"
                            ]
                        },
                        "description": "Issues a brand-new code for an existing verification. Works for both the registration and forgot-password flows — 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) — 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",
                        "auth": {
                            "type": "noauth"
                        },
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"verification_id\": \"{{verification_id}}\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Logout  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/logout",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "logout"
                            ]
                        },
                        "description": "Revokes only the token used to make the call. Other devices stay signed in.\n\nParameters:\n- device_id (string, optional) — 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",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"device_id\": \"a1b2c3d4e5f6\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Logout All Devices  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/logout-all",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "logout-all"
                            ]
                        },
                        "description": "Revokes every token for the account and clears all stored FCM tokens.\n\nRate limit: 120 / minute"
                    },
                    "response": []
                }
            ]
        },
        {
            "name": "Password Reset",
            "description": "OTP-only recovery. Entirely gated by the admin OTP switch — check /app-config first.",
            "item": [
                {
                    "name": "Forgot Password Availability  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/forgot-password/availability",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "forgot-password",
                                "availability"
                            ]
                        },
                        "description": "A tiny endpoint for deciding whether to render the “Forgot password?” link, if you would rather not cache the whole /app-config payload. Always answers 200.\n\nRate limit: 60 / minute",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": []
                },
                {
                    "name": "Forgot Password — Send OTP  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/forgot-password",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "forgot-password"
                            ]
                        },
                        "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 — an admin must reset the password from the panel.\n\nParameters:\n- mobile (string, required) — 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",
                        "auth": {
                            "type": "noauth"
                        },
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"mobile\": \"{{mobile}}\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Forgot Password — Verify OTP  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/forgot-password/verify-otp",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "forgot-password",
                                "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) — From step 1. Rules: required, uuid\n- otp (string, required) — 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 — do not persist it.\n\nRate limit: 20 / minute per IP",
                        "auth": {
                            "type": "noauth"
                        },
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"verification_id\": \"{{verification_id}}\",\n    \"otp\": \"728140\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Reset Password  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/reset-password",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "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) — From step 2. Single use. Rules: required, exactly 64 characters\n- password (string, required) — The new password. Rules: required, min:8, confirmed\n- password_confirmation (string, required) — Confirmation field. Rules: required, must equal password\n\nRate limit: 10 / minute per IP",
                        "auth": {
                            "type": "noauth"
                        },
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"reset_token\": \"{{reset_token}}\",\n    \"password\": \"NewSecret@456\",\n    \"password_confirmation\": \"NewSecret@456\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Change Password  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/auth/change-password",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "auth",
                                "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) — Verified against the authenticated account. Rules: required, must match the stored password\n- password (string, required) — The new password. Rules: required, min:8, confirmed, different from current_password\n- password_confirmation (string, required) — Confirmation field. Rules: required, must equal password\n\nRate limit: 120 / minute",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"current_password\": \"{{password}}\",\n    \"password\": \"NewSecret@456\",\n    \"password_confirmation\": \"NewSecret@456\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                }
            ]
        },
        {
            "name": "Profile & Devices",
            "description": "Authenticated account management. Send Authorization: Bearer {token} on every call.",
            "item": [
                {
                    "name": "Get Profile  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/profile",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "profile"
                            ]
                        },
                        "description": "Returns the authenticated user. Also the cheapest way to check a stored token is still valid.\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Update Profile  [PUT]",
                    "request": {
                        "method": "PUT",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/profile",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "profile"
                            ]
                        },
                        "description": "Updates name and/or email. Both fields use `sometimes`, so send only what changed. The mobile number cannot be changed here — it is the account identity.\n\nParameters:\n- name (string, optional / sometimes) — Omit the key to leave the name unchanged. Rules: sometimes required, min:2, max:100, letters / spaces / . ' - only\n- email (string, optional / sometimes) — 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",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"name\": \"Maharshi S.\",\n    \"email\": \"new@example.com\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Update FCM Token  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/profile/fcm-token",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "profile",
                                "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) — The fresh registration token. Rules: required, max:500\n- device_type (string, optional) — Platform. Rules: nullable, in:android,ios,web\n- device_id (string, optional) — Strongly recommended — without it every refresh creates a new device row. Rules: nullable, max:150\n- device_name (string, optional) — Device label. Rules: nullable, max:100\n- app_version (string, optional) — App version. Rules: nullable, max:20\n- notifications_enabled (boolean, optional) — Set false when the user turns notifications off in-app; the server then skips this device. Rules: nullable, boolean\n\nRate limit: 120 / minute",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"fcm_token\": \"fZx9K2...A1b\",\n    \"device_type\": \"android\",\n    \"device_id\": \"a1b2c3d4e5f6\",\n    \"device_name\": \"Redmi Note 13\",\n    \"app_version\": \"1.0.1\",\n    \"notifications_enabled\": true\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                },
                {
                    "name": "Delete Account  [DELETE]",
                    "request": {
                        "method": "DELETE",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/profile",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "profile"
                            ]
                        },
                        "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) — Confirms intent. Rules: required, must match the stored password\n\nRate limit: 120 / minute",
                        "body": {
                            "mode": "raw",
                            "raw": "{\n    \"password\": \"{{password}}\"\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        }
                    },
                    "response": []
                }
            ]
        },
        {
            "name": "Notifications",
            "description": "The in-app inbox. Mirrors what the admin panel pushed, so a missed push is still readable.",
            "item": [
                {
                    "name": "List Notifications  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/notifications?page=1&per_page=20&unread_only=0",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "notifications"
                            ],
                            "query": [
                                {
                                    "key": "page",
                                    "value": "1",
                                    "description": "optional. Page number. Query string. Rules: nullable, integer, min:1",
                                    "disabled": true
                                },
                                {
                                    "key": "per_page",
                                    "value": "20",
                                    "description": "optional. Items per page. Defaults to 20. Query string. Rules: nullable, integer, min:1, max:100",
                                    "disabled": true
                                },
                                {
                                    "key": "unread_only",
                                    "value": "0",
                                    "description": "optional. Pass 1 to return only unread items. Query string. Rules: nullable, boolean",
                                    "disabled": true
                                }
                            ]
                        },
                        "description": "Paginated inbox, newest first.\n\nParameters:\n- page (integer, optional) — Page number. Query string. Rules: nullable, integer, min:1\n- per_page (integer, optional) — Items per page. Defaults to 20. Query string. Rules: nullable, integer, min:1, max:100\n- unread_only (boolean, optional) — Pass 1 to return only unread items. Query string. Rules: nullable, boolean\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Unread Count  [GET]",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/notifications/unread-count",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "notifications",
                                "unread-count"
                            ]
                        },
                        "description": "Just the badge number — cheap enough to poll.\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Mark One As Read  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/notifications/{{notification_id}}/read",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "notifications",
                                "{{notification_id}}",
                                "read"
                            ]
                        },
                        "description": "Marks a single notification read. 404 if the id belongs to another user.\n\nParameters:\n- id (integer, required) — Path parameter — the notification id. Rules: required, integer, must belong to the authenticated user\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Mark All As Read  [POST]",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/notifications/read-all",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "notifications",
                                "read-all"
                            ]
                        },
                        "description": "Clears the whole unread badge in one call.\n\nRate limit: 120 / minute"
                    },
                    "response": []
                },
                {
                    "name": "Delete Notification  [DELETE]",
                    "request": {
                        "method": "DELETE",
                        "header": [
                            {
                                "key": "Accept",
                                "value": "application/json"
                            },
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "url": {
                            "raw": "{{base_url}}/api/v1/notifications/{{notification_id}}",
                            "host": [
                                "{{base_url}}"
                            ],
                            "path": [
                                "api",
                                "v1",
                                "notifications",
                                "{{notification_id}}"
                            ]
                        },
                        "description": "Removes one notification from this user's inbox.\n\nParameters:\n- id (integer, required) — Path parameter — the notification id. Rules: required, integer, must belong to the authenticated user\n\nRate limit: 120 / minute"
                    },
                    "response": []
                }
            ]
        }
    ]
}