KP Stellar Astro API
The same deterministic KP engine the website runs on, callable from your own software. JSON in, JSON out, no SDK required.
Quick start
- Sign in and open the developer portal.
- Subscribe to developer access (₹1,999 for 30 days).
- Press Create app. You are shown an App ID and an App Secret. Copy the secret now — it is never shown again.
- Confirm the keys work before writing any real payload:
# Base URL: https://kpstellarastro.com curl https://kpstellarastro.com/api/v1/ping \ -H "X-App-Id: kpsa_app_3c5b9502b9690bbb" \ -H "X-App-Secret: kpsa_sk_YOUR_SECRET_HERE"
{ "ok": true,
"app_id": "kpsa_app_3c5b9502b9690bbb",
"expires_at": 1789181891.98 }
If that returns ok: true, your credentials are good and anything failing after this point is the payload, not the keys.
Authentication
Every /api/v1 request carries two headers. There is no OAuth flow, no token exchange, and no expiry on the credentials themselves — only on the subscription behind them. (The Claude connector below is a separate family and does use OAuth.)
| Header | Value |
|---|---|
X-App-Id | Your App ID, e.g. kpsa_app_3c5b9502b9690bbb |
X-App-Secret | Your App Secret, e.g. kpsa_sk_… |
Content-Type | application/json on every POST |
Check credentials
/api/v1/pingCosts nothing, computes nothing, and confirms the credentials plus how long access runs. Use it in your deploy healthcheck.
Response 200
{ "ok": true,
"app_id": "kpsa_app_3c5b9502b9690bbb",
"expires_at": 1789181891.98 // unix seconds }
Birth chart
/api/v1/birth-chartA full natal reading: planetary positions with their full KP sub-chains, twelve house cusps, dasha periods, significators, and a verdict for every life domain.
Request body
| Field | Type | Notes | |
|---|---|---|---|
birth_date | string | required | YYYY-MM-DD. Must not be later than today — a future birth date returns 422. |
birth_time | string | required | HH:MM, 24-hour, local to the birth place |
latitude | number | required | Decimal degrees, north positive |
longitude | number | required | Decimal degrees, east positive |
place | string | optional | Echoed back; also resolves the historical time zone |
name | string | optional | Echoed back for your own labelling |
Request
curl -X POST https://kpstellarastro.com/api/v1/birth-chart \ -H "X-App-Id: $APP_ID" \ -H "X-App-Secret: $APP_SECRET" \ -H "Content-Type: application/json" \ -d '{ "name": "Asha", "birth_date": "1990-05-15", "birth_time": "10:30", "latitude": 13.0827, "longitude": 80.2707, "place": "Chennai, India" }'
Response 200 (abridged — real values)
{
"name": "Asha",
"place": "Chennai, India",
"timezone": "Asia/Kolkata",
"utc_offset_hours": 5.5,
"positions": {
"Sun": {
"name": "Sun",
"longitude": 30.485933,
"speed": 0.96426,
"retrograde": false,
"sign": "Taurus",
"sign_lord": "Venus",
"nakshatra": "Krittika",
"pada": 2,
"star_lord": "Sun",
"sub_lord": "Rahu",
"sub_sub_lord": "Ketu"
}, … 9 planets total
},
"cusps": [
{ "house": 1, "cusp": "06°58′",
"sign_lord": "Moon", "star_lord": "Saturn",
"sub_lord": "Mercury", "sub_sub_lord": "Jupiter" }
, … 12 houses total
],
"predictions": [
{
"event": "business",
"title": "Business",
"verdict": "WEAK_PROMISE",
"confidence": "WEAK",
"reasoning": "The CSL Mercury (sub lord of cusp 7) signifies
houses [3, 4, 9, 10, 11, 12], directly touching
[10, 11] the required houses [2, 7, 10, 11] …",
"windows": [
{ "start": "2027-03-14", "end": "2027-08-21",
"chain": "MD Jupiter / AD Venus / PD Moon",
"houses": [2, 7, 11], "activation": "VERY_STRONG" }
],
"timing_facts": { // added 2026-10, one JSON shape everywhere it appears
"mode": "BIRTH",
"answer": "possible", // strong|likely|possible|no_clear_period|not_indicated —
// capped at "possible" here because the VERDICT above is
// WEAK_PROMISE, whatever the row's own dasha strength is
"with_hurdles": false,
"rp_instant": "2026-09-30T14:35:00Z", // null when not promised
"rows": [
{ "start": "2027-03-14", "end": "2027-08-21",
"chain": "MD Jupiter / AD Venus / PD Moon", "houses": [2, 7, 11],
"activation": "VERY_STRONG", // same keys "windows" above carries — the row's OWN dasha
// strength, independent of the word's own cap below
"word": "possible", // strong|likely|possible — never a percentage. The stage-1
// verdict (WEAK_PROMISE) caps every row's word at "possible",
// however strong "activation" reads (decision 21's word table)
"confirmed_by": ["Venus", "Moon"], // [] = "not confirmed"; never omitted
"month_focus": null, // always null on a WEAK_PROMISE row — never computed for one.
// When narrower than the row itself, an array of objects shaped
// { "start": "2027-05-15", "end": "2027-06-14", "sign": "Taurus",
// "lord": "Venus", "label": "mid-May to mid-June 2027",
// "label_short": "mid-May to mid-June" } — only "label_short"
// is new (added 2026-10); start/end/sign/lord/label already existed.
// "label_short" is "label" with its trailing year dropped
"age_at_start": null, // the native's age when this window begins (BIRTH mode only);
// always null for HORARY rows
"hold": null // added 2026-10: null unless the row is held back, see the note below.
// Always null on birth-chart responses.
}
],
"days_to_watch": null // BIRTH is always null — see the horary example below
},
"rp_confirmed_window": { "chain": "…", "start": "…", "end": "…" } // or null
}, … 18 life domains total
],
"dasha": { /* Vimshottari periods */ },
"significators": [ /* per-planet A/B/C/D houses */ ],
"chart_layout": { /* wheel geometry, if you draw it */ },
"planetary_positions_table": [ … ],
"house_cusps_table": [ … ],
"cuspal_promise_table": [ … ],
"house_significators_abcd": [ … ],
"planetary_aspects": { /* object: "western" and "vedic" (each keyed by planet name) plus "pairs" (a list) */
"western": { "Sun": { "conjunction": ["Me"], "opposition": [], "benefic": ["Ju"], "malefic": [] }, … }, /* degree aspects on the KP orbs */
"vedic": { "Mars": { "conjunction": [], "opposition": ["Sa"], "special": ["Ve"] }, … }, /* whole-sign */
"pairs": [ /* additive: one entry per planet pair with a degree aspect, same pairs as "western" */
{ "a": "Mo", "b": "Ju", "aspect": "square", "angle": 90, "delta": 1.25, "motion": "applying" }, /* delta = degrees from exact; motion = applying|separating|stationary */
…
]
},
"vedic_details": { … },
"vedic_bhava": { /* classical Parashari graha-in-bhava */
"ascendant_sign": "Leo",
"house_system": "whole-sign (Parashari)",
"differ_count": 1, // grahas whose bhava != their KP house
"readings": [
{
"graha": "Sun", "sign": "Gemini", "degree": 0.5,
"bhava": 11, // WHOLE-SIGN house — what the reading describes
"kp_house": 10, // Placidus house, for comparison only
"houses_differ": true,
"reading": "Gains through rank and influential connections …",
"dignity": "neutral", // exalted|debilitated|moolatrikona|own|neutral
"house_nature": ["upachaya"],
"modifiers": [ /* classical rules that apply, as sentences */ ],
"karakatva": "the soul, the father, authority and vitality",
"lords": [1], // bhavas this graha RULES (depends on the lagna)
"lordship": "Sun rules your 1st (…). Sitting in the 11th, it carries
those matters into gains, income, networks …",
"node_agency": "", // Rahu/Ketu only: whose lord it acts for
"dasha": { "start": "1990-01-16", "end": "1996-01-17",
"active": false, "phase": "past" /* past|active|future */ },
"ayanamsa_sensitive": false
}, … one per graha
]
},
"doshas": { /* Kaal Sarpa + Mangal, same whole-sign houses */
"ascendant_sign": "Cancer",
"house_system": "whole-sign (Parashari)",
"mangal": {
"present": true, // decided by the LAGNA reference only — see below
"cancelled": true, // present AND at least one classical parihara applies
"severity": "mild", // none|mild|moderate
"mars_sign": "Aquarius",
"mars_bhava": 8, // WHOLE-SIGN house — what decides the verdict
"mars_kp_house": 8, // Placidus house, for comparison only
"houses_differ": false,
"houses": { "lagna": 8, "Moon": 3, "Venus": 12 },
"afflicting": ["lagna", "Venus"], // references that agree
"contested_only": false, // true = rests solely on the disputed 2nd house
"reasons": [ /* why, as sentences */ ],
"cancellations": [ /* classical parihara that apply, as sentences */ ]
},
"kaal_sarpa": {
"present": false, "partial": false,
"type_name": "Kulik", // one of the twelve, from Rahu's bhava
"rahu_bhava": 2, "ketu_bhava": 8,
"inside": [ /* grahas hemmed within the Rahu→Ketu arc */ ],
"outside": ["Moon", "Saturn"],
"on_axis": [ /* within 1° of a node — the partial (Aanshik) case */ ],
"reason": "Moon, Saturn fall outside the Rahu–Ketu arc …"
},
"remedies": [ /* only for a dosha actually found; [] otherwise */ ],
"base_rates": { "mangal_pct": 52.2, "kaal_sarpa_pct": 4.0, … }
}
}
timing_facts.rows[*].start/end (and windows[*].start/end,
the same dates) are "YYYY-MM-DD" for most rows, but "YYYY-MM-DD HH:MM"
whenever that row spans fewer than 3 days — a plain date there would make two consecutive
short rows look like duplicate dates even though they cover different hours. Check the string's
length (10 vs 16 characters) rather than assuming one format.vedic_bhava uses a different house system from the rest of this response.
Every other house number here is a KP Placidus house. Classical Parashari graha-in-bhava is read by
whole sign — the Ascendant's entire sign is the 1st bhava — and the two disagree for roughly
58% of placements (measured over 2,700). Each reading therefore carries its own bhava
alongside the kp_house, and houses_differ tells you when they part company.
Do not join these readings onto a KP house number: the sentence belongs to the whole-sign bhava.
This block is traditional correspondence, not a computed verdict, and never affects any
predictions entry.doshas follows the same whole-sign rule, and two things about it
are easy to get wrong. First, Mangal dosha is decided from the lagna alone. Counting the Moon and
Venus references as independent triggers marks 88.7% of charts Manglik; the lagna rule alone marks
52.2% (measured over 800 charts), which is what six-of-twelve houses predicts. The other references
are returned in houses/afflicting as corroboration — do not treat them as triggers.
Second, never render present without cancellations and base_rates:
about 67% of Manglik charts carry a classical cancellation, and a dosha shown without either is the
frightening half of the tradition rather than a neutral subset of it. Like vedic_bhava, this is
traditional correspondence and never affects any predictions entry.Verdict values
The verdict field on each prediction is one of a fixed set. Treat it as an enum, not as prose:
| Value | Meaning |
|---|---|
STRONGLY_PROMISED | Signified, with strong supporting significators |
PROMISED | The cuspal sub-lord signifies the required houses |
PROMISED_WITH_DELAY | Promised, but retrograde or obstructed — arrives later |
PROMISED_WITH_OBSTRUCTION | Promised against a competing signification |
WEAK_PROMISE | Thinly signified; timing is indicative only |
REQUIRES_FURTHER_TIMING_CONFIRMATION | Promise stands, timing is not yet decidable |
DENIED | Not signified. The chart does not promise it |
DENIED is not automatically bad news — for a benefic topic it means the event is not promised, but for a topic the querent does not want, denial is the good outcome. Do not render the raw enum to end users without interpreting it against the question.windows's meaning on each prediction. Previously the raw Antardasha-level candidate windows straight from the engine's own search, shown regardless of promise. It is now the SAME rows timing_facts.rows carries: Pratyantardasha-level, obstruction-screened, age-gated, nearest-first, and empty ([]) whenever the event isn't promised — a not-promised prediction no longer carries a dated window with no promise behind it.timing_facts is the one JSON shape every dated fact in this API now uses (birth chart and horary alike). rows[*].word is the ONLY confidence wording to render — never a percentage, never a raw activation label. confirmed_by is [], never omitted, when the row is not confirmed by the Ruling Planets at the moment this chart was requested (rp_instant). month_focus is null whenever it doesn't narrow a row further.timing_facts.rows[*].hold. Every row now carries a hold key. It is null when the row is not held back, and it is always null on birth-chart responses; it is filled only on horary rows (see the Horary section). The same key also appears on the website's own session endpoints: it is always null on /api/birth-chart, on /api/kp-detail and on the natal side of /api/natal-horary/compare, while the horary side of /api/natal-horary/compare carries hold exactly as /api/horary does. Additive: existing keys are unchanged.label_short. month_focus's other fields — start, end, sign, lord, label — were already present; only label_short is new. It is the same text as label with its trailing year dropped (e.g. "mid-May to mid-June" vs "mid-May to mid-June 2027"), for rendering several spans from the same year without repeating it.rp_confirmed_window is deprecated. Superseded by each row's own timing_facts.rows[*].confirmed_by. Still populated for one more release; do not build new integrations against it.Horary (Prashna)
/api/v1/horaryJudges one question from the moment it is asked, using a number from 1 to 249. No birth details needed.
kp_working, ruling_planets, dasha, house_significators_abcd. The website's own POST /api/horary (signed-in session or guest, no X-App-Id) also returns four additive keys: kp_working (the cusp's sub-lord facts: cusp, sub-lord, star lord, the houses each signifies, the houses that grant and the houses that hold back the matter, and which of them the sub-lord touches), ruling_planets (the Ruling Planets at the moment asked: day lord, the seven factors, the de-duplicated list) and dasha (the Vimshottari tree as it ran when asked, the same shape as /api/birth-chart) and house_significators_abcd (the A/B/C/D significators of each house, the same table and shape as /api/birth-chart). The same kp_working object also rides on every predictions[] item of the website's /api/birth-chart. /api/v1/horary and /api/v1/birth-chart are unchanged: these keys are absent there, and no existing key changed.reading_basis. POST /api/v1/horary may include reading_basis, a short string saying how the reading was reached. It is present only when our question-reading service is in use for the call, and absent otherwise (so a response without it is normal). Current values: exact_keyword, keyword_conflict, engine_specific, engine_kept, seed, lexicon_alias, alias_cell, composed, llm_decided, last_resort or grammar_unavailable. The list may grow: treat an unknown value as informational, never as an error. A question that is not about anything a chart can judge is still answered with the unchanged "reason": "off_topic" body, which carries no reading_basis. tag_pick in the request is ignored, and picked never appears in reading_basis.Request body
| Field | Type | Notes | |
|---|---|---|---|
question | string | required | One answerable question. Either/or questions are rejected |
horary_number | integer | required | 1–249 |
judgment_date | string | required | YYYY-MM-DD — the moment of asking |
judgment_time | string | required | HH:MM, local to the place below |
latitude | number | required | Where the question is asked |
longitude | number | required | |
place | string | optional | Resolves the time zone |
Request
curl -X POST https://kpstellarastro.com/api/v1/horary \ -H "X-App-Id: $APP_ID" \ -H "X-App-Secret: $APP_SECRET" \ -H "Content-Type: application/json" \ -d '{ "question": "Will I get the job?", "horary_number": 137, "judgment_date": "2026-08-13", "judgment_time": "14:20", "latitude": 13.0827, "longitude": 80.2707, "place": "Chennai, India" }'
Response 200 (abridged)
{
"question": "Will I get the job?",
"horary_number": 137,
"category": "Job / Employment",
"event_id": "job",
"primary_cusp": 6,
"required_houses": [2, 6, 10, 11],
"verdict": "PROMISED",
"confidence": "STRONG",
"reasoning": "…",
"windows": [ { "start": "…", "end": "…" } ], // changed 2026-10, see the callout below
"timing_facts": { // added 2026-10 — same shape as /api/v1/birth-chart's "timing_facts";
// the pre-existing "timing" key further down is the UNRELATED, pre-B2
// Predictive Timing (dasha) breakdown on /api/v1/horary, untouched here.
"mode": "HORARY", "answer": "strong", "with_hurdles": false,
"rp_instant": "2026-08-13T08:50:00Z",
"rows": [ /* same row shape as /api/v1/birth-chart's "timing_facts.rows", plus "band" -- MODERATE or stronger, at most one per band, in band order (up to 3) */ ],
"bands": [ { "band": "nearest", "from": "2026-10-04", "to": "2028-10-03", "row_index": 0 } ], // added 2026-10 (U5f): always three entries, nearest / mid / long
"horizon_end": "2036-10-03", // added 2026-10 (U5f): no row ends after this date
"asked_period": { "kind": "this", "count": 0, "unit": "year", "year": 0, "month": 0, "age": 0, "from": "2026-10-05", "to": "2026-12-31", "clipped_to": null, "answer": "yes", "row_index": 0, "row": { /* same shape as rows[*] */ }, "after_index": null, "after_start": null }, // added 2026-10 (U5a): ONLY when the question names a period we recognise
"days_to_watch": {
"days": [ { "date": "2026-08-19", "strength": "strong", "row": 0 } ],
"note": null,
"note_code": null, // added 2026-10: "no_standout" | "frame_too_far" | null — a
// client-safe identity against "note"'s own free text
"star_lords": ["Saturn", "Mercury"] // added 2026-10: the planets this scan confirmed days against
}
},
"fruitful_star_days": [
{ "date": "2026-08-19", "strength": "strong",
"matched_steps": [ "Moon in the star of Saturn", "Moon in the sign of Mercury" ],
"fruitful_planets": [ "Saturn", "Mercury" ],
"reasoning": "…",
"stretch_start": "2026-08-19", "stretch_end": "2026-08-20" }
],
"fruitful_star_days_note": null,
"rp_confirmed_window": { "chain": "…", "start": "…", "end": "…" }, // deprecated, see below
"classification_confidence": "…",
"verified": true, // added 2026-10: true when the matter-and-intent check ran (or you passed locked_event_id)
"timezone": "Asia/Kolkata",
"timing": { /* unchanged Predictive Timing (MD/AD/PD/SD/PRD) breakdown -- NOT the new "timing_facts" object above */ },
"positions": { … }, "cusps": [ … ], "significators": [ … ]
}
verified. verified is true when the check ran on the question (or you named the matter with locked_event_id); it does not by itself mean a matter was found, and false when the reading rests on the engine's own match because the check could not run this time; treat a false as a lower-trust reading. The question is still judged and the chart is returned, and it counts as a question like any other. Success responses carry no ok field at all, so test for ok === false rather than for a truthy ok. A question that was checked and is not about anything a chart can judge returns 200 with { "ok": false, "reason": "off_topic", "retry": false, "verified": true, "message": "..." } and no cusp; do not retry the same text. Branch on reason: off_topic (reword) when ok is false.multi_question_notice is not an error. When the question contains more than one ask, the field is populated and you should surface it — the verdict then applies to the primary question only.fruitful_star_days is optional, local-dated and short-horizon only. Dates are on or after the question date, never before it, and only within about the next 3 months — windows above is always the answer regardless; treat these as up to 10 days, in at most 3 short stretches, worth watching inside it. Often empty ([]), in which case fruitful_star_days_note gives the customer-safe reason. Changed 2026-09-30: previously scanned from the running dasha period's own start (which could be years before the question) and rarely narrowed at all.windows's meaning. Now the SAME rows timing_facts.rows carries (MODERATE confidence or stronger), instead of every candidate window at any confidence.timing_facts.bands always lists nearest (running now, or starting within 2 years), mid (2 to 5 years) and long (5 to 10 years), each with its from/to dates and a row_index into rows (null when no clear period falls in that band). Each filled band carries one row, and rows[*].band names it. windows and timing_facts.rows are now at most these three rows, in band order, and no row ends after timing_facts.horizon_end (10 years after the question). Rows are scored on the matter's timing houses. When the matter is promised but all three bands are empty, answer is "no_clear_period" and no date is given. days_to_watch only ever refers to the nearest band's row (row 0). Additive: bands, horizon_end and rows[*].band are absent or null on birth-chart responses.timing_facts.rows[*].hold says when a period that would already have begun is held back. Every row carries the key (additive). It is null when the row is not held back. When the row is held back it is an object: until (ISO date, YYYY-MM-DD) is the day the period can first begin and is the same day the row's own start shows; planet is the planet whose backward motion causes the hold, named in English ("Saturn"); period_start (ISO date, or null) is the day the period would have begun without the hold; by is "lord" when the planet is the period's own ruling planet, or "sub" when it is the planet in whose sub-division that ruling planet sits. Show it as one plain line, for example "held back until 11 Dec 2026, when Saturn stops moving backward". When the held start falls inside the period a question asked about, asked_period.row carries the held start: its start moves to the held date, and asked_period.answer is unchanged. A period the chart does not let through is simply absent from rows; no key says why. The hold only appears when the service's timing setting for it is switched on, and a saved reading never changes. rp_confirmed_window (deprecated, below) is not screened or held: it always shows the unmodified window.timing_facts.asked_period answers the period the question asked about. It is present only when the question names a period in a recognised English phrase ("this year", "within six months", "in 2028", "soon"); otherwise the key is absent. It is read the same way on /api/horary and /api/v1/horary. kind (this, next, in, by, before, within, soon) with count/unit/year/month/age (age is always 0 here) says what was read; from and to are the period's first and last day in the place's local calendar. answer is "yes" (a period that switches the matter on overlaps it; row is the earliest such period), "no" (none does), "not_promised" (the matter is not promised in this chart) or "beyond" (the period lies past horizon_end, so the reading does not cover it). clipped_to is set when only part of the period lies inside the reading's 10 years (the date it was clipped to), and on "beyond" (to the horizon_end date). row has the same shape as rows[*]; row_index is null when that row is not one of rows, and after_index (for "no") points at the first shown row that starts after the period. after_start is that row's start, exactly as rows[after_index].start prints it (null when there is none, and for every answer other than "no"). rows, bands and answer are never changed by it. Additive, and absent from birth-chart responses.timing_facts is the one JSON shape every dated fact in this API now uses — see the identical timing_facts object on /api/v1/birth-chart for the full field-by-field description. rows[*].word is the ONLY confidence wording to render. days_to_watch is populated only when the frame actually narrows; days_to_watch.days[*].row indexes into rows above.days_to_watch.note_code and days_to_watch.star_lords. note_code is a client-safe identity against note's own free text — "no_standout", "frame_too_far", or null when days is populated. star_lords is the list of planets this request's own scan confirmed — the Moon's star lord on each returned day is always one of these.rp_confirmed_window is deprecated. Superseded by each row's own timing_facts.rows[*].confirmed_by. Still populated for one more release; do not build new integrations against it.Panchang
/api/v1/panchangThe five limbs for a date and place, plus the inauspicious and auspicious windows.
Request
curl -X POST https://kpstellarastro.com/api/v1/panchang \ -H "X-App-Id: $APP_ID" \ -H "X-App-Secret: $APP_SECRET" \ -H "Content-Type: application/json" \ -d '{ "date": "2026-08-13", "latitude": 13.0827, "longitude": 80.2707, "place": "Chennai, India" }'
Response 200 (real values)
{
"date": "2026-08-13",
"place": "Chennai, India",
"timezone": "Asia/Kolkata",
"utc_offset_hours": 5.5,
"vara": "Thursday",
"sunrise": "05:56",
"sunset": "18:29",
"tithi": { "index": 1, "name": "Pratipada", "paksha": "Shukla" },
"nakshatra": { "name": "…", "lord": "…" },
"yoga": { "name": "…" },
"karana": { "name": "…" },
"inauspicious": {
"rahu_kalam": { "start": "13:48", "end": "15:22" },
"yamagandam": { "start": "05:56", "end": "07:30" },
"gulika_kalam": { "start": "09:04", "end": "10:39" }
},
"auspicious": { … }
}
Compatibility
/api/v1/compatibilityAshtakoot Guna matching for two charts, scored out of 36, with each of the eight koots broken out separately.
Request body
| Field | Type | Notes | |
|---|---|---|---|
boy | object | required | A birth object, same fields as birth-chart |
girl | object | required | Likewise |
Request
curl -X POST https://kpstellarastro.com/api/v1/compatibility \ -H "X-App-Id: $APP_ID" -H "X-App-Secret: $APP_SECRET" \ -H "Content-Type: application/json" \ -d '{ "boy": { "birth_date": "1990-05-15", "birth_time": "10:30", "latitude": 13.0827, "longitude": 80.2707, "place": "Chennai, India" }, "girl": { "birth_date": "1992-08-02", "birth_time": "04:15", "latitude": 19.0760, "longitude": 72.8777, "place": "Mumbai, India" } }'
Muhurta
/api/v1/muhurta/eventsThe event ids /api/v1/muhurta accepts. Call this rather than hard-coding the list — it is engine-defined and would rot silently the first time an event is added or renamed.
Response 200
{ "events": [
{ "id": "property", "label": "Property" },
{ "id": "vehicle", "label": "Vehicle" }
, … also: foreign_travel, marriage, business, education,
higher_education, job, loan, surgery
] }
/api/v1/muhurtaAuspicious windows for a personal event, ranked, each carrying the reason it scored where it did.
| Field | Type | Notes | |
|---|---|---|---|
birth | object | required | Birth object, as above |
event_name | string | required | An id from /api/v1/muhurta/events |
max_windows | integer | optional | 1–20, default 8 |
from_date, to_date | string | optional | YYYY-MM-DD. Filters which qualifying windows come back |
viewer_utc_offset_hours | number | optional | −12 to 14. The UTC offset of the person reading the result. Defaults to the birth place's offset |
from_date in the past does not reopen this. "Today" means the reader's calendar day: send viewer_utc_offset_hours if your user may be in a different time zone from the chart's birth place, or dates that are yesterday to them can appear as today.from_date/to_date filter windows that already qualify; they do not relax the promise, confidence or Moon-transit requirements to fill an empty range. An empty result means no window qualified — present it as such rather than widening the range until something appears./api/v1/muhurta/check-dateJudge one real-life date for a personal event: a date the person was handed (an interview, a registration slot) rather than one they chose. Returns the running period lords and whether each signifies the matter, the Moon's lords that day, the day's ruling planets against the matter, panchanga notes and the best hora, and one plain verdict.
| Field | Type | Notes | |
|---|---|---|---|
birth | object | required | Birth object, as above |
event_name | string | required | An id from /api/v1/muhurta/events |
check_date | string | required | YYYY-MM-DD. Today (the reader's calendar day) or later, up to the same calendar date 30 years ahead. Events on the sensitive-matter list (health, surgery, deaths and similar) are refused with 400 |
viewer_utc_offset_hours | number | optional | −12 to 14. Decides what "today" is. Defaults to the birth place's offset |
Response 200 (abridged)
{ "verdict": "supporting", // supporting | neutral | outside | not_promised
"headline": "A supporting day",
"period": { "inside": true, "all_lords_favour": true, "lords": [ … ] },
"moon": { "shown": true, "lords": [ … ] },
"ruling_planets": { "endorsing_count": 2, … },
"panchanga": { … }, "hora": { … }, "cautions": [ … ] }
moon.shown is false when the Moon's day-by-day grade does not narrow anything for this chart, and "supporting" is never returned on signals that would pass on nearly every day. and "supporting" additionally needs every running Dasa/Bhukti/Antara lord to signify the matter. A matter the chart does not promise returns verdict: "not_promised" with event_label, date, headline and a plain-language reasoning; period, moon, ruling_planets and the rest are absent. ruling_planets.informative is false when the ruling planets endorse the matter on nearly every day for this chart.TSP quick question
/api/v1/tspTheory of Short Prediction — a fast single-question read from its own 2193-segment number table. Deliberately narrower than horary, and not a substitute for it: no chart wheel is produced.
| Field | Type | Notes | |
|---|---|---|---|
tsp_number | integer | required | 1–2193 — not the 1–249 horary number |
judgment_date | string | required | YYYY-MM-DD |
judgment_time | string | required | HH:MM |
latitude, longitude | number | required | Where the question is asked |
query | string | optional | The question text, echoed back for your own labelling |
name | string | optional | Echoed back |
place | string | optional | Resolves the time zone |
query is optional and is echoed back rather than parsed — unlike horary, where the question is classified to pick the cusp. Do not infer from a TSP response that the question text was read.curl -X POST https://kpstellarastro.com/api/v1/tsp \ -H "X-App-Id: $APP_ID" -H "X-App-Secret: $APP_SECRET" \ -H "Content-Type: application/json" \ -d '{ "tsp_number": 137, "query": "Will I get the job?", "judgment_date": "2026-08-13", "judgment_time": "14:20", "latitude": 13.0827, "longitude": 80.2707, "place": "Chennai, India" }'
Cuspal interlinks
/api/v1/cuspal-interlinksCuspal Interlinks — the star / sub / sub-sub chain linking a cusp to the houses that fulfil it. Same request body as birth-chart. This is the technique behind the "why" panel in the web app, exposed on its own.
Question cusp
/api/v1/question-cuspWhich house cusp a horary question is judged from, and which houses must be signified — the same classification horary runs internally, returned directly with no chart cast. Useful for previewing how a question will be judged before spending a horary number on it.
reading_basis. POST /api/v1/question-cusp may include reading_basis, a short string saying how the reading was reached. It is present only when our question-reading service is in use for the call, and absent otherwise (so a response without it is normal). Current values: exact_keyword, keyword_conflict, engine_specific, engine_kept, seed, lexicon_alias, alias_cell, composed, llm_decided, last_resort or grammar_unavailable. The list may grow: treat an unknown value as informational, never as an error. A question that is not about anything a chart can judge is still answered with the unchanged "reason": "off_topic" body, which carries no reading_basis. tag_pick in the request is ignored, and picked never appears in reading_basis.Request body
| Field | Type | Notes | |
|---|---|---|---|
question | string | required | Up to 2000 characters. |
in_india | boolean | optional | Default true. Disambiguates domestic vs. foreign travel questions only — has no other effect on classification. |
curl -X POST https://kpstellarastro.com/api/v1/question-cusp \ -H "X-App-Id: $APP_ID" -H "X-App-Secret: $APP_SECRET" \ -H "Content-Type: application/json" \ -d '{"question": "will I win the lottery"}'
Response 200
{ "ok": true,
"event_id": "lottery",
"category": "Lottery / Prize Bond Win",
"primary_cusp": 5,
"required_houses": [5, 9, 10, 11],
"verified": true,
"multi_question_notice": null,
"notice_kind": null,
"doctrine": { "corroboration": "mixed", "note": "Classical texts admit more than one reading here" } }
Added 2026-10: verified is true when the matter-and-intent check ran, and false when the cusp rests on the engine's own match because the check could not run this time; the question is still judged. Success responses carry no ok field, so test for ok === false. A question that was checked and is not about anything a chart can judge returns 200 with { "ok": false, "reason": "off_topic", "retry": false, "verified": true, "message": "..." } and no cusp; do not retry the same text.
A question that fails validation (gibberish, too short, not a real question) returns 200 with "ok": false and a message — not an error status, since asking "can this be judged?" isn't itself a failure.
Descriptive Indicators
/api/v1/descriptiveTable-lookup traits about whatever the question's cusp actually describes — a person, a place, or a matter — plus, where the event supports it, a facet reading: which of several recognised shapes (a court case settling vs. being appealed vs. a favorable judgment; a property matter being a purchase vs. a sale vs. a tenancy) the chart leans toward. Both are advisory evidence read off the cuspal sub-lord and its star lord — never a PROMISED/DENIED verdict, and never invented: a chart that shows nothing returns an empty list, which is a legitimate, common answer. Same table lookup the website's own Descriptive Indicators panel uses, on the caller-supplied chart in this request rather than a signed-in account's saved one — but this endpoint decides which cusp a question is about by deterministic word matching only, with no AI reading of the question, while the website may use an AI-checked reading for the same wording. So the same question can land on a different cusp here than it does on the site.
Added 2026-10: a question that matches no recognised topic is not described. Because this endpoint does no AI reading, it cannot work out what such a question is about, so it returns 200 with "cusp": 0, empty indications and can_ask, and an empty event and subject, rather than a general reading of the 11th house. Name the matter in the question to get a description. A question that is not about the querent's life at all returns the same shape with "off_topic": true.
Request body
| Field | Type | Notes | |
|---|---|---|---|
mode | string | optional | Default "BIRTH". "HORARY" uses the horary object below instead of birth. |
question | string | required for BIRTH mode | The judged question — fixes which cusp is being described. For HORARY mode the question travels inside horary instead. |
ask | string | optional | A specific descriptive question ("what will their profession be") to filter to. Omit to get every indication the chart supports plus the list of questions that CAN be asked (can_ask). |
birth | object | required for BIRTH mode | Same shape as /api/v1/birth-chart's body. |
horary | object | required for HORARY mode | Same shape as /api/v1/horary's body. |
curl -X POST https://kpstellarastro.com/api/v1/descriptive \ -H "X-App-Id: $APP_ID" -H "X-App-Secret: $APP_SECRET" \ -H "Content-Type: application/json" \ -d '{"mode": "BIRTH", "question": "will there be a court case over the property?", "birth": {"name": "Test", "birth_date": "1990-06-15", "birth_time": "10:30", "latitude": 12.9716, "longitude": 77.5946, "place": "Bengaluru"}}'
Response 200
{ "subject": "the opposing party",
"cusp": 6,
"is_person": false,
"indications": [ ... ],
"can_ask": [ { "kind": "work", "question": "What kind of matter is this?" }, ... ],
"event": "Litigation",
"facets": [
{ "id": "settlement", "label": "settlement / compromise",
"agreement": 1, "because": "the 6th cuspal sub-lord Saturn and its star lord Sun between them signify house 7" }
] }
facets is empty for events with no defined facet table, or when the chart shows none of a facet's houses. It is a small, growing set — currently litigation, bail, property, health, foreign travel, loan, donation/charity, promotion, workplace trouble, vehicle, surgery, missing person, stock market, inheritance, gratuity/insurance, and lost property/theft recovery.
/api/v1 endpoint.Financial astrology
Four endpoints for money questions — an entity chart from a founding moment, its key windows, a head-to-head comparison of two candidate moments, and the astronomical events in a range.
/api/v1/financial/chartAn entity chart: a company from its incorporation moment, a listing, a deal. Takes birth (the moment) and event_name.
/api/v1/financial/muhurtaKey time windows for a financial event. Same body, plus optional max_windows.
/api/v1/financial/compareRank two candidate moments against each other. Takes original, candidate (each wrapping a birth object) and event_categories.
/api/v1/financial/eventsAstronomical events in a date range. Query parameters start_date and end_date, both YYYY-MM-DD.
curl "https://kpstellarastro.com/api/v1/financial/events?start_date=2026-08-01&end_date=2026-08-31" \ -H "X-App-Id: $APP_ID" -H "X-App-Secret: $APP_SECRET"
Question classification (session auth)
Part of the same developer subscription as /api/v1 above, but authenticated differently: it does not take X-App-Id / X-App-Secret at all. It uses a signed-in session bearer token instead — the same token the website itself uses — so a standalone server integration authenticating purely by App-Id/Secret cannot call it; the caller must be a signed-in account with active developer access.
/api/question/cuspWhich house cusp a horary question is judged from, and which houses must be signified — the same classification /api/horary runs internally, returned directly with no chart cast. Useful for previewing how a question will be judged before spending a horary number on it.
Auth
| Header | Value |
|---|---|
Authorization | Bearer <session token> — the same token a signed-in browser session uses, obtained via the site's own sign-in flow, not the developer portal. |
/api/v1 endpoints above (₹1,999 / 30 days) — no separate charge. A signed-in account without active developer access gets 402; there is no free or unauthenticated tier for this endpoint.Request body
| Field | Type | Notes | |
|---|---|---|---|
question | string | required | Up to 2000 characters. |
in_india | boolean | optional | Default true. Disambiguates domestic vs. foreign travel questions only — has no other effect on classification. |
curl -X POST https://kpstellarastro.com/api/question/cusp \ -H "Authorization: Bearer YOUR_SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{"question": "will I win the lottery"}'
Response 200
{ "ok": true,
"event_id": "lottery",
"category": "Lottery / Prize Bond Win",
"primary_cusp": 5,
"required_houses": [5, 9, 10, 11],
"multi_question_notice": null,
"notice_kind": null,
"doctrine": { "corroboration": "mixed", "note": "Classical texts admit more than one reading here" } }
A question that fails validation (gibberish, too short, not a real question) returns 200 with "ok": false and a message, the same shape /api/question/check uses — not an error status, since asking "can this be judged?" isn't itself a failure.
Birth-time proof (signed-in app, session auth)
Used by the website itself. These take the signed-in session bearer token (Authorization: Bearer <session token>), not X-App-Id / X-App-Secret, and are not part of the developer subscription: they read and write one person's stored life events. They are free to use (no credits). Adding events and the proof follow the Birth Rectification on/off switch (503 when it is off); removing an event is never switched off. Events can only be added to a birth chart (400 otherwise); dates use ASCII digits only. The events are personal data: owner-only, never sent to an AI model, deleted with the chart or the account, and included in Download my data.
/api/charts/{chart_id}/known-eventsRecord one dated life event on a saved chart. Body: category (one of marriage, children, job, promotion, business, property, vehicle, foreign_settlement, foreign_travel, higher_education; there is no free text), date (YYYY-MM or YYYY-MM-DD; not in the future, not before the birth date), optional source (manual, life_events or unverified_card). Returns the stored item {id, category, date, source, added_at}. 400 invalid category/date, 404 not your chart, 409 duplicate or more than 12 events on the chart, 429 over 60 adds per hour.
/api/charts/{chart_id}/known-events/{event_id}Remove one recorded event. Returns {"deleted": true}; 404 if it is not yours.
/api/charts/{chart_id}/birth-time-proofDoes the saved birth chart's time fit the person's own dated events (known events, plus tracked predictions answered "it happened" with a month)? Computed on read with the Birth Rectification scorer; nothing is stored. Returns status (matches, mismatch or not_enough; two or more scorable events are needed), scored, matched, approximate (some dates are month-only), suggest_rectify, max_shift_minutes (30), birth_time_confidence and events (id, title, when, matched, source). Birth charts only (400 otherwise). Limited to 30 calls per 5 minutes per account.
/api/predictions/{prediction_id}Records the outcome of a tracked prediction (outcome: happened, did_not_happen, happened_later, too_early or reopen; optional note). New optional field outcome_month (YYYY-MM, not in the future): the month it happened. Accepted only with outcome = happened or happened_later, or alone on a row already answered either way, and only for prediction kinds the birth-time proof can score (others get 400). Answering happened_later keeps a stored month; changing the outcome to anything else, or reopen, clears it (a reopened row loses its month). Never included in any aggregate or public figure.
Saved charts returned by GET /api/charts/{chart_id} include known_events, as do PUT /api/charts/{chart_id} and POST /api/charts/{chart_id}/apply-rectified-time; the list view GET /api/charts omits it. GET /api/auth/export-data now also returns tracked_predictions (with outcome_month) and muted_topics.
Calendar export (website session)
/api/me/calendar.icsWebsite session endpoint, the same family as /api/me/home: it needs a signed-in session bearer token, it is not part of the developer /api/v1 family and takes no X-App-Id / X-App-Secret. It exports the caller's own followed questions as an iCalendar file: each followed period as an all-day span, each stored day to watch as an all-day event (only when the Moon-note switch is on), and one all-day "did it happen?" check-in (only when the closing switch is on) the day after a period closes, or dated today when the period has already closed and is still unanswered. A period with no end date is one all-day event on its start date, or on today while it runs. Day and check-in events carry a morning display alarm. Event ids are keyed hashes and the link is the Home page, so no stored id appears in the file; SEQUENCE rises with each download so changes replace earlier events. Past dates, dismissed, answered, snoozed and muted-topic rows are left out, and so are health, longevity and death matters. No chart is cast, no model is called and no credit is used.
Query
| Parameter | Type | Notes |
|---|---|---|
titles | boolean | Default false: event titles are neutral ("KP window: a question you follow"). true adds the stored matter title. The question text, birth details and names are never written. |
today | string | The caller's local YYYY-MM-DD; used only within one day of UTC, as on /api/me/home. |
curl "https://kpstellarastro.com/api/me/calendar.ics?titles=false" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" -o kp-followed-windows.ics
Returns 200 with text/calendar and a Content-Disposition attachment named kp-followed-windows.ics. 401: not signed in. 404: nothing to add yet (no followed question with a current or future date). 429: more than 30 downloads an hour per account.
Claude connector (MCP, OAuth)
A remote Model Context Protocol server, so Claude (and other MCP clients) can call the KP engine on a signed-in person's behalf. It is its own authentication family: OAuth 2.1 with PKCE (S256) and a Bearer access token, not X-App-Id / X-App-Secret, and not part of the developer /api/v1 subscription. The person signs in with their KP Stellar account on the website's consent page; the client then holds a token that is only good for /mcp (a website session token is never accepted there, and an MCP token is never a session token). Scope: kp.read. All sixteen tools are read-only.
https://kpstellarastro.com/mcp, sign in and allow. A plain-words setup guide with prompts to copy is at /use-in-claude. The endpoints below are for client authors.| Item | Value |
|---|---|
| Access token | Opaque, valid 1 hour. Refresh tokens rotate on every use and last 30 days; a connection ends after 180 days and the person consents again. |
| Request limits | 60 calls a minute per account on /mcp (429 with Retry-After), plus a free daily allowance per account (resets at midnight IST) that the connector's tools count against. Over the allowance a tool returns a normal tool error with the upgrade link; initialize and tools/list are free. |
| Credits | The connector's engine tools use no credits today. Credit packs work on the same account as on the website. |
| Redirect URIs | https://claude.ai/api/mcp/auth_callback, or loopback http://localhost / 127.0.0.1 / [::1] with a path (any port) for local clients. |
/mcpThe MCP endpoint (Streamable HTTP, JSON-RPC 2.0). Also answers at /mcp/. Stateless: no session id is issued or needed, so any call can be the first. Send Authorization: Bearer <access token>, Content-Type: application/json and Accept: application/json, text/event-stream. Methods: initialize, tools/list (16 tools, each marked readOnlyHint: true) and tools/call. Replies are plain JSON.
GET and DELETE return 405 (there is no server-to-client stream). 401 without a valid token, with WWW-Authenticate: Bearer resource_metadata="https://kpstellarastro.com/.well-known/oauth-protected-resource", scope="kp.read" (and error="invalid_token" when a token was sent). 503 while the connector is switched off. 429 over 60 calls a minute.
curl -X POST https://kpstellarastro.com/mcp \ -H "Authorization: Bearer YOUR_MCP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"birth_chart","arguments":{"birth_date":"1990-05-15","birth_time":"10:30","latitude":13.0827,"longitude":80.2707}}}'
/.well-known/oauth-protected-resourceRFC 9728 metadata. Returns resource (https://kpstellarastro.com/mcp), authorization_servers and scopes_supported. The path-suffixed form /.well-known/oauth-protected-resource/mcp returns the same document. No auth.
/.well-known/oauth-authorization-serverRFC 8414 metadata: issuer, the authorize, token, registration and revocation endpoints, code_challenge_methods_supported: ["S256"], public clients only (token_endpoint_auth_methods_supported: ["none"]), client_id_metadata_document_supported and authorization_response_iss_parameter_supported both true. No auth.
/oauth/registerDynamic client registration (RFC 7591). JSON body: redirect_uris (required; the allowed forms are in the table above) and optional client_name. Public clients only; asking for a client secret returns invalid_client_metadata. 201 with client_id; a bad redirect URI returns 400 invalid_redirect_uri. Clients may instead use an HTTPS client-metadata-document URL as their client_id. Limited per IP.
/oauth/authorizeAuthorization endpoint; a browser page, not an API call. Query: client_id, redirect_uri, response_type=code, code_challenge with code_challenge_method=S256 (required), state, resource=https://kpstellarastro.com/mcp and scope=kp.read. It serves the consent page, which signs the person in with their KP Stellar account, shows what is being allowed and returns to the client's redirect_uri with code, state and iss (or error=access_denied). Codes are single use and expire after 60 seconds. The page cannot be framed.
/oauth/tokenForm-encoded (application/x-www-form-urlencoded). grant_type=authorization_code with client_id, code, code_verifier, redirect_uri and resource; or grant_type=refresh_token with client_id and refresh_token. Returns access_token, refresh_token, token_type: "Bearer", expires_in and scope. Refresh rotates both tokens. Replaying an already-used refresh token returns 400 invalid_grant, and if the replay comes after a few seconds the whole connection is ended as presumed theft. Errors use the standard OAuth error / error_description body. Limited per IP.
/oauth/revokeRFC 7009. Form body token (access or refresh) and client_id. Always answers 200, whether or not the token existed; revoking either token ends the whole connection.
/api/mcp/connectionsThe signed-in account's connected apps. This one takes the website session bearer token (Authorization: Bearer <session token>), not an MCP token. Returns {"connections": [{"id", "client_name", "redirect_host", "scope", "connected_at", "last_used_at"}]}, newest first. 401 without a session. Limited to 60 calls a minute (429).
/api/mcp/connections/{id}/revokeDisconnect one app (session bearer token). Ends its access and refresh tokens at once, so the app's next /mcp call is 401. Returns {"revoked": true}; 404 if the id is not this account's. Signing out everywhere or changing the password on the website also ends every connection.
Privacy: the connector keeps a usage record of the account, the tool name, the time and a status, and nothing else (no birth details, places, questions or IP address).
Public endpoints (no key)
A few read-only, non-personal endpoints back the free tool pages and need no App ID/Secret — call them directly from a browser or your own script. They are throttled per IP (see limits) rather than per app, since there is no key to count against.
GET /api/public/ruling-planets
The classical KP Ruling Planet set (the local sunrise-to-sunrise day lord, Ascendant and Moon sign/star/sub lords, ranked Ascendant sub, Ascendant star, Ascendant sign, Moon sub, Moon star, Moon sign, day lord) cast for this moment at a place, with factors (which planet fills each of those seven places, keyed LAGNA_SUB, LAGNA_STAR, LAGNA_SIGN, MOON_SUB, MOON_STAR, MOON_SIGN, DAY_LORD, in that order; ruling_planets is these with repeats removed), Rahu/Ketu listed as node_agents when they act for a ruling planet and per-planet flags (retrograde delays, in_retro_star is a downgrade, not a rejection), plus today's KP ayanamsa and sky, where the twelve cusps and nine planets stand at that moment (cusp_lons: 12 sidereal longitudes, house 1 first; cusp_lords: [sign lord, star lord, sub lord] for each cusp; positions: per planet longitude, retrograde, star_lord, sub_lord and house), read off the same chart. Powers /ruling-planets and the embeddable widget.
| Param | Type | Required |
|---|---|---|
latitude | float | yes |
longitude | float | yes |
place | string | no — echoed back, not used in the calculation |
GET /api/public/ruling-planets?latitude=13.0827&longitude=80.2707&place=Chennai
{
"place": "Chennai",
"timezone": "Asia/Kolkata",
"utc_offset_hours": 5.5,
"local_time": "2026-09-15 14:32",
"day_lord": "Venus",
"moon": { "sign": "Taurus", "star_lord": "Moon", "sub_lord": "Rahu" },
"ascendant": { "sign": "Libra", "star_lord": "Saturn", "sub_lord": "Mercury" },
"ruling_planets": ["Mercury", "Saturn", "Venus", "Rahu", "Moon"],
"factors": { "LAGNA_SUB": "Mercury", "LAGNA_STAR": "Saturn", "LAGNA_SIGN": "Venus", "MOON_SUB": "Rahu", "MOON_STAR": "Moon", "MOON_SIGN": "Venus", "DAY_LORD": "Venus" },
"node_agents": ["Rahu"],
"flags": { "Mercury": { "retrograde": false, "in_retro_star": false }, "Saturn": { "retrograde": false, "in_retro_star": false }, "Venus": { "retrograde": false, "in_retro_star": false }, "Rahu": { "retrograde": false, "in_retro_star": false }, "Moon": { "retrograde": false, "in_retro_star": false } },
"kp_ayanamsa": 24.156789,
"generated_at": "2026-09-15T09:02:11.123456+00:00",
"sky": { "cusp_lons": [12 sidereal longitudes, house 1 first], "cusp_lords": [["Venus", "Jupiter", "Saturn"], "...eleven more"], "positions": { "Sun": { "longitude": 183.2, "retrograde": false, "star_lord": "Mars", "sub_lord": "Saturn", "house": 1 }, "...the other eight planets" } }
}
GET /api/public/where-am-i
The visitor's own coarse, city-level place, from the site's local GeoIP database (no third-party call; the address itself is never returned or stored). The app uses it to pre-fill the Horary place when the visitor has no saved place. No parameters. Per visitor, so never cached (Cache-Control: no-store), and throttled per IP like the other public endpoints. detected is false, with no other field, when the database cannot place the visitor (private network, unknown address, not configured).
GET /api/public/where-am-i
{
"detected": true,
"city": "Pune",
"region": "Maharashtra",
"country": "India",
"latitude": 18.52,
"longitude": 73.86,
"timezone": "Asia/Kolkata"
}
GET /api/public/kp-sub-lord-table
The full 243-row zodiac → nakshatra → sub-lord reference table, straight from the engine's recursive subdivision — not location or time dependent, so the same response every time. Powers /kp-sub-lord-table.
No parameters. Returns {"count": 243, "rows": [...]}", each row: sign, sign_lord, nakshatra, pada, star_lord, sub_lord, start_dms/end_dms and their float-degree equivalents.
GET /api/public/kp-horary-number
The full 1-249 KP horary number reference table, straight from kp_astro.horary.horary_number.horary_table() — the same table the horary judgment endpoints use internally. Powers /kp-horary-number.
No parameters. Returns {"count": 249, "rows": [...]}", each row: number, sign, sign_lord, nakshatra, star_lord, sub_lord, start_dms/end_dms.
GET /api/desktop-app/latest-version
The desktop app's startup check for a newer build. Public, no auth. Returns latest_version (null until one is published), release_notes and verdict_basis — "v1" or "v2", an opaque label for the verdict basis currently in force (treat it as an opaque string; it never names the underlying method).
GET /api/prediction-diary
The public KP Prediction Diary — anonymised horary judgments published before their outcome is known, plus a running count of how many resolved inside their window. Powers /predictions.
No parameters. Returns count, unresolved_count, resolved_count, hit_count, hit_rate (null until at least one entry is resolved) and items — each with question, horary_number, category, verdict, confidence, windows, outcome and its immutable content_hash.
horizon_years (10) and each item in its windows carries a band ("nearest", "mid" or "long"), at most one window per band and in that order; windows is empty when no clear period falls within the 10 years. Entries published before this change have neither field and are unchanged. horizon_years is part of the hashed content_hash material only when present, so every earlier entry still verifies.What is not on the API
Three things the web app does are deliberately absent, and will stay absent until each has an answer to its own problem rather than being bolted onto this subscription:
| Not exposed | Why |
|---|---|
| Birth-time rectification (auto-search) | By far the heaviest operation on the platform — seconds per request, scoring many candidate birth times, each a full chart plus significators plus dasha. In the web app it is signed-in only and already held to roughly 4 calls/minute per account with at most 3 running concurrently site-wide. The developer tier's 60/minute is an order of magnitude beyond that, and the concurrency cap means one integration looping it would starve real users. It needs its own pricing and its own limits, not this subscription's. |
| AI explanation | Costs real money per call against a third-party model. It belongs on the credit system, not a flat monthly fee. |
| Saved charts, clients, tracked predictions | App credentials authenticate an application, not one of its end users. Serving a specific person's stored data through them would make one developer key a master key to their whole user base — that needs a per-user authorization flow, which is separate work. |
Errors
Errors are JSON with a detail string. The status code tells you which problem you have, and they are deliberately distinct — do not collapse them into one retry path.
| Status | Meaning | What to do |
|---|---|---|
401 | Invalid App ID or App Secret, or the app was revoked | Check both headers. Rotate the secret if it may have leaked. Do not retry — it will not start working |
402 | Developer access has expired | Renew in the portal. Your keys are still valid; only the subscription lapsed |
422 | The payload failed validation | Read field_errors — it names the exact field and problem |
429 | Over the rate limit | Back off and retry. See limits |
503 | The endpoint is temporarily disabled | Retry later; this is a server-side switch, not your request |
A validation error, verbatim
{
"detail": "Date is required.",
"field_errors": [
{ "field": "judgment_date", "message": "Date is required." },
{ "field": "judgment_time", "message": "Time is required." }
]
}
Rate limits
| Public endpoints (no key) | Developer API | |
|---|---|---|
| Access | Website endpoints only — no /api/v1 | All /api/v1 endpoints |
| Per minute | 10 | 60 6× more |
| Per day | 100 | 1,000 10× more |
| Counted against | Your IP, shared with everyone on the same network | Each app separately, up to 5 apps |
The per-app scope is often the bigger win: five apps at 1,000 calls each is 5,000 a day, and none of them is affected by other traffic from your network.
Limits are counted per app, not per IP, so several developers behind one office network do not throttle each other, and running your integration from a serverless platform with rotating addresses is fine. Create a separate app per environment (staging, production) to keep their budgets independent — you can have up to five.
Going over returns 429. Back off and retry; nothing is banned or charged extra.
Pricing
| Plan | Price | Includes |
|---|---|---|
| Developer access | ₹1,999 / 30 days | All /api/v1 endpoints, up to 5 apps, 1,000 calls per app per day |
Billed as a one-off 30-day period, not an auto-renewing mandate — nothing recurs without you buying it again. Renewing before expiry adds to the time you have left rather than replacing it. When the period ends, calls answer 402 and your apps are not deleted: renew and the same credentials resume working.
Notes & guarantees
- Same engine as the website. These endpoints are thin authenticated wrappers over the exact handlers the app itself calls, so the API cannot drift from what the site shows.
- Deterministic. Once the first check for a question has been remembered, the same input returns the same verdict every time. The only language-model step is the single matter-and-intent check described below, which decides which matter a question is about; it never produces a figure, a date or a verdict.
- Every question is checked, once. Deciding which matter a question is about (and therefore which cusp it judges from) runs on the keyword and grammar tiers, and then a single matter-and-intent check confirms or corrects that reading. Nothing else about your call uses a language model. The check is remembered, so a question already asked (here or on the website) is not checked again. Changed 2026-10: this used to be keyword and grammar only. The response field
verifiedsays whether the check ran:truewhen it did, or when you named the matter withlocked_event_id(no check is needed then);falsewhen the reading rests on the engine alone because the check could not run this time (a short outage, for example). When the check cannot run, the question is still judged on the engine's own reading, withverifiedset tofalse. If you need a specific matter, name it: passlocked_event_id(for example"job_change","property_sale") on/api/v1/horaryand that classification is used directly, with no keyword matching involved. Call/api/v1/question-cuspfirst if you want to see what a question resolves to before casting a chart. A question that was checked and is not about anything a chart can judge is answered with"reason": "off_topic"and no cusp (do not retry the same text). - Nothing is stored from your API calls. Charts computed through
/api/v1are not saved to any account; we record only the call count and last-used time against your app. - Time zones are historical. Supplying
placeresolves the zone that was actually in force on that date, including past DST rules — this matters for a 1970s birth time. - Birth time precision. KP is minute-sensitive; the cuspal sub-lord can change within four minutes. If your users supply approximate times, present the results with that caveat.
- Versioning. Breaking changes go to a new path prefix.
/api/v1keeps its response shape.