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

#ItemDecisionTarget
1Session token TTL clarificationResolved — see belowImmediate
2conversations.list paginationAcceptedv3.30.0
3Conversation metadata (lastMessageAt, messageCount, totalPointsSpent)Partially acceptedv3.31.0
4Bulk deduct endpointDeclined
5Telegram webhook retry policy documentationResolved — see belowImmediate
6Rate limiting headersAcceptedv3.30.0
7Analytics/logging endpointDeferredBacklog

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:

TokenIssued byLifetimePurpose
privyTokenwallet.asktian.com (Privy)24 hoursAuthenticate the user with the wallet; exchange for sessionToken
sessionToken (tianSessionToken)wallet.tianVerify tRPC call5 minutes, single-useAuthorize 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:

FieldDecisionRationale
lastMessageAtAcceptedAlready stored in the DB; trivial to expose. Useful for sorting conversations by recency in /history.
messageCountAcceptedRequires a COUNT join but is low cost. Helps users understand conversation depth.
totalPointsSpentDeferredRequires 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:

HeaderValueExample
X-RateLimit-LimitRequests allowed per window120
X-RateLimit-RemainingRequests remaining in current window87
X-RateLimit-ResetUnix timestamp when the window resets1744200000
Retry-AfterSeconds 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:

  1. Session token TTL understood: privyToken = 24h, sessionToken = 5 min single-use. ✓ (see Item 1)
  2. Webhook idempotency planned: store update_id in DB, skip duplicates. ✓ (see Item 5)
  3. Async webhook handler planned: respond 200 immediately, process in background. ✓ (see Item 5)
  4. TIAN Blend readings handled: show typing indicator for up to 90 seconds. ✓ (see engineering response §6)
  5. 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.