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_…orkj_test_…key is sent asAuthorization: Bearer; akj_pub_…key as?key=…. You never choose. See authentication. - An answer is the envelope, flattened one level:
dataandmeta, 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— plusnetwork_error,timeoutandbad_responsefor 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
429waits forRetry-After(at most 30 seconds), a500 engine_erroror a dropped connection gets one more try, and a400,401,402,403or422is 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.
| Language | Package | Install | Registry |
|---|---|---|---|
| TypeScript | @kaaljyoti/sdk | npm install @kaaljyoti/sdk | npm |
| Python | kaaljyoti | pip install kaaljyoti | PyPI |
| PHP | kaaljyoti/sdk | composer require kaaljyoti/sdk | Packagist |
| Dart | kaaljyoti | dart pub add kaaljyoti | pub.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()).
| Group | Methods | Credits |
|---|---|---|
| Service | health(), timezone(), reference(list) | Free (health wants no key) |
| Places | places({ q, country, limit, language }) | 1 a search |
kundli | get, chart, dasha, vargas, chalit, yogas, shadbala, bhavaBala, ashtakavarga, grahaDrishti, maitri, pace, specialLagnas, tripataki, sarvatobhadra, nakshatra28, kotaChakra | 1 each |
kundli, across time | sadeSati, events | 10 each |
panchang, calendar | panchang.daily, panchang.muhurta, calendar.vikramSamvat | 1 each |
| Months | panchang.month, ephemeris.month | 20 each, 10 a minute |
jaimini, kp, varshphal | jaimini.karakas, .arudhaPadas, .aspects, .karakamsha; kp.chart; varshphal.get, .bala, .sahams, .yogas, .dasha | 1 each |
transit | transit.now; transit.events (a year's ingresses and stations); transit.scan | 1; 20; 20 — the scan not with a publishable key |
match | match.ashtakoot, match.compare; match.batch | 1 each; 1 per pair, not with a publishable key |
reports, horoscope | reports.lagna, .nakshatra, .houseLords, .grahas, .yogas, .vimshottari, .varshphal, .lifeAreas; horoscope; reports.kundli | 5 each; the kundli report 5 per part |
pdf | pdf.kundli; pdf.match, pdf.varshphal, pdf.panchangMonth | 1,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.charttakesfirst_house:lagna(the default), a graha —moondraws the Chandra kundli — orhouse_2…house_12. The answer names the sign drawn as house 1 infirst_house_sign, and says what the chart is intitle. Ask for the chart as SVG anddatais the markup itself. See rotating the chart. - Constants.
Ayanamsa(the 47 slugsoptions.ayanamsaaccepts),Varga(d1…d60, the 16 ids),ChartStyleandHouseSystemare 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.