Router API: When Shell Scripts Meet Real-World Problems
How I turned my router’s web interface into a REST API using nothing but shell scripts and Lambda layers
The Problem That Started It All
My router doesn’t have an API. Like most consumer routers, it has a web interface for humans, but no programmatic access for automation.
I wanted to:
- Check WAN status from my monitoring system
- Get connected device lists for IoT automation
- Reboot the router remotely when needed
- Integrate router data into my smart home setup
The traditional solutions:
- Flash custom firmware (risky, warranty-voiding)
- Use SNMP (if supported, often limited)
- Build a complex scraping application
- Buy an enterprise router (expensive overkill)
My solution: Turn the web interface into an API using shell scripts.
The Insight
Every router web interface is already an API - it’s just designed for browsers instead of programs.
When you click “Reboot” in the web interface, you’re making an HTTP POST request. When you view the status page, you’re making an HTTP GET request that returns HTML.
The only difference between a web interface and an API is the response format.
The Shell Script Approach
Instead of building a complex application, I used the tools that browsers use under the hood:
For HTTP requests: curl
For HTML parsing: htmlq
For JSON formatting: jq
For logic: Basic shell scripting
Level 2 Lambda Implementation
This is a perfect Level 2 use case - provided.al2023 runtime with pre-built layers.
Layers needed:
htmlqlayer for HTML parsing- Standard
jqandcurl(built-in)
Authentication & Session Management
#!/bin/bash
router_login() {
local router_ip="${ROUTER_IP:-192.168.1.1}"
local username="${ROUTER_USER:-admin}"
local password="$ROUTER_PASS"
# Get session cookie
curl -s -c /tmp/router_cookies \
-d "username=$username&password=$password" \
"http://$router_ip/login" > /dev/null
}
Device Information
router_info() {
router_login
local info=$(curl -s -b /tmp/router_cookies \
"http://$ROUTER_IP/status.html")
echo "$info" | htmlq '.device-info' --text | jq -R -s '
split("\n") | map(select(length > 0)) | {
model: .[0],
firmware: .[1],
uptime: .[2],
wan_ip: .[3]
}'
}
Connected Devices
router_stations() {
router_login
local stations=$(curl -s -b /tmp/router_cookies \
"http://$ROUTER_IP/wireless.html")
echo "$stations" | htmlq 'table.station-list tr' --text | \
tail -n +2 | jq -R -s '
split("\n") | map(select(length > 0)) | map(split("\t")) | map({
mac: .[0],
ip: .[1],
hostname: .[2],
signal: .[3]
})'
}
Remote Reboot
router_reboot() {
router_login
# Get CSRF token from reboot page
local token=$(curl -s -b /tmp/router_cookies \
"http://$ROUTER_IP/reboot.html" | \
htmlq 'input[name="token"]' --attribute value)
# Execute reboot
curl -s -b /tmp/router_cookies \
-d "action=reboot&token=$token" \
"http://$ROUTER_IP/reboot.html"
echo '{"status": "reboot_initiated", "message": "Router reboot started"}'
}
Why This Works So Well
1. Leverages Existing Infrastructure
- No firmware modifications needed
- Works with any router that has a web interface
- Uses the same endpoints the manufacturer designed
2. Handles Real-World Complexity
- Session management with cookies
- CSRF token handling
- Form-based authentication
- HTML parsing for data extraction
3. Perfect Tool Fit
curlhandles HTTP complexity (cookies, forms, sessions)htmlqparses HTML cleanly without regex nightmaresjqformats output as proper JSON- Shell scripts handle the logic flow
4. Production Ready
- Error handling through HTTP status codes
- Stateless execution (perfect for Lambda)
- Small footprint (~25MB with layers)
- Fast cold starts (~250ms)
The Broader Pattern
This approach works for any web interface:
IoT devices without APIs (cameras, smart switches, sensors) Legacy systems with only web interfaces Internal tools that need programmatic access Third-party services without official APIs
The pattern:
- Analyze the web interface HTTP traffic
- Identify the key endpoints and data flows
- Replicate the browser behavior with
curl - Parse responses with
htmlq - Format output with
jq
Real-World Impact
Before: Manual router management, no automation integration
After:
- Automated monitoring alerts when WAN goes down
- IoT devices can query network status
- Smart home can reboot router during maintenance windows
- Network usage tracking integrated into dashboards
All with ~50 lines of shell script and two Lambda layers.
The Level 2 Sweet Spot
This project perfectly demonstrates Level 2 capabilities:
Too simple for Level 3+: No need for custom containers or full AWS CLI
Too complex for Level 1: HTML parsing requires the htmlq layer
Just right for Level 2: Standard tools + one specialized layer
Performance characteristics:
- Memory: ~25MB (vs 128MB+ for Node.js equivalent)
- Cold start: ~250ms (vs 800ms+ for traditional approach)
- Package size: ~10MB (vs 50MB+ with dependencies)
- Maintenance: Zero dependencies to manage
The Philosophy
Don’t build what already exists. Adapt what’s already there.
The router manufacturer already built the interface. They already handled authentication, session management, and data presentation.
I just changed the output format.
This is infrastructure that gets out of your way - it lets you solve the actual problem (programmatic router access) without forcing you to rebuild everything from scratch.
Conclusion
Sometimes the best API is the one that’s already there.
Your router, your IoT devices, your legacy systems - they all have interfaces. Those interfaces are just APIs waiting for the right tools to unlock them.
Level 2 gives you those tools: curl for HTTP, htmlq for parsing, jq for formatting, shell for logic.
The result: Real-world automation with boring, reliable technology.
This is part of the cloudless philosophy - infrastructure that gets out of your way.