Skip to main content

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​

ConditionModeSource
X_BEARER_TOKEN not setdemoReturns MOCK_MENTIONS (5 sample civic tweets)
Token set, API returns 401/402/403demoGraceful fallback with reason field
Token set, API returns dataliveReal 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:

ParamTypeDefaultNotes
countnumber20Clamped 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:

FieldDescription
mentionsArray of formatted tweet objects
mode"live" or "demo"
handleThe monitored X handle (from X_HANDLE env var)
contextParent 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:

ParamTypeNotes
tweetIdstring (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"
}
FieldTypeRequiredConstraints
titlestringβœ…5–200 characters
descriptionstringβœ…10–5000 characters
categorystringβœ…traffic | road | infrastructure | hygiene | healthcare | systemic
citystringβœ…2–100 characters
prioritystringβ€”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:

CategoryKeywords (sample)
roadpothole, road, highway, footpath
traffictraffic, signal, jam, flyover
infrastructurewater, drain, flood, streetlight
hygienegarbage, waste, sewage, mosquito
healthcarehospital, doctor, ambulance, health
systemiccorruption, 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​

VariableRequiredDefaultDescription
X_BEARER_TOKENβ€”(none)X API v2 Bearer token. Without this, app runs in demo mode
X_HANDLEβ€”civicpulse16094X handle to monitor (without @)
VITE_X_HANDLEβ€”civicpulse16094Frontend display handle (synced from API response)

Getting a Bearer Token​

  1. Go to developer.twitter.com β†’ Projects & Apps
  2. Create or select an app β†’ Keys and Tokens
  3. Copy the Bearer Token to X_BEARER_TOKEN in backend/.env
  4. Note: search/recent endpoint 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