Twinkle HubTwinkle Hub
Sign in
🛠️ Alpha maintenance window: daily 22:00 – 07:00 (Taiwan time / GMT+8). Service may be unstable. Apologies for any disruption.

📌 2026-05-22 added: National exam past-questions semantic search — 64K papers, 320K questions (1 datasets)

View full changelog →

Docs

10-minute setup

Twinkle Hub is an MCP endpoint — any MCP client connects. Pick the path that matches you.

Get an API key

/login → Google or GitHub sign-in. First login auto-issues a virtual API key (sk-...). New accounts start at $0 balance — self-serve top-up is coming soon.

curl (streamable-http transport)

Step 1: initialize and capture session id from the mcp-session-id response header

text
curl -i -X POST https://api.twinkleai.tw/mcp/ \
  -H "Authorization: Bearer sk-..." \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

Step 2: tools/call

text
curl -X POST https://api.twinkleai.tw/mcp/ \
  -H "Authorization: Bearer sk-..." \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -H "mcp-session-id: <from-step-1>" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"opendata-list_domains","arguments":{}}}'

Response uses SSE-style framing — find the last line starting with data: and parse the JSON.

Python (fastmcp)

python
import asyncio
from fastmcp.client import Client
from fastmcp.client.auth import BearerAuth

async def main():
    async with Client(
        "https://api.twinkleai.tw/mcp/",
        auth=BearerAuth("sk-..."),
    ) as client:
        tools = await client.list_tools()
        print([t.name for t in tools])

        r = await client.call_tool(
            "opendata-search_datasets",
            {"query": "AQI", "domain": "environment", "limit": 5},
        )
        # response 在 r.content[0].text — JSON string
        import json
        print(json.loads(r.content[0].text))

asyncio.run(main())

Tools return CallToolResult.content[0].text as a JSON string. License, citation and other metadata pass through in the _meta field.

Tools reference

ToolArgumentsReturns (brief)
opendata-list_domains[{key, name_zh, scope, typical_questions}]
opendata-search_datasets{query, domain?, limit?}[{dataset_id, title, score, license}]
opendata-get_dataset{dataset_id}{schema, columns, license, source_url}
opendata-query_rows{dataset_id, where?, limit?}[row, ...]
opendata-materialize_dataset{dataset_id, format?}全表 CSV / JSON

Full schemas via tools/list — auto-generated as JSON Schema.

Authentication

  • Bearer token — header Authorization: Bearer sk-.... A Twinkle-Hub-issued virtual key, one per user. Get yours from /dashboard.
  • No OAuth flow — no PKCE / refresh tokens needed; just a long-lived bearer.
  • Rate limit — no per-key RPM/TPM limits today; only max_budget enforces (gateway rejects when spend > max_budget).

Pricing

Coming soon

All tools are free during alpha. We'll announce per-tool pricing before billing starts, and existing users get a transition discount window.

Free during alpha. The spend tracking pipeline still runs (5–15s async lag, gateway batches DB writes); once billing starts, quota enforcement is real-time on the next request.