skills/subway-info/SKILL.md
Get real-time NYC transit information — subway, bus, ferry, and commuter rail — via the subway-info CLI or REST API at subwayinfo.nyc. Use when asked about NYC subway status, train times, bus routes, ferry schedules, transit delays, MTA service alerts, or "what's the next train to X".
npx skillsauth add ckorhonen/claude-skills subway-infoInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Real-time NYC transit information covering subway, bus, ferry, and commuter rail (LIRR/Metro-North). Covers all 496 subway stations, 16,000+ bus stops, NYC Ferry landings, and LIRR/Metro-North stations.
If subway-info CLI is available, prefer it over raw curl — it handles retries, auth, and outputs token-efficient text by default.
# From the mta-mcp repo
npm run build:cli
# Binary at ./dist/subway-info
# Or run directly
npm run cli -- arrivals --station 127
subway-info arrivals --station 127 --line 1 --direction N --limit 5
subway-info alerts --line A
subway-info stations --query "times square"
subway-info trip --from 127 --to 631
subway-info status --line L
subway-info bus arrivals --stop 402940 --route M1
subway-info bus alerts --route M1
subway-info bus stops --query "5th ave" --borough Manhattan
subway-info bus route --route M1
subway-info ferry arrivals --landing <id>
subway-info ferry alerts
subway-info ferry landings --query "wall street"
subway-info ferry routes
subway-info rail departures --station <id> --system LIRR
subway-info rail alerts --system MNR
subway-info rail stations --query "penn" --system LIRR
subway-info rail station --station <id>
--json Print raw JSON instead of compact text
--api-key <key> Override $SUBWAY_INFO_API_KEY
--base-url <url> Override https://subwayinfo.nyc
All data endpoints use POST with JSON body. Base URL: https://subwayinfo.nyc
| Tier | Requests/Min | Authentication |
|------|--------------|----------------|
| Anonymous | 10 | None (IP-based) |
| Free | 60 | X-API-Key header |
| Standard | 300 | X-API-Key header |
| Premium | 1000 | X-API-Key header |
curl -s -X POST https://subwayinfo.nyc/api/arrivals \
-H "Content-Type: application/json" \
-d '{"station_id": "127", "line": "1", "direction": "N", "limit": 5}'
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| station_id | string | Yes | Station ID (use search to find) |
| line | string | No | Filter by line (e.g., "1", "A", "F") |
| direction | "N" | "S" | No | N=uptown/Bronx, S=downtown/Brooklyn |
| limit | number | No | Max arrivals (default: 10) |
curl -s -X POST https://subwayinfo.nyc/api/alerts \
-H "Content-Type: application/json" \
-d '{"line": "A"}'
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| line | string | No | Filter by line |
| alert_type | string | No | Filter by type (e.g., "Delays", "Planned Work") |
curl -s -X POST https://subwayinfo.nyc/api/stations \
-H "Content-Type: application/json" \
-d '{"query": "union square"}'
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| query | string | No | Station name search |
| line | string | No | Filter by line |
| limit | number | No | Max results (default: 10) |
curl -s -X POST https://subwayinfo.nyc/api/station \
-H "Content-Type: application/json" \
-d '{"station_id": "127"}'
curl -s -X POST https://subwayinfo.nyc/api/trip \
-H "Content-Type: application/json" \
-d '{"origin_station_id": "127", "destination_station_id": "631"}'
POST /api/bus/arrivals {"stop_id": "402940", "route": "M1", "limit": 5}
POST /api/bus/alerts {"route": "M1"}
POST /api/bus/stops {"query": "5th ave", "borough": "Manhattan"}
POST /api/bus/route {"route_id": "M1"}
POST /api/ferry/arrivals {"landing_id": "<id>", "route": "<route>"}
POST /api/ferry/alerts {"route": "<route>"}
POST /api/ferry/landings {"query": "wall street"}
POST /api/ferry/routes {}
POST /api/rail/departures {"station_id": "<id>", "system": "LIRR"}
POST /api/rail/alerts {"system": "MNR", "branch": "Hudson"}
POST /api/rail/stations {"query": "penn", "system": "LIRR"}
POST /api/rail/station {"station_id": "<id>"}
GET /health
| Station | ID | Lines | |---------|-----|-------| | Times Sq-42 St | 127 | 1, 2, 3, 7, N, Q, R, W, S | | Grand Central-42 St | 631 | 4, 5, 6, 7, S | | 14 St-Union Sq | L03 | L, 4, 5, 6, N, Q, R, W | | 34 St-Penn Station | A28 | A, C, E, 1, 2, 3 | | Fulton St | A38 | A, C, J, Z, 2, 3, 4, 5 | | Atlantic Av-Barclays Ctr | D24 | B, D, N, Q, R, 2, 3, 4, 5 |
Use subway-info stations --query "..." or /api/stations to find any station ID.
./scripts/arrivals.sh "times square" # Search by name
./scripts/arrivals.sh 127 1 N 5 # By ID with filters
./scripts/alerts.sh A # A train alerts
./scripts/trip.sh "times square" "grand central"
./scripts/status.sh L # L train status
| Status Code | Meaning | Action | |-------------|---------|--------| | 400 | Bad Request | Check required parameters | | 401 | Unauthorized | Invalid API key | | 429 | Rate Limited | Reduce request frequency or add API key | | 500 | Server Error | Retry with backoff |
line parameter for cleaner resultsSUBWAY_INFO_API_KEY for higher limitsTransit data is notoriously tricky. These are real failure modes that catch agents and users regularly.
The Problem: Arrival times shown may be cached or stale, especially during heavy traffic or service disruptions.
Why It Happens:
How to Detect & Fix:
# Check data freshness timestamp in response
curl -s -X POST https://subwayinfo.nyc/api/arrivals \
-H "Content-Type: application/json" \
-d '{"station_id": "127"}' | jq '.data_timestamp'
# If timestamp is >10 seconds old, force fresh fetch (use new API key or IP to bypass cache)
# Or: add ?nocache=true parameter if API supports it
When This Matters: Real-time trip planning, urgent commutes, tight connections Solution: Always fetch fresh data for time-critical decisions; don't rely on stale responses
The Problem: A train arrives in 20 minutes, but there's a planned service change, track work, or delay that the arrivals endpoint didn't surface.
Why It Happens:
/api/arrivals shows train predictions but doesn't include active alertsHow to Detect & Fix:
# Always check alerts separately from arrivals
curl -s -X POST https://subwayinfo.nyc/api/alerts \
-H "Content-Type: application/json" \
-d '{"line": "1"}' | jq '.[] | select(.type | contains("Planned"))'
# Cross-reference: if planning a trip at 11 PM on Saturday, check alerts first
# Many lines have weekend/night track work that predictions don't catch early
When This Matters: Weekend trips, night commutes, planned service disruptions Solution: Always fetch alerts before planning a trip, not after seeing arrivals
The Problem: "Times Square" has 4+ stations; searching gives ambiguous results; agent picks wrong one.
Why It Happens:
How to Detect & Fix:
# Search returns ambiguous results
curl -s -X POST https://subwayinfo.nyc/api/stations \
-H "Content-Type: application/json" \
-d '{"query": "times square"}' | jq '.results[] | {name, id, lines}'
# Output: Multiple results with overlapping names
# Solution: Filter by line before picking station ID
curl -s -X POST https://subwayinfo.nyc/api/stations \
-H "Content-Type: application/json" \
-d '{"query": "times square", "line": "1"}' | jq '.results[0].id'
When This Matters: Multi-line stations, tourist areas, connections between systems Solution: Always filter searches by line if user specifies it; confirm station ID before using it
Reference Table (Ambiguous Stations): | Location | Station Names | Lines | IDs | |----------|---------------|-------|-----| | Times Square Area | 42 St-Times Sq, 42 St-Port Authority, 42 St-GCT | 1/2/3 vs A/C/E vs 4/5/6/7 | 127 vs A09 vs 631 | | 14th Street | 14 St-Union Sq, 14 St-A/C, 14 St-F/M, 14 St-1/2/3, 14 St-L | Multiple | Multiple | | Penn Station Area | 34 St-Penn, 34 St-Herald Sq, 34 St-GCT | A/C/E vs B/D/F/M vs 1/2/3 | A28 vs B24 vs 307 |
The Problem: Schedule shows 5:30 PM arrival, but user is in Pacific time and misreads it as local 2:30 PM.
Why It Happens:
How to Detect & Fix:
# API returns times in ET (no TZ field)
curl -s -X POST https://subwayinfo.nyc/api/arrivals \
-H "Content-Type: application/json" \
-d '{"station_id": "127"}' | jq '.arrivals[0].arrival_time'
# Always convert to user's timezone before displaying
# JavaScript example:
const etTime = new Date(arrivalTime); // Interpreted as ET
const userTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
const userTime = etTime.toLocaleString('en-US', { timeZone: userTz });
When This Matters: Remote users, cross-country travel planning, scheduling meetings Solution: Always note that times are in Eastern Time; convert to user's local time when displaying
The Problem: Monday's train schedule is different from Saturday's; predictions assume weekday service but it's actually a holiday.
Why It Happens:
How to Detect & Fix:
// Check if today is a holiday or weekend
const today = new Date();
const dayOfWeek = today.getDay(); // 0 = Sunday, 6 = Saturday
const holidays = ["2026-01-01", "2026-07-04", "2026-12-25"]; // NYD, July 4, Xmas
const isSpecialDay = dayOfWeek === 0 || dayOfWeek === 6 || holidays.includes(today.toISOString().split('T')[0]);
if (isSpecialDay) {
console.warn("Running reduced/modified schedule today. Arrivals may not reflect typical service.");
// Fetch fresh alerts to see if specific lines have changes
}
When This Matters: Weekend trips, holiday travel, late-night commutes Solution: Check day-of-week and holiday calendar; verify alerts if weekend/holiday
The Problem: Old code uses /arrivals endpoint but MTA deprecated it in favor of a new schema that returns different field names.
Why It Happens:
arrival_time → estimated_arrival_time)How to Detect & Fix:
# Check API version in response headers
curl -s -i -X POST https://subwayinfo.nyc/api/arrivals \
-H "Content-Type: application/json" \
-d '{"station_id": "127"}' | grep -i 'api-version'
# If response structure is unexpected, check API docs at subwayinfo.nyc/docs
# Parse defensively: use `.get()` and provide defaults
const arrival_time = response.arrivals?.[0]?.estimated_arrival_time
?? response.arrivals?.[0]?.arrival_time
?? "Unknown";
When This Matters: Long-running services, production dashboards, archival code Solution: Monitor API version headers; test after MTA updates; use defensive parsing
The Problem: You're on the Free tier (60 req/min), but a popular line gets heavy traffic and you blast 200 requests in 10 seconds checking multiple stations.
Why It Happens:
How to Detect & Fix:
# Monitor for 429 responses
curl -s -X POST https://subwayinfo.nyc/api/arrivals \
-H "Content-Type: application/json" \
-d '{"station_id": "127"}' \
-w "\nHTTP Status: %{http_code}\n"
# If 429: Implement exponential backoff
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url);
if (response.status !== 429) return response;
const retryAfter = response.headers.get('Retry-After') || (2 ** i);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
}
throw new Error("Rate limited after retries");
}
When This Matters: Dashboards, multi-station queries, production load spikes Solution: Serialize requests or batch them; monitor rate limit headers; use API key for higher quotas
documentation
Create or expand an Idea.md / IDEA.md file from a rough description, existing repo, conversation history, notes, or other early-stage product inputs. Use when the user asks to "write an Idea.md", "turn this into an idea file", "capture this product idea", "expand this concept", or wants a repo-grounded concept brief before validation, PRD, or implementation work.
development
Write structured implementation plans from specs or requirements before touching code. Use when given a spec, requirements doc, or feature description, when user says "plan this out", "write a plan for", "how should we implement", or before starting any multi-step coding task.
testing
Expert guidance for video editing with ffmpeg, encoding best practices, and quality optimization. Use when working with video files, transcoding, remuxing, encoding settings, color spaces, or troubleshooting video quality issues.
development
Opinionated constraints for building better interfaces with agents. Use when building UI components, implementing animations, designing layouts, reviewing frontend accessibility, or working with Tailwind CSS, motion/react, or accessible primitives like Radix/Base UI.