Hasta Backend
  1. nandini
  • lookup
    • Get localised seed data
      GET
    • Get core seed data
      GET
    • Get zodiac signs
      GET
    • Get subscription and pricing plans
      GET
    • Get FAQs
      GET
    • Get notification intervals
      GET
  • audit
    • Get audit logs
      GET
  • auth
    • Register device
      POST
    • Send OTP
      POST
    • Verify OTP and login
      POST
    • Resend OTP
      POST
    • Refresh access token
      POST
    • Logout
      POST
    • Google OAuth login
      POST
  • admin
    • Register admin account
      POST
    • Admin login
      POST
    • Admin logout
      POST
    • Refresh admin access token
      POST
    • Get admin profile
      GET
    • Update admin password
      PUT
    • Edit language
      PUT
    • Add language
      POST
    • Delete language
      DELETE
    • Delete user event logs
      DELETE
    • Clear lookup cache
      DELETE
    • Clear cache
      DELETE
    • List all subscription plans (admin)
      GET
    • Edit a subscription plan (admin)
      PATCH
    • Edit a subscription plan (admin)
      PATCH
    • Sync a recurring plan with Razorpay
      POST
    • Sync a recurring plan with Razorpay
      POST
    • Create a coupon (admin)
      POST
    • List coupons (admin)
      GET
    • Get a coupon (admin)
      GET
    • Get a coupon (admin)
      GET
    • Edit a coupon (admin)
      PATCH
    • Edit a coupon (admin)
      PATCH
    • List redemptions for a coupon (admin)
      GET
    • List redemptions for a coupon (admin)
      GET
    • Create a recurring scheduled notification (admin)
      POST
    • List scheduled notifications (admin)
      GET
    • Pause a scheduled notification (admin)
      PATCH
    • Resume a paused scheduled notification (admin)
      PATCH
  • blogs
    • Admin - get all blogs
    • Admin - delete a blog
    • Get all blogs
    • Get blog categories
    • Admin - get blog categories (name + slug)
    • Admin - edit a blog
    • Update blog status
    • Admin - send blog notification
    • Get blog details
    • Subscribe to blog notifications
    • Unsubscribe from blog notifications
  • feedback
    • Submit a user feedback
    • Get all queries
    • Update user query status
    • Get all reviews
    • Feature or unfeature a review
    • Get all featured reviews
  • events
    • Track a user event
    • Fetch all tracked events
  • notifications
    • Send broadcast notification
    • Send custom notification to a user
    • Send a test notification (deep link redirect check)
    • Fetch user notifications
    • Get unread notification count
    • Mark notification as read
  • reminders
    • Fetch user reminders
    • Create a reminder
    • Update a reminder
    • Delete a reminder
  • user
    • Create / complete user profile
    • Get my profile
    • Get my feature status
    • Update my intents
    • Update profile image
    • Update language preference
    • Update push notification preference
    • Update current location
    • Get my family members
    • Add a family member
    • Delete a family member
    • Update family member profile image
    • Admin - get user details with family
    • Update a family member's intents
    • Admin - list users
    • Admin - get subscribers list
    • Admin - get user details with family
    • Admin - delete user and all family members
    • Admin - update user status
  • palm
    • Get user palm reading
    • Upload user palm image
    • Upload family member palm image
    • Get family member palm reading
  • horoscopes
    • Get user kundali
    • Get family member kundali
    • Get horoscope (daily / weekly / monthly)
  • reports
    • Share a report via email
    • Get shared report PDF link
  • tarot
    • List all tarot cards
    • Get single tarot card by slug
    • Get today's daily tarot card
    • Get a 3-card tarot reading
    • Get tarot reading history
  • marriage
    • Generate marriage compatibility report
    • Get marriage compatibility report
    • List marriage report history
    • Preview the price to upgrade a compatibility report to the full Marriage Report
    • Start the compatibility -> full Marriage Report upgrade purchase
    • Consume the upgrade purchase and regenerate the full Marriage Report
  • child
    • Generate child report (baby names + birth timing)
    • Like or unlike a suggested baby name
    • Get child report (names + timing)
  • dashboard
    • Dashboard overview stats
    • User growth time-series
    • Subscription distribution by plan
    • Top-up & one-time sales distribution
    • Recent activity feed
  • Health
    • Liveness check
  • panchang
    • Get today's panchang for the user's current location
  • festivals
    • List festivals for the user's regions
    • Festival detail with full linked puja vidhis
  • library
    • List library items
    • Library filter options
    • Library content types
    • List my favourite library items
    • Add a library item to favourites
    • Remove a library item from favourites
    • Admin - list library items (incl. drafts)
    • Admin - create a library item
    • Admin - get a library item (incl. drafts)
    • Admin - edit / publish a library item
    • Stream a library item's audio
    • Get a library item
  • mantras
    • List mantras
    • List my favourite mantras
    • Add a mantra to favourites
    • Remove a mantra from favourites
    • Admin - list mantras (incl. drafts)
    • Admin - create a mantra
    • Admin - get a mantra (incl. drafts)
    • Admin - edit / publish a mantra
    • Stream a mantra's chanting audio
    • Get a mantra
  • nandini
    • Send a message and/or image(s)/PDF(s) to Nandini (reply is streamed over WebSocket)
      POST
    • List my Nandini conversations
      GET
    • Get a conversation's messages
      GET
    • Delete a conversation
      DELETE
  • payments
    • Validate a coupon code
    • Start a plan/report/topup purchase
    • Verify a completed Razorpay payment
    • Cancel a recurring subscription
    • Pause a recurring subscription
    • Resume a paused subscription
  • subscriptions
    • List my purchase history
    • Get one purchase's status
  • public
    • Get today's panchang for any location - no auth required
    • Free kundali preview - no auth, no account required
    • Search a place by name (keystroke-aware) - no auth required
    • Resolve a place_id to full location + timezone - no auth required
  • coupons
    • List currently applicable coupons
    • Get a currently applicable coupon
  • account-deletion
    • Request account & data deletion
  1. nandini

Send a message and/or image(s)/PDF(s) to Nandini (reply is streamed over WebSocket)

POST
https://uat-api.hastaai.in/api/v1/nandini/chat
nandini
Last modified:2026-08-26 16:17:40
Request: multipart/form-data; Bearer token required
Send a message to Nandini, the conversational Vedic astrologer - text, file(s), or both in the SAME call, ChatGPT-style. This endpoint is step 1 of a two-step flow: it does NOT return the reply. It saves your message (and uploads any files), queues the answer for a background worker, and returns a chat_key. You then open a WebSocket to receive the reply as it is generated, token by token.

Two-step flow#

1.
POST /nandini/chat (this call) → returns data.chat_key + data.stream_token + data.status = "processing".
2.
Open the WebSocket /ws/stream?stream_token=<stream_token> to stream the reply - use stream_token here, NOT chat_key (see below for why).
stream_token is fresh per message; chat_key is stable for the whole thread - they are not interchangeable, and this matters more than it sounds. The underlying Redis Stream is append-only per key, and the socket always replays from the start on connect (see next paragraph) - so if it were keyed on chat_key, a thread's 2nd+ message would make the socket replay the FIRST reply's still-present completion frame and stop right there. Real, previously-shipped bug (fixed 2026-08-26): exactly this - every message after the first showed the first reply's answer in real time, while GET /nandini/chats/{chat_key} (history/refetch) was always correct since it reads chat_history directly, bypassing the stream entirely. Always use the stream_token from the response you JUST got for THIS message's socket.
You do not need to connect immediately. The stream is a persisted Redis Stream, not pub/sub - the worker can finish (and finish publishing) before your socket even completes its handshake, and connecting late still replays everything from the start, nothing is lost to that race. The full reply is also always saved regardless, so GET /nandini/chats/{chat_key} is a safe fallback if a socket drops mid-stream.

WebSocket streaming protocol (/ws/stream?stream_token=<stream_token>)#

The server relays the worker's output as text frames:
Content frames - each frame is a raw chunk (token) of Nandini's reply. Append them in order to build the full message. Render these to the user.
Completion - when the reply is finished, the server sends ONE control frame {"type":"end"} (JSON) and closes the socket. This is a control signal, NOT prose - do not render it; treat it (and/or the socket close) as done.
Error - on failure the server sends {"type":"error","content":"..."} (JSON control frame, not prose) and closes. This is also what you get if a vision reply fails (see below) - the frontier model is not silently retried on a cheaper model for image turns, so a failure here means "please retry", not a bad answer.
The WebSocket takes only stream_token as a query param (no auth header).

Attaching image(s)/PDF(s) - real-time vision, not a cached description#

Pass one or more files in files (repeat the field for multiple). query and files are BOTH optional, but at least one must be present:
query only - normal text chat, unchanged.
files only (no query) - Nandini is asked to describe/answer about the attachment(s) generically.
query + files together - Nandini actually LOOKS AT the image while answering your specific question about it (real-time vision on that turn), not a generic pre-summarized description. Attaching an image also forces that turn onto the deep/frontier model, regardless of the usual intent-based routing.
Accepted types: JPEG / PNG / WebP / PDF, each ≤ 10 MB. PDFs are parsed into text and grounded into this SAME reply (not just future turns); images are seen directly via vision for this reply, and a background text description is also saved so later turns in the same thread can still refer to the image without it being re-attached.
Plan-gated: because attaching a file forces the costlier frontier model, files is Sage/Guru only (when X-Enforce-Paywall: true) - a free Seeker gets back 200 with success: false and code: PLAN_LIMIT (not an HTTP error) if they include files, even with a query. Seekers can still send unlimited text-only messages; only the attachment is blocked, not the chat itself.

Rendering Nandini's reply (images/links/audio/video)#

Nandini's reply is plain text with markdown for anything beyond prose - the client must render markdown, not just display raw text:
Images (e.g. chart links) come as markdown image syntax ![...](url) - render these as an inline image, never as a bare clickable link.
Any other link comes as markdown link syntax [...](url) - dispatch by the URL's file extension: .pdf → embedded PDF viewer, .mp3/.wav → audio player, .mp4 → video player, anything else → a plain clickable link that opens in a new tab.
This same image/link rendering rule applies wherever else the API returns URLs for user-facing content (chart_assets image_url, narration_url/audio_url on reports and library/mantras) - treat them the same way, not as raw strings.

Status values#

processing (returned here) - the reply is being generated by the background worker. Open the WebSocket to receive it. This is the only status this endpoint returns; completion/failure are observed over the WebSocket, not via REST.

New chat vs. continuing a chat#

Start a new conversation: omit chat_key. A new thread is created (its title is seeded from your first query) and a fresh chat_key (Chat-XXXXX) is returned. This works even if your first message is a file with no query - unlike attaching a file used to require an existing chat, this endpoint does not.
Continue an existing conversation: pass its chat_key. Your message is appended to that thread, and the worker feeds the last 16 messages back as context so Nandini remembers the conversation. (List threads with GET /nandini/chats.)

How Nandini answers (grounding)#

Nandini replies using the conversation history plus the subject's stored report(s). For a specific report type (kundali, marriage, palm_reading, tarot, naming) she reads only that report; if it has not been generated yet she warmly asks the user to create it first and never invents a reading. Pass family_key to ask about a family member's chart instead of the user's own.
general (the default) is different: it bundles every completed report the subject has (not just kundali), and if family_key is omitted it also tries to figure out WHO the question is about directly from the message text - a relation word ('my father') or a name, matched against the user's real family list. Zero matches -> the user themself; one match -> that person; more than one (e.g. two brothers both matching 'brother') -> ALL of them, named, so Nandini can address each individually instead of guessing or asking - that turn is also forced onto the frontier model, since comparing multiple people's charts is a harder synthesis task.
If the subject has more than one report of that type (e.g. several tarot readings or marriage matches), the default is the latest one. Pass report_key (the report's own key - Tarot-XXXXX, Match-XXXXX for marriage, Baby-XXXXX for naming, or Palm-XXXXX) when the user tapped 'Talk to Nandini' from a specific report's screen, so she grounds on THAT report instead of always the newest. kundali and panchang/puja/general ignore report_key (kundali always resolves to the subject's single current chart).

Body (form fields): query (optional if files is present - the user's message); chat_key (optional - continue a thread; omit to start a new one); family_key (optional - Family-XXXXX, ask about a family member); chat_type (optional, default general - one of general | kundali | marriage | palm_reading | tarot | panchang | puja | naming; an unknown value is coerced to general); report_key (optional - pin to one specific report instance when the subject has several; see above); files (optional - one or more JPEG/PNG/WebP/PDF, ≤10MB each).
Success: 200 - data.chat_key (continue the thread later - NOT for /ws/stream, see above) + data.stream_token (for /ws/stream - see above) + data.status (processing). Also returns 200 with success: false and code: PLAN_LIMIT (not an HTTP error) when header X-Enforce-Paywall: true is sent, and EITHER today's message count has hit the plan's messages_per_day limit, OR files were attached by a non-Sage/Guru (free Seeker) caller - image/file uploads in chat are a Sage/Guru-only feature; a Seeker can still send text-only messages freely
Errors: 401 - invalid/expired token | 404 - chat_key supplied but not found for this user | 409 - both query and files are empty, or a file is an unsupported type/over 10 MB

Request

Authorization
Bearer Token
Provide your bearer token in the
Authorization
header when making requests to protected resources.
Example:
Authorization: Bearer ********************
or
Body Params multipart/form-data

Request Code Samples

Shell
JavaScript
Java
Swift
Go
PHP
Python
HTTP
C
C#
Objective-C
Ruby
OCaml
Dart
R
Request Request Example
Shell
JavaScript
Java
Swift
curl --location 'https://uat-api.hastaai.in/api/v1/nandini/chat' \
--header 'Authorization: Bearer <token>' \
--form 'query=""' \
--form 'chat_key=""' \
--form 'family_key=""' \
--form 'chat_type="general"' \
--form 'report_key=""' \
--form 'files=""'

Responses

🟢200OK
application/json
Successful Response
Bodyapplication/json

Example
{
    "success": true,
    "message": "Stream initiated",
    "data": {
        "chat_key": "Chat-A1B2C3",
        "stream_token": "sYQm3rP1kZ9xF2vB",
        "status": "processing"
    }
}
🟠401Unauthorized
🟠404Record Not Found
🟠409Record Not Found
🟠422Parameter Error
Modified at 2026-08-26 16:17:40
Previous
Get a mantra
Next
List my Nandini conversations
Built with