X (Twitter) Integration API
CivicPulse integrates with the X API v2 to fetch recent mentions of the configured handle and convert them into trackable civic issues. The integration is surfaced in the Social Wall X tab and the /test-x developer page.
Demo vs Live Modeβ
| Condition | Mode | Source |
|---|---|---|
X_BEARER_TOKEN not set | demo | Returns MOCK_MENTIONS (5 sample civic tweets) |
| Token set, API returns 401/402/403 | demo | Graceful fallback with reason field |
| Token set, API returns data | live | Real mentions from X API v2 search/recent |
Endpointsβ
GET /api/x/mentionsβ
Fetches recent mentions of @{X_HANDLE} from X API v2. Returns demo data when X_BEARER_TOKEN is not configured.
Authentication: None required β used by Social Wall and /test-x page
Query params:
| Param | Type | Default | Notes |
|---|---|---|---|
count | number | 20 | Clamped to 10β50 |
Response 200:
{
"mentions": [
{
"id": "tw1",
"text": "@civicpulse16094 The flyover near Dadar is cracked. #MumbaiInfrastructure",
"user": "@mumbaikaar99",
"timestamp": "2 min ago",
"category_hint": "infrastructure",
"city_hint": "Mumbai",
"images": [],
"videos": [],
"context": null
}
],
"mode": "demo",
"handle": "civicpulse16094"
}
Fields:
| Field | Description |
|---|---|
mentions | Array of formatted tweet objects |
mode | "live" or "demo" |
handle | The monitored X handle (from X_HANDLE env var) |
context | Parent tweet when this mention is a reply (null otherwise) |
Headers: Cache-Control: no-store
POST /api/x/mentions/:tweetId/convertβ
Convert an X mention into a CivicPulse issue.
Authentication: Bearer token required (Authorization: Bearer <accessToken>)
Path params:
| Param | Type | Notes |
|---|---|---|
tweetId | string (1β50 chars) | Tweet ID to associate with the issue |
Request body:
{
"title": "Flyover near Dadar cracked and vibrating",
"description": "The flyover near Dadar is cracked and vibrating badly. Dangerous for commuters.",
"category": "infrastructure",
"city": "Mumbai",
"priority": "high"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
title | string | β | 5β200 characters |
description | string | β | 10β5000 characters |
category | string | β | traffic | road | infrastructure | hygiene | healthcare | systemic |
city | string | β | 2β100 characters |
priority | string | β | low | medium (default) | high |
Response 201:
{
"issueId": "ISS-007",
"title": "Flyover near Dadar cracked and vibrating",
"status": "open",
"source": "twitter",
"tweetId": "1234567890",
"tweetUrl": "https://x.com/i/web/status/1234567890"
}
Response 401: Missing or invalid Bearer token
Response 422: Validation error β { "errors": [...] }
Category Detectionβ
Both the backend service and frontend test page use the same keyword mapping:
| Category | Keywords (sample) |
|---|---|
road | pothole, road, highway, footpath |
traffic | traffic, signal, jam, flyover |
infrastructure | water, drain, flood, streetlight |
hygiene | garbage, waste, sewage, mosquito |
healthcare | hospital, doctor, ambulance, health |
systemic | corruption, bribe, policy, official |
The algorithm counts keyword matches per category and returns the highest-scoring one. Falls back to systemic when no keywords match.
Environment Variablesβ
| Variable | Required | Default | Description |
|---|---|---|---|
X_BEARER_TOKEN | β | (none) | X API v2 Bearer token. Without this, app runs in demo mode |
X_HANDLE | β | civicpulse16094 | X handle to monitor (without @) |
VITE_X_HANDLE | β | civicpulse16094 | Frontend display handle (synced from API response) |
Getting a Bearer Tokenβ
- Go to developer.twitter.com β Projects & Apps
- Create or select an app β Keys and Tokens
- Copy the Bearer Token to
X_BEARER_TOKENinbackend/.env - Note:
search/recentendpoint requires Basic plan or higher (free tier has limited access)
Dev Test Pageβ
Navigate to /test-x for a standalone test environment:
- Live mention feed from
GET /api/x/mentions - Inline convert panel per tweet
- ManualInput textarea with real-time category/city detection
- Converted Issues session log
- Handle display + API reference