Build TIAN — Your Personal Epistemic Agent
This guide walks you through integrating the askTIAN API into app.asktian.com — a conversational AI agent that synthesizes 8,000 years of civilizational wisdom into practical decision-making guidance. You will need an Enterprise API key for the full fan-out endpoints.
The askTIAN Personal Epistemic Agent is built on three layers: a Dynamic Deck frontend (swipeable cards), an Orchestration Engine backend (intent → endpoints → LLM synthesis), and a Memory Layer database (Pattern Profile, Decisions, Conversations). The API integration lives entirely in the Orchestration Engine.
| Layer | Technology | askTIAN API Role |
|---|---|---|
| Dynamic Deck (Frontend) | React 19 + Tailwind 4 + PWA | Renders SSE streams; displays tradition animations |
| Orchestration Engine (Backend) | Express + TypeScript + Bull queues | Calls API endpoints; synthesizes via LLM |
| Memory Layer (Database) | PostgreSQL + Redis | Caches Pattern Profiles (30-day TTL); stores Decisions |
tian.global, tian.eastern, tian.western, tian.african, tian.islamic, tian.indian, and predict.binaryBatch. Contact [email protected] or upgrade via Pricing.
app.asktian.com
│
├── Frontend (React PWA)
│ ├── DynamicDeck.tsx ← swipeable card carousel
│ ├── ChatInterface.tsx ← SSE consumer + animations
│ └── OnboardingFlow.tsx ← birth data → tian.global
│
├── Backend (Express)
│ ├── POST /api/chat ← main SSE endpoint
│ │ ├── intentRouter.ts ← GPT-4.1-nano classification
│ │ ├── contextAssembler.ts ← fetch Pattern Profile + history
│ │ ├── endpointSelector.ts ← intent → API endpoint list
│ │ ├── apiFanout.ts ← parallel calls to api.asktian.com
│ │ ├── llmSynthesis.ts ← TIAN personality synthesis
│ │ └── sseStreamer.ts ← tradition_start / synthesis_chunk
│ └── Bull Queues
│ ├── dailyAnchorCompute ← 3 AM: astrology.transits + LLM
│ ├── dailyAnchorDeliver ← 7 AM: Email / Push / Telegram
│ └── followUpChecker ← Hourly: pending decisions
│
└── api.asktian.com ← askTIAN API (Enterprise key)
├── GET tian.global ← onboarding (145 TIAN pts)
├── GET tian.eastern ← eastern fan-out (85 TIAN pts)
├── GET astrology.transits ← daily anchor (5 TIAN pts)
├── POST predict.binary ← decision guidance (100 TIAN pts)
└── GET twelvepalaces.* ← palace lookup (5 TIAN pts)
During onboarding, call GET /tian.global with the user's birth data. This single call fans out across all 28 traditions in parallel and returns a comprehensive cross-civilizational analysis. Store the response as a JSONB blob in your pattern_profiles table with a 30-day TTL. The user never sees this data directly — TIAN references it naturally in conversation.
# Step 1: Call tian.global during onboarding (background, while user completes MBTI/IKIGAI steps)
curl "https://api.asktian.com/api/trpc/tian.global?input=%7B%22json%22%3A%7B%22year%22%3A2026%2C%22month%22%3A4%2C%22day%22%3A7%2C%22hour%22%3A9%2C%22birthDate%22%3A%221990-03-14%22%2C%22surname%22%3A%22%E6%9D%8E%22%2C%22givenName%22%3A%22%E5%BF%97%E6%98%8E%22%2C%22luckyNumber%22%3A%2288%22%2C%22fullName%22%3A%22John+Smith%22%7D%7D" \
-H "X-API-Key: at_live_xxxx"# Step 2: Store in pattern_profiles table
# INSERT INTO pattern_profiles (user_id, tian_global_response, ttl_expires_at)
# VALUES ($1, $2, NOW() + INTERVAL '30 days')
# ON CONFLICT (user_id) DO UPDATE SET tian_global_response = $2, ttl_expires_at = NOW() + INTERVAL '30 days'profileRefresh cron (daily at 2 AM UTC) checks ttl_expires_at and refreshes only expired profiles.| Field | Source | Used In |
|---|---|---|
| birthDate | Onboarding form | tian.global, tian.eastern, astrology.transits, twelvepalaces.* |
| birthTime | Onboarding form (optional, defaults noon) | astrology.transits, tian.global |
| birthLocation | Google Places autocomplete | astrology.transits |
| surname + givenName | Onboarding form | tian.global (Chinese name analysis) |
| fullName | Onboarding form | tian.global (Western numerology) |
| luckyNumber | Onboarding form | tian.global (auspiciousness) |
| mbtiType | 4-question quick test | LLM synthesis context only |
Every user message is classified into one of five intents before any API call is made. Use GPT-4.1-nano (the cheapest model) for this — the task is pure classification, not reasoning. Temperature 0, max_tokens 20. The classification determines the entire downstream processing pipeline.
// src/orchestration/intentRouter.ts
const INTENT_ROUTER_PROMPT = `You are the intent router for askTIAN. Classify the user's message into exactly one category:
1. DECISION_GUIDANCE — asking for advice on a specific choice or dilemma
2. TRANSIT_UPDATE — asking about current astrological weather or energy
3. OUTCOME_REPORT — reporting the result of a past decision
4. PREFERENCE_CHANGE — changing notification settings or delivery channels
5. GENERAL_CHAT — general conversation, questions about Pattern Science, greetings
Return ONLY the category name. No explanation.`;
export async function classifyIntent(message: string): Promise<Intent> {
const res = await openai.chat.completions.create({
model: 'gpt-4.1-nano',
messages: [
{ role: 'system', content: INTENT_ROUTER_PROMPT },
{ role: 'user', content: message }
],
temperature: 0,
max_tokens: 20
});
return res.choices[0].message.content?.trim() as Intent;
}| Intent | Trigger Example | API Calls | TIAN Pts |
|---|---|---|---|
| DECISION_GUIDANCE | Should I take the job in Singapore? | predict.binary + astrology.transits + tian.eastern | ~155 |
| TRANSIT_UPDATE | What does this week look like for me? | astrology.transits | ~5 |
| OUTCOME_REPORT | I took the job. It's going great. | None (DB update only) | 0 |
| PREFERENCE_CHANGE | Stop sending me Telegram messages. | None (DB update only) | 0 |
| GENERAL_CHAT | Tell me about BaZi. | None (LLM only, uses cached profile) | ~0 |
The endpointSelector.ts maps each intent to a specific set of API endpoints. The selection is deterministic — no LLM involved. For DECISION_GUIDANCE, always call predict.binary first (it's the fastest signal), then fan out to the tradition endpoints in parallel.
| Intent | Primary Endpoint | Secondary Endpoints | Notes |
|---|---|---|---|
| DECISION_GUIDANCE | predict.binary (100 pts) | astrology.transits (5 pts) · tian.eastern (85 pts) | Fan out in parallel. tian.eastern covers BaZi, I Ching, Qimen, Mahabote, 十二宮, and 12 more. |
| TRANSIT_UPDATE | astrology.transits (5 pts) | almanac.today (5 pts) | Cache per user per day (Redis, 24h TTL). |
| GENERAL_CHAT (birth-related) | tian.eastern (85 pts) | — | Only if the user asks about their own chart. Use cached profile otherwise. |
| Daily Anchor (cron) | astrology.transits (5 pts) | — | Pre-computed at 3 AM local. Cached in daily_anchors table. |
| Onboarding | tian.global (145 pts) | — | One-time. Cached 30 days. Refreshed by profileRefresh cron. |
DECISION_GUIDANCE, check the Redis cache for astrology.transits first (TTL 24h). If cached, skip the transit call and use the cached result — saving 5 TIAN Points per decision.// src/orchestration/endpointSelector.ts
export function selectEndpoints(intent: Intent, hasTransitCache: boolean): string[] {
switch (intent) {
case 'DECISION_GUIDANCE':
return [
'predict.binary',
...(hasTransitCache ? [] : ['astrology.transits']),
'tian.eastern',
];
case 'TRANSIT_UPDATE':
return hasTransitCache ? [] : ['astrology.transits', 'almanac.today'];
case 'GENERAL_CHAT':
return []; // Use cached Pattern Profile only
default:
return [];
}
}All API calls for a single user message are executed in parallel using Promise.allSettled. Each call is wrapped in a Redis cache check (24h TTL). As each tradition's result arrives, the SSE streamer emits a tradition_result event — this drives the "Theatrical Computation" animations on the frontend.
// src/orchestration/apiFanout.ts — parallel execution with Redis cache
export async function executeApiFanout(
endpoints: string[],
params: Record<string, any>
): Promise<ApiCallResult[]> {
const calls = endpoints.map(async (endpoint): Promise<ApiCallResult> => {
const cacheKey = `api:${endpoint}:${JSON.stringify(params)}`;
const cached = await redis.get(cacheKey);
if (cached) return { endpoint, success: true, data: JSON.parse(cached), cached: true };
try {
const url = `https://api.asktian.com/api/trpc/${endpoint}?input=${encodeURIComponent(JSON.stringify({ json: params }))}`;
const res = await fetch(url, { headers: { 'X-API-Key': process.env.ASKTIAN_API_KEY! } });
const body = await res.json();
const data = body.result.data.json;
await redis.setex(cacheKey, 86400, JSON.stringify(data)); // 24h TTL
return { endpoint, success: true, data, cached: false };
} catch (err: any) {
return { endpoint, success: false, error: err.message, cached: false };
}
});
return Promise.allSettled(calls).then(results =>
results.map(r => r.status === 'fulfilled' ? r.value : { endpoint: 'unknown', success: false, error: 'rejected', cached: false })
);
}The SSE streamer emits five event types. The frontend listens for these and triggers the corresponding tradition animation (hexagram flip, planetary orbit, etc.).
| SSE Event | Payload | Frontend Action |
|---|---|---|
| tradition_start | {"tradition":"i_ching","label":"Consulting the I Ching..."} | Show tradition name + start animation |
| tradition_result | {"tradition":"i_ching","summary":"Hexagram 42, Increase"} | Display inline result |
| synthesis_start | {"label":"Synthesizing across traditions..."} | Trigger convergence animation |
| synthesis_chunk | {"text":"The patterns strongly favor..."} | Stream narrative token by token |
| synthesis_end | {"decision_id":"uuid","follow_up_suggested":true} | Show 'Set a reminder?' prompt |
The Daily Anchor is a pre-computed reading delivered every morning. A Bull queue processes users in batches of 250 at 3 AM local time (to stay under the 300 req/15 min rate limit), then delivers via Email, PWA Push, and Telegram at 7 AM. Only astrology.transits is called — the Pattern Profile is fetched from the database.
// src/jobs/dailyAnchorCompute.ts — batch processing with rate limit awareness
const BATCH_SIZE = 250; // Stay under 300 req / 15 min
const BATCH_DELAY_MS = 15 * 60 * 1000; // 15 min between batches
export async function computeDailyAnchors() {
const users = await db.users.findActive();
const batches = chunk(users, BATCH_SIZE);
for (let i = 0; i < batches.length; i++) {
await Promise.allSettled(
batches[i].map(async (user) => {
const profile = await db.patternProfiles.findByUserId(user.id);
if (!profile) return;
// Check Redis cache first — transit data is valid for 24h
const cacheKey = `transits:${user.birth_date}:${today()}`;
let transits = await redis.get(cacheKey);
if (!transits) {
const res = await askTianApi.get('astrology.transits', {
birthDate: user.birth_date,
birthTime: user.birth_time,
birthLocation: user.birth_location
});
transits = JSON.stringify(res.data);
await redis.setex(cacheKey, 86400, transits);
}
const reading = await llm.generate({
prompt: DAILY_ANCHOR_PROMPT,
context: { pattern_summary: extractSummary(profile), transit_data: JSON.parse(transits) }
});
await db.dailyAnchors.upsert({ userId: user.id, readingDate: today(), ...reading });
await deliveryQueue.add({ userId: user.id }, { delay: hoursUntil7AM(user.timezone) });
})
);
if (i < batches.length - 1) await delay(BATCH_DELAY_MS);
}
}| Endpoint | Cache Key | TTL | Rationale |
|---|---|---|---|
| tian.global | tian_global:{userId} | 30 days (DB) | Birth data doesn't change. Refresh only on profile TTL expiry. |
| astrology.transits | transits:{birthDate}:{date} | 24h (Redis) | Transits change daily. One call per user per day. |
| predict.binary | None | — | Each question is unique. Do not cache. |
| tian.eastern | eastern:{birthDate}:{date} | 24h (Redis) | Eastern systems are date-sensitive. Cache per day. |
| almanac.today | almanac:{date} | 24h (Redis) | Same almanac for all users on the same date. Share the cache. |
| twelvepalaces.calculate | palace:{zodiac}:{month}:{day}:{hour} | Permanent (Redis) | Pure function of birth inputs. Cache forever. |
// Shared Redis cache helper
export async function cachedApiCall<T>(
key: string,
ttlSeconds: number,
fetcher: () => Promise<T>
): Promise<T> {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached) as T;
const result = await fetcher();
await redis.setex(key, ttlSeconds, JSON.stringify(result));
return result;
}
// Usage:
const transits = await cachedApiCall(
`transits:${user.birth_date}:${today()}`,
86400, // 24h
() => askTianApi.get('astrology.transits', { birthDate: user.birth_date })
);With aggressive caching, the API cost per active user is well within the margins of the Premium tier ($14.99/month). The key insight: tian.global is called once per month, not per conversation.
| Component | Calls/Month | TIAN Pts Each | Total Pts | USD (~$0.001/pt) |
|---|---|---|---|---|
| Daily Anchor (astrology.transits) | 30 | 5 | 150 | $0.15 |
| Daily Anchor LLM synthesis | 30 | ~10 | 300 | $0.30 |
| Pattern Profile refresh (tian.global) | 1 | 140 | 140 | $0.14 |
| Decision Guidance (predict.binary) | 3 (free tier) | 100 | 300 | $0.30 |
| Decision Guidance (tian.eastern) | 3 (free tier) | 85 | 255 | $0.26 |
| Decision Guidance (astrology.transits) | 3 (cached) | 0 | 0 | $0.00 |
| Decision LLM synthesis | 3 | ~50 | 150 | $0.15 |
| Free tier total | — | — | 1,295 pts | $1.30 |
| Premium (30 decisions) | — | — | ~9,500 pts | $9.50 |
All requests to api.asktian.com require an X-API-Key header with your Enterprise API key. The key is scoped to your server — never expose it to the frontend. Use environment variables and rotate quarterly.
# Server-side only — never expose to browser
export const askTianApi = axios.create({
baseURL: 'https://api.asktian.com/api/trpc',
headers: {
'X-API-Key': process.env.ASKTIAN_API_KEY, // Enterprise key
'Content-Type': 'application/json',
},
timeout: 30000, // 30s — tian.global can take 10-15s
});
// For GET endpoints, encode params as input query param:
const res = await askTianApi.get(`/astrology.transits?input=${encodeURIComponent(JSON.stringify({ json: params }))}`);
// For POST endpoints (predict.binary, predict.binaryBatch):
const res = await askTianApi.post('/predict.binary', { json: params });users, pattern_profiles, conversations, and decisions.The official asktian-sdk (v1.6.0) wraps all 49 endpoints with full TypeScript types. Install it on your backend server.
npm install [email protected]import { AskTian } from 'asktian-sdk';
const client = new AskTian({ apiKey: process.env.ASKTIAN_API_KEY });
// Onboarding: generate Pattern Profile
const profile = await client.tian.global({
year: 2026, month: 4, day: 7, hour: 9,
birthDate: '1990-03-14',
surname: '李', givenName: '志明',
luckyNumber: '88', fullName: 'John Smith'
});
// Decision Guidance: binary prediction
const prediction = await client.predict.binary({
question: 'Should I accept the VP role at the startup?',
optionA: 'Accept', optionB: 'Decline',
resolutionDate: '2026-12-31',
privyToken: userPrivyToken // Required for non-Enterprise callers
});
// Daily Anchor: today's transits
const transits = await client.astrology.transits({
birthDate: '1990-03-14',
birthTime: '09:00',
birthLocation: 'Singapore'
});
// Palace lookup: Twelve Palaces
const palace = await client.twelvepalaces.calculate({
zodiacYear: 'Dragon', lunarMonth: 3, lunarDay: 15, birthHour: 'Si'
});As of v3.9.0, four endpoints return rich profile and 4-dimension compatibility data sourced from the YouApp dataset. Agents can use these fields to build substantially richer personality context, relationship analysis, and wellness guidance without additional LLM calls.
10.1 Sign Profile — Mantra, Tarot & Medical Astrology
Both almanac.zodiacSign (field: westernProfile) and horoscope.calculate (field: signProfile) return a HoroscopeSignProfile object for the sun sign. Key agent-facing fields:
| Field | Example (Capricorn) | Agent use-case |
|---|---|---|
| mantra | “I use.” | Affirmation prompt prefix, daily intention card |
| majorArcana | The Devil (XV) | Tarot cross-reference, shadow work prompt |
| minorArcana | 2, 3, 4 of Pentacles | Wealth / career card spread context |
| medical | Knees, skin, connective tissue | Wellness prompt, body-awareness nudge |
| herbalAllies | Horsetail, comfrey, Solomon's seal | Herbal recommendation, seasonal wellness card |
| symbolism | The Sea-Goat | Narrative metaphor, archetype framing |
// ── Option A: single call via tian.global (v1.8.0+) ──────────────────────────
// signProfile and compatProfile are returned directly — no extra almanac call needed.
const global = await client.tian.global({ birthdate: "1990-01-15", ... });
const sp = global.signProfile; // TianGlobalSignProfile
const cp = global.compatProfile; // TianGlobalCompatProfile
// sp?.westernSign → "capricorn"
// sp?.mantra → "I Use"
// sp?.majorArcana → "The Devil"
// cp?.loveDimensionLabel → "Positive"
// ── Option B: dedicated call via almanac.zodiacSign or horoscope.calculate ────
// Use when you need the full HoroscopeSignProfile (bodyParts, herbalAllies, etc.)
const zodiac = await client.almanac.zodiacSign({ birthDate: "1990-01-15" });
const profile = zodiac.westernProfile; // HoroscopeSignProfile (full)
// Build a daily intention card
const intentionCard = {
mantra: profile?.mantra, // e.g. "I Use"
tarot: profile?.majorArcana, // e.g. "The Devil (XV)"
bodyFocus: profile?.bodyParts, // e.g. "Knees, skin, connective tissue"
herb: profile?.herbalAllies, // e.g. "Horsetail, comfrey"
archetype: profile?.symbolism, // e.g. "The Sea-Goat"
};
// Inject into LLM system prompt (works with either option):
const mantra = sp?.mantra ?? profile?.mantra;
const arcana = sp?.majorArcana ?? profile?.majorArcana;
const systemPrompt = [
'You are an epistemic agent for a Capricorn (' + mantra + ').',
'Their Tarot archetype is ' + arcana + '.',
'Their body focus this season: ' + (profile?.bodyParts ?? 'unknown') + '.',
].join(' ');10.2 4-Dimension Compatibility — Love, Career, Wealth, Health
compatibility.zodiac returns dimensions (Chinese zodiac 4D).compatibility.birthday returns both dimensions (Chinese 4D) and horoscopeDimensions (Western 4D). Each dimension has a label (“Positive” / “Neutral” / “Negative”) and a prose text field.
// compatibility.zodiac — Chinese 4D
// ── compatibility.zodiac — Chinese 4D only ────────────────────────────────────
const { dimensions } = await client.compatibility.zodiac({ animal1: "rat", animal2: "dragon" });
// dimensions.love.label → "Positive"
// dimensions.career.label → "Positive"
// dimensions.wealth.label → "Positive"
// dimensions.health.label → "Positive"
// dimensions.marriage.label → "Positive"
// ── compatibility.birthday — Chinese 4D + Western 4D in one call ──────────────
const result = await client.compatibility.birthday({ date1: "1990-01-15", date2: "1992-06-20" });
const { dimensions: cDims, horoscopeDimensions: wDims } = result;
// ── Combined report builder → single LLM-ready string ─────────────────────────
function dimLine(label: string, d?: { label: string; text: string }) {
if (!d) return '';
return label + ' [' + d.label + ']: ' + d.text;
}
const relationshipSummary = [
'=== Chinese Zodiac Compatibility ===',
dimLine('Love', cDims?.love),
dimLine('Career', cDims?.career),
dimLine('Wealth', cDims?.wealth),
dimLine('Health', cDims?.health),
dimLine('Marriage', cDims?.marriage),
'',
'=== Western Horoscope Compatibility ===',
dimLine('Love', wDims?.love),
dimLine('Career', wDims?.career),
dimLine('Wealth', wDims?.wealth),
dimLine('Health', wDims?.health),
].filter(Boolean).join('
');
// Inject into LLM system prompt:
const systemPrompt =
'You are a relationship counsellor. Use the following compatibility data to answer the user.
' +
relationshipSummary;10.3 Chinese Zodiac Profile — 60-Element Enrichmentv3.9.0
almanac.zodiacSign also returns chineseProfile (ChineseZodiacProfile), covering all 60 combinations of 12 animals × 5 elements. Use it to build identity cards, onboarding flows, or compatibility pre-screens.
const { chineseProfile } = await client.almanac.zodiacSign({ birthDate: "1990-05-15" });
// chineseProfile.zodiac → "Metal Horse"
// chineseProfile.personality → prose paragraph
// chineseProfile.career → career guidance prose
// chineseProfile.love → relationship style prose
// chineseProfile.compatibility → compatible animals prose
// Onboarding identity card:
const identityCard = {
title: chineseProfile.zodiac,
summary: chineseProfile.personality.slice(0, 200) + "...",
careerHint: chineseProfile.career,
loveStyle: chineseProfile.love,
};client.tian.global.stream() returns an AsyncGenerator<TianGlobalStreamChunk> that yields four event types progressively as the server fans out to 7 tradition engines and streams the LLM synthesis. Use TianGlobalStreamChunk (v1.9.0+) instead of the generic TianStreamEvent to get typed access to signProfile and compatProfile on the done event.
Event types
Option A — Node.js / server-side (recommended)
import { AskTianClient, TianGlobalStreamChunk } from "asktian-sdk";
const client = new AskTianClient({ apiKey: process.env.ASKTIAN_API_KEY! });
let synthesis = "";
for await (const event of client.tian.global.stream({
birthdate: "1990-01-15",
birthTime: "06:30",
birthPlace: "Singapore",
question: "Should I launch my business this quarter?",
}) as AsyncGenerator<TianGlobalStreamChunk>) {
if (event.type === "system") {
// Each tradition result arrives here progressively
console.log(`[${event.name}] score: ${event.score}`);
}
if (event.type === "synthesis_chunk") {
// Stream tokens to your UI via SSE / WebSocket
process.stdout.write(event.chunk);
synthesis += event.chunk;
}
if (event.type === "done") {
// Blended score + rich profile anchors
console.log("\nBlended score:", event.blendedScore);
console.log("Mantra:", event.signProfile?.mantra);
console.log("Tarot archetype:", event.signProfile?.majorArcana);
console.log("Love label:", event.compatProfile?.loveDimensionLabel);
}
}Option B — Proxy pattern (frontend → your server → askTIAN SSE)
Never expose your API key to the browser. Instead, open an SSE route on your server that proxies the stream:
// server/routes/tianStream.ts (Express)
import express from "express";
import { AskTianClient, TianGlobalStreamChunk } from "asktian-sdk";
const router = express.Router();
const client = new AskTianClient({ apiKey: process.env.ASKTIAN_API_KEY! });
router.get("/stream/tian/global", async (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
const { birthdate, birthTime, question } = req.query as Record<string, string>;
try {
for await (const event of client.tian.global.stream({ birthdate, birthTime, question }) as AsyncGenerator<TianGlobalStreamChunk>) {
res.write(`data: ${JSON.stringify(event)}\n\n`);
if (event.type === "done" || event.type === "error") break;
}
} catch (err) {
res.write(`data: ${JSON.stringify({ type: "error", message: String(err) })}\n\n`);
} finally {
res.end();
}
});
export default router;client.tian.global.stream() returns AsyncGenerator<TianStreamEvent> at the base type level. Cast to AsyncGenerator<TianGlobalStreamChunk> to access the typed signProfile and compatProfile fields on the done event. This is safe — the server always emits these fields when available.horoscope.calculate returns a full signProfile object (identical shape to almanac.zodiacSign's westernProfile). Unlike the abbreviated TianGlobalSignProfile returned by tian.global, this full profile includes all nine Medical Astrology fields — making it the right choice when you need to build wellness cards, body-awareness prompts, or Tarot spread contexts.
When to use each source
| Source | Fields returned | Best for |
|---|---|---|
| tian.global | westernSign, mantra, majorArcana | Quick prompt anchor — no extra call needed |
| horoscope.calculate | All 9 fields incl. medical, bodyParts, herbalAllies, famousPeople | Wellness cards, Tarot spreads, full natal chart |
| almanac.zodiacSign | westernProfile (same 9 fields) + chineseProfile | When you also need the Chinese zodiac profile in one call |
Building a wellness prompt from signProfile
import { AskTianClient } from "asktian-sdk";
const client = new AskTianClient({ apiKey: process.env.ASKTIAN_API_KEY! });
// Full natal chart + signProfile in one call (1 $TIAN)
const chart = await client.horoscope.calculate({
birthdate: "1990-01-15",
birthHour: 6,
question: "What should I focus on for my health this season?",
});
const sp = chart.signProfile;
// ── Daily wellness card ───────────────────────────────────────────────────────
const wellnessCard = {
sign: sp?.sign ?? chart.sunSign,
bodyFocus: sp?.bodyParts, // e.g. "Knees, skin, connective tissue"
herbs: sp?.herbalAllies, // e.g. "Horsetail, comfrey, Solomon's seal"
tarot: sp?.majorArcana, // e.g. "The Devil (XV)"
mantra: sp?.mantra, // e.g. "I Use"
medical: sp?.medical, // e.g. "Skeletal system, joints, skin"
};
// ── Inject into LLM system prompt ────────────────────────────────────────────
const systemPrompt = [
'You are a wellness guide for a ' + (sp?.sign ?? chart.sunSign) + ' sun sign.',
sp?.mantra ? 'Their affirmation mantra is: "' + sp.mantra + '".' : '',
sp?.bodyParts ? 'Body areas to focus on: ' + sp.bodyParts + '.' : '',
sp?.medical ? 'Medical astrology note: ' + sp.medical + '.' : '',
sp?.herbalAllies ? 'Supportive herbs: ' + sp.herbalAllies + '.' : '',
sp?.majorArcana ? 'Their Tarot archetype is ' + sp.majorArcana + '.' : '',
sp?.symbolism ? 'Symbolic anchor: ' + sp.symbolism + '.' : '',
].filter(Boolean).join(' ');
console.log(systemPrompt);
// → "You are a wellness guide for a capricorn sun sign. Their affirmation mantra is: 'I Use'. Body areas to focus on: Knees, skin, connective tissue. Medical astrology note: Skeletal system, joints, skin. Supportive herbs: Horsetail, comfrey, Solomon's seal. Their Tarot archetype is The Devil (XV). Symbolic anchor: The Sea-Goat."Tarot spread context builder
// Build a Tarot reading context block from signProfile
function buildTarotContext(sp: { sign?: string; majorArcana?: string; minorArcana?: string; symbolism?: string } | undefined) {
if (!sp) return '';
return [
'=== Tarot Context for ' + (sp.sign ?? 'Unknown') + ' ===',
sp.majorArcana ? 'Major Arcana: ' + sp.majorArcana : '',
sp.minorArcana ? 'Minor Arcana: ' + sp.minorArcana : '',
sp.symbolism ? 'Symbolic archetype: ' + sp.symbolism : '',
].filter(Boolean).join('\n');
}
const tarotContext = buildTarotContext(chart.signProfile);
// → "=== Tarot Context for capricorn ===\nMajor Arcana: The Devil (XV)\nMinor Arcana: 2, 3, 4 of Pentacles\nSymbolic archetype: The Sea-Goat"compatibility.zodiac returns a dimensions block with five YouApp-sourced dimensions for any Chinese zodiac animal pair. Each dimension carries a label (Positive / Neutral / Negative) and a prose text field. This is the lightest-weight compatibility call — no birth dates required, just two animal names.
Dimension overview
| Dimension | Field path | Agent use-case |
|---|---|---|
| Love | dimensions.love | Romantic compatibility card, dating app feature |
| Career | dimensions.career | Business partner assessment, co-founder fit |
| Wealth | dimensions.wealth | Financial partnership guidance |
| Health | dimensions.health | Wellness synergy, lifestyle compatibility |
| Marriage | dimensions.marriage | Long-term relationship assessment |
Quick compatibility card builder
import { AskTianClient } from "asktian-sdk";
const client = new AskTianClient({ apiKey: process.env.ASKTIAN_API_KEY! });
// Lightweight call — no birth dates needed (0 $TIAN for zodiac pair lookup)
const compat = await client.compatibility.zodiac({ animal1: "rat", animal2: "dragon" });
const { score, level, description, dimensions: d } = compat;
// ── Compatibility card ────────────────────────────────────────────────────────
const compatCard = {
headline: 'Rat + Dragon: ' + level + ' (' + score + '/100)',
summary: description,
love: { label: d?.love?.label, detail: d?.love?.text },
career: { label: d?.career?.label, detail: d?.career?.text },
wealth: { label: d?.wealth?.label, detail: d?.wealth?.text },
health: { label: d?.health?.label, detail: d?.health?.text },
marriage: { label: d?.marriage?.label, detail: d?.marriage?.text },
};
// ── LLM prompt injection ──────────────────────────────────────────────────────
function dimLine(name: string, dim?: { label: string | null; text: string | null }) {
if (!dim?.label) return '';
return name + ' [' + dim.label + ']: ' + (dim.text ?? '');
}
const compatContext = [
'Overall compatibility score: ' + score + '/100 (' + level + ')',
dimLine('Love', d?.love),
dimLine('Career', d?.career),
dimLine('Wealth', d?.wealth),
dimLine('Health', d?.health),
dimLine('Marriage', d?.marriage),
].filter(Boolean).join('\n');
const systemPrompt =
'You are a relationship counsellor specialising in Chinese astrology.\n' +
'Use the following Rat + Dragon compatibility data to answer the user.\n\n' +
compatContext;Upgrade path: add Western 4D via compatibility.birthday
When you have both users' birth dates, upgrade to compatibility.birthday to get both the Chinese 4D dimensions and the Western 4D horoscopeDimensions in a single call. See Section 10.2 for the combined report builder pattern.
The v3.24.0 release adds four new fields and one new event to the SSE streaming contract for alltian.* blended endpoints. This section documents the complete event schema so the app.asktian.com frontend can map each event to the correct animation and UI transition without a separate catalogue lookup.
New in v3.24.0 — event additions
| Event / Field | Type | Description | Frontend use |
|---|---|---|---|
| synthesis_start event | event | Emitted once, immediately before the first synthesis_chunk token | Transition from tradition animation phase to synthesis reading phase |
| system.tradition | string | "eastern" | "western" | "african" | "islamic" | "indian" | Map to tradition-specific animation: Eastern → hexagram spin, Western → planetary orbit, African → shimmer pulse, Islamic → geometric pattern, Indian → mandala |
| system.iconUrl | string | CDN URL for the tradition icon (original variant) | Show tradition icon during theatrical animation without a separate catalogue lookup |
| system.systemSlug | string | e.g. "qimen", "tarot", "ifa", "jyotish" | Match against icon pack and system catalogue for rich card rendering |
Complete SSE event sequence
The table below shows every event emitted by a tian.global stream call in order, with the v3.24.0 additions highlighted.
| Event type | Emitted | Key fields | App action |
|---|---|---|---|
| system | Once per tradition (up to 29×) | name, result, score, error?, tradition ✦, iconUrl ✦, systemSlug ✦ | Show tradition card with icon; start animation |
| synthesis_start | Once, before first token ✦ | (no payload) | Transition to synthesis reading phase; hide tradition grid |
| synthesis_chunk | One per LLM token | text (string) | Append token to synthesis display |
| done | Once, after last token | blendedScore, synthesis, traditionScores, creditsUsed, responseTimeMs, signProfile?, compatProfile? | Show final score; store reading in DB; enable share |
| error | On failure | code, message | Show error toast; allow retry |
✦ New in v3.24.0
Minimal TypeScript consumer (v3.24.0 contract)
import { AskTianClient } from "asktian-sdk";
const client = new AskTianClient({ apiKey: process.env.ASKTIAN_API_KEY! });
for await (const chunk of client.tian.global.stream({
birthdate: "1983-08-03",
birthTime: "22:05",
question: "Should I change careers?",
fullName: "Douglas Gan",
})) {
if (chunk.type === "system") {
// v3.24.0: tradition, iconUrl, systemSlug are now available
console.log(`[${chunk.tradition}] ${chunk.name} — score ${chunk.score}`);
console.log(` icon: ${chunk.iconUrl} slug: ${chunk.systemSlug}`);
}
if (chunk.type === "synthesis_start") {
// v3.24.0: transition from tradition animation to synthesis reading
console.log("Synthesis starting — switch UI phase");
}
if (chunk.type === "synthesis_chunk") {
process.stdout.write(chunk.text);
}
if (chunk.type === "done") {
console.log(`Blended score: ${chunk.blendedScore}`);
console.log(`Credits used: ${chunk.creditsUsed}`);
// signProfile and compatProfile available here for Pattern Card
}
if (chunk.type === "error") {
console.error(`Stream error ${chunk.code}: ${chunk.message}`);
}
}Tradition → animation mapping
| tradition value | Systems included | Suggested animation |
|---|---|---|
| eastern | Qimen, BaZi, Zi Wei, I Ching, Feng Shui, Mahabote, 十二宮 … | Hexagram spin / trigram reveal |
| western | Tarot, Numerology, Runes, Astrology, Horoscope … | Planetary orbit / card flip |
| african | Ifá, Vodun, Hakata | Shimmer pulse / cowrie scatter |
| islamic | Rammal, Khatt al-Raml | Geometric pattern unfold |
| indian | Jyotish, Anka Shastra | Mandala bloom |
App-side event type alignment
The app.asktian.com frontend uses internal event names that differ from the API SSE event types. The table below maps the app's internal events to the API contract so the SSE consumer can translate without an adapter layer.
| App event | API event | Notes |
|---|---|---|
| tradition_start | (none) | Use synthesis_start as the phase-transition signal instead |
| tradition_result | system | system.tradition + system.iconUrl + system.systemSlug added in v3.24.0 |
| tradition_complete | (none) | Use done event as the all-traditions-complete signal |
| synthesis_start | synthesis_start | Direct 1:1 mapping — new in v3.24.0 |
| synthesis_chunk | synthesis_chunk | Direct 1:1 mapping |
| synthesis_complete | done | Full synthesis text available in done.synthesis |
| error | error | Direct 1:1 mapping |
The Daily Anchor card is the primary daily engagement hook in the app.asktian.com Dynamic Deck. It shows a personalised daily reading that combines the Chinese almanac energy for today with the user's natal chart context. A dedicated daily.anchor endpoint is under active development for v3.24.0 API. Until it lands, the same result can be composed from two existing calls.
Interim approach — two-call composition (available now)
Combine almanac.daily (today's almanac energy) withhoroscope.calculate (user's natal chart + sign profile) to build the Daily Anchor card. Cache the horoscope result for 90 days — the natal chart never changes.
import { AskTianClient, HoroscopeResponse, AlmanacZodiacResponse } from "asktian-sdk";
const client = new AskTianClient({ apiKey: process.env.ASKTIAN_API_KEY! });
// Call 1: Today's almanac (no birth data required — cache for 24 hours per date)
const almanac = await client.almanac.daily({
date: new Date().toISOString().split("T")[0], // "2026-04-09"
});
// Call 2: User's natal chart (cache for 90 days — natal chart never changes)
const horoscope: HoroscopeResponse = await client.horoscope.calculate({
birthdate: user.birthdate, // "1983-08-03"
birthHour: user.birthHour, // 22
});
// Compose the Daily Anchor card
const dailyAnchor = {
date: almanac.date,
twelveValue: almanac.twelveValue, // e.g. "成" (Achievement)
deity: almanac.deity, // e.g. "青龍" (Azure Dragon)
fortune: almanac.fortune, // "good_fortune" | "neutral" | "caution"
auspicious: almanac.auspiciousActivities, // array of activity strings
inauspicious: almanac.inauspiciousActivities,
sunSign: horoscope.sunSign, // e.g. "Leo"
mantra: horoscope.signProfile?.mantra, // e.g. "I Will"
majorArcana: horoscope.signProfile?.majorArcana, // e.g. "Strength"
synthesis: horoscope.synthesis, // LLM natal chart interpretation
};Response field reference — two-call composition
| Field | Source | Description | Cache TTL |
|---|---|---|---|
| twelveValue | almanac.daily | Chinese twelve-value (成, 破, 危 …) | 24 hours |
| deity | almanac.daily | Day deity (青龍, 朱雀, 白虎 …) | 24 hours |
| fortune | almanac.daily | Overall fortune: good_fortune | neutral | caution | 24 hours |
| auspicious | almanac.daily | Activities favoured today (Chinese) | 24 hours |
| inauspicious | almanac.daily | Activities to avoid today (Chinese) | 24 hours |
| sunSign | horoscope.calculate | User's Western sun sign | 90 days |
| mantra | horoscope.calculate | Sign mantra for daily focus (via signProfile) | 90 days |
| majorArcana | horoscope.calculate | Tarot archetype for the sign (via signProfile) | 90 days |
| synthesis | horoscope.calculate | LLM natal chart interpretation | 90 days |
Future approach — daily.anchor single call (v3.24.0 API)
When daily.anchor lands, a single call replaces both interim calls and adds two new fields: luckyHours (derived from the user's BaZi day master element) and dominantTheme(a plain-language summary of the most significant current transit). Cost: 3 TIAN Points.
// Future — single call once daily.anchor is available (v3.24.0 API)
const anchor = await client.daily.anchor({
birthdate: user.birthdate, // "1983-08-03"
birthHour: user.birthHour, // 22
question: "What should I focus on today?", // optional
});
// anchor.date — "2026-04-09"
// anchor.dominantTheme — "Mercury retrograde squares natal Saturn — review before acting"
// anchor.almanacHighlight — { twelveValue, deity, fortune }
// anchor.dailyMessage — LLM-synthesised 2-3 sentence daily reading
// anchor.luckyHours — ["10:00-12:00", "15:00-17:00"]
// anchor.avoidHours — ["13:00-14:00"]
// anchor.creditsUsed — 3Migration path
Design the Daily Anchor card component to accept a single DailyAnchorData prop. Populate it from the two-call composition today; swap the data source todaily.anchor when the endpoint is available. The card component itself requires no changes.
| Field | Interim source | Future source | Notes |
|---|---|---|---|
| twelveValue / deity / fortune | almanac.daily | daily.anchor.almanacHighlight | Same data, restructured |
| auspicious / inauspicious | almanac.daily | daily.anchor.almanacHighlight | English via language param in v3.24.0 API |
| sunSign / mantra / majorArcana | horoscope.calculate | daily.anchor (inline) | No separate call needed |
| dailyMessage | (compose manually) | daily.anchor.dailyMessage | LLM synthesis included |
| luckyHours / avoidHours | (not available) | daily.anchor | BaZi day master derived |
| dominantTheme | (not available) | daily.anchor | Current transit summary |
More integration guides
The /guides section has step-by-step engineering guides for the app.asktian.com integration, the Telegram bot, and the Chat API — each with working code, error handling, and a sprint roadmap.
Sprint 1 — Foundation
Sprint 2 — Orchestration
Sprint 3 — Daily Anchor & Delivery
Sprint 4 — Telegram Bot (separate service)
/wallet-callback endpoint URL with the askTIAN team. Email [email protected] with your bot username and callback URL. Include TIAN_WALLET_API_KEY request in the same email. Allow 1 business day for provisioning.