# Navapatra API documentation > Navapatra is a REST API for kundali (Vedic birth charts), dashas, guna milan, daily panchang and the Nepali Bikram Sambat calendar, for apps in Nepal and India. Base URL https://api.navapatra.com/v1, auth `Authorization: Bearer `, dated versions via the `Navapatra-Version` header (newest 2026-11-01). The public key `demo` works for the sample subjects listed by GET /v1/meta. Every page below is also available as Markdown at the linked URL. Responses are JSON; errors are RFC 9457 problem details with a stable `code`. --- # Overview > Kundali, dashas, guna milan, panchang and Bikram Sambat dates from one REST API for apps in Nepal and India, with every convention named in the response. Navapatra is a JSON API at `https://api.navapatra.com/v1`. Send `Authorization: Bearer `, get JSON back. Start with the public `demo` key: it needs no signup and works for five bundled sample births. ## Start here - [Quickstart](https://navapatra.com/docs/quickstart/): Make a real call in a minute with the demo key, in curl, TypeScript, Python or PHP. - [Authentication and keys](https://navapatra.com/docs/authentication/): Secret, test, publishable and client-token keys, and when to use each. - [Mobile apps](https://navapatra.com/docs/guides/backend-proxy/): Keep secret keys on your server and hand short-lived tokens to the app. - [Conventions](https://navapatra.com/docs/conventions/): Five ayanamshas, node type, sunrise and tradition, and what meta echoes back. ## Build - [Kundali](https://navapatra.com/docs/guides/kundali/): D1, D9 and all vargas, nakshatra and pada, birth panchang and sensitivity flags. - [Dashas](https://navapatra.com/docs/guides/dashas/): Vimshottari, Yogini and Tribhagi timelines to three levels, with asOf. - [Matching](https://navapatra.com/docs/guides/matching/): Ashtakoota guna milan for one pair or a hundred, and rashi compatibility. - [Panchang and calendar](https://navapatra.com/docs/guides/panchang/): Daily panchang anywhere, choghadiya, BS month grids and Nepal Sambat. - [Bikram Sambat dates](https://navapatra.com/docs/bikram-sambat/): Convert between BS and AD, and what published and projected mean. - [Charts and SVG theming](https://navapatra.com/docs/guides/charts/): North and South Indian charts you can theme with CSS variables. ## Run it in production - [Credits and metering](https://navapatra.com/docs/credits/): What costs a credit, the monthly free grant and how lots are spent. - [Errors](https://navapatra.com/docs/errors/): Every error code the API returns, with its status and the fix. - [Versioning](https://navapatra.com/docs/versioning/): Dated versions, pinning and why data updates are not pinned. - [Accuracy](https://navapatra.com/docs/accuracy/): How the engine is tested against Swiss Ephemeris, the latest results and the public corpus. ## For AI tools Every page is also plain Markdown: add `.md` to its path, for example [/quickstart.md](https://navapatra.com/docs/quickstart.md). [llms.txt](https://navapatra.com/docs/llms.txt) lists them all and [llms-full.txt](https://navapatra.com/docs/llms-full.txt) has the whole site in one file, including the API reference. --- # Quickstart > Make your first call with the public demo key, read the response, then switch to your own test key. Every call is an HTTPS request to `https://api.navapatra.com/v1` with a key in the `Authorization` header. You can make a real one right now, before you sign up, with the public key `demo`. ## 1. Make a call with the demo key The demo key accepts five bundled sample births (listed below) and allows 10 requests per minute per IP address. It is not tied to any account and never uses credits. This request computes the kundali of the Pokhara sample, born on Bhadra 12, BS 2050 at 14:35 local time. `POST /v1/kundali` (credits: 1) **curl** ```bash curl https://api.navapatra.com/v1/kundali \ -H "Authorization: Bearer demo" \ -H "Content-Type: application/json" \ -d '{ "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": { "lat": 28.2096, "lng": 83.9856 } } }' ``` **TypeScript** ```ts // quickstart.ts (Node 18+, Deno or Bun; fetch is built in) const res = await fetch('https://api.navapatra.com/v1/kundali', { method: 'POST', headers: { Authorization: 'Bearer demo', 'Content-Type': 'application/json', }, body: JSON.stringify({ birth: { calendar: 'BS', date: '2050-05-12', time: '14:35', place: { lat: 28.2096, lng: 83.9856 }, }, }), }); if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`); const kundali = await res.json(); console.log(kundali.lagna.sign.name, kundali.moon.nakshatra.name); ``` **Python** ```python # quickstart.py (pip install requests) import requests res = requests.post( "https://api.navapatra.com/v1/kundali", headers={"Authorization": "Bearer demo"}, json={ "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": {"lat": 28.2096, "lng": 83.9856}, } }, ) res.raise_for_status() kundali = res.json() print(kundali["lagna"]["sign"]["name"], kundali["moon"]["nakshatra"]["name"]) ``` **PHP** ```php post('https://api.navapatra.com/v1/kundali', [ 'headers' => ['Authorization' => 'Bearer demo'], 'json' => [ 'birth' => [ 'calendar' => 'BS', 'date' => '2050-05-12', 'time' => '14:35', 'place' => ['lat' => 28.2096, 'lng' => 83.9856], ], ], ]); $kundali = json_decode((string) $res->getBody(), true); echo $kundali['lagna']['sign']['name'], ' ', $kundali['moon']['nakshatra']['name'], PHP_EOL; ``` > **Tip: Prefer a typed client?** > > `@navapatra/sdk` wraps every endpoint with generated types, retries and metering headers, installed with `npm install @navapatra/sdk` once it is published to npm. See the [TypeScript SDK](https://navapatra.com/docs/guides/typescript-sdk/) guide. In Python and PHP, the plain HTTP examples above are all you need. ## 2. Read the response The call prints `Dhanu Uttara Ashadha`: the lagna (ascendant) is in Dhanu and the Moon is in the Uttara Ashadha nakshatra. Here is the shape of the full response, shortened: ```json { "subject": { "instantUtc": "1993-08-28T08:50:00Z", "place": { "lat": 28.2096, "lng": 83.9856 }, "tzid": "Asia/Kathmandu", "utcOffset": "+05:45", "offsetSource": "tzdb", "birth": { "ad": "1993-08-28", "bs": "2050-05-12" } }, "lagna": { "sign": { "index": 9, "name": "Dhanu" }, "longitude": 245.8854, "nakshatra": { "index": 19, "name": "Mula" }, "pada": 2 }, "grahas": [ { "id": "sun", "name": "Sun", "sign": { "index": 5, "name": "Simha" }, "longitude": 131.2923, "speed": 0.9658, "house": 9, "retrograde": false, "nakshatra": { "index": 10, "name": "Magha" }, "pada": 4 } ], "vargas": { "D1": { "...": "..." }, "D9": { "...": "..." } }, "moon": { "rashi": { "index": 9, "name": "Dhanu" }, "nakshatra": { "index": 21, "name": "Uttara Ashadha" }, "pada": 1, "syllable": { "index": 81, "en": "Bhe", "ne": "भे", "hi": "भे" } }, "dashas": { "vimshottari": { "...": "..." } }, "meta": { "engineVersion": "0.1.0", "apiVersion": "2026-11-01", "ayanamsha": { "id": "lahiri", "degrees": 23.773159 }, "nodeType": "mean", "tradition": "nepal", "sunriseConvention": "upper_limb_refracted", "tzdbVersion": "unknown", "deltaTModelVersion": "iers-c01-2026.5+quad2100-v1", "bsTableVersion": "2026.10.1", "overridesVersion": null } } ``` A few things hold for every endpoint: - **`subject`** shows how the birth was read: the UTC instant, the time zone and offset applied, where the offset came from (`tzdb` or `override`) and the date in both AD and BS. Check it first when a chart looks wrong. - **Catalog values** come back as `{ index, name }`. The `index` is stable and 1-based (weekday 0 is Sunday); the `name` follows `lang` (`en`, `ne` or `hi`). Store the index, show the name. - **`meta`** names everything needed to reproduce the result: engine version, API version, ayanamsha and its value, node type and the data versions. See [Conventions](https://navapatra.com/docs/conventions/). - **Headers**: every response carries `x-request-id`, `x-credits-charged` and, for keys tied to an account, `x-credits-remaining`. To get Nepali labels with Devanagari digits, add `"lang": "ne"` to the body. Numerals follow the language unless you set `"numerals": "latin"` or `"numerals": "devanagari"`. ## 3. The sample subjects The demo key only accepts these births, given exactly as below (the place may be coordinates or a place name that resolves within about 5 km). `GET /v1/meta` returns the same list. Daily panchang, month grids and time zone lookups with the demo key accept the same five places. | Sample | Calendar | Date | Time | Latitude, longitude | | --- | --- | --- | --- | --- | | Pokhara | BS | 2050-05-12 | 14:35 | 28.2096, 83.9856 | | Kathmandu | AD | 1990-05-15 | 10:30 | 27.7172, 85.324 | | Biratnagar | AD | 2001-01-26 | 21:05 | 26.4525, 87.2718 | | New Delhi | AD | 1985-11-02 | 06:15 | 28.6139, 77.209 | | Varanasi | AD | 1975-03-08 | 04:40 | 25.3176, 82.9739 | Any other birth returns `403` with `code: "demo_restricted"`. Batch matching, panchang ranges, `/v1/tokens` and `/v1/usage` need a real key. ## 4. Switch to your own key [Sign up](https://app.navapatra.com/signup) and the dashboard shows a test key that starts with `sk_test_`. Test keys return exactly what live keys return, never use credits, and are limited to a daily quota of 100 (counted in credits) and 10 requests per minute for your organization. Keep the key in an environment variable and send it the same way: **curl** ```bash export NAVAPATRA_API_KEY="sk_test_..." curl "https://api.navapatra.com/v1/calendar/convert?date=2083-01-01&calendar=BS" \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" \ -H "Navapatra-Version: 2026-11-01" ``` **TypeScript** ```ts const res = await fetch('https://api.navapatra.com/v1/calendar/convert?date=2083-01-01&calendar=BS', { headers: { Authorization: `Bearer ${process.env.NAVAPATRA_API_KEY}`, 'Navapatra-Version': '2026-11-01', }, }); const { ad, bs } = await res.json(); console.log(bs.text, '=', ad.date); // 1 Baishakh 2083 = 2026-04-14 ``` **Python** ```python import os import requests res = requests.get( "https://api.navapatra.com/v1/calendar/convert", params={"date": "2083-01-01", "calendar": "BS"}, headers={ "Authorization": f"Bearer {os.environ['NAVAPATRA_API_KEY']}", "Navapatra-Version": "2026-11-01", }, ) data = res.json() print(data["bs"]["text"], "=", data["ad"]["date"]) # 1 Baishakh 2083 = 2026-04-14 ``` **PHP** ```php get('https://api.navapatra.com/v1/calendar/convert', [ 'query' => ['date' => '2083-01-01', 'calendar' => 'BS'], 'headers' => [ 'Authorization' => 'Bearer ' . getenv('NAVAPATRA_API_KEY'), 'Navapatra-Version' => '2026-11-01', ], ]); $data = json_decode((string) $res->getBody(), true); echo $data['bs']['text'], ' = ', $data['ad']['date'], PHP_EOL; // 1 Baishakh 2083 = 2026-04-14 ``` The `Navapatra-Version` header is optional: without it you get the version your key is pinned to. Sending it makes the version explicit in your code. See [Versioning](https://navapatra.com/docs/versioning/). When you go live, swap in an `sk_live_` key. Live calls use credits from your organization's balance, which includes 2,000 free credits every month. See [Credits and metering](https://navapatra.com/docs/credits/). > **Caution: Secret keys stay on servers** > > `sk_` keys must never ship in a browser, mobile app or public repository. Calls from a browser with an `sk_` key are refused with `secret_key_in_browser`. For apps, read [Mobile apps](https://navapatra.com/docs/guides/backend-proxy/). ## Next steps - [Authentication and keys](https://navapatra.com/docs/authentication/): every key type and where it is allowed. - [Kundali](https://navapatra.com/docs/guides/kundali/): `include`, options, vargas and sensitivity flags. - [Panchang and calendar](https://navapatra.com/docs/guides/panchang/): daily panchang, choghadiya and BS month grids. - [Errors](https://navapatra.com/docs/errors/): what each `code` means and how to fix it. --- # Authentication and keys > Secret, test and publishable keys, short-lived client tokens and the demo key, and where each one is allowed. Send a key or token on every request as a bearer token: ```http Authorization: Bearer sk_live_... ``` A missing header returns `401 unauthorized`. So does a header that is not of the form `Bearer `, an unknown key and a revoked key. The only operation that needs no key is `GET /v1/meta`; it accepts any credential or none (a secret key sent from a browser is still refused). A `HEAD` request is gated and billed exactly like the `GET` it mirrors: the same credential checks, rate limits and credits apply, and only the body is left out. A path under `/v1` that is not an operation returns `404 not_found`, and a method other than `GET`, `HEAD` or `POST` returns `405 method_not_allowed`. ## Key types | Prefix | Where it runs | Credits | Limits | | --- | --- | --- | --- | | `sk_live_` | Your servers only | Drawn from your organization | The key's rate limit (requests per minute), an optional monthly credit cap | | `sk_test_` | Your servers only | None | 10 requests per minute and 100 credits' worth of requests per day (UTC) for the whole organization | | `pk_` | Browsers, on the origins you allow | Drawn from your organization | Its own rate limit per visitor IP, a required monthly credit cap (default 2,000) | | `ct_` | Mobile and browser apps, issued by your backend | Drawn from your organization | At most 15 minutes, only the scoped endpoints, the smaller of its own cap and the issuing key's | | `demo` | Anywhere | None | Sample subjects only, 10 requests per minute per IP | You create, name, roll and revoke keys in the dashboard. Secrets are shown once and stored only as a hash. A revoked key stops working within two minutes, because key lookups are cached briefly at the edge. ### Secret keys: `sk_live_` and `sk_test_` Secret keys belong on servers. A request that carries a secret key and an `Origin` header (which every browser sends on cross-origin calls) is refused: ```json { "type": "https://navapatra.com/docs/errors#secret_key_in_browser", "title": "Secret key used from a browser", "status": 403, "detail": "Secret keys (sk_) are server-only. From a browser use a publishable key (pk_) with an origin allowlist, or a short-lived client token from POST /v1/tokens via your backend.", "code": "secret_key_in_browser", "requestId": "1a5823b0-da30-4d5c-bcab-8ad6ff3749f6" } ``` Test keys (`sk_test_`) return exactly what live keys return and never use credits, which makes them right for CI and local development. They share one budget per organization: 10 requests per minute and a daily quota of 100 per UTC day. The quota counts what each request would cost a live key, so a 62-day panchang range uses 2 of it and a batch of 10 pairs uses 10. Requests to zero-credit endpoints such as `/v1/places/search` do not count. Test keys cannot create client tokens. ### Publishable keys: `pk_` A publishable key is safe to put in web page source because it only works from the origins on its allowlist. Each allowlist entry is an exact origin such as `https://app.example.com` (scheme, host and port). A request without an `Origin` header, or from any other origin, is refused with `403 origin_not_allowed`. > **Caution: Origin is advisory** > > Browsers set `Origin` honestly, but any non-browser client can send whatever it likes. A `pk_` key therefore limits casual reuse, not determined abuse. That is why every publishable key has a monthly credit cap: 2,000 credits unless you set another value in the dashboard, and it cannot be removed. Keep its rate limit low too. When the cap is reached the key returns `402 credit_cap_reached` until the next UTC month. Use a publishable key for widgets and for web apps that call the API directly. For anything that needs a tighter grip, use client tokens. ### Client tokens: `ct_` A client token is a short-lived credential your backend creates with a live secret key and hands to an app. It lasts at most 15 minutes, works only for the endpoints you list, and can carry its own credit cap. It is the right choice for mobile apps: see [Mobile apps](https://navapatra.com/docs/guides/backend-proxy/) for the full pattern. `POST /v1/tokens` (credits: 0) ```bash curl https://api.navapatra.com/v1/tokens \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "scopes": ["kundali", "charts.svg"], "ttlSeconds": 600, "creditCap": 20 }' ``` ```json { "token": "ct_eyJ2IjoxLCJqdGkiOiJkOTcxYjg3NS01MGZlLTRk...", "expiresAt": "2026-10-10T15:57:53Z", "scopes": ["kundali", "charts.svg"], "creditCap": 20 } ``` | Field | Rules | | --- | --- | | `scopes` | Required, at least one endpoint id from the list below | | `ttlSeconds` | 60 to 900. Default and maximum 900 (15 minutes) | | `creditCap` | Optional. Most credits the token may use over its lifetime | Scope ids: `kundali`, `kundali.planets`, `dasha`, `matching.ashtakoota`, `matching.ashtakoota.batch`, `matching.rashi`, `panchang.day`, `panchang.range`, `calendar.convert`, `calendar.month`, `charts.svg`, `places.search`, `timezone`. A token inherits the organization, plan and pinned API version of the key that created it. It also shares that key's limits: its calls count toward the key's monthly credit cap and draw from the key's rate-limit bucket, and its effective cap is the smaller of its own `creditCap` and what is left of the key's cap. Revoking the key stops every token it issued within 60 seconds. Tokens are opaque strings; do not parse them. Tokens issued before 11 October 2026 used an older format and are refused with `401 unauthorized`: ask your backend for a new one. Calling an endpoint outside its scopes returns `403 forbidden`, and that includes `/v1/usage` and `/v1/tokens`; `/v1/meta` needs no scope. After expiry the token returns `401 token_expired`: ask your backend for a new one. Only `sk_live_` keys may call `/v1/tokens`; anything else gets `403 forbidden`. ### The demo key The public key `demo` exists so you can try the API before signing up, from the [Quickstart](https://navapatra.com/docs/quickstart/) or the request console in the [API reference](https://navapatra.com/docs/api/). It accepts the five sample births listed by `GET /v1/meta` (and the same five places for panchang, month grids and time zones), allows 10 requests per minute per IP address, and never touches an account. Anything else returns `403 demo_restricted`. The demo key cannot call batch matching, panchang ranges, `/v1/tokens` or `/v1/usage`, and it cannot send a kundali payload in place of birth input. ## Which key should I use? - **A backend, script or cron job:** `sk_test_` while you build, `sk_live_` in production. - **A web page or widget:** a `pk_` key with your site's origins on its allowlist and a credit cap, or a client token from your backend. - **A React Native or Flutter app:** a client token from your backend. Never ship `sk_` or `pk_` keys in an app bundle. - **Trying things out:** `demo`. ## Check your balance `GET /v1/usage` returns your organization's balance by credit lot and this key's usage this month. It needs a secret key (`sk_live_` or `sk_test_`); publishable keys, client tokens and the demo key get `403 forbidden`. See [Credits and metering](https://navapatra.com/docs/credits/). --- # TypeScript SDK > The typed client for Node, Deno, Bun, Workers, browsers and React Native, with retries, metering headers and typed errors. `@navapatra/sdk` is a small TypeScript client generated from the same OpenAPI spec as the [API reference](https://navapatra.com/docs/api/). It has no runtime dependencies, ships ESM and CommonJS with type declarations, and runs on Node 18+, Deno, Bun, Cloudflare Workers, browsers and React Native. ## Install ```bash npm install @navapatra/sdk ``` The package is published to npm with the first public release. For other languages, the Python, PHP and HTTP examples in every guide work as they are, or generate a client from the OpenAPI spec behind the [API reference](https://navapatra.com/docs/api/). ## First call ```ts import { Navapatra } from '@navapatra/sdk'; const client = new Navapatra({ apiKey: process.env.NAVAPATRA_API_KEY! }); const kundali = await client.kundali.create({ birth: { calendar: 'BS', date: '2050-05-12', time: '14:35', place: { lat: 28.2096, lng: 83.9856 } }, include: ['svg'], lang: 'ne', }); console.log(kundali.lagna.sign.name, kundali.moon.nakshatra.name); ``` To try it without signing up, pass `apiKey: 'demo'`; it accepts the five sample subjects returned by `client.meta.get()`. ## Options ```ts new Navapatra({ apiKey: 'sk_live_...', // required: a key or a client token baseUrl: 'https://api.navapatra.com', // API origin without /v1 (this is the default) version: '2026-11-01', // sent as Navapatra-Version; default: your key's pinned version fetch: customFetch, // default: the global fetch timeoutMs: 30_000, // per attempt maxRetries: 2, // on 408, 429, 5xx, timeouts and network errors }); ``` Every method also accepts a last argument `{ signal, timeoutMs, maxRetries, version }`. Retries back off and honour `Retry-After`. ## Methods | Method | Endpoint | Credits | | --- | --- | --- | | `kundali.create(body)` | `POST /v1/kundali` | 1 | | `kundali.planets(body)` | `POST /v1/kundali/planets` | 1 | | `dasha.create(body)` | `POST /v1/dasha` | 1 | | `matching.ashtakoota(body)` | `POST /v1/matching/ashtakoota` | 1 | | `matching.batch(body)` | `POST /v1/matching/ashtakoota/batch` | 1 per pair | | `matching.rashi(body)` | `POST /v1/matching/rashi` | 1 | | `panchang.day(query)` | `GET /v1/panchang/day` | 1 | | `panchang.range(query)` | `GET /v1/panchang/range` | 1 per started 31 days | | `calendar.convert(query)` | `GET /v1/calendar/convert` | 1 | | `calendar.month(query)` | `GET /v1/calendar/month` | 1 | | `charts.svg(body)` | `POST /v1/charts/svg` (JSON `{ svg }`) | 1 | | `places.search(query)` | `GET /v1/places/search` | 0 | | `timezone.get(query)` | `GET /v1/timezone` | 1 | | `tokens.create(body)` | `POST /v1/tokens` (`sk_live_` only) | 0 | | `usage.get()` | `GET /v1/usage` | 0 | | `meta.get()` | `GET /v1/meta` | 0 | ## Credits, rate limits and request ids Awaiting a method gives you the parsed body. Call `.withResponse()` to get the metering headers too: ```ts const { data, meta } = await client.panchang .day({ date: '2083-01-01', calendar: 'BS', lat: 27.7172, lng: 85.324 }) .withResponse(); meta.creditsCharged; // 1 meta.creditsRemaining; // your organization's balance meta.requestId; // quote this to support meta.rateLimit; // { limit, remaining, reset } or null ``` ## Errors Every failure is a `NavapatraError`. API errors carry the [problem document](https://navapatra.com/docs/errors/): ```ts import { NavapatraError } from '@navapatra/sdk'; try { await client.kundali.create({ birth }); } catch (err) { if (err instanceof NavapatraError) { err.status; // 422 err.code; // 'ambiguous_place' err.requestId; // for support err.problem; // { title, detail, candidates, errors, topUpUrl, ... } } } ``` Failures before any response arrives have `status: 0` and one of the codes `connection_error`, `timeout`, `aborted`, `invalid_response`, `missing_api_key` or `secret_key_in_browser`. ## Browsers and apps The client refuses to start with a secret key (`sk_live_`, `sk_test_`) when a `window` global exists, which covers browsers and React Native. In client code use a publishable key restricted to your origins, or a client token from your backend: ```ts // On your server, with sk_live_ const { token } = await server.tokens.create({ scopes: ['kundali', 'charts.svg'], ttlSeconds: 600 }); // In the app const client = new Navapatra({ apiKey: token }); ``` The full pattern, with React Native and Flutter code, is in [Mobile apps and the backend proxy](https://navapatra.com/docs/guides/backend-proxy/). --- # Mobile apps and the backend proxy > Keep secret keys on your server, issue short-lived client tokens, and call the API from React Native or Flutter. Anything inside an app bundle can be extracted, so an `sk_` key shipped in a React Native or Flutter app is a leaked key. The fix is a small endpoint on your own backend that trades your user's session for a **client token**: a credential that lasts at most 15 minutes, can call only the endpoints you list and can carry its own credit cap. ```text App ──(your session)──▶ Your backend ──(sk_live_)──▶ POST /v1/tokens App ◀──── ct_ token ──── Your backend App ──(Bearer ct_...)─────────────────────────────▶ https://api.navapatra.com/v1/kundali ``` Your backend decides who gets a token, and the API enforces the token's limits. Birth data goes straight from the app to the API and never passes through your server. ## 1. Issue tokens from your backend `POST /v1/tokens` (credits: 0) Only `sk_live_` keys can create tokens. Authenticate the user with your own session first, then ask for the narrowest scopes the app needs. **TypeScript** ```ts // A web-standard handler: works in Hono, Next.js route handlers, Bun, Deno and Workers. export async function issueToken(req: Request): Promise { const user = await getSessionUser(req); // your own auth check if (!user) return new Response('Unauthorized', { status: 401 }); const res = await fetch('https://api.navapatra.com/v1/tokens', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NAVAPATRA_API_KEY}`, // sk_live_..., server only 'Content-Type': 'application/json', }, body: JSON.stringify({ scopes: ['kundali', 'charts.svg', 'places.search'], ttlSeconds: 900, creditCap: 10, }), }); if (!res.ok) return new Response('Token service unavailable', { status: 502 }); const { token, expiresAt } = await res.json(); return Response.json({ token, expiresAt }); } ``` **Python** ```python # Flask (pip install flask requests) import os import requests from flask import Flask, abort, jsonify app = Flask(__name__) @app.post("/api/astro-token") def issue_token(): user = get_session_user() # your own auth check if user is None: abort(401) res = requests.post( "https://api.navapatra.com/v1/tokens", headers={"Authorization": f"Bearer {os.environ['NAVAPATRA_API_KEY']}"}, json={ "scopes": ["kundali", "charts.svg", "places.search"], "ttlSeconds": 900, "creditCap": 10, }, timeout=10, ) if not res.ok: abort(502) body = res.json() return jsonify(token=body["token"], expiresAt=body["expiresAt"]) ``` **PHP** ```php 10]); $res = $client->post('https://api.navapatra.com/v1/tokens', [ 'headers' => ['Authorization' => 'Bearer ' . getenv('NAVAPATRA_API_KEY')], 'json' => [ 'scopes' => ['kundali', 'charts.svg', 'places.search'], 'ttlSeconds' => 900, 'creditCap' => 10, ], ]); $body = json_decode((string) $res->getBody(), true); header('Content-Type: application/json'); echo json_encode(['token' => $body['token'], 'expiresAt' => $body['expiresAt']]); ``` The response from `/v1/tokens`: ```json { "token": "ct_eyJ2IjoxLCJqdGkiOiJkOTcxYjg3NS01MGZlLTRk...", "expiresAt": "2026-10-10T15:57:53Z", "scopes": ["kundali", "charts.svg", "places.search"], "creditCap": 10 } ``` Rules worth knowing: - `ttlSeconds` is 60 to 900; the default and the maximum is 900. - `creditCap` limits the token's whole lifetime. Once reached, calls return `402 credit_cap_reached`. - Credits come from your organization's balance, like any other call. - The token uses the rate limit of the key that created it, counted per token, and the API version that key was pinned to when the token was issued. - Creating a token costs no credits, but it does count toward the issuing key's rate limit. ## 2. Use the token in the app Keep the token in memory, reuse it until shortly before `expiresAt`, and fetch a fresh one when the API answers `401` with `code: "token_expired"`. **React Native** ```ts // api.ts type Cached = { token: string; expiresAt: number }; let cached: Cached | null = null; async function clientToken(force = false): Promise { if (!force && cached && cached.expiresAt - Date.now() > 60_000) return cached.token; const res = await fetch('https://your-backend.example.com/api/astro-token', { method: 'POST', credentials: 'include', // or your own session header }); if (!res.ok) throw new Error(`token: ${res.status}`); const body = await res.json(); cached = { token: body.token, expiresAt: Date.parse(body.expiresAt) }; return cached.token; } export async function callApi(path: string, body: unknown, retried = false): Promise { const res = await fetch(`https://api.navapatra.com/v1${path}`, { method: 'POST', headers: { Authorization: `Bearer ${await clientToken(retried)}`, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); const json = await res.json(); if (res.status === 401 && json.code === 'token_expired' && !retried) return callApi(path, body, true); if (!res.ok) throw new Error(`${json.code}: ${json.detail}`); return json; } // const kundali = await callApi('/kundali', { birth: { ... }, lang: 'ne' }); ``` **Flutter** ```dart // api.dart (dependencies: http) import 'dart:convert'; import 'package:http/http.dart' as http; class AstroApi { String? _token; DateTime _expiresAt = DateTime.fromMillisecondsSinceEpoch(0); Future _clientToken({bool force = false}) async { final fresh = _expiresAt.difference(DateTime.now()) > const Duration(minutes: 1); if (!force && _token != null && fresh) return _token!; final res = await http.post(Uri.parse('https://your-backend.example.com/api/astro-token')); if (res.statusCode != 200) throw Exception('token: ${res.statusCode}'); final body = jsonDecode(res.body) as Map; _token = body['token'] as String; _expiresAt = DateTime.parse(body['expiresAt'] as String); return _token!; } Future> post(String path, Map body, {bool retried = false}) async { final res = await http.post( Uri.parse('https://api.navapatra.com/v1$path'), headers: { 'Authorization': 'Bearer ${await _clientToken(force: retried)}', 'Content-Type': 'application/json', }, body: jsonEncode(body), ); final json = jsonDecode(utf8.decode(res.bodyBytes)) as Map; if (res.statusCode == 401 && json['code'] == 'token_expired' && !retried) { return post(path, body, retried: true); } if (res.statusCode >= 400) throw Exception('${json['code']}: ${json['detail']}'); return json; } } ``` ## Protect the token endpoint Your token endpoint spends your credits, so treat it like any other paid action: - **Authenticate** every request with your own session. Never issue tokens to anonymous callers unless that is a product decision. - **Scope narrowly.** A chart screen needs `kundali` and maybe `charts.svg`, not `matching.ashtakoota.batch`. - **Cap credits** per token with `creditCap`, and rate-limit the endpoint per user on your side. - **Keep the `sk_live_` key in server configuration**, never in source control. Roll it in the dashboard if it leaks. ## Web apps, widgets and the SDK The same pattern works in the browser: - **Widgets** take a client token directly. Put it in the element's `publishable-key` attribute and the widget calls the API itself; when the token expires, set a fresh one and it refetches. See [Widgets](https://navapatra.com/docs/guides/widgets/#fetch-mode). - **The TypeScript SDK** accepts a token as its `apiKey` (`new Navapatra({ apiKey: token })`), and your server can mint tokens with `client.tokens.create(...)`. See [TypeScript SDK](https://navapatra.com/docs/guides/typescript-sdk/#browsers-and-apps). > **Note: What about publishable keys?** > > `pk_` keys depend on the browser `Origin` header, which native apps do not send in a trustworthy way. Use them for websites and widgets, and client tokens for apps. --- # Conventions > Ayanamsha, node type, houses, sunrise, lunar month tradition and dasha year length, and how meta echoes each one. Vedic results depend on choices that vendors rarely state: which ayanamsha, which lunar node, when the day starts. Navapatra names every one of them. You can change them per request, and the response tells you which ones were used. ## Options at a glance | Option | Values | Default | Where | | --- | --- | --- | --- | | `ayanamsha` | `lahiri`, `lahiri_icrc`, `true_chitra`, `raman`, `kp` | `lahiri` | All chart, dasha, matching and panchang endpoints | | `nodeType` | `mean`, `true` | `mean` | Chart, dasha and matching endpoints | | `sunriseConvention` | `upper_limb_refracted`, `center_geometric` | `upper_limb_refracted` | Kundali (birth panchang) and panchang | | `tradition` | `nepal`, `amanta` | `nepal` | Kundali (birth panchang), panchang and month grids | | `dasha.yearDays` (or `yearDays` on `/v1/dasha`) | Any number up to 400 | `365.25` | Kundali and dasha | | `nepalHistoricOffset` | `tzdb`, `historic_0540` | `tzdb` | Anything that takes birth input; see [Timezones](https://navapatra.com/docs/timezones/) | | `lang` | `en`, `ne`, `hi` | `en` | Every endpoint with labels | | `numerals` | `latin`, `devanagari` | `latin` for `en`, `devanagari` for `ne` and `hi` | Every endpoint with labels | POST endpoints take these inside `options` (except `lang` and `numerals`, which sit at the top level of the body). GET endpoints take them as query parameters. ## Ayanamsha The sidereal zodiac is the tropical one shifted by the ayanamsha, so every sign, nakshatra and varga depends on it. - **`lahiri`** (default): Lahiri (Chitrapaksha) with the 1985 Indian Astronomical Ephemeris value, 23°15′00.658″ on 21 March 1956. - **`lahiri_icrc`**: Lahiri with the original Calendar Reform Committee value of 23°15′00″ on the same date, about 0.66″ less. - **`true_chitra`**: keeps the star Chitra (Spica) at exactly 180° sidereal. - **`raman`**: B. V. Raman's ayanamsha, about 1.45° less than Lahiri. - **`kp`**: the Krishnamurti Paddhati (KP) ayanamsha, about 6′ less than Lahiri. `meta.ayanamsha` returns the id and the value in degrees at the instant computed, for example `{ "id": "lahiri", "degrees": 23.773159 }` for a 1993 birth. ## Lunar node Rahu and Ketu use the **mean** node by default. Set `nodeType: "true"` for the true (osculating) node. Ketu is always exactly opposite Rahu. Mean nodes move steadily backwards, so Rahu and Ketu show `retrograde: true`. ## Houses Houses are **whole sign**: house 1 is the sign holding the lagna, house 2 the next sign, and so on. Each graha's `house` field follows this rule. The lagna itself is computed from sidereal time at the birthplace, its latitude and the true obliquity of the ecliptic. ## Sunrise Daily elements (the vara, and tithi, nakshatra, yoga and karana "at sunrise") depend on when sunrise happens. - **`upper_limb_refracted`** (default): the moment the upper edge of the Sun's disc appears, allowing for atmospheric refraction. This is what printed Nepali and Indian panchangs use, and what most sunrise tables publish. - **`center_geometric`**: the moment the centre of the disc crosses the geometric horizon, about 4 minutes later at sunrise (earlier at sunset). The choice matters on the one or two days a year when a tithi or nakshatra ends within those minutes: for example on 2021-11-04 in Kathmandu the default gives Chaturdashi at sunrise, the geometric centre Amavasya. The convention also sets the sunset and the choghadiya boundaries. The panchang `meta` echoes the convention used. Moonrise and moonset do not depend on this option: they are always the moment the centre of the Moon's disc crosses the horizon as seen from the place, which is how common panchangs give them. ## Lunar month tradition - **`nepal`** (default): purnimanta months, which end at the full moon, as used in Nepali almanacs and in most of northern India. - **`amanta`**: months end at the new moon, as used in much of southern and western India. `lunarMonth.scheme` in every panchang says which one applies, and `lunarMonth.adhik` flags an intercalary month (its `name` is then prefixed, for example `Adhik Bhadrapada`). Lunar month indexes run 1 = Vaishakha to 12 = Chaitra. ## Dasha year Dasha periods are measured in years of **365.25 days** by default. Set `yearDays` (for example `360` for the savana year) if your tradition uses another length. The value is echoed as `yearDays` on every timeline. Dasha output never uses the server clock; see [Dashas](https://navapatra.com/docs/guides/dashas/). ## Labels and indexes Every catalog value comes back as `{ index, name }`, and some also carry a string `id` (grahas, dasha lords, paksha, kootas, choghadiya): ```json { "id": "krishna", "index": 2, "name": "कृष्ण" } ``` - `index` and `id` never change between languages or versions. Store and compare these. - `name` is in the requested `lang` and `numerals`, ready to display. - Indexes are 1-based (sign 1 = Mesha, nakshatra 1 = Ashvini, tithi 1 to 15 shukla and 16 to 30 krishna) except the weekday, where 0 is Sunday. Choosing Nepali or Hindi never costs extra credits. ## What `meta` echoes `meta` lists everything needed to reproduce a response. Identical input with identical `meta` values gives byte-identical JSON. | Field | Meaning | Returned by | | --- | --- | --- | | `engineVersion` | Version of the computation engine | Kundali, planets, dasha, matching, panchang | | `apiVersion` | The dated API version that served the request | All of the above, plus calendar convert and month | | `ayanamsha` | `{ id, degrees }` actually applied | Kundali, planets, dasha, panchang; matching when a subject was computed from birth input | | `nodeType` | `mean` or `true` | Kundali, planets, dasha; matching as for `ayanamsha` | | `tradition` | `nepal` or `amanta` | Kundali, panchang day and range | | `sunriseConvention` | Convention used for sunrise | Kundali, panchang day and range | | `tzdbVersion` | IANA tz database release behind every offset | Kundali, planets, dasha, matching, panchang | | `deltaTModelVersion` | Model of ΔT, the difference between Earth-rotation time and uniform time, used to place every instant | Kundali, planets, dasha, matching, panchang | | `bsTableVersion` | Version of the Bikram Sambat month-length table | All of the above, plus calendar convert and month | | `overridesVersion` | Version of the Panchang Samiti tithi override table | Kundali, planets, dasha, matching, panchang | | `tzid` | Zone used for sunrise-based values | Calendar month | A real `meta` from a kundali request: ```json "meta": { "engineVersion": "0.1.0", "apiVersion": "2026-11-01", "ayanamsha": { "id": "lahiri", "degrees": 23.773159 }, "nodeType": "mean", "tradition": "nepal", "sunriseConvention": "upper_limb_refracted", "tzdbVersion": "unknown", "deltaTModelVersion": "iers-c01-2026.5+quad2100-v1", "bsTableVersion": "2026.10.1", "overridesVersion": null } ``` > **Note: Current data versions** > > `overridesVersion` is `null` until the Samiti tithi override table ships; until then no override is applied and none is flagged. `tzdbVersion` reads `unknown` where the hosting runtime does not report its tz database release; offsets still come from the IANA database. In matching responses, `ayanamsha` and `nodeType` come from the first subject computed from birth input (the male side first), and are left out when both sides are kundali payloads, which carry their own `meta`. Changes are tracked in the [changelog](https://navapatra.com/docs/changelog/). Data updates (tz database releases, BS table updates and Samiti overrides) are corrections and apply to every API version; see [Versioning](https://navapatra.com/docs/versioning/). --- # Bikram Sambat dates > Convert between BS and AD, send BS birth dates, and understand published and projected years and Nepal Sambat. Bikram Sambat (BS) is Nepal's official calendar. It is solar: each month starts when the Sun enters a sidereal sign, so month lengths vary from 29 to 32 days and change from year to year. There is no formula that gets every year right, which is why Navapatra uses a month-length table checked against the calendars published by the Nepal Panchanga Nirnayak Samiti. BS is about 56 years and 8 months ahead of AD. BS 2083 Baishakh 1 is 14 April 2026. ## Months | Index | English | Nepali | | --- | --- | --- | | 1 | Baishakh | वैशाख | | 2 | Jestha | जेठ | | 3 | Asar | असार | | 4 | Shrawan | साउन | | 5 | Bhadra | भदौ | | 6 | Ashwin | असोज | | 7 | Kartik | कात्तिक | | 8 | Mangsir | मंसिर | | 9 | Poush | पुस | | 10 | Magh | माघ | | 11 | Falgun | फागुन | | 12 | Chaitra | चैत | Dates are always written `YYYY-MM-DD` with the month as a number, in both calendars: BS 2050 Bhadra 12 is `2050-05-12`. ## Convert a date `GET /v1/calendar/convert` (credits: 1) Pass `date` and the `calendar` it is in. The response always has both sides, the weekday and the BS `status`. **curl** ```bash curl "https://api.navapatra.com/v1/calendar/convert?date=2050-05-12&calendar=BS&include=nepalSambat" \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" ``` **TypeScript** ```ts const url = new URL('https://api.navapatra.com/v1/calendar/convert'); url.search = new URLSearchParams({ date: '2050-05-12', calendar: 'BS', include: 'nepalSambat' }).toString(); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.NAVAPATRA_API_KEY}` } }); const { ad, bs, nepalSambat } = await res.json(); ``` **Python** ```python import os import requests res = requests.get( "https://api.navapatra.com/v1/calendar/convert", params={"date": "2050-05-12", "calendar": "BS", "include": "nepalSambat"}, headers={"Authorization": f"Bearer {os.environ['NAVAPATRA_API_KEY']}"}, ) data = res.json() ``` **PHP** ```php get('https://api.navapatra.com/v1/calendar/convert', [ 'query' => ['date' => '2050-05-12', 'calendar' => 'BS', 'include' => 'nepalSambat'], 'headers' => ['Authorization' => 'Bearer ' . getenv('NAVAPATRA_API_KEY')], ]); $data = json_decode((string) $res->getBody(), true); ``` ```json { "ad": { "date": "1993-08-28", "year": 1993, "month": 8, "day": 28, "weekday": { "index": 6, "name": "Saturday" } }, "bs": { "date": "2050-05-12", "year": 2050, "month": { "index": 5, "name": "Bhadra" }, "day": 12, "status": "published", "text": "12 Bhadra 2050" }, "nepalSambat": { "year": 1113, "month": { "index": 13, "name": "Anala" }, "paksha": { "index": 1, "name": "Thwa" }, "day": 12, "adhik": true, "text": "1113 Anala Thwa 12" }, "meta": { "bsTableVersion": "2026.10.1", "apiVersion": "2026-11-01" } } ``` `bs.text` is ready to display. With `lang=ne` it reads `१२ भदौ २०५०`; with `lang=ne&numerals=latin` it keeps Latin digits. ## Published and projected years Every BS date carries a `status`, and month grids carry `bsStatus`: - **`published`**: the year's month lengths come from the source-cited table of published calendars, which covers BS 1970 through BS 2083 (`bsPublishedThrough` in `GET /v1/meta`). - **`projected`**: every other supported year, before BS 1970 or after BS 2083, comes from an astronomical projection of the Sun's sign ingresses. A projected month can end up a day longer or shorter than the calendar eventually printed for that year. Whenever a response contains a projected BS date, its `meta` also carries `bsProjectionVersion`, the version of the projection model that produced it. Responses with only published dates leave the field out. If you store BS dates for projected years, store the AD date and `bsProjectionVersion` as well, or re-convert when the year is published. Table updates are announced in the [changelog](https://navapatra.com/docs/changelog/) and show up as a new `bsTableVersion`. ## Supported range Every endpoint uses one window: **AD 1900-01-01 to 2100-12-31**, which is **BS 1956-09-19 to 2157-09-15** (`supportedRange` in `GET /v1/meta`). Conversions work for every day in it, in both directions. A BS month grid is served when the whole month lies inside it. Anything outside fails with `422 out_of_range`: ```json { "type": "https://navapatra.com/docs/errors#out_of_range", "title": "Out of supported range", "status": 422, "detail": "2101-01-01 AD is outside the supported range, AD 1900-01-01 to 2100-12-31 (BS 1956-09-19 to 2157-09-15).", "code": "out_of_range", "requestId": "d29da36a-af66-4efd-b954-15aed8501d42" } ``` A day that does not exist in a month, such as Bhadra 33, fails with `400 invalid_input` and a message giving the month's real length (`BS 2050-5 has 31 days, got day 33`). ## BS birth dates Any birth input can be given in BS. Set `calendar: "BS"`: ```json { "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": { "query": "Pokhara" } } } ``` The response's `subject.birth` has both dates, `{ "ad": "1993-08-28", "bs": "2050-05-12" }`. Every supported birth gets a BS date; for births before BS 1970 (about April 1913) it is projected rather than published, so a 1905 Kathmandu birth returns `"bs": "1962-02-02"`. ## Nepal Sambat Nepal Sambat is the lunar calendar of the Newar community. Add `include=nepalSambat` to a conversion to get it. It is computed from the lunar month and tithi at sunrise, so it depends on place: Kathmandu by default, or the `lat` and `lng` you pass. - Months run 1 = Kachhala to 12 = Kaula, with 13 = Anala for the intercalary month (`adhik: true`). - `paksha` is 1 = Thwa (bright half) or 2 = Ga (dark half), and `day` is the tithi within it. Daily panchang includes `nepalSambat` for every day automatically. ## Month grids For a calendar view, `GET /v1/calendar/month` returns every day of a BS month with its AD date, weekday and tithi at sunrise. See [Panchang and calendar](https://navapatra.com/docs/guides/panchang/#bs-month-grid). --- # Timezones and historical offsets > How a birth time becomes a UTC instant, Nepal's 1986 offset change, overrides, and what to do when a local time is ambiguous. A birth time is local civil time at the birthplace. To compute a chart, Navapatra turns it into a UTC instant using the time zone rules **of the birthplace on that date**, never your server's zone or the user's device zone. A wrong offset moves the lagna by about one degree for every four minutes, so this step matters more than any other. ## How the offset is chosen For every birth, in this order: 1. **`birth.utcOffset`**, if you send one (for example `"+05:30"`), is used as is. `offsetSource` is `override`. 2. Otherwise the zone is **`birth.tzid`** if you send one (for example `"Asia/Kathmandu"`), else the zone of the place you named in `place.query`, else a lookup from `lat` and `lng`. 3. The offset for that zone on that date comes from the IANA tz database, including every historical change. `offsetSource` is `tzdb`. The one exception is `options.nepalHistoricOffset: "historic_0540"`, described below, which reports `offsetSource: "historic_0540"`. The response's `subject` shows the result, so you can always check what was applied: ```json "subject": { "instantUtc": "1993-08-28T08:50:00Z", "place": { "lat": 28.2096, "lng": 83.9856 }, "tzid": "Asia/Kathmandu", "utcOffset": "+05:45", "offsetSource": "tzdb", "birth": { "ad": "1993-08-28", "bs": "2050-05-12" } } ``` Before standard time was adopted, places kept local mean time, and the offset can include seconds. A 1905 birth in Kathmandu returns `"utcOffset": "+05:41:16"`. > **Note: Zone lookup from coordinates** > > The coordinate lookup uses present-day zone boundaries and can be wrong within a few kilometres of a border. It also does not know about zones that have since been merged. If you know the zone, send `birth.tzid`; if you know the exact offset the birth was recorded in, send `birth.utcOffset`. Places resolved from `place.query` use the zone recorded for that place in the gazetteer. ## Nepal before 1986 Nepal's clocks ran at **UTC+05:30** until the end of 1985 and have run at **UTC+05:45** since 1 January 1986, 00:00 local time. The tz database records exactly this, and it is the default. Some older Nepali software and records used **UTC+05:40** for births before the switch instead. If your users' existing charts were made that way and you need to match them, set `options.nepalHistoricOffset` to `historic_0540`. It only affects births in `Asia/Kathmandu` before 1986-01-01; later births and other zones are unchanged. The response reports `utcOffset: "+05:40"` with `offsetSource: "historic_0540"`, so a stored chart always shows which convention it used. The difference is visible. The same Kathmandu birth on 1980-05-15 at 10:30: | Setting | `utcOffset` | `instantUtc` | Lagna | | --- | --- | --- | --- | | default (`tzdb`) | `+05:30` | `1980-05-15T05:00:00Z` | Karka 106.96°, Ashlesha pada 1 | | `historic_0540` | `+05:40` | `1980-05-15T04:50:00Z` | Karka 104.82°, Pushya pada 4 | Ten minutes of offset moved the lagna into a different nakshatra. Use [`sensitivityMinutes`](https://navapatra.com/docs/guides/kundali/#birth-time-sensitivity) to see how close a chart sits to such a boundary. ## Overrides ```json { "birth": { "calendar": "AD", "date": "1980-05-15", "time": "10:30", "place": { "lat": 27.7172, "lng": 85.324 }, "utcOffset": "+05:30" } } ``` - `utcOffset` is `±HH:mm`, at most 14 hours. It wins over everything else and skips the ambiguity checks below. - `tzid` must be a valid IANA zone name. It replaces the lookup but still uses the tz database for the offset. - Both are echoed in `subject`, so a saved response shows how it was computed. ## Ambiguous local times Where clocks change for daylight saving time, some local times never happen (the clocks jump forward over them) and some happen twice (the clocks fall back). Navapatra will not guess. It returns `422` with `code: "ambiguous_local_time"`, the kind of problem and both candidate instants: ```json { "type": "https://navapatra.com/docs/errors#ambiguous_local_time", "title": "Ambiguous local time", "status": 422, "detail": "local time is skipped or repeated in this zone", "code": "ambiguous_local_time", "requestId": "c3f18232-b4cb-4a64-ba5b-5946d0b00c17", "details": { "tzid": "America/New_York", "kind": "overlap", "candidates": ["2021-11-07T05:30:00Z", "2021-11-07T06:30:00Z"] } } ``` That birth, 01:30 on 7 November 2021 in New York, happened twice. Ask the user (or your records) which one, then repeat the request with `options.disambiguation`: - `"earlier"` picks the first candidate instant. - `"later"` picks the second. For a gap (`kind: "gap"`, for example 02:30 on 14 March 2021 in New York, which never happened), the two candidates are the wall time read with the offset before and after the change; `earlier` and `later` choose between them the same way. ```json { "birth": { "calendar": "AD", "date": "2021-11-07", "time": "01:30", "place": { "lat": 40.7128, "lng": -74.006 } }, "options": { "disambiguation": "later" } } ``` The response then shows `"instantUtc": "2021-11-07T06:30:00Z"` and `"utcOffset": "-05:00"`. Nepal has never used daylight saving time, so births in Nepal are never ambiguous. ## Look up a zone `GET /v1/timezone` (credits: 1) Returns the zone for a coordinate and its offset at an instant (now, if you leave `instant` out): ```bash curl "https://api.navapatra.com/v1/timezone?lat=27.7172&lng=85.324&instant=1985-06-01T00:00:00Z" \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" ``` ```json { "tzid": "Asia/Kathmandu", "utcOffset": "+05:30", "instant": "1985-06-01T00:00:00Z", "source": "tz-lookup" } ``` ## Supported range Birth dates from **AD 1900-01-01 to 2100-12-31**. Anything outside returns `422 out_of_range` with the AD date in `details`. --- # Versioning > Dated API versions sent in a request header, how pinning works, and why data updates apply to every version. The API uses **dated versions** such as `2026-11-01`. A version fixes the request and response schema and the computation conventions. Once you are on a version, a newer release cannot change what your integration receives. ## Which version a request uses 1. The `Navapatra-Version` request header, if you send one. 2. Otherwise the version your key is **pinned** to. You set and change pins in the dashboard. 3. A key without a pin gets the newest version. A client token is fixed to the version that served the `POST /v1/tokens` call that created it (that call's header, or the issuing key's pin). Every response says which version served it, in the `Navapatra-Version` response header and in `meta.apiVersion`: ```http Navapatra-Version: 2026-11-01 ``` An unknown version returns `400 unsupported_version`. `GET /v1/meta` lists the versions that exist: ```json { "apiVersion": "2026-11-01", "apiVersions": ["2026-11-01"], "...": "..." } ``` Sending the header on every request is good practice: your code states the version it was written against, and a change of pin in the dashboard cannot surprise it. ## Upgrading 1. Read the [changelog](https://navapatra.com/docs/changelog/) entry for the new version. 2. Send `Navapatra-Version: ` from a test environment and compare responses. 3. Deploy with the new header, or change the key's pin in the dashboard. You can move one key at a time, so a gradual rollout needs nothing more than two keys. ## Data updates are not pinned Some data changes because the world does, not because the API does: - **tz database releases**, when a country changes its clocks or historians correct an old offset; - **Bikram Sambat table updates**, when the Samiti publishes a new year's calendar; - **Samiti tithi overrides**, the official corrections to computed tithis; - **holiday data**, once available. These are corrections, so they apply to **every** API version as soon as they ship. Each one is announced in the changelog, and each has its own version in `meta` (`tzdbVersion`, `bsTableVersion`, `overridesVersion`), so you can tell exactly which data produced a stored response. The same holds for engine fixes that bring results within the published [accuracy tolerances](https://navapatra.com/docs/accuracy/); they are announced with the new `engineVersion`. ## What counts as a breaking change A new dated version is released for any change that could break a correct integration: - removing or renaming a field, parameter or endpoint; - changing a field's type, format or meaning; - changing a default convention (for example the default ayanamsha or sunrise convention); - adding a required parameter; - changing an error `code` or status for the same situation. These are **not** breaking and can ship to existing versions: - new endpoints, new optional parameters and new response fields; - new values in `include` or in other option lists; - new error codes for situations that previously succeeded incorrectly; - data updates as described above; - changes to `title` and `detail` text. Write your client to ignore unknown fields. Breaking versions get at least 12 months of notice; see the [deprecation policy](https://navapatra.com/docs/deprecation-policy/). ## SDKs SDKs follow semantic versioning, separately from API versions. The two latest major versions are supported. --- # Kundali > Generate a full birth chart with D1 and D9, nakshatra, naming syllable, dashas, birth panchang, SVG and birth-time sensitivity flags. One call returns a complete kundali: the lagna and nine grahas with their signs, houses, nakshatras and padas, the D1 and D9 charts, the Moon's nakshatra and naming syllable, the Vimshottari dasha timeline and `meta`. Everything else is opt-in through `include`, and it all costs one credit. `POST /v1/kundali` (credits: 1) ## Request **curl** ```bash curl https://api.navapatra.com/v1/kundali \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" \ -H "Navapatra-Version: 2026-11-01" \ -H "Content-Type: application/json" \ -d '{ "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35:00", "place": { "lat": 28.2096, "lng": 83.9856 } }, "options": { "ayanamsha": "lahiri", "nodeType": "mean", "sensitivityMinutes": 10 }, "include": ["panchang", "svg"] }' ``` **TypeScript** ```ts const res = await fetch('https://api.navapatra.com/v1/kundali', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NAVAPATRA_API_KEY}`, 'Navapatra-Version': '2026-11-01', 'Content-Type': 'application/json', }, body: JSON.stringify({ birth: { calendar: 'BS', date: '2050-05-12', time: '14:35:00', place: { lat: 28.2096, lng: 83.9856 } }, options: { ayanamsha: 'lahiri', nodeType: 'mean', sensitivityMinutes: 10 }, include: ['panchang', 'svg'], }), }); const kundali = await res.json(); ``` **Python** ```python import os import requests res = requests.post( "https://api.navapatra.com/v1/kundali", headers={ "Authorization": f"Bearer {os.environ['NAVAPATRA_API_KEY']}", "Navapatra-Version": "2026-11-01", }, json={ "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35:00", "place": {"lat": 28.2096, "lng": 83.9856}, }, "options": {"ayanamsha": "lahiri", "nodeType": "mean", "sensitivityMinutes": 10}, "include": ["panchang", "svg"], }, ) kundali = res.json() ``` **PHP** ```php post('https://api.navapatra.com/v1/kundali', [ 'headers' => [ 'Authorization' => 'Bearer ' . getenv('NAVAPATRA_API_KEY'), 'Navapatra-Version' => '2026-11-01', ], 'json' => [ 'birth' => [ 'calendar' => 'BS', 'date' => '2050-05-12', 'time' => '14:35:00', 'place' => ['lat' => 28.2096, 'lng' => 83.9856], ], 'options' => ['ayanamsha' => 'lahiri', 'nodeType' => 'mean', 'sensitivityMinutes' => 10], 'include' => ['panchang', 'svg'], ], ]); $kundali = json_decode((string) $res->getBody(), true); ``` ### `birth` | Field | Required | Notes | | --- | --- | --- | | `calendar` | No | `AD` (default) or `BS`; the calendar of `date` | | `date` | Yes | `YYYY-MM-DD`. AD 1900-01-01 to 2100-12-31 | | `time` | Yes | `HH:mm` or `HH:mm:ss`, local civil time at the birthplace | | `place` | Yes | `{ "lat", "lng" }`, or `{ "query" }` with an optional two-letter `country` | | `tzid` | No | IANA zone, replaces the coordinate lookup | | `utcOffset` | No | `±HH:mm`, replaces the tz database; see [Timezones](https://navapatra.com/docs/timezones/) | **Places by name.** `place.query` is matched against a bundled gazetteer (GeoNames cities of 15,000 people or more worldwide, about 3,800 of them in India, plus every Nepal district, local level and town). Places in Nepal rank a little higher (more so with `lang: "ne"`), though an exact name elsewhere still wins over a partial match in Nepal. If the name fits more than one place, the API refuses to guess and returns `422 ambiguous_place` with up to 10 `candidates`; show them to the user and send the chosen one's `lat` and `lng`. Adding a country (`"Pokhara, Nepal"` or `"country": "NP"`) usually settles it. No match returns `422 place_not_found`. To build a place picker, use `GET /v1/places/search`, which costs nothing. Its `ambiguous` flag is `true` when the same rule would refuse the name, so you know to ask the user before sending the birth. ### `include` | Value | Adds | | --- | --- | | `vargas` | All 16 divisional charts: D1, D2, D3, D4, D7, D9, D10, D12, D16, D20, D24, D27, D30, D40, D45, D60 | | `dashas.yogini` | `dashas.yogini` timeline | | `dashas.tribhagi` | `dashas.tribhagi` timeline | | `panchang` | Birth panchang: vara, tithi with paksha, nakshatra, yoga, karana, lunar month | | `svg` | `svg.north`, the North Indian D1 chart | | `svg.south` | `svg.north` and `svg.south` | Without `include` you get the default sections only. None of them changes the price. ### `options` | Option | Default | Notes | | --- | --- | --- | | `ayanamsha` | `lahiri` | `lahiri`, `lahiri_icrc`, `true_chitra`, `raman`, `kp` | | `nodeType` | `mean` | `mean` or `true` | | `sensitivityMinutes` | none | Integer 1 to 60; adds `sensitivity` | | `dasha` | levels 2 | `{ levels, yearDays, asOf }`; see [Dashas](https://navapatra.com/docs/guides/dashas/) | | `tradition` | `nepal` | Lunar month scheme of the birth panchang | | `sunriseConvention` | `upper_limb_refracted` | Used by the birth panchang | | `disambiguation` | none | `earlier` or `later` for clock-change times | | `nepalHistoricOffset` | `tzdb` | `historic_0540` for the +05:40 convention before 1986 | Plus `lang` (`en`, `ne`, `hi`) and `numerals` (`latin`, `devanagari`) at the top level. See [Conventions](https://navapatra.com/docs/conventions/) for what each one means. ## Response ```json { "subject": { "instantUtc": "1993-08-28T08:50:00Z", "place": { "lat": 28.2096, "lng": 83.9856 }, "tzid": "Asia/Kathmandu", "utcOffset": "+05:45", "offsetSource": "tzdb", "birth": { "ad": "1993-08-28", "bs": "2050-05-12" } }, "lagna": { "sign": { "index": 9, "name": "Dhanu" }, "longitude": 245.8854, "nakshatra": { "index": 19, "name": "Mula" }, "pada": 2 }, "grahas": [ { "id": "moon", "name": "Moon", "sign": { "index": 9, "name": "Dhanu" }, "longitude": 269.0004, "speed": 12.5652, "house": 1, "retrograde": false, "nakshatra": { "index": 21, "name": "Uttara Ashadha" }, "pada": 1 } ], "vargas": { "D1": { "lagna": { "index": 9, "name": "Dhanu" }, "moon": { "index": 9, "name": "Dhanu" }, "...": "..." }, "D9": { "lagna": { "index": 2, "name": "Vrishabha" }, "moon": { "index": 9, "name": "Dhanu" }, "...": "..." } }, "moon": { "rashi": { "index": 9, "name": "Dhanu" }, "nakshatra": { "index": 21, "name": "Uttara Ashadha" }, "pada": 1, "syllable": { "index": 81, "en": "Bhe", "ne": "भे", "hi": "भे" } }, "dashas": { "vimshottari": { "...": "see Dashas" } }, "panchang": { "vara": { "index": 6, "name": "Saturday" }, "tithi": { "index": 12, "name": "Dwadashi", "start": "1993-08-27T21:04:12Z", "end": "1993-08-28T21:54:23Z", "paksha": { "id": "shukla", "index": 1, "name": "Shukla" } }, "nakshatra": { "index": 21, "name": "Uttara Ashadha", "start": "1993-08-28T04:22:55Z", "end": "1993-08-29T05:58:22Z" }, "yoga": { "index": 4, "name": "Saubhagya", "start": "1993-08-28T08:18:52Z", "end": "1993-08-29T08:06:32Z" }, "karana": { "index": 1, "name": "Bava", "start": "1993-08-27T21:04:12Z", "end": "1993-08-28T09:26:14Z" }, "lunarMonth": { "index": 5, "name": "Adhik Bhadrapada", "start": "1993-08-17T19:28:15Z", "end": "1993-09-16T03:10:15Z", "adhik": true, "scheme": "purnimanta" } }, "sensitivity": [ { "field": "lagnaD9", "from": { "index": 2, "name": "Vrishabha" }, "to": { "index": 3, "name": "Mithuna" }, "changesAt": "1993-08-28T08:53:24Z" } ], "svg": { "north": "" }, "meta": { "engineVersion": "0.1.0", "apiVersion": "2026-11-01", "ayanamsha": { "id": "lahiri", "degrees": 23.773159 }, "nodeType": "mean", "tradition": "nepal", "sunriseConvention": "upper_limb_refracted", "tzdbVersion": "unknown", "deltaTModelVersion": "iers-c01-2026.5+quad2100-v1", "bsTableVersion": "2026.10.1", "overridesVersion": null } } ``` - **`grahas`** always lists nine entries in the order sun, moon, mars, mercury, jupiter, venus, saturn, rahu, ketu. Longitudes are sidereal degrees in [0, 360), `speed` is degrees per day and negative values mean `retrograde: true`. - **`house`** is whole-sign: house 1 is the lagna's sign. - **`vargas`** maps each chart to the sign of the lagna and every graha. You get D1 and D9 by default, all 16 with `include: ["vargas"]`. - **`moon.syllable`** is the naming syllable (namakshar) for the Moon's nakshatra and pada, in all three scripts at once. - **`panchang`** describes the moment of birth: the tithi, nakshatra, yoga and karana running then, with their start and end instants. ## Birth-time sensitivity Recorded birth times are often rounded. `options.sensitivityMinutes: N` checks whether anything important changes within N minutes either side of the given time and lists each change: | `field` | What changes | | --- | --- | | `lagnaD1` | Sign of the lagna | | `lagnaD9` | Sign of the lagna in the D9 chart | | `moonNakshatra` | The Moon's nakshatra | | `moonPada` | The Moon's pada | In the example above, a birth 3 minutes and 24 seconds later than recorded would have a Mithuna D9 lagna instead of Vrishabha. When nothing changes within the window, `sensitivity` is `[]`. Without the option, the field is absent. Surface this to users: "Your D9 lagna changes if you were born after 14:38." ## Graha positions only `POST /v1/kundali/planets` (credits: 1) The same `birth` and chart `options` (`ayanamsha`, `nodeType`, `disambiguation`, `nepalHistoricOffset`), returning only `subject`, `lagna`, `grahas` and `meta`. Use it when you need positions and nothing else. ## Response size A default kundali is about 13 KB of JSON. Every response is capped at 512 KB; a request that would exceed it (for example all vargas, every dasha system and `levels: 3`) returns `413 response_too_large` with a hint such as dropping `vargas` or lowering `options.dasha.levels`. Responses are compressed with gzip or brotli when your client asks. ## Determinism The same request with the same `meta` versions returns byte-identical JSON, and nothing in it depends on when you call. That makes kundali responses safe to cache on your side, keyed by the request body and `meta`. --- # Dashas > Vimshottari, Yogini and Tribhagi timelines to three levels, the balance at birth, and finding the running period with asOf. A dasha system divides life into periods ruled by grahas (or, for Yogini, by the eight yoginis). The starting period depends on the Moon's nakshatra at birth, and how much of it remains depends on how far the Moon has travelled through that nakshatra. `POST /v1/dasha` (credits: 1) `POST /v1/kundali` also returns the Vimshottari timeline by default; use `/v1/dasha` when you need only a timeline, a different system or a deeper level. ## Systems | `system` | Cycle | Sequence | | --- | --- | --- | | `vimshottari` (default) | 120 years | Ketu 7, Venus 20, Sun 6, Moon 10, Mars 7, Rahu 18, Jupiter 16, Saturn 19, Mercury 17 | | `yogini` | 36 years, repeated | Mangala 1, Pingala 2, Dhanya 3, Bhramari 4, Bhadrika 5, Ulka 6, Siddha 7, Sankata 8 | | `tribhagi` | 40 years, run three times | The Vimshottari sequence with every period cut to one third | Every timeline covers 120 years from the start of the period running at birth. Yogini lords map to grahas: Mangala to the Moon, Pingala to the Sun, Dhanya to Jupiter, Bhramari to Mars, Bhadrika to Mercury, Ulka to Saturn, Siddha to Venus and Sankata to Rahu. ## Request **curl** ```bash curl https://api.navapatra.com/v1/dasha \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": { "lat": 28.2096, "lng": 83.9856 } }, "options": { "system": "vimshottari", "levels": 3, "asOf": "2026-11-01T00:00:00Z" } }' ``` **TypeScript** ```ts const res = await fetch('https://api.navapatra.com/v1/dasha', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NAVAPATRA_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ birth: { calendar: 'BS', date: '2050-05-12', time: '14:35', place: { lat: 28.2096, lng: 83.9856 } }, options: { system: 'vimshottari', levels: 3, asOf: '2026-11-01T00:00:00Z' }, }), }); const { dasha } = await res.json(); console.log(dasha.current.map((p: { lord: { name: string } }) => p.lord.name).join(' / ')); // Rahu / Ketu / Jupiter ``` **Python** ```python import os import requests res = requests.post( "https://api.navapatra.com/v1/dasha", headers={"Authorization": f"Bearer {os.environ['NAVAPATRA_API_KEY']}"}, json={ "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": {"lat": 28.2096, "lng": 83.9856}, }, "options": {"system": "vimshottari", "levels": 3, "asOf": "2026-11-01T00:00:00Z"}, }, ) dasha = res.json()["dasha"] print(" / ".join(p["lord"]["name"] for p in dasha["current"])) # Rahu / Ketu / Jupiter ``` **PHP** ```php post('https://api.navapatra.com/v1/dasha', [ 'headers' => ['Authorization' => 'Bearer ' . getenv('NAVAPATRA_API_KEY')], 'json' => [ 'birth' => [ 'calendar' => 'BS', 'date' => '2050-05-12', 'time' => '14:35', 'place' => ['lat' => 28.2096, 'lng' => 83.9856], ], 'options' => ['system' => 'vimshottari', 'levels' => 3, 'asOf' => '2026-11-01T00:00:00Z'], ], ]); $dasha = json_decode((string) $res->getBody(), true)['dasha']; echo implode(' / ', array_map(fn ($p) => $p['lord']['name'], $dasha['current'])), PHP_EOL; ``` | Option | Values | Default | | --- | --- | --- | | `system` | `vimshottari`, `yogini`, `tribhagi` | `vimshottari` | | `levels` | `1` maha dasha, `2` adds antar dasha, `3` adds pratyantar dasha | `2` | | `asOf` | An ISO 8601 instant with offset, such as `2026-11-01T00:00:00Z` | none | | `yearDays` | Days per dasha year, up to 400 | `365.25` | The chart options (`ayanamsha`, `nodeType`, `disambiguation`, `nepalHistoricOffset`) apply too, because they change the Moon's position. ## Response ```json { "subject": { "instantUtc": "1993-08-28T08:50:00Z", "tzid": "Asia/Kathmandu", "...": "..." }, "dasha": { "system": { "id": "vimshottari", "index": 1, "name": "Vimshottari" }, "levels": 3, "yearDays": 365.25, "balanceAtBirth": { "lord": { "id": "sun", "index": 1, "name": "Sun" }, "years": 4.9498 }, "periods": [ { "lord": { "id": "sun", "index": 1, "name": "Sun" }, "start": "1992-08-09T18:54:18Z", "end": "1998-08-10T06:54:18Z", "periods": [ { "lord": { "id": "sun", "index": 1, "name": "Sun" }, "start": "1992-08-09T18:54:18Z", "end": "1992-11-27T08:42:18Z", "periods": [ "..." ] } ] } ], "current": [ { "level": 1, "lord": { "id": "rahu", "index": 8, "name": "Rahu" }, "start": "2015-08-10T12:54:18Z", "end": "2033-08-10T00:54:18Z" }, { "level": 2, "lord": { "id": "ketu", "index": 9, "name": "Ketu" }, "start": "2026-02-08T15:54:18Z", "end": "2027-02-27T04:12:18Z" }, { "level": 3, "lord": { "id": "jupiter", "index": 5, "name": "Jupiter" }, "start": "2026-09-13T23:40:30Z", "end": "2026-11-04T02:54:54Z" } ] }, "meta": { "engineVersion": "0.1.0", "ayanamsha": { "id": "lahiri", "degrees": 23.773159 }, "...": "..." } } ``` How to read it: - **`balanceAtBirth`** is how much of the first period was left at birth: here 4.9498 years of a 6-year Sun dasha. - **The first period starts before birth.** `periods[0].start` is when that dasha began, so the timeline lines up with standard tables. Count from `subject.instantUtc` if you only want the part after birth. - **Sub-periods** follow the same sequence, starting from their parent's lord, with lengths in proportion to the lords' years. - All instants are UTC, to the second. ## The running period: `asOf` `current` lists the period running at each level at the instant you pass as `asOf`, from the maha dasha down. It is returned **only** when you send `asOf`. The API never reads its own clock for dashas, so the same request returns the same response whenever you make it, which keeps results cacheable and reproducible. To show "current dasha" in an app, send the current time from your side: ```ts options: { levels: 3, asOf: new Date().toISOString() } ``` ## Size With `levels: 3` a 120-year Vimshottari timeline has 729 pratyantar periods. That is fine on `/v1/dasha`. Inside `/v1/kundali`, combining level 3 with extra systems and all vargas can pass the 512 KB response limit, which returns `413 response_too_large` with a hint about what to drop. See [Kundali](https://navapatra.com/docs/guides/kundali/#response-size). ## Dashas inside a kundali `POST /v1/kundali` always includes `dashas.vimshottari` at 2 levels. Add Yogini or Tribhagi with `include`, and set depth or `asOf` with `options.dasha`: ```json { "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": { "lat": 28.2096, "lng": 83.9856 } }, "include": ["dashas.yogini", "dashas.tribhagi"], "options": { "dasha": { "levels": 1, "asOf": "2026-11-01T00:00:00Z" } } } ``` The same `options.dasha` applies to every system in the response. --- # Matching > Ashtakoota guna milan out of 36 with dosha flags, batch scoring for up to 100 pairs, and symmetric rashi compatibility. Ashtakoota (guna milan) scores a pair on eight kootas, all derived from each person's Moon sign and nakshatra, for a total out of 36. Navapatra returns each koota's points, what produced them and the Nadi, Bhakuta and Gana dosha flags, with labels in the language you ask for. ## Score one pair `POST /v1/matching/ashtakoota` (credits: 1) Each side is either **birth input** or a **kundali payload** the API returned earlier. Nothing is stored on our side, so there are no profile ids: send the data each time. **curl** ```bash curl https://api.navapatra.com/v1/matching/ashtakoota \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "male": { "type": "birth", "birth": { "calendar": "AD", "date": "1990-05-15", "time": "10:30", "place": { "lat": 27.7172, "lng": 85.324 } } }, "female": { "type": "birth", "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": { "lat": 28.2096, "lng": 83.9856 } } }, "lang": "en" }' ``` **TypeScript** ```ts const res = await fetch('https://api.navapatra.com/v1/matching/ashtakoota', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NAVAPATRA_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ male: { type: 'birth', birth: { calendar: 'AD', date: '1990-05-15', time: '10:30', place: { lat: 27.7172, lng: 85.324 } }, }, female: { type: 'birth', birth: { calendar: 'BS', date: '2050-05-12', time: '14:35', place: { lat: 28.2096, lng: 83.9856 } }, }, }), }); const match = await res.json(); console.log(`${match.total} / ${match.max}`); // 28 / 36 ``` **Python** ```python import os import requests res = requests.post( "https://api.navapatra.com/v1/matching/ashtakoota", headers={"Authorization": f"Bearer {os.environ['NAVAPATRA_API_KEY']}"}, json={ "male": { "type": "birth", "birth": {"calendar": "AD", "date": "1990-05-15", "time": "10:30", "place": {"lat": 27.7172, "lng": 85.324}}, }, "female": { "type": "birth", "birth": {"calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": {"lat": 28.2096, "lng": 83.9856}}, }, }, ) match = res.json() print(f"{match['total']} / {match['max']}") # 28 / 36 ``` **PHP** ```php post('https://api.navapatra.com/v1/matching/ashtakoota', [ 'headers' => ['Authorization' => 'Bearer ' . getenv('NAVAPATRA_API_KEY')], 'json' => [ 'male' => ['type' => 'birth', 'birth' => [ 'calendar' => 'AD', 'date' => '1990-05-15', 'time' => '10:30', 'place' => ['lat' => 27.7172, 'lng' => 85.324], ]], 'female' => ['type' => 'birth', 'birth' => [ 'calendar' => 'BS', 'date' => '2050-05-12', 'time' => '14:35', 'place' => ['lat' => 28.2096, 'lng' => 83.9856], ]], ], ]); $match = json_decode((string) $res->getBody(), true); echo $match['total'], ' / ', $match['max'], PHP_EOL; // 28 / 36 ``` Both of these births are demo samples, so the request also works with the `demo` key. ### Subjects ```json { "type": "birth", "birth": { "...": "BirthInput" }, "options": { "ayanamsha": "lahiri", "nodeType": "mean" } } ``` ```json { "type": "kundali", "kundali": { "grahas": [ { "id": "moon", "longitude": 269.0004 } ] } } ``` - `type: "birth"` takes the same `birth` object as `/v1/kundali` and the chart options (`ayanamsha`, `nodeType`, `disambiguation`, `nepalHistoricOffset`). - `type: "kundali"` takes a kundali you got from `/v1/kundali` or `/v1/kundali/planets`. Only the Moon's longitude in `grahas` is read, so you can pass the whole response or just that entry. This is the cheap path for platforms that already hold computed charts. It is not available with the demo key. ## Response ```json { "male": { "rashi": { "index": 9, "name": "Dhanu" }, "nakshatra": { "index": 21, "name": "Uttara Ashadha" }, "moonLongitude": 269.7143 }, "female": { "rashi": { "index": 9, "name": "Dhanu" }, "nakshatra": { "index": 21, "name": "Uttara Ashadha" }, "moonLongitude": 269.0004 }, "kootas": [ { "id": "varna", "index": 1, "name": "Varna", "obtained": 1, "max": 1, "detail": { "male": { "id": "kshatriya", "index": 2, "name": "Kshatriya" }, "female": { "id": "kshatriya", "index": 2, "name": "Kshatriya" } } }, { "id": "nadi", "index": 8, "name": "Nadi", "obtained": 0, "max": 8, "detail": { "male": { "id": "antya", "index": 3, "name": "Antya" }, "female": { "id": "antya", "index": 3, "name": "Antya" } } } ], "total": 28, "max": 36, "percent": 77.78, "dosha": { "nadi": true, "bhakuta": false, "gana": false }, "meta": { "engineVersion": "0.1.0", "apiVersion": "2026-11-01", "ayanamsha": { "id": "lahiri", "degrees": 23.725755 }, "nodeType": "mean", "tzdbVersion": "unknown", "deltaTModelVersion": "iers-c01-2026.5+quad2100-v1", "bsTableVersion": "2026.10.1", "overridesVersion": null } } ``` `kootas` always has all eight, in this order: | `id` | Max | `detail` shows each person's | | --- | --- | --- | | `varna` | 1 | Varna | | `vashya` | 2 | Vashya group | | `tara` | 3 | Tara from the other's nakshatra | | `yoni` | 4 | Yoni animal | | `graha-maitri` | 5 | Moon sign lord | | `gana` | 6 | Gana (Deva, Manushya, Rakshasa) | | `bhakuta` | 7 | Moon sign | | `nadi` | 8 | Nadi (Adi, Madhya, Antya) | `dosha` flags the three classic doshas: `nadi` (same nadi), `bhakuta` (an unfavourable sign relationship) and `gana`. The example pair shares a nakshatra, so it scores well overall but has Nadi dosha. How you present doshas, and any cancellation rules your tradition applies, is up to you; the API reports the raw flags. `meta` names the versions behind the score. Its `ayanamsha` and `nodeType` are those of the first subject computed from birth input (the male side first; here the 1990 birth, hence a slightly different ayanamsha value from the 1993 one). When both sides are kundali payloads, those two fields are left out, because each payload carries its own `meta`. ## Score many pairs `POST /v1/matching/ashtakoota/batch` (credits: 1 credit per pair) Send 1 to 100 pairs. Each can carry your own `id` (up to 64 characters), and `results` come back in the same order with the same ids, followed by one `meta` for the whole batch. The call costs one credit per pair. ```json { "pairs": [ { "id": "p1", "male": { "type": "birth", "birth": { "...": "..." } }, "female": { "type": "birth", "birth": { "...": "..." } } }, { "id": "p2", "male": { "type": "kundali", "kundali": { "...": "..." } }, "female": { "type": "kundali", "kundali": { "...": "..." } } } ], "lang": "en" } ``` ```json { "results": [ { "id": "p1", "total": 28, "max": 36, "percent": 77.78, "dosha": { "nadi": true, "bhakuta": false, "gana": false }, "...": "..." }, { "id": "p2", "total": 13, "max": 36, "percent": 36.11, "dosha": { "nadi": true, "bhakuta": true, "gana": true }, "...": "..." } ], "meta": { "engineVersion": "0.1.0", "apiVersion": "2026-11-01", "...": "..." } } ``` If any pair is invalid the whole batch fails with `400 invalid_input` (or `422` for a place or time problem) and nothing is charged. Error paths point at the pair, for example `pairs.3.female.birth.date`. Batch matching needs a real key; the demo key cannot call it. ## Rashi compatibility `POST /v1/matching/rashi` (credits: 1) When you only know two Moon signs, score them on Graha Maitri (out of 5) and Bhakuta (out of 7), for 12 in all. The score is symmetric: `(a, b)` and `(b, a)` always match. ```bash curl https://api.navapatra.com/v1/matching/rashi \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "a": 3, "b": 9, "lang": "en" }' ``` ```json { "a": { "index": 3, "name": "Mithuna" }, "b": { "index": 9, "name": "Dhanu" }, "grahaMaitri": { "obtained": 0.5, "max": 5 }, "bhakuta": { "obtained": 7, "max": 7 }, "total": 7.5, "max": 12, "percent": 62.5, "meta": { "engineVersion": "0.1.0", "apiVersion": "2026-11-01", "tzdbVersion": "unknown", "deltaTModelVersion": "iers-c01-2026.5+quad2100-v1", "bsTableVersion": "2026.10.1", "overridesVersion": null } } ``` Signs are indexes 1 (Mesha) to 12 (Meena). --- # Panchang and calendar > Daily panchang for any place with sunrise, moonrise and choghadiya, date ranges, BS month grids and Nepal Sambat. The panchang describes a day: its tithi, nakshatra, yoga and karana, the lunar month, and the times of sunrise, sunset, moonrise and moonset. All of these depend on **where** you are, because the Hindu day starts at local sunrise. Every panchang endpoint therefore takes a place. ## One day `GET /v1/panchang/day` (credits: 1) **curl** ```bash curl "https://api.navapatra.com/v1/panchang/day?date=2083-01-01&calendar=BS&lat=27.7172&lng=85.324" \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" ``` **TypeScript** ```ts const url = new URL('https://api.navapatra.com/v1/panchang/day'); url.search = new URLSearchParams({ date: '2083-01-01', calendar: 'BS', lat: '27.7172', lng: '85.324' }).toString(); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.NAVAPATRA_API_KEY}` } }); const day = await res.json(); console.log(day.tithi.name, day.sunrise.local); // Dwadashi 2026-04-14T05:44:16+05:45 ``` **Python** ```python import os import requests res = requests.get( "https://api.navapatra.com/v1/panchang/day", params={"date": "2083-01-01", "calendar": "BS", "lat": 27.7172, "lng": 85.324}, headers={"Authorization": f"Bearer {os.environ['NAVAPATRA_API_KEY']}"}, ) day = res.json() print(day["tithi"]["name"], day["sunrise"]["local"]) # Dwadashi 2026-04-14T05:40:27+05:45 ``` **PHP** ```php get('https://api.navapatra.com/v1/panchang/day', [ 'query' => ['date' => '2083-01-01', 'calendar' => 'BS', 'lat' => 27.7172, 'lng' => 85.324], 'headers' => ['Authorization' => 'Bearer ' . getenv('NAVAPATRA_API_KEY')], ]); $day = json_decode((string) $res->getBody(), true); echo $day['tithi']['name'], ' ', $day['sunrise']['local'], PHP_EOL; ``` | Parameter | Required | Notes | | --- | --- | --- | | `date` | Yes | `YYYY-MM-DD` | | `calendar` | No | `AD` (default) or `BS`, the calendar of `date` | | `lat`, `lng` | Yes | The place. Kathmandu is `27.7172`, `85.324` | | `tzid` | No | Overrides the zone lookup | | `ayanamsha` | No | `lahiri` (default), `lahiri_icrc`, `true_chitra`, `raman`, `kp` | | `tradition` | No | `nepal` (default, purnimanta) or `amanta` | | `sunriseConvention` | No | `upper_limb_refracted` (default) or `center_geometric` | | `lang`, `numerals` | No | Label language and digits | The response for BS 2083 Baishakh 1 in Kathmandu (shortened to three choghadiya periods): ```json { "date": "2026-04-14", "bs": { "date": "2083-01-01", "year": 2083, "month": { "index": 1, "name": "Baishakh" }, "day": 1, "status": "published", "text": "1 Baishakh 2083" }, "tzid": "Asia/Kathmandu", "sunrise": { "utc": "2026-04-13T23:55:27Z", "local": "2026-04-14T05:40:27+05:45" }, "sunset": { "utc": "2026-04-14T12:43:00Z", "local": "2026-04-14T18:28:00+05:45" }, "moonrise": { "utc": "2026-04-13T21:49:09Z", "local": "2026-04-14T03:34:09+05:45" }, "moonset": { "utc": "2026-04-14T09:30:11Z", "local": "2026-04-14T15:15:11+05:45" }, "vara": { "index": 2, "name": "Tuesday" }, "tithi": { "index": 27, "name": "Dwadashi", "start": "2026-04-13T19:39:13Z", "end": "2026-04-14T18:42:54Z", "paksha": { "id": "krishna", "index": 2, "name": "Krishna" } }, "nakshatra": { "index": 24, "name": "Shatabhisha", "start": "2026-04-13T10:33:49Z", "end": "2026-04-14T10:36:10Z" }, "yoga": { "index": 24, "name": "Shukla", "start": "2026-04-13T11:46:48Z", "end": "2026-04-14T10:09:35Z" }, "karana": { "index": 3, "name": "Kaulava", "start": "2026-04-13T19:39:13Z", "end": "2026-04-14T07:16:57Z" }, "lunarMonth": { "index": 1, "name": "Vaishakha", "start": "2026-04-02T02:12:02Z", "end": "2026-05-01T17:23:11Z", "adhik": false, "scheme": "purnimanta" }, "nepalSambat": { "year": 1146, "month": { "index": 6, "name": "Chaula" }, "paksha": { "index": 2, "name": "Ga" }, "day": 12, "adhik": false, "text": "1146 Chaula Ga 12" }, "choghadiya": { "day": [ { "period": 1, "id": "rog", "index": 7, "name": "Rog", "start": "2026-04-13T23:55:27Z", "end": "2026-04-14T01:31:23Z" }, { "period": 2, "id": "udveg", "index": 1, "name": "Udveg", "start": "2026-04-14T01:31:23Z", "end": "2026-04-14T03:07:20Z" } ], "night": [ { "period": 1, "id": "kaal", "index": 5, "name": "Kaal", "start": "2026-04-14T12:43:00Z", "end": "2026-04-14T14:06:55Z" } ] }, "meta": { "engineVersion": "0.1.0", "apiVersion": "2026-11-01", "ayanamsha": { "id": "lahiri", "degrees": 24.22586 }, "tradition": "nepal", "sunriseConvention": "upper_limb_refracted", "tzdbVersion": "unknown", "deltaTModelVersion": "iers-c01-2026.5+quad2100-v1", "bsTableVersion": "2026.10.1", "overridesVersion": null } } ``` How to read it: - **`date`** is always the AD date; `bs` gives the same day in Bikram Sambat. - **Tithi, nakshatra, yoga and karana** are the ones running **at sunrise**, with the instants they started and end. A tithi can start before sunrise and end after the next one, or be skipped entirely between two sunrises (a kshaya tithi); the start and end instants show this. - **Times** for rise and set come as `{ utc, local }`; the local form carries the place's offset. Element start and end instants are UTC. - **`sunrise`, `sunset`, `moonrise` and `moonset` can be `null`**: the Moon does not rise every calendar day, and near the poles the Sun may not either. - **Tithi indexes** run 1 to 30: 1 to 15 shukla paksha, 16 to 30 krishna paksha. Index 27 is krishna Dwadashi. ### Choghadiya (barbela) `choghadiya.day` has 8 periods from sunrise to sunset and `choghadiya.night` 8 periods from sunset to the next sunrise, each one eighth of its span. Each period has its position (`period` 1 to 8), a stable `id` and a localized `name`: | `id` | `index` | Character | | --- | --- | --- | | `udveg` | 1 | Inauspicious | | `char` | 2 | Neutral, good for travel | | `labh` | 3 | Auspicious | | `amrit` | 4 | Most auspicious | | `kaal` | 5 | Inauspicious | | `shubh` | 6 | Auspicious | | `rog` | 7 | Inauspicious | The sequence depends on the weekday. The API returns the periods in order, so you do not need to know the rotation. ## A date range `GET /v1/panchang/range` (credits: 1 credit per started 31 days) The same parameters, with `from` and `to` (inclusive, both in `calendar`) instead of `date`. A range covers 1 to 366 days and costs one credit per started 31 days: a 31-day month is 1 credit, a full year 12. The cost is counted on the AD span, so a BS range costs the same as the matching AD dates. Choghadiya is left out unless you add `include=choghadiya`. ```bash curl "https://api.navapatra.com/v1/panchang/range?from=2083-01-01&to=2083-01-31&calendar=BS&lat=27.7172&lng=85.324" \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" ``` ```json { "from": "2026-04-14", "to": "2026-05-14", "days": [ { "date": "2026-04-14", "bs": { "text": "1 Baishakh 2083", "...": "..." }, "tithi": { "index": 27, "name": "Dwadashi", "...": "..." }, "...": "..." } ], "meta": { "...": "..." } } ``` A longer range, or `to` before `from`, returns `400 invalid_input`. The demo key cannot call this endpoint. ## BS month grid `GET /v1/calendar/month` (credits: 1) Everything a calendar view needs for one BS month: every day's AD date, weekday and tithi at sunrise, plus the month's `bsStatus`. ```bash curl "https://api.navapatra.com/v1/calendar/month?year=2083&month=1&calendar=BS" \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" ``` ```json { "calendar": "BS", "year": 2083, "month": { "index": 1, "name": "Baishakh" }, "bsStatus": "published", "days": [ { "ad": "2026-04-14", "bs": { "date": "2083-01-01", "day": 1, "text": "1" }, "weekday": { "index": 2, "name": "Tuesday" }, "tithi": { "index": 27, "name": "Dwadashi", "paksha": { "id": "krishna", "index": 2, "name": "Krishna" } } } ], "meta": { "engineVersion": "0.1.0", "apiVersion": "2026-11-01", "tzdbVersion": "unknown", "deltaTModelVersion": "iers-c01-2026.5+quad2100-v1", "bsTableVersion": "2026.10.1", "overridesVersion": null, "tzid": "Asia/Kathmandu" } } ``` Tithi at sunrise is computed for Kathmandu unless you pass `lat` and `lng`. Send both or neither: only one of them returns `400 invalid_input`. `tradition` and `lang` / `numerals` work as above; with `lang=ne`, `bs.text` is the day in Devanagari digits, ready for a grid cell. > **Note: Holidays and festivals** > > Public holidays and festivals are not in the month grid yet. They will be added, with a `source` for every entry, once the data licence is settled; the [changelog](https://navapatra.com/docs/changelog/) will announce it. ## Convert a date `GET /v1/calendar/convert` turns a BS date into AD or back, with Nepal Sambat on request. See [Bikram Sambat dates](https://navapatra.com/docs/bikram-sambat/). ## Demo key With the `demo` key, the panchang day, month grid and Nepal Sambat conversions accept the five sample places (Pokhara, Kathmandu, Biratnagar, New Delhi, Varanasi; see the [Quickstart](https://navapatra.com/docs/quickstart/#3-the-sample-subjects)) and any date. --- # Charts and SVG theming > Server-rendered North and South Indian charts as SVG, and how to theme them with --kc-* CSS variables and class names. Navapatra draws charts on the server and returns plain SVG. There is no chart library to install and nothing to compute in the browser; you drop the SVG into the page and style it with CSS. These are real responses for the Pokhara sample, themed live by this page's CSS. ## Get a chart `POST /v1/charts/svg` (credits: 1) Send either `birth` (computed for you) or `kundali` (a payload you already have from `/v1/kundali` or `/v1/kundali/planets`, of which only the lagna and graha longitudes are read). Exactly one of the two. **curl** ```bash curl https://api.navapatra.com/v1/charts/svg \ -H "Authorization: Bearer $NAVAPATRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": { "lat": 28.2096, "lng": 83.9856 } }, "style": "north", "chart": "D1", "lang": "ne" }' \ -o chart.svg ``` **TypeScript** ```ts const res = await fetch('https://api.navapatra.com/v1/charts/svg', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NAVAPATRA_API_KEY}`, 'Content-Type': 'application/json', Accept: 'application/json', // JSON { svg, style, chart } instead of image/svg+xml }, body: JSON.stringify({ birth: { calendar: 'BS', date: '2050-05-12', time: '14:35', place: { lat: 28.2096, lng: 83.9856 } }, style: 'north', chart: 'D1', lang: 'ne', }), }); const { svg } = await res.json(); document.querySelector('#chart')!.innerHTML = svg; ``` **Python** ```python import os import requests res = requests.post( "https://api.navapatra.com/v1/charts/svg", headers={"Authorization": f"Bearer {os.environ['NAVAPATRA_API_KEY']}"}, json={ "birth": {"calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": {"lat": 28.2096, "lng": 83.9856}}, "style": "north", "chart": "D1", "lang": "ne", }, ) open("chart.svg", "w", encoding="utf-8").write(res.text) ``` **PHP** ```php post('https://api.navapatra.com/v1/charts/svg', [ 'headers' => ['Authorization' => 'Bearer ' . getenv('NAVAPATRA_API_KEY')], 'json' => [ 'birth' => [ 'calendar' => 'BS', 'date' => '2050-05-12', 'time' => '14:35', 'place' => ['lat' => 28.2096, 'lng' => 83.9856], ], 'style' => 'north', 'chart' => 'D1', 'lang' => 'ne', ], ]); file_put_contents('chart.svg', (string) $res->getBody()); ``` | Field | Values | Default | | --- | --- | --- | | `style` | `north` (diamond houses, fixed houses) or `south` (square, fixed signs) | `north` | | `chart` | `D1` or `D9` | `D1` | | `lang`, `numerals` | Labels and digits inside the chart | `en`, then by language | | `options.showDegrees` | Adds each graha's degree in its sign (D1 only) | `false` | | `options.size` | Width and height in pixels, 100 to 2000 | `400` | | `options.ayanamsha`, `nodeType`, ... | The usual chart options, for `birth` input | | The response is `image/svg+xml`, or JSON `{ "svg", "style", "chart" }` when you send `Accept: application/json`. To get charts alongside a kundali in one call, use `include: ["svg"]` (North) or `["svg.south"]` (both) on `/v1/kundali`; those are D1 charts. Charts made on the free plan or the Starter pack carry a small "Powered by Navapatra" line in the bottom corner. Growth, Scale and Volume packs, annual and enterprise plans do not. An org that holds Starter and a larger pack gets the larger pack's charts. ## Theme with CSS variables Every colour and font in the SVG reads a CSS custom property, with a sensible fallback. Set them on the SVG or any ancestor: | Variable | Used for | Fallback | | --- | --- | --- | | `--kc-bg` | Chart background | transparent | | `--kc-house-bg` | House fill | transparent | | `--kc-line` | House lines | grey | | `--kc-text` | Graha labels | `currentColor` | | `--kc-muted` | Sign numbers, degrees, watermark | grey | | `--kc-accent` | Lagna label and marker | vermilion | | `--kc-accent-soft` | Lagna house fill | translucent vermilion | | `--kc-font` | Text | inherited | | `--kc-font-mono` | Degrees | `ui-monospace` | ```css /* Match the chart to your design tokens. */ .kc-root { --kc-line: var(--border-strong); --kc-text: var(--text); --kc-muted: var(--text-3); --kc-accent: var(--accent-text); --kc-accent-soft: var(--accent-soft); --kc-font: var(--font-sans); --kc-font-mono: var(--font-mono); } ``` Because the variables resolve in your page, the same SVG follows your light and dark themes with no extra request. ## Class names and data attributes For anything variables cannot do, target the elements directly: | Selector | Element | | --- | --- | | `.kc-root` | The ``, also `.kc-north` or `.kc-south` | | `.kc-house`, `.kc-house-1` to `.kc-house-12` | House polygons; `.kc-lagna-house` marks house 1 | | `.kc-lines` | The chart's line drawing | | `.kc-sign` | Sign numbers | | `.kc-graha`, `.kc-graha-sun` ... `.kc-graha-ketu` | Graha labels | | `.kc-retro` | The retrograde marker | | `.kc-degree` | Degrees, with `showDegrees` | | `.kc-lagna`, `.kc-lagna-label`, `.kc-lagna-mark` | The lagna label and its marker | | `.kc-watermark` | The "Powered by" line (free plan and Starter pack) | Houses, signs and grahas also carry `data-house`, `data-sign` and `data-graha` (and `data-retrograde="true"`), which makes it easy to add hover states or tooltips: ```css .kc-house:hover { fill: var(--accent-soft); } .kc-graha[data-retrograde='true'] { font-style: italic; } ``` ## Accessibility Each SVG has `role="img"` with a `` (for example "Birth chart, North Indian style") and a `` that lists the lagna and every graha's house and sign, so screen readers get the whole chart as text. Labels use the `lang` you requested, and the `` carries a matching `lang` attribute. ## Embedding safely The SVG contains no scripts, links or external references. You can inline it with `innerHTML` or serve it as an image. Each chart's internal ids are unique to its content, so several charts can sit on one page. For interactive charts with tooltips and keyboard navigation, use the [`` widget](https://navapatra.com/docs/guides/widgets/). --- # Widgets > Web components that display API responses, kundali-chart, dasha-table, panchang-card and match-score, in any framework. `@navapatra/widgets` is a set of web components (built with Lit 3) that display API JSON. They work in plain HTML, React, Vue, Svelte and anything else that renders DOM. | Element | Shows | `data` accepts | | --- | --- | --- | | `` | The chart SVG, with tooltips and keyboard navigation | `/v1/kundali` with `include: ["svg"]` (add `"svg.south"` for both styles), `/v1/charts/svg` JSON, or an SVG string | | `` | A dasha timeline, current period highlighted | `/v1/dasha`, or `/v1/kundali` (its `dashas`) | | `` | A day's panchang and choghadiya | `/v1/panchang/day` | | `` | A guna milan score with kootas and doshas | `/v1/matching/ashtakoota`, or one item of a batch `results` | `` never draws a chart in the browser. It shows the SVG the API rendered and adds interaction; data without an `svg` shows an error state. ## Install ```bash npm install @navapatra/widgets ``` ```js import '@navapatra/widgets'; // registers all four elements ``` Or load one self-registering file from a CDN (Lit included): ```html ``` The package is published to npm with the first public release. ## Display data from your server The recommended setup: your backend calls the API with a secret key, and the page hands the JSON to the element. ```html ``` You can also fetch with a [client token](https://navapatra.com/docs/guides/backend-proxy/) in your own code and set `.data`, or let the widget fetch with it (below). ## Fetch mode A widget can call the API itself when you give it a browser-safe credential in its `publishable-key` attribute: - a **publishable key** (`pk_`) whose allowlist includes your site's origin, or - a **client token** (`ct_`) that your server created with `POST /v1/tokens`: at most 15 minutes, only the endpoints you scoped, an optional credit cap. See [Mobile apps and the backend proxy](https://navapatra.com/docs/guides/backend-proxy/). ```html ``` `endpoint` is resolved against `https://api.navapatra.com/v1/` (a full URL also works). With `request` the widget sends a POST with that JSON body; without it, a GET. The element fires `kc-load` (detail: the JSON) and `kc-error` (detail: `{ code, status, message }`). When a client token expires, the API answers `token_expired` and the widget fires `kc-error`; fetch a new token from your backend and set `publishable-key` again, and the widget fetches again. A secret key (`sk_`) is refused in the browser with a console warning and a `secret_key_in_browser` error, and any other value fails with `invalid_key`. Remember that `Origin` is advisory: give publishable keys a credit cap. See [Authentication](https://navapatra.com/docs/authentication/#publishable-keys-pk_). ## Attributes | Attribute | Elements | Meaning | | --- | --- | --- | | `lang` | all | `en`, `ne` or `hi` for the widget's own labels; inherited from the nearest `[lang]`. Labels inside the data come from the API in the language you requested. | | `numerals` | all | `latin` or `devanagari`. Default: Devanagari for `ne` and `hi`. | | `chart-style` | `kundali-chart` | `north` (default) or `south`, for kundali responses that carry both | | `system` | `dasha-table` | `vimshottari` (default), `yogini` or `tribhagi`, for kundali responses | | `hide-choghadiya` | `panchang-card` | Hides the choghadiya lists | `` highlights the current period when the request included `options.asOf`. ## Theming Set custom properties on the element or an ancestor. Defaults follow the design tokens when the page loads them, with built-in light and dark values otherwise. | Property | Used for | | --- | --- | | `--kc-bg`, `--kc-surface`, `--kc-border` | Card background, inset panels, borders | | `--kc-line` | Chart lines | | `--kc-text`, `--kc-text-2`, `--kc-muted` | Text, secondary rows, labels | | `--kc-accent`, `--kc-accent-soft` | Lagna, current period, score; their highlights | | `--kc-success`, `--kc-danger` | Dosha badges, errors | | `--kc-font`, `--kc-font-mono` | Text; dates, times and points | | `--kc-radius`, `--kc-radius-sm` | Corners | | `--kc-tooltip-bg`, `--kc-tooltip-text` | Chart tooltip | | `--kc-duration` | Transitions | The chart SVG inside reads the variables in [Charts and SVG theming](https://navapatra.com/docs/guides/charts/#theme-with-css-variables). ## Accessibility - `` is one focusable group named by the chart title. Arrow keys, Home and End move between the lagna and the grahas, Escape hides the tooltip, and each placement is announced. A visually hidden list gives every placement as text. - Tooltips open on hover and touch as well as focus. - Motion is limited to short fades and is removed under `prefers-reduced-motion`. ## React ```tsx import { KundaliChart, PanchangCard } from '@navapatra/widgets/react'; console.warn(e.detail)} />; ``` The wrapper sets `data` and the other properties as DOM properties, so objects are not stringified into attributes. `react` is an optional peer dependency. --- # MCP for AI agents > Connect Claude, ChatGPT and other agents to kundali, dasha, matching, panchang and calendar tools over the Model Context Protocol. Navapatra runs a remote [Model Context Protocol](https://modelcontextprotocol.io) server, so an AI agent can compute charts and calendars with correct, structured data instead of guessing. It speaks **Streamable HTTP** at the path `/mcp` and authenticates with your API key. ## Connect Add the server to any MCP client that supports remote HTTP servers, sending your key as a bearer token: ```json { "url": "/mcp", "headers": { "Authorization": "Bearer sk_live_..." } } ``` The production server address is published in the [changelog](https://navapatra.com/docs/changelog/) when it goes live. A `GET` on the server root returns its name, endpoint, transport and auth scheme, so you can check a connection by hand. A missing or malformed `Authorization` header gets an HTTP 401 with a JSON-RPC error asking for the key. Use a `sk_test_` key while you build an agent: results are identical and nothing is charged. The public `demo` key works too, for the sample subjects only. In this repository, `pnpm --filter @navapatra/mcp dev` serves a local server at `http://localhost:8789/mcp` against a local API. ## Tools | Tool | What it does | REST call | Credits | | --- | --- | --- | --- | | `generate_kundali` | Birth chart: lagna, grahas with sign, house, degree, nakshatra and pada, Moon sign and syllable, Vimshottari dasha; optional vargas, Yogini and Tribhagi dashas, birth panchang and SVG | `POST /v1/kundali` | 1 | | `get_dasha_periods` | Dasha timeline and the periods running on a date | `POST /v1/dasha` | 1 | | `match_kundali` | Ashtakoota guna milan for two births | `POST /v1/matching/ashtakoota` | 1 | | `get_panchang` | Daily panchang and choghadiya for a place | `GET /v1/panchang/day` (plus a free place search when given a name) | 1 | | `convert_date` | BS to AD or AD to BS, optionally with Nepal Sambat | `GET /v1/calendar/convert` | 1 | | `search_places` | Find a place's coordinates and time zone | `GET /v1/places/search` | 0 | Every tool call is billed exactly like its REST equivalent, from the same organization balance, under the same rate limits. Tool inputs mirror the REST fields (birth date, local time, calendar, place as a name or coordinates, ayanamsha, language), with descriptions written for models. Dates run from AD 1900 to 2100 (BS 1957 to 2157). The tools offer the `lahiri`, `lahiri_icrc` and `true_chitra` ayanamshas; for `raman` or `kp`, call the REST API. ## What the agent gets back Each result has two parts: - **A short text summary** for the model to read, such as the lagna, the grahas and the running dasha, ending with the credits charged and the request id. - **`structuredContent`**: the full API JSON, plus `_metering` with `requestId`, `creditsCharged` and `creditsRemaining`. API errors come back as tool errors (`isError: true`) with a next step the model can act on. For example, an `ambiguous_place` error lists the candidate places and tells the model to ask the user which one (`get_panchang` does this too for a place name that fits several places, rather than taking the first search result); `ambiguous_local_time` tells it to ask, then retry with `disambiguation`. ## Behaviour to know - **The current dasha.** `get_dasha_periods` sends `asOf` as the current time when the agent does not give one, so "which dasha am I in" works out of the box. The REST API itself never reads the clock; see [Dashas](https://navapatra.com/docs/guides/dashas/). - **Places.** Agents are told to prefer coordinates and to ask the user rather than guess when a place or time is ambiguous. - **Statelessness.** One server instance handles each request and nothing is stored. Your key is used only for the API calls of that request. - **Version.** Send the `Navapatra-Version` header from your MCP client to pin the API version the tools use; otherwise your key's pinned version applies. --- # Credits and metering > What a call costs, the 2,000-credit monthly grant, how lots are spent, test and demo keys, and how to read your balance. Navapatra is prepaid: your organization holds a balance of credits and each successful call uses some. Every language is the same price, and a full kundali costs the same as a date conversion. ## The rules 1. **Only successful responses cost credits.** Credits are charged only for `2xx` responses. `4xx` and `5xx` responses are free: credits are reserved before the computation and released if it fails. 2. **Units.** - 1 credit per request. - Batch matching: 1 credit per pair. - Panchang range: 1 credit per started 31 days, counted on the AD span. A BS range is converted to AD dates first, so its cost matches the same days asked for in AD. - Free endpoints (0 credits): `/v1/places/search`, `/v1/meta`, `/v1/usage` and `/v1/tokens`. 3. **One pool per organization.** Credits belong to the organization. All its keys and client tokens draw from the same balance. A cap per secret key or per token is optional. Publishable keys always have one (2,000 credits a month by default). Client tokens count toward the cap of the key that issued them, and a token's own cap can only lower that. 4. **Spending order.** The monthly free grant is used first, then paid lots, earliest expiry first. 5. **Free grant.** Every organization, free or paid, receives 2,000 credits each calendar month (UTC). Unused grant credits do not roll over. 6. **Test keys** (`sk_test_`) are never charged. They are limited to a daily quota of 100 and 10 requests per minute per organization, and return the same output as live keys. The quota counts each request's credit cost, so a 62-day panchang range uses 2. 7. **The demo key** (public) accepts sample subjects only, allows 10 requests per minute per IP, and is not counted against any organization. 8. **MCP** tool calls are billed as their REST equivalents. ## Price per endpoint | Endpoint | Credits | | --- | --- | | `POST /v1/kundali` (any `include`) | 1 | | `POST /v1/kundali/planets` | 1 | | `POST /v1/dasha` | 1 | | `POST /v1/matching/ashtakoota` | 1 | | `POST /v1/matching/ashtakoota/batch` | 1 per pair (1 to 100) | | `POST /v1/matching/rashi` | 1 | | `GET /v1/panchang/day` | 1 | | `GET /v1/panchang/range` | 1 per started 31 days of the AD span (up to 12 for 366 days) | | `GET /v1/calendar/convert` | 1 | | `GET /v1/calendar/month` | 1 | | `POST /v1/charts/svg` | 1 | | `GET /v1/timezone` | 1 | | `GET /v1/places/search` | 0 | | `POST /v1/tokens` | 0 | | `GET /v1/meta` | 0 | | `GET /v1/usage` | 0 | ## Headers on every response | Header | Meaning | | --- | --- | | `x-request-id` | Unique id for the request; quote it to support | | `x-credits-charged` | Credits this request used (`0` for errors, test keys and the demo key) | | `x-credits-remaining` | Your organization's balance after the request (absent for the demo key) | ```http HTTP/1.1 200 OK x-request-id: b5d35e5f-faff-4af5-8152-bec267ed9af4 x-credits-charged: 1 x-credits-remaining: 6977 ratelimit-limit: 120 ratelimit-remaining: 119 ratelimit-reset: 1 ``` ## Check the balance `GET /v1/usage` (credits: 0) Needs a secret key (`sk_live_` or `sk_test_`); publishable keys, client tokens and the demo key get `403 forbidden`. Returns the balance by lot, the free grant for this month, this key's use this month and the test-key quota for today: ```json { "orgId": "org_dev", "balance": 6872, "month": "2026-10", "freeGrant": { "granted": 2000, "remaining": 1872, "expiresAt": "2026-11-01T00:00:00Z" }, "lots": [ { "id": "grant-2026-10", "source": "grant", "remaining": 1872, "expiresAt": "2026-11-01T00:00:00Z" }, { "id": "dev-pack-1", "source": "pack", "remaining": 5000, "expiresAt": "2027-01-01T00:00:00Z" } ], "key": { "id": "key_dev_live", "type": "sk_live", "creditCap": null, "usedThisMonth": 126 }, "testQuota": { "day": "2026-10-10", "used": 0, "limit": 100 } } ``` Lots appear in spending order. A lot's `source` is `grant` (the monthly free grant), `pack` (a credit pack), `annual` (an annual plan) or `adjust` (a manual adjustment). ## When credits run out | Situation | Response | | --- | --- | | The organization's balance is 0 | `402 insufficient_credits`, with a `topUpUrl` to the dashboard and `x-credits-remaining: 0` | | A key reached its monthly credit cap | `402 credit_cap_reached` until the next UTC month | | A client token reached its `creditCap` | `402 credit_cap_reached`; issue a new token if appropriate | | The credits store is briefly unreachable and the key has no recent successful call | `503 service_unavailable` with `Retry-After`; nothing is charged | ```json { "type": "https://navapatra.com/docs/errors#insufficient_credits", "title": "Insufficient credits", "status": 402, "detail": "Your organization has no credits left. Top up to continue.", "code": "insufficient_credits", "requestId": "...", "topUpUrl": "https://app.navapatra.com/billing" } ``` Credits are bought as packs on the [billing page](https://app.navapatra.com/billing) of the dashboard and paid in NPR by bank transfer, with a QR code or the account details. Credits from bank transfers are added after our team confirms the payment, usually within 12 to 16 hours. Indian customers: email [hello@navapatra.com](mailto:hello@navapatra.com) for invoicing. The free grant arrives on the first of each UTC month even when the balance is zero. --- # Rate limits > Per-key request limits, the RateLimit headers, 429 with Retry-After, and the separate limits for test keys, the demo key and place search. Rate limits protect the service and your balance from runaway loops. They are counted in requests per minute, separately from credits. ## The limits | Caller | Limit | | --- | --- | | `sk_live_` key | The key's own limit, shown in the dashboard | | `pk_` key | The key's own limit, counted per visitor IP, and the key's overall limit | | Client token (`ct_`) | The limit of the key that created it, shared with that key and all its other tokens | | `sk_test_` keys | 10 requests per minute and a daily quota of 100 per UTC day counted in credits, shared by the organization | | `demo` | 10 requests per minute per IP | | `GET /v1/places/search` | An extra 30 requests per minute per key or token, on top of the above | Limits work as a token bucket: a key with a limit of 120 can burst up to 120 requests, and capacity refills continuously at 120 per minute. ## Headers Successful and rate-limited responses carry the IETF `RateLimit` headers for the bucket that applied: | Header | Meaning | | --- | --- | | `ratelimit-limit` | Requests per minute for this bucket | | `ratelimit-remaining` | Requests you can make right now | | `ratelimit-reset` | Seconds until the bucket is full again | ## When you hit a limit The API returns `429` with `code: "rate_limited"` and a `Retry-After` header in seconds. Nothing is charged. ```http HTTP/1.1 429 Too Many Requests Retry-After: 5 ratelimit-limit: 10 ratelimit-remaining: 0 ratelimit-reset: 59 ``` ```json { "type": "https://navapatra.com/docs/errors#rate_limited", "title": "Too many requests", "status": 429, "detail": "Rate limit exceeded. Retry in 5 s.", "code": "rate_limited", "requestId": "7b02b780-7584-4888-b40f-0d701653609e" } ``` When a test key passes 100 requests in a day, the `detail` says so and `Retry-After` points to the next UTC midnight. ## Retrying well Wait for `Retry-After`, then retry. For bulk jobs, pace requests below the limit instead of relying on retries: ```ts async function call(url: string, init: RequestInit, attempts = 3): Promise { for (let i = 0; ; i++) { const res = await fetch(url, init); if (res.status !== 429 || i >= attempts - 1) return res; const wait = Number(res.headers.get('retry-after') ?? '1'); await new Promise((r) => setTimeout(r, Math.min(wait, 60) * 1000)); } } ``` The [TypeScript SDK](https://navapatra.com/docs/guides/typescript-sdk/) does this for you. For many guna milan scores, use [batch matching](https://navapatra.com/docs/guides/matching/#score-many-pairs): 100 pairs in one request count once against the rate limit. Need a higher limit? Write to hello@navapatra.com with your expected volume. --- # Errors > The problem+json format and every error code the API returns, with its HTTP status, cause and fix. Errors use [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details with `Content-Type: application/problem+json`. Branch on **`code`**: it is stable and never renamed. `title` and `detail` are for people and may be reworded. ```json { "type": "https://navapatra.com/docs/errors#invalid_input", "title": "Invalid input", "status": 400, "detail": "The request failed validation; see errors.", "code": "invalid_input", "requestId": "104cb9e8-3807-46aa-bfb9-4f62a03bc53f", "errors": [ { "path": "birth.date", "message": "date must be YYYY-MM-DD" }, { "path": "birth.place.lng", "message": "lat and lng are required unless query is given" } ] } ``` | Member | Always | Meaning | | --- | --- | --- | | `type` | Yes | A link to the code's section on this page | | `title` | Yes | Short summary of the code | | `status` | Yes | The HTTP status, repeated | | `detail` | Yes | What went wrong in this request | | `code` | Yes | Stable, machine-readable code | | `requestId` | Yes | Same as the `x-request-id` header; quote it to support | | `errors` | Sometimes | Field-level problems as `{ path, message }`, with dotted paths into your request | | `candidates` | Sometimes | Places to choose from, for `ambiguous_place` | | `details` | Sometimes | Extra context, for `ambiguous_local_time` and `out_of_range` | | `topUpUrl` | Sometimes | Where to buy credits, for `insufficient_credits` | No error response ever costs credits. ## All codes | Status | Code | In short | | --- | --- | --- | | 400 | [`invalid_input`](#invalid_input) | The request is malformed or a value is invalid | | 400 | [`unsupported_version`](#unsupported_version) | Unknown `Navapatra-Version` | | 401 | [`unauthorized`](#unauthorized) | Missing, invalid or revoked key or token | | 401 | [`token_expired`](#token_expired) | The client token has expired | | 402 | [`insufficient_credits`](#insufficient_credits) | The organization has no credits | | 402 | [`credit_cap_reached`](#credit_cap_reached) | A key or token cap is used up | | 403 | [`secret_key_in_browser`](#secret_key_in_browser) | A secret key was sent from a browser | | 403 | [`origin_not_allowed`](#origin_not_allowed) | A publishable key was used from another origin | | 403 | [`demo_restricted`](#demo_restricted) | Not available with the demo key | | 403 | [`forbidden`](#forbidden) | The key or token may not call this endpoint | | 404 | [`not_found`](#not_found) | No such route | | 405 | [`method_not_allowed`](#method_not_allowed) | The path exists but not with this method | | 413 | [`response_too_large`](#response_too_large) | The response would exceed 512 KB | | 422 | [`out_of_range`](#out_of_range) | A date is outside the supported range | | 422 | [`ambiguous_local_time`](#ambiguous_local_time) | The local time is skipped or repeated by a clock change | | 422 | [`ambiguous_place`](#ambiguous_place) | The place name matches several places | | 422 | [`place_not_found`](#place_not_found) | The place name matches nothing | | 429 | [`rate_limited`](#rate_limited) | Too many requests | | 500 | [`server_misconfigured`](#server_misconfigured) | A server setting is missing | | 500 | [`internal_error`](#internal_error) | Something failed on our side | | 503 | [`service_unavailable`](#service_unavailable) | Credit checks, rate limiting or key validation are briefly unavailable | ## 400 ### invalid_input The body is not valid JSON, a `POST` body was sent without `Content-Type: application/json`, a field is missing or has the wrong type or format, or a value is impossible (such as BS month 5 day 33, AD 2025-02-30 or a `tzid` that is not an IANA time zone). `errors` lists each problem with a dotted `path`, for example `birth.place.lng` or `pairs.3.female.birth.date`. **Fix:** correct the fields named in `errors`. Retrying unchanged will fail the same way. ### unsupported_version The `Navapatra-Version` header names a version that does not exist, for example `Unknown Navapatra-Version "2020-01-01"`. **Fix:** send a date listed in `apiVersions` from `GET /v1/meta`, or leave the header out to use your key's pinned version. See [Versioning](https://navapatra.com/docs/versioning/). ## 401 ### unauthorized No `Authorization` header, a header not in the form `Bearer `, an unknown or revoked key, or an invalid client token. **Fix:** send `Authorization: Bearer `. Check the key has not been rolled or revoked in the dashboard. To try the API without a key, use `demo`. ### token_expired A client token (`ct_`) is past its `expiresAt`. **Fix:** ask your backend for a new token and retry once. See [Mobile apps](https://navapatra.com/docs/guides/backend-proxy/). ## 402 ### insufficient_credits The organization's balance is 0. The response includes `topUpUrl` and `x-credits-remaining: 0`. **Fix:** buy credits on the [billing page](https://app.navapatra.com/billing), or wait for the monthly free grant on the first of the month (UTC). Test keys never hit this. See [Credits](https://navapatra.com/docs/credits/). ### credit_cap_reached The key has used its monthly credit cap ("This key reached its monthly credit cap.") or the client token has used its `creditCap` ("This client token reached its credit cap."). **Fix:** for a key, raise the cap in the dashboard or wait for the next UTC month. For a token, issue a new one if the user should continue. ## 403 ### secret_key_in_browser An `sk_live_` or `sk_test_` key arrived with an `Origin` header, meaning it was sent from a browser. **Fix:** move the call to your server. In browsers, use a `pk_` key with an origin allowlist or a client token. If the key was ever in client-side code, roll it. See [Authentication](https://navapatra.com/docs/authentication/). ### origin_not_allowed A `pk_` key was used without an `Origin` header or from an origin that is not on its allowlist. **Fix:** add the exact origin (scheme, host and port, such as `https://app.example.com`) to the key's allowlist in the dashboard. ### demo_restricted The `demo` key was used with a birth or place that is not one of the samples, with a kundali payload instead of birth input, or on an endpoint it cannot call (batch matching, panchang range, tokens, usage). **Fix:** use one of the [sample subjects](https://navapatra.com/docs/quickstart/#3-the-sample-subjects), or sign up for a test key. ### forbidden The credential is valid but not allowed here: a client token calling an endpoint outside its scopes, a key other than `sk_live_` calling `POST /v1/tokens`, or anything but a secret key (`sk_live_` or `sk_test_`) calling `GET /v1/usage`. **Fix:** add the endpoint id to the token's `scopes`, or use the right key type. ## 404 ### not_found No operation exists at that path, for example any unknown path under `/v1`. A known path called with the wrong method, such as `GET /v1/kundali` (it is a `POST`), gets [`405 method_not_allowed`](#method_not_allowed) instead, as does any method other than `GET`, `HEAD` and `POST`. **Fix:** check the method and path in the [API reference](https://navapatra.com/docs/api/). ## 405 ### method_not_allowed A request under `/v1` used a method the path does not support: `GET` (or `HEAD`) on a `POST` operation such as `/v1/kundali`, `POST` on a `GET` operation, or a method other than `GET`, `HEAD` or `POST`, such as `PUT` or `DELETE`. The `Allow` header lists the methods that path accepts. `HEAD` works wherever `GET` does. **Fix:** use the method shown for the operation in the [API reference](https://navapatra.com/docs/api/). ## 413 ### response_too_large The response would be larger than 512 KB, usually a kundali with all vargas, every dasha system and `levels: 3`, or a long panchang range with choghadiya. `detail` suggests what to drop. **Fix:** narrow `include`, lower `options.dasha.levels`, shorten the range, or split the request. ## 422 ### out_of_range A birth date outside AD 1900-01-01 to 2100-12-31 (`details.ad` has the date), or a BS date that falls outside that window (BS 1956-09-19 to 2157-09-15). **Fix:** check the date and its `calendar`. Supported ranges are listed in `GET /v1/meta`. ### ambiguous_local_time The birth's local time is skipped (`details.kind: "gap"`) or repeated (`"overlap"`) by a daylight saving change. `details.candidates` holds both possible UTC instants. **Fix:** ask which was meant and resend with `options.disambiguation: "earlier"` or `"later"`. See [Timezones](https://navapatra.com/docs/timezones/#ambiguous-local-times). ### ambiguous_place `place.query` matches more than one place. `candidates` lists up to 10, each with `name`, `admin1`, `country`, `lat`, `lng` and `tzid`. The same rule sets `ambiguous: true` on a `GET /v1/places/search` response, so a place picker can ask before a birth request fails. **Fix:** let the user choose and resend with that candidate's `lat` and `lng`, or narrow the query: add a province or district after a comma (`"Pokhara, Kaski"`), or set `place.country`. ### place_not_found `place.query` matches nothing in the gazetteer. `errors` points at the field. **Fix:** check the spelling, try `GET /v1/places/search`, or send `lat` and `lng`. ## 429 ### rate_limited Too many requests for the bucket that applies, or a test key's daily 100 is used up. The response has `Retry-After` in seconds. **Fix:** wait `Retry-After` seconds and retry. See [Rate limits](https://navapatra.com/docs/rate-limits/). ## 500 ### server_misconfigured A required server setting is missing, for example client tokens are not configured. This is a fault on our side. **Fix:** contact hello@navapatra.com with the `requestId`. ### internal_error An unexpected failure. The request was not charged. **Fix:** retry with backoff. If it persists, contact hello@navapatra.com with the `requestId`. ## 503 ### service_unavailable A store the API depends on could not be reached in time: - **Credits.** The store that holds your organization's credits did not answer, and the request could not be served in degraded mode. During such an outage a key that made a successful call in the last 10 minutes keeps working, up to a small credit allowance per organization; any other key gets this error. `GET /v1/usage` answers this error while the store is down. - **Rate limiting.** The per-IP limiter for the demo key and publishable (`pk_`) keys is down. These requests fail closed rather than run unlimited. - **Key validation.** The key store is down and this key was not validated recently by the server handling the request. The response has `Retry-After` in seconds and nothing is charged. **Fix:** wait `Retry-After` seconds and retry with backoff. --- # Privacy > The API is stateless. Birth data is used to compute your response and is never stored or logged. Birth date, time and place are personal data. Navapatra treats them as input to a calculation and nothing more. This page describes the API; the [privacy policy](https://navapatra.com/legal/privacy) is the binding text and includes the Data Processing Addendum that applies to every plan. ## Who is responsible For data in your API requests, Navapatra is a processor (a Data Processor under India's Digital Personal Data Protection Act, 2023) acting on your instructions. You are the controller (Data Fiduciary): you give your end users notice and obtain any consent the law requires, including verifiable parental consent for children's data where required. ## What happens to a request - **Request and response bodies are never stored or logged.** That covers birth data, kundali payloads sent for matching or charts, and every result. - **Each request writes one log line** with the request id, the route (for example `POST /v1/kundali`), the HTTP status, the duration and your organization id. No body, no query string, no IP address, no birth data. The platform's own per-request invocation logs, which would include the URL, are switched off. - **Usage records** hold the organization, key, endpoint, status, credits charged, duration and time. They feed your dashboard charts and billing. - **Unexpected errors** are logged by error type and code location only, because error messages can echo request values. - **Rate limits** for the `demo` key and publishable (`pk_`) keys are counted per client IP address (IPv6 by its /64 prefix). The counter holds only a token count and a timestamp. - **Nothing is cached** between requests: responses carry `Cache-Control: no-store`. There are no server-side profile ids. Matching and chart endpoints take the data each time, which is why they accept a kundali payload you saved yourself. ## What you control If you store charts, you are responsible for them under the privacy law that applies to your users. Responses are deterministic, so you can store only the input and recompute when needed, with the same `meta` versions giving byte-identical results. The API itself keeps nothing: there is no option to store birth data on our side. ## Where data is processed | Sub-processor | Location | Purpose | | --- | --- | --- | | Cloudflare, Inc. | United States, global edge network | Hosting, logs, KV, Durable Objects (including rate-limit counters), queues, Turnstile | | Supabase Inc. | Mumbai, India | Database: account, organization, billing, usage and audit data | | Resend | United States | Transactional email | | PostHog | EU | Website analytics, only with consent (the docs use none) | Payment providers process payments as independent controllers. ## Retention | Data | Kept for | | --- | --- | | Request log lines | Up to 30 days | | Usage rollups (counts per hour, key and endpoint) | 25 months | | Audit log IP addresses | 30 days | | Payment records and invoices | As long as Nepal tax law requires | | Request and response bodies | Never stored | Requests to delete account data are completed within 30 days. ## The docs and the request console The request console in the [API reference](https://navapatra.com/docs/api/) sends requests from your browser straight to `https://api.navapatra.com/v1` with the key you enter (the `demo` key by default). The key is kept in memory for that page only, and the console's third-party telemetry is switched off. Questions about privacy, data requests and grievances: see the contact in the [privacy policy](https://navapatra.com/legal/privacy#privacy-contact-and-grievances). General support: hello@navapatra.com. --- # Accuracy and verification > How the engine is tested against Swiss Ephemeris, the tolerances it must meet, the latest results and the public golden corpus. A chart is only useful if it is right, and you should not have to take that on trust. This page describes how the engine is tested, the limits it must meet, the latest results and the data you can use to check the numbers yourself. ## Method - **Tested against Swiss Ephemeris.** The engine is compared with Swiss Ephemeris 2.10.03 and its JPL-based ephemeris files on **5,433 births** drawn from a fixed random seed (`0x1a6a2026`), so the same sample is used every time: - 5,000 random births from 1900 to 2100: half in 29 cities weighted towards Nepal and India, half at random places between 60° S and 65° N. - 433 edge cases placed seconds to minutes away from sign, nakshatra, pada, lagna sign, tithi and yoga boundaries, and at clock changes in Nepal, the UK, the US and Australia. Five of the clock-change cases are local times that repeat or never happen, sent without `disambiguation`. The engine correctly answers `ambiguous_local_time` for them, so **5,428 births** are compared. - **Same instant, same conventions.** Both sides are evaluated at the same UT instant: geocentric apparent positions, true equinox of date, Lahiri ayanamsha including nutation, mean node, and the lagna as the tropical ascendant minus the ayanamsha. Vimshottari dates on the reference side come from its sidereal Moon with 365.25-day years. - **Sunrise and sunset** are compared for every day of 1990, 2026 and 2075 in Kathmandu with the `center_geometric` convention, and for 2026 with `upper_limb_refracted` in Kathmandu and `center_geometric` in London and Reykjavik. - **Hard limits block the release.** If any sample crosses a hard limit below, the release does not ship. - **Reviewed conventions.** Guna tables, dasha rules and choghadiya sequences are reviewed by a contracted jyotish reviewer before they ship. - **Deterministic output.** The same input with the same engine and data versions returns byte-identical JSON. Dasha output never depends on the server clock. - **Everything named.** `meta` in every response states the engine version, ayanamsha and its value, node type and data versions. See [Conventions](https://navapatra.com/docs/conventions/). ## Tolerances The target is what each release aims for. The hard limit is the line no sample may cross. | Quantity | Target | Hard limit | | --- | --- | --- | | Moon longitude | ≤ 15″ | > 60″ | | Sun and planet longitudes | ≤ 5″ | > 60″ | | Lagna longitude, same UT | ≤ 10″ | > 30″ | | Ayanamsha | ≤ 1″ | > 1″ | | Mean node | ≤ 5″ | > 10″ | | Discrete fields: sign, D9, nakshatra, pada, tithi, yoga | Exact | A mismatch is allowed only when the underlying value is within that quantity's hard limit of a boundary | | Dasha boundary dates | ± 1 day | > 1 day | | Sunrise and sunset, against the configured convention | ± 1 min | > 2 min | The "discrete fields" rule matters in practice. A Moon 20″ from a nakshatra boundary is inside the Moon's 60″ hard limit, so two correct engines may legitimately disagree on its nakshatra. Everywhere else, discrete fields must match exactly. ## Latest results Run of **2026-10-21**, engine `0.1.0`, against Swiss Ephemeris 2.10.03 with JPL-based ephemeris files. Differences are |engine − reference| over the 5,428 compared births, using the longitudes the API returns (4 decimal places). | Quantity | Worst case | 99th percentile | Target | Result | | --- | --- | --- | --- | --- | | Moon longitude | 4.20″ | 2.43″ | ≤ 15″ | Target met | | Sun longitude | 2.19″ | 1.63″ | ≤ 5″ | Target met | | Mercury longitude | 11.33″ | 7.28″ | ≤ 5″ | Target missed, inside hard limit | | Venus longitude | 20.24″ | 12.73″ | ≤ 5″ | Target missed, inside hard limit | | Mars longitude | 12.68″ | 7.51″ | ≤ 5″ | Target missed, inside hard limit | | Jupiter longitude | 10.14″ | 8.86″ | ≤ 5″ | Target missed, inside hard limit | | Saturn longitude | 13.07″ | 11.06″ | ≤ 5″ | Target missed, inside hard limit | | Lagna longitude | 4.67″ | 2.42″ | ≤ 10″ | Target met | | Ayanamsha (Lahiri) | 0.43″ | 0.35″ | ≤ 1″ | Target met | | Mean node | 0.43″ | 0.36″ | ≤ 5″ | Target met | | Dasha boundary dates (maha and antar) | 0.56 days | 0.31 days | ± 1 day | Target met | | Sunrise and sunset, `center_geometric`, Kathmandu | 0.80 s | 0.70 s or less | ± 1 min | Target met | | Sunrise and sunset, `upper_limb_refracted`, Kathmandu 2026 | 4.80 s | 4.64 s | ± 1 min | Target met | **Discrete fields:** 227,976 checks of sign, D9, nakshatra, pada, tithi and yoga gave 57 mismatches, every one with the reference value within 2.3″ of a boundary, so all are allowed by the boundary rule. Most are the edge cases placed on purpose next to a boundary. No sample crossed a hard limit. The Moon, Sun, lagna, ayanamsha, node, dasha and sunrise figures meet their targets. Mercury, Venus, Mars, Jupiter and Saturn do not yet meet the 5″ target, at up to 20.24″ for Venus, but stay well inside the 60″ hard limit. Seven births are left out of the dasha row because the Moon sits less than 1″ from a nakshatra boundary and the two sides start from a different lord; they are counted under discrete fields. ## Birth-time sensitivity Ephemeris error is rarely the biggest uncertainty in a chart: the recorded birth time is. Set `options.sensitivityMinutes` on a kundali request and the response lists any change of the D1 lagna sign, D9 lagna sign, Moon nakshatra or Moon pada within that many minutes, with the instant of each change. The engine's tests check that each `changesAt` falls within 1.5 seconds of the change in the engine's own positions. Compared with a reference, the position differences above move these instants slightly: a 4.7″ lagna difference is about 0.3 seconds of time, and a 4.2″ Moon difference about 8 seconds. See [Kundali](https://navapatra.com/docs/guides/kundali/#birth-time-sensitivity). ## The golden corpus Version 1 of the golden corpus is public: **500 of the compared births**, with the request, our output, the Swiss Ephemeris reference values and the difference for each. 400 cases are spread evenly through the random births and 100 through the edge cases. - Corpus: [golden-corpus-v1.json](https://navapatra.com/corpus/golden-corpus-v1.json) (JSON, 1.7 MB, published 2026-10-21, engine `0.1.0`) - Summary: [golden-corpus-v1.summary.json](https://navapatra.com/corpus/golden-corpus-v1.summary.json), with the worst case, 99th percentile, target and hard limit of every quantity above, the sample size and seed, and the SHA-256 of the corpus file The current engine is a pre-release (`engineVersion` `0.1.0`), and its results may still change within the tolerances above. When an engine release changes the results, a new corpus version will be published and announced in the [changelog](https://navapatra.com/docs/changelog/). ### Format The file is one JSON object. The top level describes the run: | Field | Meaning | | --- | --- | | `version`, `published` | Corpus version and publication date. | | `engine` | `engineVersion`, the source `commit`, `deltaTModelVersion`, and the versions of the ephemeris library, time zone library and tzdb. | | `reference` | The reference (Swiss Ephemeris 2.10.03), the SHA-256 of each ephemeris file it used, and the conventions in plain words. | | `sample` | The seed, the number of cases, how they were picked and the size of the full run. | | `cases` | The 500 cases. | Each case has these fields: | Field | Meaning | | --- | --- | | `id` | `R0000` to `R4999` for random births, `E-…` for boundary edge cases, `TZ-…` for clock-change cases. | | `kind` | `random-place`, `city:`, or `edge:` such as `edge:moon-nakshatra` or `edge:timezone`. | | `request` | The body to send to `POST /v1/kundali`: `birth` (`calendar`, `date`, `time`, `place`, sometimes `utcOffset`) and `options` (`ayanamsha`, `nodeType`, and `disambiguation` or `nepalHistoricOffset` when the case needs them). | | `resolved` | `instantUtc`, `tzid` and `utcOffset`: the UTC instant every value below was computed for. | | `engine` | Our output: `ayanamsha` in degrees; `lagna` and `grahas.` (`sun`, `moon`, `mercury`, `venus`, `mars`, `jupiter`, `saturn`, `rahu`, `ketu`), each with sidereal `longitude` in degrees and 1-based `sign`, `nakshatra`, `pada` and `d9` (D9 sign); 1-based `tithi` and `yoga`; and `vimshottari`, the maha dasha periods with `lord`, `start` and `end`. | | `reference` | The same fields from Swiss Ephemeris. Longitudes have 6 decimal places; the signs, nakshatras, padas, tithi and yoga are derived from them the same way. | | `deltaArcsec` | Engine minus reference in arcseconds, signed, for each graha (Rahu stands for both nodes), the lagna and the ayanamsha. | ### Compare with your own reference 1. Download the corpus and check its SHA-256 against the summary. 2. For each case, compute positions in your own reference at `resolved.instantUtc`, at `request.birth.place`, with the conventions above: geocentric apparent, true equinox of date, Lahiri including nutation, mean node, lagna as tropical ascendant minus ayanamsha. Use `resolved.instantUtc` directly so time zone handling does not mix with the astronomy. 3. Compare each longitude with `reference` and with `engine` on a circle, so 359.9999° and 0.0001° are close: ```js const diffArcsec = (a, b) => ((((a - b) % 360) + 540) % 360 - 180) * 3600; ``` 4. Compare the discrete fields exactly, and apply the boundary rule above to any mismatch. 5. To check the live API, send each `request` to `POST /v1/kundali`. With the same `meta.engineVersion` as the corpus, the longitudes should match `engine`. If `subject.instantUtc` differs from `resolved.instantUtc`, the difference comes from time zone data, not from the astronomy. Without the corpus, you can compare any of your own results the same way: 1. Record the full request and the response's `meta`. 2. Configure your reference to the same conventions: the same ayanamsha (`meta.ayanamsha.id`; compare `meta.ayanamsha.degrees` first), node type, whole-sign houses and the UTC instant from `subject.instantUtc`. 3. Compare longitudes in arcseconds, and discrete fields with the boundary rule above. Most disagreements turn out to be a different time zone offset (check `subject.utcOffset` and `offsetSource`), a different ayanamsha, or the true node against the mean node. ## Report a problem If a result is outside the tolerances, write to hello@navapatra.com with the request, the response's `meta` and `x-request-id`, and your reference values. We aim to fix confirmed accuracy bugs within 7 days; see [Reporting wrong results](https://navapatra.com/legal/terms#reporting-wrong-results) in the Terms. --- # Changelog > API versions, engine releases and data updates, newest first, with the meta versions each one changes. Every change that can affect a response is listed here: new API versions, engine releases and data updates. Each entry names the `meta` field that changes, so you can match it to stored responses. See [Versioning](https://navapatra.com/docs/versioning/) for what is pinned and what is not. ## Current versions | `meta` field | Value | | --- | --- | | `apiVersion` | `2026-11-01` (the only version) | | `engineVersion` | `0.1.0` (pre-release) | | `bsTableVersion` | `2026.10.1`: published BS 1970 to 2083, projected BS 1956 to 1969 and 2084 to 2157 | | `overridesVersion` | `null` (no overrides yet) | | `tzdbVersion` | `unknown` (the runtime does not report its release) | | `deltaTModelVersion` | `iers-c01-2026.5+quad2100-v1` | ## 2026-10-21: Golden corpus v1 and latest accuracy results No response changes; engine `0.1.0` is unchanged. - **Golden corpus v1 is public**: 500 births with the request, our output, Swiss Ephemeris 2.10.03 reference values and the difference for each, plus a summary file. Download links, the format and how to compare with your own reference are on [Accuracy](https://navapatra.com/docs/accuracy/#the-golden-corpus). - The accuracy page now lists the results of the 2026-10-21 run on 5,428 compared births: no hard limit crossed; Moon 4.20″, lagna 4.67″ and dasha dates 0.56 days at worst. Mercury to Saturn miss the 5″ target, at up to 20.24″ for Venus, inside the 60″ hard limit. - The accuracy page now describes the test sample as it is (5,000 random births and 433 edge cases from a fixed seed) and no longer promises a diff report per release. ## 2026-10-11: Sunrise default, polar lagna and date range - **Sunrise now defaults to `upper_limb_refracted`** (the upper edge of the Sun, with refraction), as printed Nepali and Indian panchangs use. Before, the default was `center_geometric`, about 4 minutes later at sunrise. This changes the tithi or nakshatra at sunrise on one or two days a year (for example 2021-11-04 in Kathmandu is now Chaturdashi), and shifts sunset and choghadiya times by a few minutes. Send `sunriseConvention: "center_geometric"` for the old behaviour. - Moonrise and moonset no longer depend on `sunriseConvention`: they are always the moment the Moon's centre crosses the horizon, as in common panchangs. - **Lagna inside the polar circles** (above about 66.5° latitude) could be the setting point instead of the rising one, 180° off. It is now always the rising point. - **One supported window, AD 1900-01-01 to 2100-12-31 (BS 1956-09-19 to 2157-09-15), for every endpoint.** AD dates from 1 January to 11 April 1900 now have a BS date. `/v1/calendar/convert` no longer returns dates after 2100-12-31, and `/v1/calendar/month` serves every month that lies fully inside the window. `supportedRange.bs` in `GET /v1/meta` gains `fromDate` and `toDate`. ## 2026-10-11: Access and metering fixes Fixes to API version `2026-11-01`. Requests that followed these docs keep working; only client tokens issued before this change must be replaced. - `HEAD` requests are authenticated, rate limited and charged like the matching `GET`. - Unknown paths under `/v1` return `404 not_found`; methods other than `GET`, `HEAD` and `POST` return `405 method_not_allowed` with an `Allow` header. - New `503 service_unavailable` with `Retry-After` when credit checks are briefly unavailable and the key cannot be served in degraded mode. - The test-key daily quota of 100 counts each request's credit cost, not one per request. - Publishable keys always carry a monthly credit cap, 2,000 credits by default. - Client tokens use a new format and tokens issued earlier are refused, so request new ones. A token counts toward the issuing key's monthly cap and rate-limit bucket, its effective cap is the smaller of its own and the key's, and it stops within 60 seconds of the key being revoked. - `GET /v1/usage` accepts secret keys only. - A BS panchang range is priced on its AD span. - `GET /v1/places/search` returns `ambiguous`, true when the same name in a birth request would fail with `ambiguous_place`. - `meta.bsProjectionVersion` appears whenever a BS date in the response is projected. - `GET /v1/calendar/month` returns `400 invalid_input` when only one of `lat` and `lng` is sent, and its `meta` adds `engineVersion`, `tzdbVersion`, `deltaTModelVersion` and `overridesVersion`. - MCP: `get_panchang` returns `ambiguous_place` with candidates for an ambiguous place name instead of taking the first match. - `topUpUrl` points at the dashboard's [billing page](https://app.navapatra.com/billing). ## 2026-10-10: Conventions and metadata update Additive changes to API version `2026-11-01`; no request needs to change. - Two more ayanamshas: `raman` and `kp`. See [Conventions](https://navapatra.com/docs/conventions/#ayanamsha). - Bikram Sambat support widened to **BS 1957 to 2157**. Years outside the published table are returned with `status: "projected"`. - Every `meta` now carries `deltaTModelVersion`. The kundali `meta` also echoes `tradition` and `sunriseConvention`. - Matching responses (single, batch and rashi) now carry `meta`. - `subject.offsetSource` reports `historic_0540` when the pre-1986 Nepal +05:40 convention was applied (previously `override`). - Every timestamp is a whole-second ISO 8601 instant, including `ambiguous_local_time` candidates and `/v1/usage` expiry times. - `GET /v1/meta` accepts any credential, including client tokens, or none. - Place search ranks places in Nepal higher, more so with `lang=ne`. - Some computed instants moved slightly with this update: panchang element times by a few seconds, and dasha boundaries for the sample births by about two minutes. All remain inside the [accuracy tolerances](https://navapatra.com/docs/accuracy/). ## 2026-10-10: Developer docs and beta API **API version `2026-11-01`**, the first dated version, with engine `0.1.0` and BS table `2026.10.1`. - 16 operations: kundali, graha positions, dashas, guna milan (single, batch) and rashi compatibility, daily and range panchang, BS conversion and month grids, chart SVG, place search, time zones, client tokens, usage and metadata. - Keys: `sk_live_`, `sk_test_`, `pk_`, client tokens from `POST /v1/tokens`, and the public `demo` key with five sample subjects. - Labels in English, Nepali and Hindi, with Latin or Devanagari digits. - Errors as RFC 9457 problem details with stable codes. - The [TypeScript SDK](https://navapatra.com/docs/guides/typescript-sdk/), [widgets](https://navapatra.com/docs/guides/widgets/) and the [MCP server](https://navapatra.com/docs/guides/mcp/). - These docs, the [API reference](https://navapatra.com/docs/api/), `llms.txt` and a Markdown copy of every page. **Known gaps in this version**, each to be announced here when it ships: - Public holidays and festivals (`/v1/calendar/holidays` and `/v1/calendar/festivals`) wait on the data licence. - The Samiti tithi override table; until then `overridesVersion` is `null` and no override is flagged. - The golden corpus and per-release diff reports arrive with engine v1.0. See [Accuracy](https://navapatra.com/docs/accuracy/). ## How to follow changes This page is the record. Each entry gives the date, what changed and the `meta` values involved. Breaking API changes are announced at least 12 months before the old version is retired; see the [deprecation policy](https://navapatra.com/docs/deprecation-policy/). --- # Deprecation policy > At least 12 months of notice before a breaking API version is retired, and how data updates and SDK versions are handled. You should be able to build on Navapatra and not have to revisit working code on our schedule. These are the commitments. ## API versions - **Breaking changes only ship in a new dated version.** What counts as breaking is listed in [Versioning](https://navapatra.com/docs/versioning/#what-counts-as-a-breaking-change). Your pinned version keeps its schema and conventions. - **At least 12 months of notice** before any API version stops being served. The notice appears in the [changelog](https://navapatra.com/docs/changelog/) with the retirement date and what to change. - **During the notice period** the old version keeps working exactly as before, including data updates. - **After retirement**, requests that name the retired version in `Navapatra-Version` fail with `400 unsupported_version`. Move your pins and headers during the notice period. ## Data updates Time zone database releases, Bikram Sambat table updates, Samiti tithi overrides and holiday data are corrections, not API changes. They apply to every supported version without notice periods, because keeping wrong data would be worse. Each update is announced in the changelog with the `meta` version it changes (`tzdbVersion`, `bsTableVersion` or `overridesVersion`), so you can find the responses it affects. ## Fields and parameters When a field or parameter is replaced within a version (for example, a better-named field is added), the old one keeps working for the life of that version. It is marked deprecated in the [API reference](https://navapatra.com/docs/api/) and in the changelog, and is removed only in a later dated version. ## SDKs and widgets - SDK and widget packages follow semantic versioning. - The two latest major versions are supported with fixes. - A major version is supported for at least 12 months after its successor is released. ## Endpoints in beta Endpoints marked beta in the reference may change within a version, with notice in the changelog. Nothing is marked beta today. --- # Navapatra API reference > OpenAPI 3.1.0, API version 2026-11-01. Base URL: https://api.navapatra.com/v1. Spec: https://navapatra.com/docs/openapi.json Kundali, panchang, matching and Nepali (Bikram Sambat) calendar API for apps in Nepal and India. **Auth.** `Authorization: Bearer `. Secret keys (`sk_live_`, `sk_test_`) are server-only; browsers use a publishable key (`pk_`, origin allowlist) or a short-lived client token from `POST /v1/tokens`. The public key `demo` accepts the five sample subjects listed in `GET /v1/meta` (10 requests per minute per IP). **Versioning.** `Navapatra-Version: YYYY-MM-DD`, pinned per key. Data updates (tzdb, BS table, overrides) are not pinned; `meta` echoes each data version. **Requests.** POST bodies carry `birth`, `options`, `include`, `lang` (`en`, `ne`, `hi`) and `numerals` (`latin`, `devanagari`). Every catalog value comes back as `{ index, name }` with `name` in the requested language. No credit surcharge for any language. **Credits.** Only 2xx responses consume credits. Every response carries `x-request-id`, `x-credits-charged` and `x-credits-remaining`; rate limits use `RateLimit-*` headers and `429` comes with `Retry-After`. **Errors.** RFC 9457 `application/problem+json` with a stable `code`. **Privacy.** Stateless: request and response bodies are never logged or stored. ## Kundali Charts, positions and dasha timelines. ### POST /v1/kundali **Full kundali** (`createKundali`) D1 and D9 for the lagna and nine grahas, Moon nakshatra, pada and naming syllable, the Vimshottari timeline (2 levels by default) and `meta`. `include` adds `vargas` (all 16), `dashas.yogini`, `dashas.tribhagi`, `panchang` and `svg` (North; `svg.south` adds South). `options.sensitivityMinutes` adds `sensitivity`. Deterministic: the same request and `meta` versions give byte-identical JSON. 1 credit. Auth: `Authorization: Bearer `. Request body (`KundaliRequest`): - `birth`, BirthInput, required: Birth date, local time and place. Supported range AD 1900-01-01 to 2100-12-31. - `options`, KundaliOptions, optional - `include`, "vargas" | "dashas.yogini" | "dashas.tribhagi" | "panchang" | "svg" | "svg.south"[], optional - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. ```json { "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35:00", "place": { "lat": 28.2096, "lng": 83.9856 } }, "options": { "ayanamsha": "lahiri", "nodeType": "mean", "sensitivityMinutes": 10 }, "include": [ "panchang" ], "lang": "ne", "numerals": "devanagari" } ``` Responses: - `200` `Kundali`: The kundali. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `413` `Problem`: The response would exceed 512 KB (`response_too_large`); narrow `include` or the range. - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ### POST /v1/kundali/planets **Graha positions** (`getPlanets`) Lagna and the nine grahas (sign, longitude, speed, house, retrograde, nakshatra, pada). 1 credit. Auth: `Authorization: Bearer `. Request body (`PlanetsRequest`): - `birth`, BirthInput, required: Birth date, local time and place. Supported range AD 1900-01-01 to 2100-12-31. - `options`, ChartOptions, optional - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. ```json { "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35:00", "place": { "lat": 28.2096, "lng": 83.9856 } }, "lang": "en" } ``` Responses: - `200` `Planets`: Positions. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ### POST /v1/dasha **Dasha timeline** (`getDasha`) Vimshottari, Yogini or Tribhagi timeline to `levels` 1-3. `current` is returned only when `asOf` is given; the server clock is never used. 1 credit. Auth: `Authorization: Bearer `. Request body (`DashaRequest`): - `birth`, BirthInput, required: Birth date, local time and place. Supported range AD 1900-01-01 to 2100-12-31. - `options`, object, optional - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. ```json { "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35:00", "place": { "lat": 28.2096, "lng": 83.9856 } }, "options": { "system": "vimshottari", "levels": 2, "asOf": "2026-11-01T00:00:00Z" } } ``` Responses: - `200` `Dasha`: The timeline. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `413` `Problem`: The response would exceed 512 KB (`response_too_large`); narrow `include` or the range. - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ## Matching Ashtakoota guna milan and rashi compatibility. ### POST /v1/matching/ashtakoota **Ashtakoota guna milan** (`matchAshtakoota`) Eight kootas with obtained and maximum points, the total out of 36 and Nadi, Bhakuta and Gana dosha flags. Each subject is birth input or a kundali payload returned earlier. 1 credit. Auth: `Authorization: Bearer `. Request body (`AshtakootaRequest`): - `male`, MatchSubject, required: Birth input, or a kundali payload the API returned earlier. No server-side ids. - `female`, MatchSubject, required: Birth input, or a kundali payload the API returned earlier. No server-side ids. - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. ```json { "male": { "type": "birth", "birth": { "calendar": "AD", "date": "1990-05-15", "time": "10:30", "place": { "query": "Kathmandu" } } }, "female": { "type": "birth", "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": { "lat": 28.2096, "lng": 83.9856 } } }, "lang": "en" } ``` Responses: - `200` `AshtakootaResponse`: Guna milan. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ### POST /v1/matching/ashtakoota/batch **Ashtakoota for 1-100 pairs** (`matchAshtakootaBatch`) Scores every pair; results keep the request order. 1 credit per pair. Auth: `Authorization: Bearer `. Request body (`AshtakootaBatchRequest`): - `pairs`, object[], required - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. ```json { "pairs": [ { "id": "p1", "male": { "type": "birth", "birth": { "calendar": "AD", "date": "1990-05-15", "time": "10:30", "place": { "query": "Kathmandu" } } }, "female": { "type": "birth", "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35", "place": { "lat": 28.2096, "lng": 83.9856 } } } } ] } ``` Responses: - `200` `AshtakootaBatch`: One result per pair. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ### POST /v1/matching/rashi **Rashi compatibility** (`matchRashi`) Sign-to-sign compatibility from Graha Maitri and Bhakuta (symmetric, out of 12). 1 credit. Auth: `Authorization: Bearer `. Request body (`RashiRequest`): - `a`, integer, required - `b`, integer, required - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. ```json { "a": 3, "b": 9, "lang": "en" } ``` Responses: - `200` `RashiCompatibility`: Compatibility. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ## Panchang Daily panchang and choghadiya for any place. ### GET /v1/panchang/day **Daily panchang** (`getPanchangDay`) Tithi, nakshatra, yoga and karana at local sunrise with start and end instants; paksha; lunar month (adhik flag); sunrise, sunset, moonrise and moonset (local and UTC); 8 day and 8 night choghadiya; the BS and Nepal Sambat dates. Any location. 1 credit. Auth: `Authorization: Bearer `. Query parameters: - `date`, string, required - `calendar`, "AD" | "BS", optional: Calendar of `date` (default AD). - `lat`, number, required - `lng`, number, required - `tzid`, string, optional: Overrides the coordinate lookup. - `ayanamsha`, "lahiri" | "lahiri_icrc" | "true_chitra" | "raman" | "kp", optional - `tradition`, "nepal" | "amanta", optional - `sunriseConvention`, "center_geometric" | "upper_limb_refracted", optional - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. Responses: - `200` `PanchangDay`: The day. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ### GET /v1/panchang/range **Panchang for a date range** (`getPanchangRange`) Up to 366 days. Choghadiya only with `include=choghadiya`. 1 credit per started 31 days. Auth: `Authorization: Bearer `. Query parameters: - `from`, string, required - `to`, string, required - `calendar`, "AD" | "BS", optional: Calendar of `from` and `to` (default AD). - `lat`, number, required - `lng`, number, required - `tzid`, string, optional: Overrides the coordinate lookup. - `ayanamsha`, "lahiri" | "lahiri_icrc" | "true_chitra" | "raman" | "kp", optional - `tradition`, "nepal" | "amanta", optional - `sunriseConvention`, "center_geometric" | "upper_limb_refracted", optional - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. - `include`, "choghadiya", optional: `choghadiya` adds the periods to every day. Responses: - `200` `PanchangRange`: One entry per day. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `413` `Problem`: The response would exceed 512 KB (`response_too_large`); narrow `include` or the range. - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ## Calendar Bikram Sambat, AD and Nepal Sambat. ### GET /v1/calendar/convert **Convert between AD and BS** (`convertDate`) Converts a date between AD (Gregorian) and Bikram Sambat, with the weekday and the BS `status` (`published` by the Samiti or `projected`). `include=nepalSambat` adds Nepal Sambat. 1 credit. Auth: `Authorization: Bearer `. Query parameters: - `date`, string, required - `calendar`, "AD" | "BS", required: Calendar of `date`. - `include`, "nepalSambat", optional: `nepalSambat` adds the Nepal Sambat date (from the lunar month and tithi at sunrise). - `lat`, number, optional: Place for the Nepal Sambat sunrise (default Kathmandu). - `lng`, number, optional - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. Responses: - `200` `DateConversion`: The date in both calendars. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ### GET /v1/calendar/month **BS month grid** (`getCalendarMonth`) Every day of a BS month with its AD date, weekday and tithi at sunrise, plus the month `bsStatus`. Give `lat` and `lng` together for another place (default Kathmandu). 1 credit. Auth: `Authorization: Bearer `. Query parameters: - `year`, integer, required - `month`, integer, required - `calendar`, "BS", optional: Month grids are Bikram Sambat months. - `lat`, number, optional: Place for tithi at sunrise (default Kathmandu). - `lng`, number, optional - `tradition`, "nepal" | "amanta", optional - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. Responses: - `200` `CalendarMonth`: The month. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ## Charts Server-rendered SVG charts. ### POST /v1/charts/svg **Chart SVG** (`renderChartSvg`) North or South Indian chart (D1 or D9) from birth input or a kundali payload returned earlier. Returns `image/svg+xml`, or JSON `{ svg }` when the request sends `Accept: application/json`. Theme it with the CSS custom properties `--kc-line`, `--kc-text` and `--kc-bg`; houses and grahas carry class names. Charts on the Free plan and the Starter pack carry a small "Powered by" mark. 1 credit. Auth: `Authorization: Bearer `. Request body (`ChartSvgRequest`): - `birth`, BirthInput, optional: Birth date, local time and place. Supported range AD 1900-01-01 to 2100-12-31. - `kundali`, ChartPayload, optional: A kundali payload returned by this API (lagna and graha longitudes are read). - `style`, "north" | "south", optional, default `"north"` - `chart`, "D1" | "D9", optional, default `"D1"` - `options`, object, optional - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `numerals`, "latin" | "devanagari", optional: Digits inside labels and display strings. Default: `latin` for `en`, `devanagari` for `ne` and `hi`. ```json { "birth": { "calendar": "BS", "date": "2050-05-12", "time": "14:35:00", "place": { "lat": 28.2096, "lng": 83.9856 } }, "style": "north", "chart": "D1", "lang": "ne" } ``` Responses: - `200` `ChartSvg`: The chart. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `422` `Problem`: `out_of_range`, `ambiguous_local_time` (resolve with `options.disambiguation`), `ambiguous_place` (with `candidates`) or `place_not_found`. - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ## Places Geocoding and time zones. ### GET /v1/places/search **Search places** (`searchPlaces`) Ranked places from the bundled GeoNames gazetteer (cities over 15,000 people worldwide plus Nepal districts, local levels and towns): exact and prefix matches first, with a Nepal boost (stronger when `lang=ne`), then population. Names in en, ne and hi where GeoNames has them. Add a country, province or district after a comma to narrow. Free (0 credits), 30 requests per minute per key. Auth: `Authorization: Bearer `. Query parameters: - `q`, string, required - `lang`, "en" | "ne" | "hi", optional: Language of every `name` label. No credit surcharge for any language. - `limit`, integer, optional: Default 10. - `country`, string, optional Responses: - `200` `PlaceSearch`: Matches, best first. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. ### GET /v1/timezone **Time zone for a coordinate** (`getTimezone`) IANA zone for `lat`/`lng` and its UTC offset at `instant` (default now). Zone lookup uses present-day boundaries; pass an explicit `tzid` to kundali requests when a historical zone split matters. 1 credit. Auth: `Authorization: Bearer `. Query parameters: - `lat`, number, required - `lng`, number, required - `instant`, string, optional: Instant for the offset (default: now). Responses: - `200` `Timezone`: Zone and offset. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `402` `Problem`: No credits left (`insufficient_credits`, with `topUpUrl`) or a key or token cap reached (`credit_cap_reached`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ## Account Client tokens, usage and metadata. ### POST /v1/tokens **Create a client token** (`createClientToken`) For mobile and browser apps: your backend calls this with an `sk_live_` key and hands the token to the app, which sends it as `Authorization: Bearer ct_...`. TTL at most 15 minutes, scoped to the listed endpoints, with an optional credit cap (never above the issuing key's own cap). Each use is re-checked against the issuing key: revoking the key stops its tokens within a minute, and token calls also count against the key's rate limit and monthly cap. Credits come from your organization. Free (0 credits). Auth: `Authorization: Bearer `. Request body (`TokenRequest`): - `scopes`, "kundali" | "kundali.planets" | "dasha" | "matching.ashtakoota" | "matching.ashtakoota.batch" | "matching.rashi" | "panchang.day" | "panchang.range" | "calendar.convert" | "calendar.month" | "charts.svg" | "places.search" | "timezone"[], required: Endpoint ids the token may call. - `ttlSeconds`, integer, optional: Default and maximum 900 (15 minutes). - `creditCap`, integer, optional: Maximum credits the token may consume. ```json { "scopes": [ "kundali", "charts.svg" ], "ttlSeconds": 600, "creditCap": 20 } ``` Responses: - `200` `ClientToken`: The token. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`). - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. ### GET /v1/usage **Credit balance** (`getUsage`) Organization balance by lot (the monthly free grant first, then paid lots by earliest expiry), the free grant remaining this UTC month, and this key's usage. Free (0 credits). Auth: `Authorization: Bearer `. Responses: - `200` `Usage`: Balance and lots. - `401` `Problem`: Missing, invalid or revoked key (`unauthorized`), or expired client token (`token_expired`). - `403` `Problem`: `secret_key_in_browser`, `origin_not_allowed`, `demo_restricted` or `forbidden` (scope or key type). - `429` `Problem`: Rate limited (`rate_limited`); see `Retry-After`. - `503` `Problem`: Credit checks, rate limiting or key validation are briefly unavailable and the request cannot be served in degraded mode (`service_unavailable`); see `Retry-After`. Nothing is charged. ### GET /v1/meta **Versions, ranges and attributions** (`getMeta`) Engine, API, tzdb, BS-table and override versions, supported options and ranges, data attributions and the demo key's sample subjects. No key needed. Free (0 credits). Auth: none required. Responses: - `200` `ApiMeta`: Metadata. - `400` `Problem`: Invalid input (`invalid_input`, with field paths in `errors`) or unknown version (`unsupported_version`).