{
  "info": {
    "name": "Astrology API — Consultation & Settlement",
    "description": "Finance settlement flow end to end.\n\nMoney path: customer recharges -> starts a chat/audio/video session -> tick keeps checking the balance -> end bills the customer, takes the platform fee and parks the astrologer's earning at settlementStatus = PENDING -> an admin reviews it and approves to TO_BE_SETTLED -> the settlement job credits the astrologer wallet (SETTLED) -> the astrologer withdraws.\n\nNothing is credited to the astrologer before the settlement run, so an unsettled earning can never be withdrawn.\n\nSet the collection variables first: base_url, user_token, astro_token, admin_token, admin_api_key.\n\nResponse envelopes differ by stack and are NOT a mistake:\n- user + admin: {status, statusCode, message, data}\n- astrologer: {status, message, data}",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    { "key": "base_url", "value": "http://localhost:8080", "type": "string" },
    { "key": "user_token", "value": "", "type": "string" },
    { "key": "astro_token", "value": "", "type": "string" },
    { "key": "admin_token", "value": "", "type": "string" },
    { "key": "admin_api_key", "value": "", "type": "string" },
    { "key": "consultation_id", "value": "1", "type": "string" },
    { "key": "settlement_id", "value": "1", "type": "string" },
    { "key": "astrologer_id", "value": "1", "type": "string" }
  ],
  "item": [
    {
      "name": "1. User — Consultation lifecycle",
      "description": "Auth: JWTAuthMiddleware. The token must also still be stored on users.jwt_token, so log in again after a logout.\n\nNo request here carries an amount, a rate or a trusted duration: the rate is read off the astrologer row, the fee off systemflag, and the duration off the session's own timestamps.",
      "item": [
        {
          "name": "Start consultation",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{user_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/user/consultation/start", "host": ["{{base_url}}"], "path": ["api", "user", "consultation", "start"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"astrologer_id\": 1,\n  \"medium\": \"CHAT\",\n  \"channel_name\": \"room_9981\",\n  \"name\": \"Ravi Kumar\",\n  \"birth_date\": \"1994-08-21\",\n  \"birth_time\": \"04:35\",\n  \"birth_place\": \"Jaipur\",\n  \"gender\": \"Male\"\n}"
            },
            "description": "astrologer_id (required, uint)\nmedium (required) — CHAT | AUDIO | VIDEO\nchannel_name (optional) — your SDK's room id, stored for support only\nname, birth_date (YYYY-MM-DD), birth_time, birth_place, gender (optional) — the consultee, who is often not the account holder\n\nFails with 400 when: medium invalid, astrologer missing/inactive, astrologer has no rate for that medium, balance below MinConsultationMinutes x rate, or a session is already running (a stale one is auto-closed and billed instead).\n\nmax_billable_seconds is the hard cap: billing never exceeds it, so the wallet cannot go negative if a disconnect is missed."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Consultation started\",\n  \"data\": {\n    \"consultation_id\": 41,\n    \"consultation_no\": \"CHT2609151412330007\",\n    \"astrologer_id\": 1,\n    \"astrologer_name\": \"Acharya Vinod\",\n    \"medium\": \"CHAT\",\n    \"status\": \"ONGOING\",\n    \"rate_per_minute\": 25,\n    \"wallet_balance\": 500,\n    \"max_billable_seconds\": 1200,\n    \"max_billable_minutes\": 20,\n    \"channel_name\": \"room_9981\",\n    \"started_at\": \"2026-09-15 14:12:33\",\n    \"tick_interval_seconds\": 120\n  }\n}"
            },
            {
              "name": "400 insufficient balance",
              "code": 400,
              "status": "Bad Request",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"statusCode\": 400,\n  \"message\": \"insufficient wallet balance, 50.00 required to start a chat session\"\n}"
            }
          ]
        },
        {
          "name": "Tick (balance check)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{user_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/user/consultation/tick", "host": ["{{base_url}}"], "path": ["api", "user", "consultation", "tick"] },
            "body": { "mode": "raw", "raw": "{\n  \"consultation_id\": {{consultation_id}}\n}" },
            "description": "consultation_id (required)\n\nCall this on a timer, more often than tick_interval_seconds. It bills nothing — it reports what is left and stamps lastTickAt.\n\nWhen should_disconnect is true, hang up and call /end with end_reason = INSUFFICIENT_BALANCE. If the app never does, the stale-session sweeper closes and bills the session for the same capped amount."
          },
          "response": [
            {
              "name": "200 running",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Consultation status\",\n  \"data\": {\n    \"consultation_id\": 41,\n    \"status\": \"ONGOING\",\n    \"elapsed_seconds\": 185,\n    \"remaining_seconds\": 1015,\n    \"current_charge\": 100,\n    \"wallet_balance\": 500,\n    \"should_disconnect\": false\n  }\n}"
            },
            {
              "name": "200 out of balance",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Consultation status\",\n  \"data\": {\n    \"consultation_id\": 41,\n    \"status\": \"ONGOING\",\n    \"elapsed_seconds\": 1205,\n    \"remaining_seconds\": 0,\n    \"current_charge\": 500,\n    \"wallet_balance\": 500,\n    \"should_disconnect\": true,\n    \"disconnect_reason\": \"wallet balance exhausted\"\n  }\n}"
            }
          ]
        },
        {
          "name": "End consultation (bills the session)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{user_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/user/consultation/end", "host": ["{{base_url}}"], "path": ["api", "user", "consultation", "end"] },
            "body": { "mode": "raw", "raw": "{\n  \"consultation_id\": {{consultation_id}},\n  \"end_reason\": \"USER_ENDED\"\n}" },
            "description": "consultation_id (required)\nend_reason (optional, default USER_ENDED) — USER_ENDED | ASTROLOGER_ENDED | INSUFFICIENT_BALANCE. Any other value is rejected; ABANDONED / REJECTED / CANCELLED are written by the server only.\nclient_duration_seconds (optional) — honoured only when SHORTER than what the server measured, so a client can shorten its own bill but never lengthen it.\n\nOne transaction: debit the wallet, write wallettransaction, mirror into user_chat_histories or user_call_histories, take the platform fee, and set settlementStatus = PENDING.\n\nastrologer_earning is OWED, not paid — the astrologer wallet is untouched here.\n\nIdempotent: calling it twice returns the first receipt rather than billing again. A session shorter than ConsultationGraceSeconds is not billed at all and comes back with settlement_status = NA."
          },
          "response": [
            {
              "name": "200 billed",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Consultation ended\",\n  \"data\": {\n    \"consultation_id\": 41,\n    \"consultation_no\": \"CHT2609151412330007\",\n    \"medium\": \"CHAT\",\n    \"status\": \"COMPLETED\",\n    \"end_reason\": \"USER_ENDED\",\n    \"duration_seconds\": 375,\n    \"duration\": \"06:15\",\n    \"billed_minutes\": 7,\n    \"rate_per_minute\": 25,\n    \"deducted_amount\": 175,\n    \"wallet_balance\": 325,\n    \"platform_fee\": 35,\n    \"astrologer_earning\": 140,\n    \"settlement_status\": \"PENDING\",\n    \"ended_at\": \"2026-09-15 14:18:48\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Active consultation",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{user_token}}" }],
            "url": { "raw": "{{base_url}}/api/user/consultation/active", "host": ["{{base_url}}"], "path": ["api", "user", "consultation", "active"] },
            "description": "No params. Lets an app that was killed mid-session rejoin instead of starting a second one.\n\nIf the session has gone stale (no tick for longer than ConsultationTickTimeoutSecs) it is billed and closed here, and the response reports has_active = false so the app can start fresh."
          },
          "response": [
            {
              "name": "200 has active",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Active consultation\",\n  \"data\": {\n    \"has_active\": true,\n    \"consultation_id\": 41,\n    \"consultation_no\": \"CHT2609151412330007\",\n    \"astrologer_id\": 1,\n    \"astrologer_name\": \"Acharya Vinod\",\n    \"medium\": \"CHAT\",\n    \"status\": \"ONGOING\",\n    \"channel_name\": \"room_9981\",\n    \"rate_per_minute\": 25,\n    \"elapsed_seconds\": 185,\n    \"remaining_seconds\": 1015,\n    \"current_charge\": 100,\n    \"wallet_balance\": 500,\n    \"started_at\": \"2026-09-15 14:12:33\"\n  }\n}"
            },
            {
              "name": "200 none",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Active consultation\",\n  \"data\": {\n    \"has_active\": false\n  }\n}"
            }
          ]
        },
        {
          "name": "Consultation history",
          "request": {
            "method": "POST",
            "header": [{ "key": "Authorization", "value": "Bearer {{user_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/user/consultation/history?page=1&limit=10&medium=ALL",
              "host": ["{{base_url}}"],
              "path": ["api", "user", "consultation", "history"],
              "query": [
                { "key": "page", "value": "1", "description": "default 1" },
                { "key": "limit", "value": "10", "description": "default 10" },
                { "key": "medium", "value": "ALL", "description": "CHAT | AUDIO | VIDEO | ALL" }
              ]
            },
            "description": "Filters come off the QUERY STRING even though the route is a POST — the same convention as the other paginated lists in this API. No body needed."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Consultation history\",\n  \"data\": {\n    \"items\": [\n      {\n        \"consultation_id\": 41,\n        \"consultation_no\": \"CHT2609151412330007\",\n        \"astrologer_id\": 1,\n        \"astrologer_name\": \"Acharya Vinod\",\n        \"astrologer_image\": \"uploads/astrologer/1.jpg\",\n        \"medium\": \"CHAT\",\n        \"status\": \"COMPLETED\",\n        \"duration\": \"06:15\",\n        \"billed_minutes\": 7,\n        \"rate_per_minute\": 25,\n        \"deducted_amount\": 175,\n        \"consultee_name\": \"Ravi Kumar\",\n        \"date\": \"15 Sep 2026 02:12 PM\"\n      }\n    ],\n    \"page\": 1,\n    \"limit\": 10,\n    \"total\": 1,\n    \"total_pages\": 1\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "2. Admin — Finance manager",
      "description": "Auth: AdminAuthMiddleware. Either\n  Authorization: Bearer <admin token>   (signature valid AND the user holds the admin role)\nor\n  X-ADMIN-API-KEY: <ADMIN_API_KEY>      (for the panel's scheduler; rejected unless ADMIN_API_KEY is set in .env)\n\nA key-authenticated call is recorded as SYSTEM rather than ADMIN in the settlement trail.\n\nList filters are all query-string. settlement_status = ALL excludes NA (sessions that were never billed).",
      "item": [
        {
          "name": "Finance summary",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": { "raw": "{{base_url}}/api/admin/settlement/summary", "host": ["{{base_url}}"], "path": ["api", "admin", "settlement", "summary"] },
            "description": "No params. The finance manager's header cards.\n\nastrologer_wallet_balance is money already credited but not yet withdrawn — the platform's outstanding liability.\nnext_settlement_at is blank when the schedule is a custom cron expression, which this API does not parse."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Finance summary\",\n  \"data\": {\n    \"pending_count\": 18,\n    \"pending_amount\": 4820.5,\n    \"to_be_settled_count\": 6,\n    \"to_be_settled_amount\": 1740,\n    \"on_hold_count\": 1,\n    \"on_hold_amount\": 140,\n    \"rejected_count\": 0,\n    \"rejected_amount\": 0,\n    \"settled_count\": 212,\n    \"settled_amount\": 68240,\n    \"total_gross_billed\": 93000,\n    \"total_platform_fee\": 18600,\n    \"month_gross_billed\": 12400,\n    \"month_platform_fee\": 2480,\n    \"month_settled\": 8900,\n    \"astrologer_wallet_balance\": 15320,\n    \"last_settlement_at\": \"08 Sep 2026 02:00 AM\",\n    \"next_settlement_at\": \"2026-09-21 02:00:00\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Settlement pending (review queue)",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/admin/settlement/pending?page=1&limit=10&medium=ALL&search=&from_date=&to_date=&astrologer_id=0&user_id=0&sort_by=created_at&sort_dir=desc",
              "host": ["{{base_url}}"],
              "path": ["api", "admin", "settlement", "pending"],
              "query": [
                { "key": "page", "value": "1" },
                { "key": "limit", "value": "10" },
                { "key": "medium", "value": "ALL", "description": "CHAT | AUDIO | VIDEO | ALL" },
                { "key": "search", "value": "", "description": "consultation no, customer name/mobile, astrologer name" },
                { "key": "from_date", "value": "", "description": "YYYY-MM-DD, on the session date" },
                { "key": "to_date", "value": "" },
                { "key": "astrologer_id", "value": "0", "description": "0 = all" },
                { "key": "user_id", "value": "0" },
                { "key": "sort_by", "value": "created_at", "description": "created_at | earning | gross | duration" },
                { "key": "sort_dir", "value": "desc", "description": "asc | desc" }
              ]
            },
            "description": "Billed sessions whose earning nobody has released yet. This is the screen the admin reviews before approving.\n\nEach row carries the customer's latest review of that astrologer (review_rating / review_text) so the payout can be judged without opening another screen.\n\nThe total_* fields are over every row the filter matches, not just this page."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Settlement pending consultations\",\n  \"data\": {\n    \"items\": [\n      {\n        \"consultation_id\": 41,\n        \"consultation_no\": \"CHT2609151412330007\",\n        \"user_id\": 7,\n        \"user_name\": \"Ravi Kumar\",\n        \"user_mobile\": \"9876543210\",\n        \"astrologer_id\": 1,\n        \"astrologer_name\": \"Acharya Vinod\",\n        \"medium\": \"CHAT\",\n        \"status\": \"COMPLETED\",\n        \"end_reason\": \"USER_ENDED\",\n        \"duration\": \"06:15\",\n        \"billed_minutes\": 7,\n        \"rate_per_minute\": 25,\n        \"gross_amount\": 175,\n        \"platform_fee_percent\": 20,\n        \"platform_fee_amount\": 35,\n        \"astrologer_earning\": 140,\n        \"settlement_status\": \"PENDING\",\n        \"review_rating\": 4.5,\n        \"review_text\": \"Very accurate reading\",\n        \"consultation_date\": \"15 Sep 2026 02:12 PM\"\n      }\n    ],\n    \"page\": 1,\n    \"limit\": 10,\n    \"total\": 18,\n    \"total_pages\": 2,\n    \"total_gross\": 6025.63,\n    \"total_platform_fee\": 1205.13,\n    \"total_earning\": 4820.5\n  }\n}"
            }
          ]
        },
        {
          "name": "To be settled",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/admin/settlement/to-be-settled?page=1&limit=10",
              "host": ["{{base_url}}"],
              "path": ["api", "admin", "settlement", "to-be-settled"],
              "query": [{ "key": "page", "value": "1" }, { "key": "limit", "value": "10" }]
            },
            "description": "Approved and waiting for the job — exactly what the next settlement run will pay out. Same filters and response shape as /pending."
          },
          "response": []
        },
        {
          "name": "Settled history (per consultation)",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/admin/settlement/settled?page=1&limit=10&from_date=&to_date=&astrologer_id=0",
              "host": ["{{base_url}}"],
              "path": ["api", "admin", "settlement", "settled"],
              "query": [
                { "key": "page", "value": "1" },
                { "key": "limit", "value": "10" },
                { "key": "from_date", "value": "" },
                { "key": "to_date", "value": "" },
                { "key": "astrologer_id", "value": "0" }
              ]
            },
            "description": "Every session a batch has closed. Rows carry settlement_id, settlement_no and settled_at on top of the /pending shape. Same filters and response shape otherwise."
          },
          "response": []
        },
        {
          "name": "On hold",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/admin/settlement/on-hold?page=1&limit=10",
              "host": ["{{base_url}}"],
              "path": ["api", "admin", "settlement", "on-hold"],
              "query": [{ "key": "page", "value": "1" }, { "key": "limit", "value": "10" }]
            },
            "description": "Parked rows, usually pending a dispute or a bad review. Same shape as /pending."
          },
          "response": []
        },
        {
          "name": "All consultations (media history)",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/admin/settlement/consultations?page=1&limit=10&settlement_status=ALL&medium=CHAT&search=&from_date=&to_date=&astrologer_id=0&user_id=0",
              "host": ["{{base_url}}"],
              "path": ["api", "admin", "settlement", "consultations"],
              "query": [
                { "key": "page", "value": "1" },
                { "key": "limit", "value": "10" },
                { "key": "settlement_status", "value": "ALL", "description": "PENDING | TO_BE_SETTLED | SETTLED | ON_HOLD | REJECTED | ALL" },
                { "key": "medium", "value": "CHAT", "description": "set per media history screen" },
                { "key": "search", "value": "" },
                { "key": "from_date", "value": "" },
                { "key": "to_date", "value": "" },
                { "key": "astrologer_id", "value": "0" },
                { "key": "user_id", "value": "0" }
              ]
            },
            "description": "Any status, any medium — this backs the chat, call and video media history screens, each pinned to its own medium. Same response shape as /pending."
          },
          "response": []
        },
        {
          "name": "Consultation detail (review screen)",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/admin/settlement/consultation/{{consultation_id}}",
              "host": ["{{base_url}}"],
              "path": ["api", "admin", "settlement", "consultation", "{{consultation_id}}"]
            },
            "description": "Path param: consultation id.\n\nEverything the admin needs to judge one payout: the session, the money split, the customer's review, what actually left the customer's wallet (wallet_debit), and the full settlement trail.\n\nhit_balance_cap = true means the session was cut off by the balance rather than ended by either party — the usual cause of a complaint.\n\n404 when the id does not exist."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Consultation detail\",\n  \"data\": {\n    \"consultation_id\": 41,\n    \"consultation_no\": \"CHT2609151412330007\",\n    \"user_id\": 7,\n    \"user_name\": \"Ravi Kumar\",\n    \"user_mobile\": \"9876543210\",\n    \"astrologer_id\": 1,\n    \"astrologer_name\": \"Acharya Vinod\",\n    \"medium\": \"CHAT\",\n    \"status\": \"COMPLETED\",\n    \"end_reason\": \"USER_ENDED\",\n    \"duration\": \"06:15\",\n    \"billed_minutes\": 7,\n    \"rate_per_minute\": 25,\n    \"gross_amount\": 175,\n    \"platform_fee_percent\": 20,\n    \"platform_fee_amount\": 35,\n    \"astrologer_earning\": 140,\n    \"settlement_status\": \"PENDING\",\n    \"review_rating\": 4.5,\n    \"review_text\": \"Very accurate reading\",\n    \"consultation_date\": \"15 Sep 2026 02:12 PM\",\n    \"channel_name\": \"room_9981\",\n    \"started_at\": \"15 Sep 2026 02:12 PM\",\n    \"ended_at\": \"15 Sep 2026 02:18 PM\",\n    \"duration_seconds\": 375,\n    \"billed_seconds\": 375,\n    \"max_billable_seconds\": 1200,\n    \"hit_balance_cap\": false,\n    \"consultee_name\": \"Ravi Kumar\",\n    \"consultee_birth_date\": \"1994-08-21\",\n    \"consultee_birth_time\": \"04:35\",\n    \"consultee_birth_place\": \"Jaipur\",\n    \"consultee_gender\": \"Male\",\n    \"astrologer_mobile\": \"9812345678\",\n    \"user_email\": \"ravi@example.com\",\n    \"wallet_debit\": 175,\n    \"history\": [\n      {\n        \"from_status\": \"PENDING\",\n        \"to_status\": \"TO_BE_SETTLED\",\n        \"remarks\": \"Reviewed, rating good\",\n        \"action_by\": 3,\n        \"action_source\": \"ADMIN\",\n        \"action_at\": \"16 Sep 2026 10:02 AM\"\n      }\n    ]\n  }\n}"
            }
          ]
        },
        {
          "name": "Approve (PENDING -> TO_BE_SETTLED)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settlement/approve", "host": ["{{base_url}}"], "path": ["api", "admin", "settlement", "approve"] },
            "body": { "mode": "raw", "raw": "{\n  \"consultation_ids\": [41, 42, 43],\n  \"remarks\": \"Reviewed, sessions and reviews look good\"\n}" },
            "description": "consultation_ids (required, array) — bulk by design; the pending list is reviewed with checkboxes\nremarks (optional here, REQUIRED for hold and reject)\n\nAllowed from PENDING or ON_HOLD. The action is fixed by the route, so anything in an \"action\" field is ignored.\n\nOne bad id does not fail the call: rows that cannot move come back in skipped with a reason. A SETTLED row is always skipped — the money has moved and may already be withdrawn."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Consultations approved for settlement\",\n  \"data\": {\n    \"action\": \"APPROVE\",\n    \"updated_count\": 2,\n    \"updated_ids\": [41, 42],\n    \"skipped\": [\n      {\n        \"consultation_id\": 43,\n        \"reason\": \"already settled, the amount is in the astrologer wallet\"\n      }\n    ],\n    \"total_earning\": 280\n  }\n}"
            }
          ]
        },
        {
          "name": "Hold",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settlement/hold", "host": ["{{base_url}}"], "path": ["api", "admin", "settlement", "hold"] },
            "body": { "mode": "raw", "raw": "{\n  \"consultation_ids\": [44],\n  \"remarks\": \"Customer raised a complaint, holding until resolved\"\n}" },
            "description": "Allowed from PENDING or TO_BE_SETTLED. remarks REQUIRED.\n\nA hold decides whether the ASTROLOGER is paid. It does not refund the customer — a refund is a separate wallet credit and is not part of this flow."
          },
          "response": []
        },
        {
          "name": "Reject",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settlement/reject", "host": ["{{base_url}}"], "path": ["api", "admin", "settlement", "reject"] },
            "body": { "mode": "raw", "raw": "{\n  \"consultation_ids\": [45],\n  \"remarks\": \"Astrologer disconnected immediately, earning refused\"\n}" },
            "description": "Allowed from PENDING, TO_BE_SETTLED or ON_HOLD. remarks REQUIRED. Never credited afterwards."
          },
          "response": []
        },
        {
          "name": "Revert to pending",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settlement/revert", "host": ["{{base_url}}"], "path": ["api", "admin", "settlement", "revert"] },
            "body": { "mode": "raw", "raw": "{\n  \"consultation_ids\": [44],\n  \"remarks\": \"Complaint withdrawn, back in the review queue\"\n}" },
            "description": "Allowed from TO_BE_SETTLED, ON_HOLD or REJECTED — an approval can be taken back as long as the job has not swept it yet."
          },
          "response": []
        },
        {
          "name": "Review (action in the body)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settlement/review", "host": ["{{base_url}}"], "path": ["api", "admin", "settlement", "review"] },
            "body": { "mode": "raw", "raw": "{\n  \"consultation_ids\": [41, 42],\n  \"action\": \"APPROVE\",\n  \"remarks\": \"Bulk approved for this week's run\"\n}" },
            "description": "The same four transitions with action in the body: APPROVE | HOLD | REJECT | REVERT. Use this or the dedicated routes, whichever suits the panel."
          },
          "response": []
        },
        {
          "name": "RUN SETTLEMENT (the cron target)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "X-ADMIN-API-KEY", "value": "{{admin_api_key}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settlement/run", "host": ["{{base_url}}"], "path": ["api", "admin", "settlement", "run"] },
            "body": { "mode": "raw", "raw": "{\n  \"astrologer_id\": 0,\n  \"up_to_date\": \"\",\n  \"dry_run\": false\n}" },
            "description": "This is what your panel's scheduler calls on the configured schedule, and what the panel's \"Run now\" button calls with an admin token.\n\nEvery field is optional — an EMPTY BODY settles everything that is TO_BE_SETTLED:\nastrologer_id (0 = all)\nup_to_date (YYYY-MM-DD, inclusive) — only sessions that ended on or before this date, so a late run still closes the period cleanly\ndry_run (true) — reports what WOULD be settled and writes nothing; use it for the confirmation screen\n\nWhat it does per astrologer, in its own transaction: claims the rows under a lock, creates the wallet_settlements batch (settlement_type = CONSULTATION_BATCH), CREDITS THE ASTROLOGER WALLET, writes wallet_ledger + wallettransaction, and stamps the consultations SETTLED.\n\nSafe to call twice — a second run finds nothing and reports \"already settled by another run\". One astrologer failing does not stop the others (skipped = true with a reason).\n\nPENDING rows are included ONLY when SettlementAutoApprove is on, which means paying out sessions nobody reviewed. Off by default."
          },
          "response": [
            {
              "name": "200 settled",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Settlement completed\",\n  \"data\": {\n    \"dry_run\": false,\n    \"astrologer_count\": 2,\n    \"consultation_count\": 9,\n    \"settled_amount\": 2380,\n    \"batches\": [\n      {\n        \"astrologer_id\": 1,\n        \"astrologer_name\": \"Acharya Vinod\",\n        \"settlement_id\": 14,\n        \"settlement_no\": \"STL2609210200010001\",\n        \"consultation_count\": 6,\n        \"gross_amount\": 2050,\n        \"platform_fee\": 410,\n        \"settled_amount\": 1640,\n        \"wallet_balance_after\": 4890,\n        \"period_from\": \"2026-09-14\",\n        \"period_to\": \"2026-09-20\",\n        \"skipped\": false\n      },\n      {\n        \"astrologer_id\": 4,\n        \"astrologer_name\": \"Pandit Suresh\",\n        \"consultation_count\": 1,\n        \"gross_amount\": 60,\n        \"platform_fee\": 12,\n        \"settled_amount\": 48,\n        \"period_from\": \"2026-09-19\",\n        \"period_to\": \"2026-09-19\",\n        \"skipped\": true,\n        \"reason\": \"below minimum payout of 100.00, rolled to the next run\"\n      }\n    ],\n    \"run_at\": \"2026-09-21 02:00:01\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Sweep stale consultations",
          "request": {
            "method": "POST",
            "header": [{ "key": "X-ADMIN-API-KEY", "value": "{{admin_api_key}}" }],
            "url": {
              "raw": "{{base_url}}/api/admin/consultation/sweep-stale?limit=100",
              "host": ["{{base_url}}"],
              "path": ["api", "admin", "consultation", "sweep-stale"],
              "query": [{ "key": "limit", "value": "100", "description": "max sessions per sweep, default 100" }]
            },
            "description": "Closes and bills sessions whose balance ticks stopped arriving more than ConsultationTickTimeoutSecs ago (app killed, network gone). They are billed as ABANDONED, capped at maxBillableSeconds, through the same billing path as a normal end.\n\nMeant for a SHORT-interval cron — every few minutes — separate from the settlement run. No body needed."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Stale consultations closed\",\n  \"data\": {\n    \"closed_count\": 3,\n    \"run_at\": \"2026-09-15 14:35:02\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Settlement history (batches)",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/admin/settlement/history?page=1&limit=10&search=&astrologer_id=0&from_date=&to_date=",
              "host": ["{{base_url}}"],
              "path": ["api", "admin", "settlement", "history"],
              "query": [
                { "key": "page", "value": "1" },
                { "key": "limit", "value": "10" },
                { "key": "search", "value": "", "description": "settlement no or astrologer name" },
                { "key": "astrologer_id", "value": "0" },
                { "key": "from_date", "value": "" },
                { "key": "to_date", "value": "" }
              ]
            },
            "description": "One row per astrologer per run. Withdraw payouts share the wallet_settlements table and are excluded — only settlement_type = CONSULTATION_BATCH appears here.\n\nprocessed_by 0 means the scheduler ran it, reported as processed_by_name = \"Scheduled job\"."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Settlement history\",\n  \"data\": {\n    \"items\": [\n      {\n        \"settlement_id\": 14,\n        \"settlement_no\": \"STL2609210200010001\",\n        \"astrologer_id\": 1,\n        \"astrologer_name\": \"Acharya Vinod\",\n        \"consultation_count\": 6,\n        \"gross_amount\": 2050,\n        \"platform_fee\": 410,\n        \"amount\": 1640,\n        \"status\": \"Completed\",\n        \"period_from\": \"14 Sep 2026\",\n        \"period_to\": \"20 Sep 2026\",\n        \"processed_by\": 0,\n        \"processed_by_name\": \"Scheduled job\",\n        \"remarks\": \"Settlement run 21 Sep 2026 02:00\",\n        \"settlement_date\": \"21 Sep 2026 02:00 AM\",\n        \"created_at\": \"21 Sep 2026 02:00 AM\"\n      }\n    ],\n    \"page\": 1,\n    \"limit\": 10,\n    \"total\": 14,\n    \"total_pages\": 2,\n    \"total_amount\": 68240\n  }\n}"
            }
          ]
        },
        {
          "name": "Settlement batch detail",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/admin/settlement/history/{{settlement_id}}",
              "host": ["{{base_url}}"],
              "path": ["api", "admin", "settlement", "history", "{{settlement_id}}"]
            },
            "description": "Path param: settlement id. The batch row plus every consultation it closed, in the /pending row shape. 404 when the id does not exist."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Settlement detail\",\n  \"data\": {\n    \"settlement_id\": 14,\n    \"settlement_no\": \"STL2609210200010001\",\n    \"astrologer_id\": 1,\n    \"astrologer_name\": \"Acharya Vinod\",\n    \"consultation_count\": 6,\n    \"gross_amount\": 2050,\n    \"platform_fee\": 410,\n    \"amount\": 1640,\n    \"status\": \"Completed\",\n    \"period_from\": \"14 Sep 2026\",\n    \"period_to\": \"20 Sep 2026\",\n    \"processed_by\": 0,\n    \"processed_by_name\": \"Scheduled job\",\n    \"settlement_date\": \"21 Sep 2026 02:00 AM\",\n    \"created_at\": \"21 Sep 2026 02:00 AM\",\n    \"consultations\": [\n      {\n        \"consultation_id\": 41,\n        \"consultation_no\": \"CHT2609151412330007\",\n        \"user_id\": 7,\n        \"user_name\": \"Ravi Kumar\",\n        \"medium\": \"CHAT\",\n        \"duration\": \"06:15\",\n        \"billed_minutes\": 7,\n        \"gross_amount\": 175,\n        \"platform_fee_amount\": 35,\n        \"astrologer_earning\": 140,\n        \"settlement_status\": \"SETTLED\",\n        \"settlement_id\": 14,\n        \"settlement_no\": \"STL2609210200010001\",\n        \"settled_at\": \"21 Sep 2026 02:00 AM\",\n        \"consultation_date\": \"15 Sep 2026 02:12 PM\"\n      }\n    ]\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "3. Admin — General settings (schedule)",
      "description": "The finance half of the general settings screen. Everything lives in systemflag, which is the single source of truth this API and your scheduler share.\n\nThis API schedules NOTHING. It stores the schedule and derives settlement_cron_expression from it; your panel's scheduler reads that and calls /settlement/run.",
      "item": [
        {
          "name": "Get general settings",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{admin_token}}" }],
            "url": { "raw": "{{base_url}}/api/admin/settings/general", "host": ["{{base_url}}"], "path": ["api", "admin", "settings", "general"] },
            "description": "No params. Returns the stored values plus three derived fields: settlement_cron_expression, schedule_description and next_run_at (blank for a CUSTOM schedule, which this API does not parse)."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"General settings\",\n  \"data\": {\n    \"platform_commission_percent\": 20,\n    \"min_consultation_minutes\": 2,\n    \"consultation_grace_seconds\": 0,\n    \"consultation_tick_timeout_secs\": 120,\n    \"settlement_frequency\": \"WEEKLY\",\n    \"settlement_day_of_week\": 1,\n    \"settlement_day_of_month\": 1,\n    \"settlement_run_time\": \"02:00\",\n    \"settlement_cron_expression\": \"0 2 * * 1\",\n    \"schedule_description\": \"Weekly on Monday at 02:00\",\n    \"next_run_at\": \"2026-09-21 02:00:00\",\n    \"settlement_auto_approve\": false,\n    \"settlement_min_payout\": 0\n  }\n}"
            }
          ]
        },
        {
          "name": "Update — weekly schedule",
          "request": {
            "method": "PUT",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settings/general", "host": ["{{base_url}}"], "path": ["api", "admin", "settings", "general"] },
            "body": { "mode": "raw", "raw": "{\n  \"settlement_frequency\": \"WEEKLY\",\n  \"settlement_day_of_week\": 5,\n  \"settlement_run_time\": \"23:30\"\n}" },
            "description": "PARTIAL update — only the fields you send are written, so the screen can save one field at a time.\n\nValidation:\nplatform_commission_percent 0-100\nmin_consultation_minutes >= 0\nconsultation_grace_seconds >= 0\nconsultation_tick_timeout_secs >= 30 (below that an ordinary network hiccup would start closing live sessions)\nsettlement_frequency WEEKLY | MONTHLY | CUSTOM\nsettlement_day_of_week 0 (Sunday) - 6 (Saturday)\nsettlement_day_of_month 1-28 (capped so every month has the day)\nsettlement_run_time HH:MM, 24h\nsettlement_min_payout >= 0\n\nFor WEEKLY and MONTHLY the cron expression is DERIVED — a settlement_cron_expression sent with them is ignored rather than left to disagree with the parts.\n\nA rejected value writes nothing at all (400)."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"General settings updated\",\n  \"data\": {\n    \"platform_commission_percent\": 20,\n    \"min_consultation_minutes\": 2,\n    \"consultation_grace_seconds\": 0,\n    \"consultation_tick_timeout_secs\": 120,\n    \"settlement_frequency\": \"WEEKLY\",\n    \"settlement_day_of_week\": 5,\n    \"settlement_day_of_month\": 1,\n    \"settlement_run_time\": \"23:30\",\n    \"settlement_cron_expression\": \"30 23 * * 5\",\n    \"schedule_description\": \"Weekly on Friday at 23:30\",\n    \"next_run_at\": \"2026-09-18 23:30:00\",\n    \"settlement_auto_approve\": false,\n    \"settlement_min_payout\": 0\n  }\n}"
            },
            {
              "name": "400 validation",
              "code": 400,
              "status": "Bad Request",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"statusCode\": 400,\n  \"message\": \"settlement day of week must be between 0 (Sunday) and 6 (Saturday)\"\n}"
            }
          ]
        },
        {
          "name": "Update — monthly schedule",
          "request": {
            "method": "PUT",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settings/general", "host": ["{{base_url}}"], "path": ["api", "admin", "settings", "general"] },
            "body": { "mode": "raw", "raw": "{\n  \"settlement_frequency\": \"MONTHLY\",\n  \"settlement_day_of_month\": 1,\n  \"settlement_run_time\": \"02:00\"\n}" },
            "description": "Derives \"0 2 1 * *\"."
          },
          "response": []
        },
        {
          "name": "Update — custom cron",
          "request": {
            "method": "PUT",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settings/general", "host": ["{{base_url}}"], "path": ["api", "admin", "settings", "general"] },
            "body": { "mode": "raw", "raw": "{\n  \"settlement_frequency\": \"CUSTOM\",\n  \"settlement_cron_expression\": \"0 3 */10 * *\"\n}" },
            "description": "Only CUSTOM takes a typed expression. It is checked for shape only (five space-separated fields) — going further would mean reimplementing your scheduler's parser here and disagreeing with it at the edges. next_run_at comes back blank for CUSTOM."
          },
          "response": []
        },
        {
          "name": "Update — commission and payout rules",
          "request": {
            "method": "PUT",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/settings/general", "host": ["{{base_url}}"], "path": ["api", "admin", "settings", "general"] },
            "body": { "mode": "raw", "raw": "{\n  \"platform_commission_percent\": 25,\n  \"min_consultation_minutes\": 3,\n  \"consultation_grace_seconds\": 15,\n  \"consultation_tick_timeout_secs\": 120,\n  \"settlement_min_payout\": 100,\n  \"settlement_auto_approve\": false\n}" },
            "description": "A commission change affects FUTURE sessions only — the percent in force is stored on each consultation row when it is billed.\n\nconsultation_grace_seconds 15 means a session under 15s is free, so a call that never really connected is not charged.\n\nsettlement_auto_approve true lets the job settle PENDING rows nobody reviewed. Leave it false unless you mean it."
          },
          "response": []
        }
      ]
    },
    {
      "name": "4. Astrologer — Settlement & earnings",
      "description": "Auth: AstroAuthMiddleware (signature only, the token is not looked up on the user row).\n\nRead-only: an earning is released by an admin and credited by the job. Note these responses use camelCase and the {status, message, data} envelope — the astrologer stack's own convention.",
      "item": [
        {
          "name": "Upcoming settlement (what am I owed)",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{astro_token}}" }],
            "url": { "raw": "{{base_url}}/api/astrologer/settlement/upcoming", "host": ["{{base_url}}"], "path": ["api", "astrologer", "settlement", "upcoming"] },
            "description": "No params. Everything billed to a customer that has not been credited yet, split by where it is in the review flow.\n\ntotalUnsettled = pendingAmount + toBeSettledAmount. On-hold money is deliberately excluded: it is not on its way anywhere until an admin releases it.\n\nNone of this is withdrawable — the withdraw screen reads the wallet balance, and only settled money is in it."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"Upcoming settlement fetched successfully.\",\n  \"data\": {\n    \"pendingCount\": 4,\n    \"pendingAmount\": 560,\n    \"toBeSettledCount\": 2,\n    \"toBeSettledAmount\": 280,\n    \"onHoldCount\": 1,\n    \"onHoldAmount\": 140,\n    \"totalUnsettled\": 840,\n    \"nextSettlementAt\": \"21 Sep 2026 02:00 AM\",\n    \"consultations\": [\n      {\n        \"consultationId\": 41,\n        \"consultationNo\": \"CHT2609151412330007\",\n        \"medium\": \"CHAT\",\n        \"duration\": \"06:15\",\n        \"billedMinutes\": 7,\n        \"ratePerMinute\": 25,\n        \"grossAmount\": 175,\n        \"platformFee\": 35,\n        \"astrologerEarning\": 140,\n        \"settlementStatus\": \"PENDING\",\n        \"statusLabel\": \"Awaiting review\",\n        \"consultationDate\": \"15 Sep 2026 02:12 PM\"\n      }\n    ]\n  }\n}"
            }
          ]
        },
        {
          "name": "Settlement history",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{astro_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/astrologer/settlement/history?page=1&limit=10",
              "host": ["{{base_url}}"],
              "path": ["api", "astrologer", "settlement", "history"],
              "query": [{ "key": "page", "value": "1" }, { "key": "limit", "value": "10" }]
            },
            "description": "The batches credited to this astrologer, newest first. totalSettled is everything ever credited, not just this page."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"Settlement history fetched successfully.\",\n  \"data\": {\n    \"items\": [\n      {\n        \"id\": 14,\n        \"settlementNo\": \"STL2609210200010001\",\n        \"amount\": 1640,\n        \"grossAmount\": 2050,\n        \"platformFee\": 410,\n        \"consultationCount\": 6,\n        \"status\": \"Completed\",\n        \"periodFrom\": \"14 Sep 2026\",\n        \"periodTo\": \"20 Sep 2026\",\n        \"remarks\": \"Settlement run 21 Sep 2026 02:00\",\n        \"settlementDate\": \"21 Sep 2026 02:00 AM\",\n        \"createdAt\": \"21 Sep 2026 02:00 AM\"\n      }\n    ],\n    \"page\": 1,\n    \"limit\": 10,\n    \"total\": 3,\n    \"totalPages\": 1,\n    \"totalSettled\": 4890\n  }\n}"
            }
          ]
        },
        {
          "name": "Settlement detail (sessions in a batch)",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{astro_token}}" }],
            "url": {
              "raw": "{{base_url}}/api/astrologer/settlement/history/{{settlement_id}}",
              "host": ["{{base_url}}"],
              "path": ["api", "astrologer", "settlement", "history", "{{settlement_id}}"]
            },
            "description": "Path param: settlement id. Scoped to the logged-in astrologer, so another astrologer's settlement cannot be read by guessing an id (400 \"settlement not found\")."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"Settlement detail fetched successfully.\",\n  \"data\": {\n    \"id\": 14,\n    \"settlementNo\": \"STL2609210200010001\",\n    \"amount\": 1640,\n    \"grossAmount\": 2050,\n    \"platformFee\": 410,\n    \"consultationCount\": 6,\n    \"status\": \"Completed\",\n    \"periodFrom\": \"14 Sep 2026\",\n    \"periodTo\": \"20 Sep 2026\",\n    \"settlementDate\": \"21 Sep 2026 02:00 AM\",\n    \"createdAt\": \"21 Sep 2026 02:00 AM\",\n    \"consultations\": [\n      {\n        \"consultationId\": 41,\n        \"consultationNo\": \"CHT2609151412330007\",\n        \"medium\": \"CHAT\",\n        \"duration\": \"06:15\",\n        \"billedMinutes\": 7,\n        \"ratePerMinute\": 25,\n        \"grossAmount\": 175,\n        \"platformFee\": 35,\n        \"astrologerEarning\": 140,\n        \"settlementStatus\": \"SETTLED\",\n        \"statusLabel\": \"Settled\",\n        \"consultationDate\": \"15 Sep 2026 02:12 PM\",\n        \"settledAt\": \"21 Sep 2026 02:00 AM\"\n      }\n    ]\n  }\n}"
            }
          ]
        },
        {
          "name": "End consultation (astrologer hangs up)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{astro_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/astrologer/consultation/end", "host": ["{{base_url}}"], "path": ["api", "astrologer", "consultation", "end"] },
            "body": { "mode": "raw", "raw": "{\n  \"consultation_id\": {{consultation_id}}\n}" },
            "description": "consultation_id (required). The astrologer must be a party to the session, or it comes back \"consultation not found\".\n\nBills through the same code as the customer ending it, with end_reason = ASTROLOGER_ENDED, and parks the earning at PENDING. The response is the snake_case receipt shape, wrapped in the astrologer envelope."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"Consultation ended successfully.\",\n  \"data\": {\n    \"consultation_id\": 41,\n    \"consultation_no\": \"CHT2609151412330007\",\n    \"medium\": \"CHAT\",\n    \"status\": \"COMPLETED\",\n    \"end_reason\": \"ASTROLOGER_ENDED\",\n    \"duration_seconds\": 375,\n    \"duration\": \"06:15\",\n    \"billed_minutes\": 7,\n    \"rate_per_minute\": 25,\n    \"deducted_amount\": 175,\n    \"wallet_balance\": 325,\n    \"platform_fee\": 35,\n    \"astrologer_earning\": 140,\n    \"settlement_status\": \"PENDING\",\n    \"ended_at\": \"2026-09-15 14:18:48\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Settlement history (legacy statement tab)",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{astro_token}}" }],
            "url": { "raw": "{{base_url}}/api/astrologer/wallet/settlement-history", "host": ["{{base_url}}"], "path": ["api", "astrologer", "wallet", "settlement-history"] },
            "description": "The pre-existing endpoint, unchanged in shape so the current app keeps working. It now lists only consultation settlement batches — withdraw payouts moved out to /wallet/withdraw-history where they belong.\n\nPrefer /settlement/history for new screens: it carries the consultation count, the period and the gross."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"Settlement history fetched successfully.\",\n  \"data\": [\n    {\n      \"id\": 14,\n      \"amount\": 1640,\n      \"status\": \"Completed\",\n      \"paymentMethod\": \"WALLET\",\n      \"tdsPercent\": 0,\n      \"tdsAmount\": 0,\n      \"instantCharge\": 0,\n      \"netAmount\": 0,\n      \"isInstant\": false,\n      \"requestDate\": \"21 Sep 2026 02:00 AM\",\n      \"settlementDate\": \"21 Sep 2026 02:00 AM\"\n    }\n  ]\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "5. User — Auth & FCM token registration",
      "description": "Where the handset's FCM token reaches the API.\n\nBefore this change only social login accepted a device_token, so an account created or logged in through the normal OTP flow had no push token at all and could never be notified. All four entry points below now take one.\n\nWhat happens to the token: it is written to THREE columns on the users row — device_token, fcm_token and token. The schema has always had all three; only device_token was ever populated, so anything reading fcm_token (the obvious name) found nothing. They are written together now and cannot drift apart.\n\ndevice_token is optional everywhere: a web client has none, and an omitted or empty value never wipes a registration that is still live.\n\nIMPORTANT for the apps: FCM rotates registration tokens on its own, and a reinstall issues a new one. Sending the token only at login leaves the account quietly unreachable mid-session — it still has a token on file, it just no longer routes anywhere. Call POST /device-token from the app's onTokenRefresh handler.",
      "item": [
        {
          "name": "Register (with device token)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/api/user/register", "host": ["{{base_url}}"], "path": ["api", "user", "register"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Rajesh\",\n  \"email\": \"rajesh27005@gmail.com\",\n  \"password\": \"Maapapa@1985\",\n  \"birthDate\": \"1985-05-27\",\n  \"birthTime\": \"05:50AM\",\n  \"profile\": \"RajuKumawat\",\n  \"birthPlace\": \"Nimbahera\",\n  \"addressLine1\": \"G11 Om vihar\",\n  \"location\": \"Nimbahera\",\n  \"pincode\": \"312601\",\n  \"gender\": \"Male\",\n  \"countryCode\": \"91\",\n  \"contactNo\": \"7976888256\",\n  \"device_name\": \"Samsung Galaxy S24 Ultra\",\n  \"ip_address\": \"192.168.1.100\",\n  \"platform\": \"Android\",\n  \"device_token\": \"fGhTestToken:APA91bH-paste-a-real-fcm-token-here\",\n  \"device_type\": \"ANDROID\"\n}"
            },
            "description": "Required: name, contactNo, password. Everything else optional.\n\nNEW handset fields, all optional — and note they do NOT all go to the same table:\n\n  device_token  -> users.device_token AND users.fcm_token AND users.token\n                   (all three written together; an empty value never wipes an\n                   existing registration)\n  device_type   -> users.device_type. ANDROID | IPHONE | WEB. When omitted,\n                   platform is used instead, so sending \"platform\": \"Android\"\n                   alone is enough.\n  ip_address    -> login_histories.ip_address\n  device_name   -> login_histories.device_name\n  platform      -> login_histories.platform\n\nWHY THE SPLIT: the users table has no ip_address, device_name or platform column. login_histories does, and it already stores exactly these three for /verify-login-otp. Registering now writes a login_histories row too, so the first session is recorded the same way every later login is — one row per session rather than one value per account, which is what you want for \"where has this account signed in from\".\n\nIf you would rather have the latest values denormalised onto users as well, that needs a migration adding the columns — ask and I'll write it.\n\nA failure to write the history row does not fail registration; it is logged."
          },
          "response": []
        },
        {
          "name": "Verify mobile OTP (with device token)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/api/user/verify-mobile-otp", "host": ["{{base_url}}"], "path": ["api", "user", "verify-mobile-otp"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userId\": 781,\n  \"mobile\": \"9876543210\",\n  \"otp\": \"123456\",\n  \"device_token\": \"fGhTestToken:APA91bH-paste-a-real-fcm-token-here\",\n  \"device_type\": \"ANDROID\"\n}"
            },
            "description": "otp is required; either userId or mobile must be present.\n\ndevice_token / device_type are new and optional — verification is the first moment a freshly registered app is a real account, so taking the token here saves waiting for the next login.\n\nA failure to store the token does NOT fail verification: the number is verified either way and the failure is logged. An account without a push token is usable, it just misses notifications until the next login or /device-token call."
          },
          "response": []
        },
        {
          "name": "Verify login OTP (with device token)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/api/user/verify-login-otp", "host": ["{{base_url}}"], "path": ["api", "user", "verify-login-otp"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"mobile\": \"7976888256\",\n  \"otp\": \"123456\",\n  \"ip_address\": \"192.168.1.100\",\n  \"device_name\": \"Samsung Galaxy S24 Ultra\",\n  \"platform\": \"Android\",\n  \"device_token\": \"fGhTestToken:APA91bH-paste-a-real-fcm-token-here\"\n}"
            },
            "description": "This is the main one — the app should send its CURRENT token on every login, because the token captured at registration goes stale.\n\nRequired: otp, ip_address, device_name, platform, and one of mobile / email.\nNew and optional: device_token, device_type.\n\nWhere it all lands:\n  device_token -> users.device_token + fcm_token + token\n  device_type  -> users.device_type; falls back to platform, which is already\n                  required here, so device_type rarely needs sending\n  ip_address / device_name / platform -> a login_histories row, as before\n\nThe token is written onto the same row update that stores the JWT, so it costs no extra query. The response is unchanged (LoginResponse with token, refreshToken and the user object).\n\nNOTE: /login must be called first to issue the OTP, and the account must be mobile AND email verified — /login answers \"email not verified....!\" otherwise."
          },
          "response": []
        },
        {
          "name": "Refresh device token (FCM rotation)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{user_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/user/device-token", "host": ["{{base_url}}"], "path": ["api", "user", "device-token"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"device_token\": \"fGhTestToken:APA91bH-paste-a-real-fcm-token-here\",\n  \"device_type\": \"ANDROID\"\n}"
            },
            "description": "NEW ENDPOINT. Authenticated — updates the logged-in customer's token.\n\ndevice_token (required)\ndevice_type (optional; left as it was when omitted)\n\nThe app should call this from its FCM onTokenRefresh handler, not only at login. Without it, a rotated token means the API holds a registration that no longer reaches the device, and notifications silently stop.\n\n(The astrologer app already had its own equivalent at POST /api/astrologer/device-token.)"
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": true,\n  \"message\": \"Device token updated successfully\",\n  \"data\": {\n    \"userId\": 781,\n    \"device_token\": \"fGhTestToken:APA91bH-...\",\n    \"device_type\": \"ANDROID\",\n    \"updated\": true\n  }\n}"
            },
            {
              "name": "400 missing token",
              "code": 400,
              "status": "Bad Request",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Key: 'UpdateDeviceTokenRequest.DeviceToken' Error:Field validation for 'DeviceToken' failed on the 'required' tag\"\n}"
            }
          ]
        },
        {
          "name": "Check what was stored (SQL)",
          "request": {
            "method": "GET",
            "header": [{ "key": "Authorization", "value": "Bearer {{user_token}}" }],
            "url": { "raw": "{{base_url}}/api/user/profile", "host": ["{{base_url}}"], "path": ["api", "user", "profile"] },
            "description": "The profile response echoes DeviceToken, which is the quickest in-app check.\n\nTo confirm the token columns landed:\n\n  SELECT id, device_token, fcm_token, token, device_type\n    FROM users WHERE contactNo = '7976888256';\n\nAll three token columns should hold the same value. If fcm_token and token are empty but device_token is set, the row was written by an older build or by the PHP side.\n\nTo confirm the session details landed:\n\n  SELECT id, ip_address, device_name, platform, login_at\n    FROM login_histories WHERE userId = <id> ORDER BY id DESC;\n\nOne row per register and per login-OTP verification.\n\nFrom the command line, the same check plus what the notifier would actually use:\n\n  go run ./cmd/notifytest -inspect -user <id>\n\n(That works with no Firebase credentials configured — it only reads the database.)"
          },
          "response": []
        }
      ]
    },
    {
      "name": "6. Admin — Push notifications (Firebase)",
      "description": "Firebase Cloud Messaging, over FCM HTTP v1.\n\nAuth is the same as the rest of the admin API: an admin bearer token, or X-ADMIN-API-KEY for a machine caller.\n\nTwo kinds of trigger live here, and only these two — every other notification in the product fires automatically from the operation itself (a recharge crediting, an astrologer coming online, a session's balance running low, a kundali finishing, a settlement being approved or credited, a customer joining the waiting queue). Those need no API call and are not in this folder.\n\n  /notification/send   a message an admin writes\n  /notification/event  the notification for a decision the ADMIN PANEL made\n                       (profile verification, withdrawal approval) — the panel\n                       reports what it did and the API sends the message in the\n                       same wording the apps use everywhere else\n\nWhat a 200 means: the notification was queued and the in-app user_notifications rows were written. Delivery is asynchronous and per device, so a 200 is not proof a handset received it. To check actual delivery use:\n\n  go run ./cmd/notifytest -token <device-fcm-token>      direct send, no DB\n  go run ./cmd/notifytest -inspect -user <users.id>      which tokens would be reached\n\nIf nothing arrives, the cause is almost always one of: FIREBASE_CREDENTIALS_FILE not set, or the account having no registered FCM token (users.device_token / user_device_details.fcmToken).",
      "item": [
        {
          "name": "Send — to one customer",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/notification/send", "host": ["{{base_url}}"], "path": ["api", "admin", "notification", "send"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"audience\": \"CUSTOMER\",\n  \"user_id\": 781,\n  \"title\": \"Special offer\",\n  \"body\": \"Get 20% extra on your next wallet recharge, today only.\"\n}"
            },
            "description": "This is customer notification #7, \"when admin user sends information\".\n\naudience (required) — CUSTOMER | ASTROLOGER | ALL_CUSTOMERS | ALL_ASTROLOGERS\nuser_id (required for CUSTOMER) — the customer's users.id\nastrologer_id (required for ASTROLOGER) — the astrologers.id, NOT the login id; the API resolves it to the linked user account\ntitle (required)\nbody (required)\nlimit (optional) — caps a broadcast for a staged rollout; ignored for a single recipient\n\nrecipient_count is accounts queued, not handsets: one account can have several registered devices."
          },
          "response": [
            {
              "name": "200 queued",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Notification queued\",\n  \"data\": {\n    \"audience\": \"CUSTOMER\",\n    \"recipient_count\": 1,\n    \"queued\": true\n  }\n}"
            },
            {
              "name": "400 missing user_id",
              "code": 400,
              "status": "Bad Request",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"statusCode\": 400,\n  \"message\": \"user_id is required for a customer notification\"\n}"
            }
          ]
        },
        {
          "name": "Send — to one astrologer",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/notification/send", "host": ["{{base_url}}"], "path": ["api", "admin", "notification", "send"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"audience\": \"ASTROLOGER\",\n  \"astrologer_id\": {{astrologer_id}},\n  \"title\": \"Documents required\",\n  \"body\": \"Please re-upload your ID proof; the previous file was unreadable.\"\n}"
            },
            "description": "astrologer_id is the astrologers.id. An astrologer with no linked users.id cannot be notified — /notification/event and this endpoint both resolve through astrologers.userId."
          },
          "response": [
            {
              "name": "200 queued",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Notification queued\",\n  \"data\": {\n    \"audience\": \"ASTROLOGER\",\n    \"recipient_count\": 1,\n    \"queued\": true\n  }\n}"
            }
          ]
        },
        {
          "name": "Send — broadcast to all customers",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/notification/send", "host": ["{{base_url}}"], "path": ["api", "admin", "notification", "send"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"audience\": \"ALL_CUSTOMERS\",\n  \"title\": \"Navratri special\",\n  \"body\": \"Talk to our top astrologers at 30% off all week.\",\n  \"limit\": 50\n}"
            },
            "description": "ALL_CUSTOMERS excludes astrologers by role, so a customer broadcast never lands on the astrologer app.\n\nlimit 50 sends to the first 50 accounts — use it to try a broadcast on a small group before removing the limit (0 or omitted = everybody). Every recipient also gets an in-app row, so a large broadcast writes a lot of rows."
          },
          "response": [
            {
              "name": "200 queued",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Notification queued\",\n  \"data\": {\n    \"audience\": \"ALL_CUSTOMERS\",\n    \"recipient_count\": 50,\n    \"queued\": true\n  }\n}"
            }
          ]
        },
        {
          "name": "Send — broadcast to all astrologers",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/notification/send", "host": ["{{base_url}}"], "path": ["api", "admin", "notification", "send"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"audience\": \"ALL_ASTROLOGERS\",\n  \"title\": \"Settlement schedule changed\",\n  \"body\": \"Settlements now run every Friday at 11:30 PM.\"\n}"
            },
            "description": "Every active, non-deleted astrologer with a linked login."
          },
          "response": []
        },
        {
          "name": "Event — profile approved",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/notification/event", "host": ["{{base_url}}"], "path": ["api", "admin", "notification", "event"] },
            "body": { "mode": "raw", "raw": "{\n  \"event\": \"PROFILE_APPROVED\",\n  \"astrologer_id\": {{astrologer_id}}\n}" },
            "description": "Astrologer notification #1. Call it after the panel has actually set the profile to approved — this endpoint only notifies, it does not change verification state.\n\nevent (required) — PROFILE_APPROVED | PROFILE_REJECTED | WITHDRAW_APPROVED | WITHDRAW_REJECTED\nastrologer_id (required) — astrologers.id\n\nSends: \"Profile verified — Your profile has been approved. You can go online and start taking consultations.\""
          },
          "response": [
            {
              "name": "200 queued",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Notification queued\",\n  \"data\": {\n    \"event\": \"PROFILE_APPROVED\",\n    \"astrologer_id\": 130,\n    \"queued\": true\n  }\n}"
            },
            {
              "name": "400 unknown event",
              "code": 400,
              "status": "Bad Request",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"statusCode\": 400,\n  \"message\": \"event must be PROFILE_APPROVED, PROFILE_REJECTED, WITHDRAW_APPROVED or WITHDRAW_REJECTED\"\n}"
            }
          ]
        },
        {
          "name": "Event — profile rejected",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/notification/event", "host": ["{{base_url}}"], "path": ["api", "admin", "notification", "event"] },
            "body": { "mode": "raw", "raw": "{\n  \"event\": \"PROFILE_REJECTED\",\n  \"astrologer_id\": {{astrologer_id}},\n  \"reason\": \"The uploaded ID proof did not match the name on the profile.\"\n}" },
            "description": "reason (optional) is shown to the astrologer verbatim, so write it for them to read.\n\nNot on the original requirement list — added because an astrologer left waiting with no word is worse than being told."
          },
          "response": []
        },
        {
          "name": "Event — withdraw approved",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/notification/event", "host": ["{{base_url}}"], "path": ["api", "admin", "notification", "event"] },
            "body": { "mode": "raw", "raw": "{\n  \"event\": \"WITHDRAW_APPROVED\",\n  \"astrologer_id\": {{astrologer_id}},\n  \"amount\": 2000,\n  \"reference\": \"UTR123456789\"\n}" },
            "description": "Astrologer notification #5.\n\namount (required, > 0) — quoted in the message\nreference (optional) — the bank or UPI reference to show the astrologer\n\nSends: \"Withdrawal approved — ₹2000.00 has been approved and transferred to your bank account. Reference UTR123456789.\"\n\nNotification only: approving the withdrawal and moving the money is the panel's job, since this API has no withdraw-approval endpoint."
          },
          "response": [
            {
              "name": "200 queued",
              "code": 200,
              "status": "OK",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"statusCode\": 200,\n  \"message\": \"Notification queued\",\n  \"data\": {\n    \"event\": \"WITHDRAW_APPROVED\",\n    \"astrologer_id\": 130,\n    \"queued\": true\n  }\n}"
            },
            {
              "name": "400 missing amount",
              "code": 400,
              "status": "Bad Request",
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"statusCode\": 400,\n  \"message\": \"amount is required for a withdrawal notification\"\n}"
            }
          ]
        },
        {
          "name": "Event — withdraw rejected",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization", "value": "Bearer {{admin_token}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": { "raw": "{{base_url}}/api/admin/notification/event", "host": ["{{base_url}}"], "path": ["api", "admin", "notification", "event"] },
            "body": { "mode": "raw", "raw": "{\n  \"event\": \"WITHDRAW_REJECTED\",\n  \"astrologer_id\": {{astrologer_id}},\n  \"amount\": 2000,\n  \"reason\": \"Bank account details could not be verified.\"\n}" },
            "description": "Astrologer notification #6. amount required; reason optional and shown verbatim.\n\nSends: \"Withdrawal rejected — Your withdrawal request of ₹2000.00 was rejected. Reason: …\""
          },
          "response": []
        }
      ]
    },
    {
      "name": "7. Automatic notifications — reference only",
      "description": "These fire from the operation itself. There is NOTHING to call here — the folder exists so the list is in one place when you are testing on a device.\n\nTo trigger each one, run the request named in brackets from the other folders:\n\nTO THE CUSTOMER\n  1. Wallet recharged            [1. User > verify-payment, i.e. a real PhonePe recharge]\n                                 fires after the wallet write commits, cashback included\n  2. Favourite astrologer online [astrologer app: PUT /api/astrologer/dashboard/online-status]\n                                 only on the OFFLINE -> ONLINE transition, to every follower\n  3. Astrologer accepted request NOT WIRED — the current flow has no accept step, since\n                                 /consultation/start connects immediately. The helper exists;\n                                 it needs either a request/accept flow or a call from your\n                                 socket layer at the moment of acceptance.\n  4. Astrologer free again       [1. User > End consultation] and the astrologer's chat/call/\n                                 busy status transitions — sent to everyone still WAITING\n  5. Insufficient funds (~30s)   [1. User > Tick] on the tick that CROSSES 30s remaining,\n                                 once per session, not on every tick after it\n  6. Kundali generated           [POST /api/user/kundali/add] after the rows commit,\n                                 signed-in users only (a guest kundali has no account)\n  7. Admin sends information     folder 6 > Send\n\nTO THE ASTROLOGER\n  1. Profile verified            folder 6 > Event — profile approved\n  2. Chat/call/video request      [1. User > Start consultation] — a started session IS the\n                                 incoming request from the astrologer's side\n  3. Customer waiting            [POST /api/astrologer/activity/waitlist/add]\n  4. Settlement status updates   PENDING -> TO_BE_SETTLED: [2. Admin > Approve]\n                                 TO_BE_SETTLED -> SETTLED + wallet credited:\n                                 [2. Admin > RUN SETTLEMENT] — one message per astrologer\n                                 per action, not one per consultation\n  5. Withdraw approved           folder 6 > Event — withdraw approved\n  6. Withdraw rejected           folder 6 > Event — withdraw rejected\n\nEvery one of these also writes a user_notifications row, so you can confirm it fired even with no handset attached:\n\n  SELECT id, userId, title, description, notification_type, created_at\n    FROM user_notifications ORDER BY id DESC LIMIT 10;\n\nnotification_type codes: 1 recharge, 2 astrologer online, 3 request accepted, 4 astrologer free, 5 low balance, 6 kundali ready, 7 admin message, 11 profile approved, 12 request received, 13 customer waiting, 14 settlement update, 15 withdraw approved, 16 withdraw rejected.",
      "item": []
    }
  ]
}
