Kaal Jyoti APIGet a key

SDKs

Four typed clients, one per language: TypeScript, Python, Dart and PHP. Each is one class with one method per endpoint, and every request and response type is generated from the API's own OpenAPI document. No calculation happens in them. They add the key, retries, error mapping and the types.

They share one design, so what you learn in one holds in the others:

  • Pass the key explicitly. None of them reads the environment for you, so a process holding two keys cannot pick the wrong one by accident.
  • The prefix decides where the key goes. A kj_live_… or kj_test_… key is sent as Authorization: Bearer; a kj_pub_… key as ?key=…. You never choose. See authentication.
  • An answer is the envelope, flattened one level: data and meta, plus the request id, the plan, whether the cache answered, the rate-limit bucket, what the request cost in credits (X-KJ-Credits) and — for a secret key only — what is left of the month (X-KJ-Credits-Remaining).
  • Every failure is one exception type carrying the API's error code — plus network_error, timeout and bad_response for failures that never reached the API. Branch on the code, never on the message.
  • Retries are built in and narrow. Up to two by default: a 429 waits for Retry-After (at most 30 seconds), a 500 engine_error or a dropped connection gets one more try, and a 400, 401, 402, 403 or 422 is never retried, because the identical body would be refused identically.
  • Staging is one option, a base URL, not a separate build.

Each example below sends the same body as the quick start: a birth in New Delhi at half past ten on the morning of 14 May 1990, as a wall clock with its zone beside it.

LanguagePackageInstallRegistry
TypeScript@kaaljyoti/sdknpm install @kaaljyoti/sdknpm
Pythonkaaljyotipip install kaaljyotiPyPI
PHPkaaljyoti/sdkcomposer require kaaljyoti/sdkPackagist
Dartkaaljyotidart pub add kaaljyotipub.dev

TypeScript#

@kaaljyoti/sdk — Node 18+, browsers and edge runtimes. ESM with a CommonJS build beside it.

TypeScript: install#

npm install @kaaljyoti/sdk

TypeScript: quick start#

import { Kaaljyoti } from '@kaaljyoti/sdk';

const kj = new Kaaljyoti({ apiKey: process.env.KAALJYOTI_API_KEY! });

const { data, meta } = await kj.kundli.get({
  birth: {
    datetime: '1990-05-14T10:30:00', // wall clock at the birth place
    timezone: 'Asia/Kolkata',
    latitude: 28.6139,
    longitude: 77.209,
    place: 'New Delhi',
  },
  options: { ayanamsa: 'lahiri', language: 'en' },
});

console.log(data.ascendant_dms); //  99°02'48.2"
console.log(meta?.timezone); //  { name: 'Asia/Kolkata', utc_offset: '+05:30', source: 'given' }

TypeScript: what comes back#

answer.data; //  the calculation, typed per endpoint
answer.meta; //  ayanamsa, timezone, engine, compute_ms, language_fallback
answer.requestId; //  X-KJ-Request-Id — quote it to support
answer.plan; //  the plan this answer was served under
answer.cached; //  true when the 24-hour cache answered. Same credits.
answer.credits; //  X-KJ-Credits — what this request cost, as meta.credits says
answer.creditsRemaining; //  X-KJ-Credits-Remaining — secret keys only, else null
answer.rateLimit; //  { limit, remaining, reset }

meta is null on the answers that carry none: kj.health(), a chart asked for as SVG, and a PDF. credits is still there on those, and null only on the free answers (health, time zone, the reference tables). Field names are the wire names, in snake_case.

TypeScript: errors#

Every failure is a thrown KaaljyotiError with code, status, message, field, docs, requestId and retryAfter (seconds, on a 429). isKaaljyotiError(x) is a type guard, and isRetryable(x) says whether the identical request is worth sending again.

import { isKaaljyotiError } from '@kaaljyoti/sdk';

try {
  await kj.kundli.get({ birth });
} catch (error) {
  if (!isKaaljyotiError(error)) throw error;
  if (error.code === 'validation_error')
    showFieldError(error.field); // 'birth.utc_offset'
  else console.warn(error.code, error.status, error.requestId);
}

Every POST method also takes (body, { signal }), an AbortSignal of your own; the client's timeout still applies beside it. Staging: new Kaaljyoti({ apiKey, baseUrl: 'https://api-staging.kaaljyoti.com' }).

Python#

kaaljyoti — Python 3.10+, no runtime dependencies (the standard library's HTTP, or an optional adapter for an httpx.Client you already have), fully typed and clean under mypy --strict.

Python: install#

pip install kaaljyoti

Python: quick start#

import os

from kaaljyoti import Birth, CalculationOptions, Kaaljyoti, KundliRequest

kj = Kaaljyoti(api_key=os.environ["KAALJYOTI_API_KEY"])

answer = kj.kundli.get(
    KundliRequest(
        birth=Birth(
            datetime="1990-05-14T10:30:00",  # wall clock at the birth place
            timezone="Asia/Kolkata",
            latitude=28.6139,
            longitude=77.209,
            place="New Delhi",
        ),
        options=CalculationOptions(ayanamsa="lahiri", language=["en"]),
    )
)

print(answer.data.ascendant_dms)  #  99°02'48.2"
print(answer.data.lagna_sign.name)  #  Cancer
print(answer.meta.timezone.source)  #  given

Python: what comes back#

A Result[Document, Meta]: answer.data, answer.meta, answer.request_id, answer.plan, answer.cached, answer.credits, answer.credits_remaining (secret keys only) and answer.rate_limit. Every document is a frozen dataclass with from_dict() and to_dict(), so an answer is JSON-serialisable and compares by value. Field names are the wire names. Every id is a LabelledId with id, name and, when you asked for two languages, names.

Python: errors#

Every failure is a raised KaaljyotiError with code, status, message, field, docs, request_id, retry_after and is_retryable. A malformed answer is bad_response, with the original exception on __cause__.

from kaaljyoti import KaaljyotiError

try:
    answer = kj.kundli.get(KundliRequest(birth=birth))
except KaaljyotiError as error:
    if error.code == "validation_error":
        form.reject(error.field)  # 'birth.utc_offset'
    else:
        log.warning("%s %s %s", error.code, error.status, error.request_id)

The client sleeps in-process while it waits out a 429. Inside a web request, construct it with max_retries=0 and retry from a job queue instead.

Staging: Kaaljyoti(api_key=..., base_url="https://api-staging.kaaljyoti.com").

Dart#

kaaljyoti — Dart 3.6+, Flutter included. One dependency, package:http; no build_runner and no code generation in your project.

Dart: install#

dart pub add kaaljyoti

Dart: quick start#

import 'dart:io';

import 'package:kaaljyoti/kaaljyoti.dart';

Future<void> main() async {
  final kj = Kaaljyoti(apiKey: Platform.environment['KAALJYOTI_API_KEY']!);

  final answer = await kj.kundli.get(KundliRequest(
    birth: Birth(
      datetime: '1990-05-14T10:30:00', // wall clock at the birth place
      timezone: 'Asia/Kolkata',
      latitude: 28.6139,
      longitude: 77.209,
      place: 'New Delhi',
    ),
    options: CalculationOptions(ayanamsa: 'lahiri', language: ['en']),
  ));

  print(answer.data.ascendantDms); //  99°02'48.2"
  print(answer.data.lagnaSign.name); //  Cancer
  print(answer.meta.timezone.source); //  given

  kj.close();
}

Dart: what comes back#

A KjResult<Document, Meta>: answer.data, answer.meta, answer.requestId, answer.plan, answer.cached, answer.credits, answer.creditsRemaining (secret keys only) and answer.rateLimit. Field names are camelCase — ascendantDms, computeMs. Every id is a LabelledId. Call kj.close() when you are done with a client you created.

Dart: errors#

Every failure is a thrown KaaljyotiException with code, status, message, field, docs, requestId, retryAfter (a Duration) and isRetryable. The codes are strings, and the ones the package adds are on KjErrorCode.

try {
  await kj.kundli.get(KundliRequest(birth: birth));
} on KaaljyotiException catch (error) {
  switch (error.code) {
    case 'validation_error':
      showFieldError(error.field); // 'birth.utc_offset'
    default:
      log('${error.code} ${error.status} ${error.requestId}');
  }
}

Never ship a kj_live_… or kj_test_… key in an app binary — it can be unpacked. Flutter web can use a kj_pub_… key with the page's origin listed on it; a native app sends no Origin, and a publishable key without one is refused with forbidden_origin, so a native app should reach the API through a server of yours. Staging: Kaaljyoti(apiKey: ..., baseUrl: 'https://api-staging.kaaljyoti.com').

PHP#

kaaljyoti/sdk — PHP 8.2+, no runtime dependencies (HTTP goes through ext-curl), so it can sit inside a WordPress plugin without colliding with another plugin's Guzzle. It is the SDK the WordPress plugin renders with.

PHP: install#

composer require kaaljyoti/sdk

PHP: quick start#

<?php

use Kaaljyoti\Client;
use Kaaljyoti\Models\{Birth, CalculationOptions, KundliRequest};

require __DIR__ . '/vendor/autoload.php';

$kj = new Client(apiKey: getenv('KAALJYOTI_API_KEY'));

$answer = $kj->kundli->get(new KundliRequest(
    birth: new Birth(
        datetime: '1990-05-14T10:30:00', // wall clock at the birth place
        timezone: 'Asia/Kolkata',
        latitude: 28.6139,
        longitude: 77.209,
        place: 'New Delhi',
    ),
    options: new CalculationOptions(ayanamsa: 'lahiri', language: ['en']),
));

echo $answer->data->ascendantDms;      //  99°02'48.2"
echo $answer->data->lagnaSign->name;   //  Cancer
echo $answer->meta->timezone->source;  //  given

PHP: what comes back#

A Result<Document, Meta>: $answer->data, $answer->meta, $answer->requestId, $answer->plan, $answer->cached, $answer->credits, $answer->creditsRemaining (secret keys only) and $answer->rateLimit. Every document is a final readonly class with fromArray() and toArray(). Field names are camelCase. Every id is a LabelledId.

PHP: errors#

Every failure is a thrown KaaljyotiException with status, field, docs, requestId, retryAfter and isRetryable(). The code is code(), not getCode(): PHP's own getCode() is an integer, and the API's codes are strings.

use Kaaljyoti\KaaljyotiException;

try {
    $answer = $kj->kundli->get(new KundliRequest(birth: $birth));
} catch (KaaljyotiException $error) {
    match ($error->code()) {
        'validation_error' => $form->reject($error->field), // 'birth.utc_offset'
        default => $log->warning("{$error->code()} {$error->status} {$error->requestId}"),
    };
}

Like the Python client, it sleeps in-process while it waits out a 429; inside a page load, use maxRetries: 0. Staging: new Client(apiKey: ..., baseUrl: 'https://api-staging.kaaljyoti.com').

Every method, and what it costs#

The four clients have the same methods, grouped the way the paths are, so a path in the API reference is a method without looking anything up. The names below are TypeScript's; Dart's are the same, Python's are in snake_case (reports.life_areas, pdf.panchang_month) and PHP's use -> ($kj->kundli->get()).

GroupMethodsCredits
Servicehealth(), timezone(), reference(list)Free (health wants no key)
Placesplaces({ q, country, limit, language })1 a search
kundliget, chart, dasha, vargas, chalit, yogas, shadbala, bhavaBala, ashtakavarga, grahaDrishti, maitri, pace, specialLagnas, tripataki, sarvatobhadra, nakshatra28, kotaChakra1 each
kundli, across timesadeSati, events10 each
panchang, calendarpanchang.daily, panchang.muhurta, calendar.vikramSamvat1 each
Monthspanchang.month, ephemeris.month20 each, 10 a minute
jaimini, kp, varshphaljaimini.karakas, .arudhaPadas, .aspects, .karakamsha; kp.chart; varshphal.get, .bala, .sahams, .yogas, .dasha1 each
transittransit.now; transit.events (a year's ingresses and stations); transit.scan1; 20; 20 — the scan not with a publishable key
matchmatch.ashtakoot, match.compare; match.batch1 each; 1 per pair, not with a publishable key
reports, horoscopereports.lagna, .nakshatra, .houseLords, .grahas, .yogas, .vimshottari, .varshphal, .lifeAreas; horoscope; reports.kundli5 each; the kundli report 5 per part
pdfpdf.kundli; pdf.match, pdf.varshphal, pdf.panchangMonth1,000; 500 each — paid plans, secret keys only

Every plan can call every method; the credits do the limiting. The one exception is PDFs, which are not on Free. A refusal costs nothing, and an answer from the 24-hour cache costs the same as a fresh one. The whole price list is Credits per API, and reference('credits') returns it as data — one { route, credits, per? } row per route, as it is in force.

PDFs#

The pdf methods take the JSON route's body plus template (classic, modern, minimal, traditional), and on the kundli, match and varshphal chart_style and name (the match adds partner_name). The kundli PDF also takes edition (basic or professional), sections and vargas, and its options take house_system — the bhava chalit it prints, and the only route that accepts one. branding in the body is Enterprise only.

The answer is the file, not an envelope: data is a PdfFile with the raw bytes, the content type, the filename from Content-Disposition and the credits it cost, and meta is null.

import { writeFile } from 'node:fs/promises';

const { data: file, creditsRemaining } = await kj.pdf.kundli({
  birth,
  name: 'Ravi Kumar',
  edition: 'professional',
  template: 'traditional',
  options: { language: ['en', 'hi'], house_system: 'kp' },
});

file.filename; //  'kundli-ravi-kumar.pdf'
file.credits; //  1000
await writeFile(file.filename ?? 'kundli.pdf', file.bytes);

A kundli PDF costs 1,000 credits and the others 500, and each uses one PDF of the month's allowance: Starter 50, Growth 200, Scale 500, Enterprise 2,500. Past it the error is pdf_quota_exceeded; on Free it is plan_required. A failure is the usual JSON error and the usual exception, never a PDF. See PDFs.

Charts, constants and the health check#

  • Rotated charts. kundli.chart takes first_house: lagna (the default), a graha — moon draws the Chandra kundli — or house_2 … house_12. The answer names the sign drawn as house 1 in first_house_sign, and says what the chart is in title. Ask for the chart as SVG and data is the markup itself. See rotating the chart.
  • Constants. Ayanamsa (the 47 slugs options.ayanamsa accepts), Varga (d1 … d60, the 16 ids), ChartStyle and HouseSystem are generated beside the models, each with its list of values for a form's choices. They are plain strings, so a slug from a form or a database still goes in as it is.
  • The health check. health() answers the engine's version and the ephemeris the API is running, and needs no key.

Which to use#

On a server, any of them with a kj_live_… key. In a web page — including Flutter web — the TypeScript or Dart client with a kj_pub_… key whose origin list names the page. If what you want in a page is a panchang or a chart, the widgets do it without code.

All four are MIT licensed. Source, issues and pull requests: kaaljyoti-integrations.