Skip to content

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:

# pyproject.toml
dependencies = ["astrolinkers-sdk~=0.2"]
// package.json
"dependencies": {
  "@astrolinkers/sdk-ts": "~0.1.0"
}

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
}

(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.