Chat API Wishlist — Engineering Response
Document type: Engineering response to Telegram bot developer wishlist
Date: April 9, 2026
Prepared by: askTIAN Engineering Team
For: External Telegram Bot Developer
Decision Summary
| # | Item | Decision | Target |
|---|---|---|---|
| 1 | Session token TTL clarification | Resolved — see below | Immediate |
| 2 | conversations.list pagination | Accepted | v3.30.0 |
| 3 | Conversation metadata (lastMessageAt, messageCount, totalPointsSpent) | Partially accepted | v3.31.0 |
| 4 | Bulk deduct endpoint | Declined | — |
| 5 | Telegram webhook retry policy documentation | Resolved — see below | Immediate |
| 6 | Rate limiting headers | Accepted | v3.30.0 |
| 7 | Analytics/logging endpoint | Deferred | Backlog |
Item 1 — Session Token TTL: Resolved
This is the most important item and the answer is unambiguous: the chat engineering response is correct. Session tokens issued by wallet.tianVerify are single-use and expire in 5 minutes. The wallet team's earlier statement of "24 hours" referred to the Privy access token (the privyToken you receive after wallet sign-in), not the sessionToken that wallet.tianVerify exchanges it for.
The two tokens have different lifetimes and different purposes:
| Token | Issued by | Lifetime | Purpose |
|---|---|---|---|
privyToken | wallet.asktian.com (Privy) | 24 hours | Authenticate the user with the wallet; exchange for sessionToken |
sessionToken (tianSessionToken) | wallet.tianVerify tRPC call | 5 minutes, single-use | Authorize a single chat.ask call; consumed on use |
Your token refresh strategy should be: store privyToken (24-hour lifetime) permanently per user. Before each paid reading, check whether sessionTokenExpiresAt is within 30 seconds of expiry or null. If so, call wallet.tianVerify with the stored privyToken to get a fresh sessionToken. After chat.ask returns (success or error), immediately set sessionToken = NULL in your DB.
You do not need to re-authenticate the user with Privy (i.e., send them back to the wallet sign-in link) unless their privyToken has also expired (after 24 hours of inactivity). In practice, most users will only need to sign in once.
Item 2 — conversations.list Pagination: Accepted for v3.30.0
The current conversations.list procedure accepts a limit parameter (default 5, max 50) but does not support cursor-based or offset pagination. This will be extended in v3.30.0.
Planned interface:
// Request
trpcQuery("conversations.list", {
sessionId: "your-uuid",
limit: 10, // default 5, max 50
cursor: 42, // optional: conversationId to paginate from (exclusive)
});
// Response
{
conversations: [...],
nextCursor: 31, // null if no more results
hasMore: true,
}
For the /history command, a limit: 5 call without a cursor is sufficient for the initial view. You can add a "Load more" inline button that passes the nextCursor from the previous response.
Item 3 — Conversation Metadata: Partially Accepted for v3.31.0
The three requested fields have different implementation costs and privacy implications:
| Field | Decision | Rationale |
|---|---|---|
lastMessageAt | Accepted | Already stored in the DB; trivial to expose. Useful for sorting conversations by recency in /history. |
messageCount | Accepted | Requires a COUNT join but is low cost. Helps users understand conversation depth. |
totalPointsSpent | Deferred | Requires joining across the wallet ledger, which is a separate service. Will be considered once the wallet API exposes a per-conversation ledger endpoint. |
lastMessageAt and messageCount will be added to the conversations.list response in v3.31.0. totalPointsSpent is on the backlog pending wallet API changes.
Item 4 — Bulk Deduct Endpoint: Declined
The wallet.deduct call is intentionally atomic and tied to a single tianSessionToken. This design is not accidental: it prevents double-spend, ensures each deduction is auditable against a specific reading, and limits the blast radius of a compromised session token to a single transaction.
A batch deduct endpoint would require issuing multi-use session tokens, which would significantly weaken the security model. The network overhead of sequential deductions is negligible for the bot use case (readings are user-initiated, not bulk-scheduled), and the current architecture already handles rapid successive readings correctly via the token refresh flow described in Item 1.
If you are building a feature that genuinely requires multiple simultaneous deductions (e.g., a "run all traditions" button), the correct pattern is to call chat.ask with a TIAN Blend system (e.g., tian_global) rather than calling individual systems in parallel. TIAN Blend systems are priced as a single atomic deduction and return a synthesized result.
Item 5 — Telegram Webhook Retry Policy: Resolved (Third-Party Documentation)
This is a Telegram platform question, not an askTIAN API question. The authoritative answer is in the Telegram Bot API documentation:
Telegram retries failed webhook deliveries (any response other than HTTP 200) with exponential backoff. The retry schedule is approximately: immediately → 20 seconds → 20 seconds → 20 seconds → 20 seconds (up to 5 attempts over ~80 seconds). After 5 failed attempts, Telegram stops retrying that update.
Implications for your bot:
Your webhook handler must respond with HTTP 200 within 10 seconds. If chat.ask takes longer (TIAN Blend readings can take 30–90 seconds), you must respond 200 immediately and process the reading asynchronously. The recommended pattern:
app.post("/telegram-webhook", (req, res) => {
// Validate secret token
if (req.headers["x-telegram-bot-api-secret-token"] !== process.env.TELEGRAM_WEBHOOK_SECRET) {
return res.sendStatus(403);
}
// Respond 200 immediately — never block the webhook handler
res.sendStatus(200);
// Process asynchronously
processUpdate(req.body).catch(err => console.error("Update processing error:", err));
});
Idempotency: Yes, you must implement idempotency on the webhook handler side. Each Telegram update has a unique update_id. Store processed update_id values in your DB and skip duplicates. This prevents double-processing if your handler returns 200 but Telegram retries anyway due to a network issue.
async function processUpdate(update: TelegramUpdate) {
const alreadyProcessed = await db.isUpdateProcessed(update.update_id);
if (alreadyProcessed) return;
await db.markUpdateProcessed(update.update_id);
// ... handle the update
}
Item 6 — Rate Limiting Headers: Accepted for v3.30.0
The chat.asktian.com tRPC layer will add standard rate limit headers to all responses in v3.30.0:
| Header | Value | Example |
|---|---|---|
X-RateLimit-Limit | Requests allowed per window | 120 |
X-RateLimit-Remaining | Requests remaining in current window | 87 |
X-RateLimit-Reset | Unix timestamp when the window resets | 1744200000 |
Retry-After | Seconds to wait (only on 429 responses) | 14 |
Until v3.30.0, implement a simple exponential backoff on 429 errors: wait 1 second, then 2, then 4, up to a maximum of 60 seconds. The current rate limits are 120 req/min general and 20 req/min for LLM-backed procedures (chat.ask).
Item 7 — Analytics/Logging Endpoint: Deferred to Backlog
The request is reasonable and the proposed schema is well-designed. It is deferred for two reasons:
First, the askTIAN API platform already captures server-side analytics for all chat.ask calls (system, duration, points spent, session). Adding a client-side event endpoint would create a second source of truth that could diverge from the server-side record, complicating debugging.
Second, for the bot use case, the recommended approach is to log events to your own analytics service (e.g., PostHog, Mixpanel, or a simple DB table) using the data already available in the chat.ask response (tianPointsNewBalance, messageId, system, duration you measure client-side). This gives you full control over the schema and does not require a round-trip to the askTIAN API.
If there is a specific event type that is only observable server-side and not derivable from the chat.ask response, raise it as a separate request and it will be evaluated on its own merits.
Recommended Pre-Build Checklist
Before starting Sprint 1, confirm the following:
- Session token TTL understood:
privyToken= 24h,sessionToken= 5 min single-use. ✓ (see Item 1) - Webhook idempotency planned: store
update_idin DB, skip duplicates. ✓ (see Item 5) - Async webhook handler planned: respond 200 immediately, process in background. ✓ (see Item 5)
- TIAN Blend readings handled: show typing indicator for up to 90 seconds. ✓ (see engineering response §6)
- Wallet callback URL registration submitted to [email protected]. (Required before Sprint 4 Step 2)
Questions
For further clarifications, contact the askTIAN engineering team at [email protected] or join the developer community on Telegram. Reference this document (Chat API Wishlist Response v1.0, April 2026) in your communications.
