Platform

The architecture behind every answer

Eleven stages sit between your request and the JSON you receive. Each one is inspectable, and the last of them can show you what the others decided.

Request lifecycle

From HTTP request to explained response

This is the actual execution order. Nothing here is aspirational — every stage exists in the deployed engine.

  1. Client

    Your application
    datelocation_id or lat/lonlangpack
  2. REST API

    Routing and contract
    OpenAPI 3.181 endpointsJSON only
  3. Authentication

    Identity and entitlement
    X-API-Keyplan tier402 on gated endpoint
  4. Swiss Ephemeris engine

    Astronomy
    Lahiri ayanamsatithi intervalssunrise / sunset / moonrise
  5. Candidate builder

    Day context assembly
    DayContext3-day windowkshaya / vriddhi natural
  6. Kala evaluation

    Time-of-day windows
    madhyahnaaparahnapradoshanishita
  7. Festival Engine V2

    Rule evaluation
    9 evaluatorsTieBreakEngineoverlap in minutes
  8. Rule versioning

    Provenance
    DRAFT → ARCHIVEDsupersedes_rule_idreproducible
  9. Verification framework

    Continuous checking
    Tier A 42/42Tier B hash lockTier C 26/26
  10. Response builder

    Serialisation
    _engine envelopeexplain trace?observatory=true
  11. SDK

    Your language
    OpenAPI 3.1generate your ownofficial SDKs in progress

Layer detail

What each layer is responsible for

Client

Your application

An HTTP request carrying a date, a location, and an optional rule pack. Everything downstream is deterministic: the same inputs always produce the same answer, including the reasoning.

REST API

Routing and contract

FastAPI surface described by an OpenAPI 3.1 document covering all 81 endpoints. Request validation happens here, so malformed input never reaches the engine.

Authentication

Identity and entitlement

API key resolution, plan lookup, and per-endpoint tier gating. Endpoints outside your plan return 402 rather than a degraded answer — you never get a silently reduced result.

Swiss Ephemeris engine

Astronomy

Sidereal positions of Sun and Moon under Lahiri ayanamsa, from which tithi, nakshatra, yoga and karana intervals are derived. Sunrise, sunset and moonrise for the requested location come from the same layer.

Candidate builder

Day context assembly

Loads days D-1, D and D+1 in a single query and stitches tithi intervals across row boundaries. Kshaya tithis appear as intervals touching no sunrise; vriddhi as intervals touching two. Both fall out of the representation — neither is special-cased.

Kala evaluation

Time-of-day windows

KalaCalculator divides the day into classical windows from sunrise, sunset and next sunrise — madhyahna, aparahna, pradosha, nishita, brahma muhurta, abhijit, and the 15 day and night muhurtas. Pure functions with no database and no festival logic.

Festival Engine V2

Rule evaluation

Nine evaluators test vyapti — the overlap between a tithi interval and a required kala window. The TieBreakEngine resolves days where a tithi qualifies twice or never, using full-overlap, then max-overlap, then the rule’s declared shastric strategy.

Rule versioning

Provenance

Rules are never edited in place. A change inserts a new row with an incremented version and a pointer to the row it supersedes. The engine loads only CURRENT rules, so any past response stays reproducible from its recorded rule id and version.

Verification framework

Continuous checking

Three tiers run against every build: reference comparison with DrikPanchang, a determinism hash lock, and replay of a production accuracy audit. A rule change that moves an unrelated date breaks the lock immediately.

Response builder

Serialisation

Assembles the JSON payload, attaches the engine envelope, and — when explainability is requested — the evaluation trace. Observatory mode adds the raw astronomical facts; it is presentation only and costs no extra computation.

SDK

Your language

The OpenAPI document is the contract clients are built against. Official SDKs are in progress and will be generated from it rather than hand-written, so they cannot drift from the API they wrap. Today you call the REST surface directly, or generate a client yourself from the spec.

Astronomy

Where the numbers come from

The calendar layer never invents a position. Everything downstream is a function of the ephemeris output and the location's sunrise, sunset and moonrise.

Swiss Ephemerissidereal positions
Lahiri ayanamsasidereal offset
Tithi / nakshatra / yoga / karanaintervals
Sunrise / sunset / moonriseper location
DayContextD-1, D, D+1

Why a three-day window

A tithi interval routinely spans midnight, and a kshaya tithi may touch no sunrise at all. Loading D-1, D and D+1 together lets the builder stitch intervals across row boundaries, so kshaya and vriddhi need no special-casing further down the pipeline.
GET /v1/panchang — interval boundaries, not just a sunrise label
{
  "date": "2026-11-08",
  "tithi": {
    "num": 30,
    "name": "Amavasya",
    "paksha": "KRISHNA",
    "starts_at": "2026-11-08T11:28:00+05:30",
    "ends_at":   "2026-11-09T13:01:00+05:30"
  },
  "sunrise": "06:39:02",
  "sunset":  "17:31:14",
  "ayanamsa": { "system": "LAHIRI", "value": 24.2216 }
}

Kala evaluation

Vyapti is the whole idea

A festival rule does not ask which tithi was running at sunrise. It asks whether the target tithi prevails during a specific window of the day — and by how many minutes.

Vyapti — does Amavasya prevail during Pradosha?

Diwali 2026. Amavasya runs 08 Nov 11:28 → 09 Nov 13:01. Pradosha is ~96 minutes after sunset on each day.

2026-11-08overlap 96 min — selected
2026-11-09overlap 0 min
Tithi interval Required kala Overlap

On 8 November, Amavasya begins at 11:28 and is still running when Pradosha opens at sunset. It covers the entire window — 96 minutes of overlap, a full match. On 9 November, Amavasya has already ended at 13:01, hours before Pradosha begins. Overlap is zero.

The engine therefore selects 8 November with confidence 100, and records why. A sunrise-snapshot engine would have seen Amavasya running at sunrise on 9 November and chosen the wrong day — which is precisely the class of bug this architecture removes.

EvaluatorWindowExample
SUNRISE_VYAPINISunriseLegacy rules; tithi containing sunrise
MADHYAHNA_VYAPINIMidday (2/5–3/5 of day)Ganesh Chaturthi
APARAHNA_VYAPINIAfternoon (3/5–4/5 of day)Vijayadashami
PRADOSHA_VYAPINISunset + ~96 minDiwali, Dhanteras
NISHITA_VYAPINIMidnight muhurtaMaha Shivaratri, Janmashtami
MOONRISELocal moonriseKarwa Chauth, Sankashti
SANKRANTISolar ingressMakar Sankranti
NAKSHATRANakshatra × kalaOnam, Karthigai Deepam
COMBINEDAND of conditionsTithi + nakshatra + kala

Explainability

The trace is the product

Every evaluation emits a trace — whether the rule matched or not. This is what /v1/festivals/explain returns, and it is stored server-side for regression runs.

GET /v1/festivals/explain?date=2026-11-08&location_id=1
{
  "festival": "FESTIVAL_DIWALI",
  "rule_code": "DIWALI_V2",
  "rule_version": 2,
  "confidence": 100,
  "confidence_label": "FULL_KALA_COVERAGE",
  "evaluator": "PRADOSHA_VYAPINI",
  "candidate_days": [
    "2026-11-08",
    "2026-11-09"
  ],
  "target_tithi": "Amavasya (Kartika amanta)",
  "tithi_interval": "2026-11-08T11:28+05:30 → 2026-11-09T13:01+05:30",
  "kala": {
    "name": "Pradosha",
    "2026-11-08": "17:31–19:07",
    "2026-11-09": "17:31–19:07"
  },
  "overlap_minutes": {
    "2026-11-08": 96,
    "2026-11-09": 0
  },
  "selected": "2026-11-08",
  "reason": "Amavasya fully covers Pradosha on Nov 8; absent during Pradosha on Nov 9",
  "tie_break_applied": null,
  "authority": [
    "Dharmasindhu",
    "Nirnaya Sindhu"
  ]
}

evaluator

which strategy ran

Identifies the rule family, so you can reason about the whole class rather than one date.

overlap_minutes

the evidence

The measured overlap per candidate day. This is the number the decision was made on.

confidence

100 / 60–99 / 55 / 40

Deterministic, never heuristic. Computed from the overlap fraction and which tie-break branch fired, so identical inputs always yield an identical score.

authority

shastric source

The texts the rule is drawn from — Dharmasindhu, Nirnaya Sindhu. A date you can attribute is a date you can defend.

rule_version

reproducibility

Pinning the exact rule row means any response we served in the past can be regenerated exactly, even after the rule has been superseded.

Observatory mode

Raw astronomical facts, on request

Adding ?observatory=true attaches the underlying intervals to each festival. It is assembled from data the pipeline already produced, so it costs no extra computation.

GET /v1/festivals?date=2026-11-08&observatory=true
{
  "festival": "FESTIVAL_DIWALI",
  "selected_date": "2026-11-08",
  "confidence": 100,
  "astronomical_facts": {
    "amavasya": {
      "start": "2026-11-08 11:28",
      "end":   "2026-11-09 13:01"
    },
    "pradosha": { "start": "17:31", "end": "19:07" }
  },
  "evaluation": {
    "rule": "PRADOSHA_VYAPINI",
    "overlap_minutes": 96
  },
  "authority": ["Dharmasindhu", "Nirnaya Sindhu"]
}

Observatory mode exists because a derivation you cannot check is only marginally better than no derivation at all. With the raw intervals attached, you can recompute the overlap yourself and confirm the engine reached the right conclusion.

Off by default

Observatory output is opt-in so that ordinary responses stay small. It does not consume extra quota — an observatory call counts as one request, exactly like a plain one.

Rule versioning

Rules are never edited in place

A correction inserts a new row with an incremented version and a pointer to the row it supersedes. Nothing is deleted, so history stays queryable forever.

DRAFT

Nobody

Authored but invisible to the engine. Reviewable in the database.

EXPERIMENTAL

Opt-in only

Loadable via ?rules=experimental. Never served by default.

CURRENT

Production

The only status the engine loads under normal operation.

DEPRECATED

History

Superseded by a newer version. Retained for reproducibility.

ARCHIVED

History

Retired entirely. Past traces still resolve against it.

Why this matters for you

If a festival date changes between two of your releases, you can identify exactly which rule version produced each answer. Without versioning, a date change is indistinguishable from a bug in your own code.

Draft rules are invisible

A rule authored by a researcher starts as DRAFT and is not loaded by the engine at all. Only CURRENT rules serve production traffic; EXPERIMENTAL requires an explicit opt-in per request.

Verification

Three tiers, running against every build

Reference comparison catches wrong rules. The determinism lock catches accidental drift. Audit replay catches deployment regressions.

Tier A42 / 42

Reference comparison

7 festivals × 6 years, spanning 1950–2100

Diwali, Janmashtami, Maha Shivaratri, Ganesh Chaturthi, Vijayadashami, Holi and Makar Sankranti are compared against DrikPanchang across a 150-year span. This catches rules that happen to be right for the current decade but wrong historically.

Tier BLocked

Determinism lock

Every-day festival hash across 8 years

A hash of every festival on every day for eight years is stored in tier_b_lock.json. Re-running the engine must reproduce it exactly. This is the guard against silent drift: change a rule for one festival and accidentally move another, and the lock breaks immediately.

Tier C26 / 26

Audit replay

22 days from the 2026–27 accuracy audit

Assertions taken from a manual accuracy audit are replayed against production. Where Tier A proves agreement with an external reference, Tier C proves the deployed system still behaves as audited.

Response building

What comes back, and how to read it

_engine

envelope

Reports the festival engine version, the resolved rule pack, and the rules snapshot version. Present on every response so support can reproduce any answer.

confidence

always present

Available on every plan, including Free. You should never have to guess how certain a date is.

explain

Starter and above

The full evaluation trace, as shown earlier on this page.

astronomical_facts

?observatory=true

Raw intervals behind the decision.
The engine envelope, present on every response
"_engine": {
  "festival_engine": "v2.0.0",
  "rule_pack": "NORTH_INDIA",
  "rules_snapshot_version": 14
}

Read the derivation yourself

The playground issues a real request against the production API and shows you the raw response, the explanation and the code side by side.