Examples

Worked integrations, end to end

Three problems developers actually bring to us, solved in full. Each one is runnable once you substitute an API key.

Generate a regional holiday calendar

An HR system needs next year’s public holidays for offices in different states, regenerated automatically rather than typed in each December.

Writing the rule code and version into the CSV means that when someone queries a date next November, you can say precisely which rule produced it.

import os, requests, csv

API = "https://api.tathaastuapi.com/v1"
KEY = os.environ["TATHAASTU_API_KEY"]
OFFICES = {"Delhi": 1, "Mumbai": 2, "Chennai": 3}

def holidays(location_id: int, year: int):
    r = requests.get(
        f"{API}/festivals/year",
        headers={"X-API-Key": KEY},
        params={"year": year, "location_id": location_id},
        timeout=30,
    )
    r.raise_for_status()
    # Only observed public holidays, not every minor observance.
    return [
        f for f in r.json()["festivals"]
        if f.get("priority_tier") == "MAJOR" and f.get("primary")
    ]

with open("holidays_2027.csv", "w", newline="") as fh:
    w = csv.writer(fh)
    w.writerow(["office", "date", "festival", "confidence", "rule"])
    for office, loc in OFFICES.items():
        for f in holidays(loc, 2027):
            w.writerow([
                office, f["date"], f["festival_key"],
                f["confidence"], f"{f['rule_code']} v{f['rule_version']}",
            ])

Festival notifications without polling

A devotional app wants to notify users a week before each major festival, without running a cron job that hits the API every morning.

Verify the signature against the raw bytes before parsing. Parsing first and re-serialising will change the body and break the comparison.

// Subscribe once.
await fetch("https://api.tathaastuapi.com/v1/webhooks/subscribe", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.TATHAASTU_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://your-app.example/hooks/tathaastu",
    events: ["festival.upcoming"],
    lead_days: 7,
  }),
});

// Then receive. Delivery is at-least-once, so dedupe is mandatory.
import crypto from "node:crypto";

export async function POST(req) {
  const raw = Buffer.from(await req.arrayBuffer());
  const sig = req.headers.get("X-TathaAstu-Signature");
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", process.env.WEBHOOK_SECRET)
          .update(raw).digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return new Response("bad signature", { status: 401 });
  }

  const eventId = req.headers.get("X-TathaAstu-Event-Id");
  if (await alreadyProcessed(eventId)) return new Response("ok");

  const event = JSON.parse(raw.toString());
  await enqueuePush(event.festival, event.date);
  await markProcessed(eventId);
  return new Response("ok");
}

Ground an AI agent instead of letting it guess

A language model asked when Diwali falls in 2028 will answer confidently and often wrongly, because festival dates move and it is interpolating from training data.

Because the response carries confidence and a label, the agent can distinguish a full-overlap match from a kshaya fallback and phrase its answer accordingly, rather than sounding equally certain about both.

TOOLS = [{
    "name": "get_festivals",
    "description": (
        "Return Hindu festivals for a date with the derivation that "
        "produced each one. Always call this instead of recalling a date."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "date": {"type": "string", "description": "YYYY-MM-DD"},
            "location_id": {"type": "integer", "default": 1},
        },
        "required": ["date"],
    },
}]

def get_festivals(date: str, location_id: int = 1) -> dict:
    r = requests.get(
        "https://api.tathaastuapi.com/v1/festivals",
        headers={"X-API-Key": os.environ["TATHAASTU_API_KEY"]},
        params={"date": date, "location_id": location_id,
                "observatory": "true"},
        timeout=10,
    )
    r.raise_for_status()
    return r.json()

# The agent can now cite: rule code, version, confidence and authority —
# and hedge honestly when confidence indicates a tie-break fired.

Before you ship

Two things that catch people out

Deduplicate webhook deliveries

Delivery is at-least-once. If you do not dedupe on X-TathaAstu-Event-Id, a retry will send your users a second notification.

Parse responses defensively

New fields may appear in responses without a major version bump. Do not assume an exhaustive key set, and do not fail on unknown keys.