SDKs and snippets¶
The API is JSON / REST — any HTTP client works. But if you are on
Python or TypeScript, use the official SDK: typed everything,
retries with exponential back-off, Retry-After honoured, streaming
as typed events, and a typed error hierarchy you can except /
catch precisely.
Official SDKs¶
| Language | Install | Import | Repo |
|---|---|---|---|
| Python | pip install astrolinkers-sdk |
from astrolinkers import Astrolinkers |
fedorello/astrolinkers-sdk-python |
| TypeScript | npm install @astrolinkers/sdk-ts |
import { Astrolinkers } from "@astrolinkers/sdk-ts" |
fedorello/astrolinkers-sdk-ts |
Both cover the full public API surface — charts, talent profiles,
template-driven interpretations, LLM interpretations (with streaming),
compatibility (synastry + Ashtakoota), feedback, async PDF reports,
the full Vedic engine (divisional charts, dasha, shadbala, panchanga,
predictive, KP, muhurta), self-service API keys, plan management, and
usage analytics. For endpoints not yet wrapped, every client also
exposes an escape hatch — client.request(...) / client.stream(...)
that still gives you authenticated requests with the SDK's retry +
error-mapping behaviour.
Releases ship with cryptographic build provenance: PEP 740 attestations on PyPI for the Python SDK, SLSA attestations via npm Trusted Publishing for the TypeScript SDK.
Python — quickstart¶
from datetime import datetime, UTC
from astrolinkers import Astrolinkers, InterpretationTier, Language
client = Astrolinkers(api_key="ak_live_…")
# 1. Compute a natal chart.
chart = client.charts.create(
moment=datetime(1990, 4, 15, 2, 0, tzinfo=UTC),
latitude=28.6139, longitude=77.2090,
timezone="Asia/Kolkata",
location_name="New Delhi, India",
)
# 2. Get the talent profile.
profile = client.profiles.talent(chart.id)
for skill in profile.scores:
print(skill.skill_id, skill.value)
# 3. Ask the LLM for a per-life-area interpretation, streaming.
from astrolinkers import MetaEvent, DeltaEvent, DoneEvent
for event in client.llm.chart_reading_stream(
chart_id=chart.id,
language=Language.EN,
tier=InterpretationTier.STANDARD,
):
match event:
case MetaEvent():
ui.render_engine_blocks(event.engine_context)
case DeltaEvent():
ui.append_text(event.content)
case DoneEvent():
ui.show_cost(event.cost_usd, cached=event.cached)
Async usage is identical — swap Astrolinkers for AsyncAstrolinkers
and for for async for. See the Python SDK
README
for the complete resource map.
TypeScript — quickstart¶
import {
Astrolinkers,
InterpretationTier,
Language,
} from "@astrolinkers/sdk-ts";
const client = new Astrolinkers({ apiKey: process.env.ASTRO_TOKEN! });
// 1. Compute a natal chart.
const chart = await client.charts.create({
moment: new Date("1990-04-15T02:00:00Z"),
latitude: 28.6139,
longitude: 77.209,
timezone: "Asia/Kolkata",
locationName: "New Delhi, India",
});
// 2. Get the talent profile.
const profile = await client.profiles.talent(chart.id);
for (const skill of profile.scores) {
console.log(skill.skill_id, skill.value);
}
// 3. Stream a chart reading.
for await (const event of client.llm.chartReadingStream({
chartId: chart.id,
language: Language.EN,
tier: InterpretationTier.STANDARD,
})) {
switch (event.kind) {
case "meta":
renderEngineBlocks(event.engine_context);
break;
case "delta":
appendText(event.content);
break;
case "done":
showCost(event.cost_usd, event.cached);
break;
}
}
Works on Node 18+, Bun, Deno, Cloudflare Workers, and modern browsers
(global fetch). See the
TypeScript SDK README
for the complete resource map.
Typed errors¶
Both SDKs map every documented error code to a discrete exception class — you do not parse the error envelope yourself.
from astrolinkers import (
Astrolinkers,
RateLimitedError, AuthenticationError, BudgetExceededError,
NotFoundError, ServerError,
)
try:
reading = client.llm.chart_reading(chart_id="bogus", tier="premium")
except NotFoundError:
...
except RateLimitedError as e:
print(f"Slow down for {e.retry_after_seconds:.0f}s")
except BudgetExceededError as e:
print(f"Budget hit: ${e.spent_usd:.2f} of ${e.cap_usd:.2f}")
except AuthenticationError:
...
except ServerError:
... # The SDK already retried; this is a real outage.
The full hierarchy and idempotency-key handling are documented in each SDK's README.
Versioning¶
Both SDKs follow SemVer. While they are below 1.0 (current: Python
0.2.x, TypeScript 0.1.x), minor versions may carry breaking
changes — pin the minor when integrating:
Each SDK keeps its own CHANGELOG.md. Releases are tag-triggered and publish via OIDC — every artifact is cryptographically tied to the exact commit it was built from.
Need another language?¶
A Go / Rust / Ruby / Java client is not on the official roadmap yet.
Open an issue
to vote for one — we prioritise by demand. Until then, hand-roll
against the OpenAPI spec at GET /openapi.json and use the snippets
below as a starting point.
Hand-rolled clients (any language)¶
cURL¶
export ASTRO_URL=https://api.astrolinkers.com
export ASTRO_TOKEN=ak_live_...
# 1) Create chart
CHART=$(curl -sS -X POST $ASTRO_URL/v1/charts \
-H "Authorization: Bearer $ASTRO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"birth": {
"moment": "1984-12-21T18:16:00+07:00",
"latitude": 55.0084,
"longitude": 82.9357
},
"system": "western",
"house_system": "placidus"
}')
CHART_ID=$(echo "$CHART" | jq -r .id)
# 2) Talent profile
curl -sS $ASTRO_URL/v1/charts/$CHART_ID/profile/talent \
-H "Authorization: Bearer $ASTRO_TOKEN" | jq
# 3) Interpretation
curl -sS -X POST $ASTRO_URL/v1/interpretations \
-H "Authorization: Bearer $ASTRO_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"chart_id\":\"$CHART_ID\",\"locale\":\"en\",\"tone\":\"corporate\",\"use_llm_rewrite\":false}" | jq
Python (httpx) — fallback, prefer the SDK¶
import httpx
API = "https://api.astrolinkers.com"
TOKEN = "ak_live_..."
async def create_chart(client: httpx.AsyncClient) -> str:
response = await client.post(
f"{API}/v1/charts",
json={
"birth": {
"moment": "1984-12-21T18:16:00+07:00",
"latitude": 55.0084,
"longitude": 82.9357,
},
"system": "western",
"house_system": "placidus",
},
headers={"Authorization": f"Bearer {TOKEN}"},
)
response.raise_for_status()
return response.json()["id"]
async def talent_profile(client: httpx.AsyncClient, chart_id: str) -> dict:
response = await client.get(
f"{API}/v1/charts/{chart_id}/profile/talent",
headers={"Authorization": f"Bearer {TOKEN}"},
)
response.raise_for_status()
return response.json()
Error handling¶
import httpx
class AstrolinkersError(Exception):
def __init__(self, code: str, message: str, request_id: str | None) -> None:
super().__init__(f"{code}: {message}")
self.code = code
self.request_id = request_id
async def call(client: httpx.AsyncClient, method: str, url: str, **kw) -> dict:
r = await client.request(method, url, **kw)
if r.status_code >= 400:
body = r.json()["error"]
raise AstrolinkersError(body["code"], body["message"], body.get("request_id"))
return r.json()
The uniform error envelope makes this exactly this simple — see Errors. The Python SDK builds on this same envelope to give you a full typed hierarchy.
TypeScript (fetch) — fallback, prefer the SDK¶
const API = "https://api.astrolinkers.com";
const TOKEN = process.env.ASTRO_TOKEN!;
interface ChartCreateRequest {
birth: { moment: string; latitude: number; longitude: number };
system: "western" | "vedic";
house_system: "placidus" | "whole_sign" | "equal";
ayanamsha?: "lahiri" | null;
}
interface Chart {
id: string;
system: string;
house_system: string;
ayanamsha: string | null;
planets: Array<{
planet: string;
sign: string;
longitude: number;
degree_in_sign: number;
speed_per_day: number;
is_retrograde: boolean;
}>;
houses: Array<{ house: number; sign: string; longitude: number }>;
}
export async function createChart(req: ChartCreateRequest): Promise<Chart> {
const r = await fetch(`${API}/v1/charts`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${TOKEN}`,
},
body: JSON.stringify(req),
});
if (!r.ok) throw new Error((await r.json()).error.message);
return r.json();
}
Go (net/http)¶
package astrolinkers
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type ChartCreateRequest struct {
Birth struct {
Moment string `json:"moment"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
} `json:"birth"`
System string `json:"system"`
HouseSystem string `json:"house_system"`
}
func CreateChart(token string, req ChartCreateRequest) (map[string]any, error) {
body, _ := json.Marshal(req)
httpReq, _ := http.NewRequest("POST",
"https://api.astrolinkers.com/v1/charts", bytes.NewReader(body))
httpReq.Header.Set("Authorization", "Bearer "+token)
httpReq.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(httpReq)
if err != nil { return nil, err }
defer resp.Body.Close()
var out map[string]any
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return nil, err }
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("%v", out["error"])
}
return out, nil
}
Retries — the recommended pattern¶
(Both official SDKs implement this for you. Use the rest of this section only if you are hand-rolling a client.)
Idempotency-Key + exponential backoff + jitter. Pseudo-code:
def with_retry(call):
for attempt in range(5):
try:
return call(idempotency_key=stable_key)
except RateLimited as e:
sleep(e.retry_after_seconds)
except (LLMUnavailable, ServerError):
sleep(min(60, (2 ** attempt) + random.uniform(0, 1)))
Pass the same Idempotency-Key across retries — see
Errors / Idempotency.