DEV Community

Cover image for 404ping: From "It Works" to "It's Actually Really Good" ๐Ÿš€
Nimesh Thakur
Nimesh Thakur

Posted on

404ping: From "It Works" to "It's Actually Really Good" ๐Ÿš€

How I spent 3 months turning a simple CLI into something I'm genuinely proud of


Where We Left Off

Back in November, I wrote about 404ping v2 โ€” my lightweight API testing CLI that was basically "curl with memory."

It could:

  • Send HTTP requests (obviously)
  • Store variables globally or per-collection
  • Save requests to collections
  • Run saved requests with overrides
  • Show different output modes (headers, connection info, TLS details)

People actually used it. Bug hunters messaged me. Developers starred it on GitHub. I was happy.

But then I kept using it. And every time I reached for 404ping in my actual workflow, I'd hit a wall and think: "Why can't it just...?"

So I spent the next three months answering that question.


The "Why Can't It Just..." Moments

Moment 1: "Why can't it just SAVE the damn request while I'm testing it?"

The old way:

# Test the request first
404ping request {{host}}/api/login -X POST -d '{"email":"test@test.com"}'

# Looks good? Now save it (copy-paste hell)
404ping collection save myapp login -X POST {{host}}/api/login -d '{"email":"test@test.com"}'
Enter fullscreen mode Exit fullscreen mode

I was literally typing the same request twice. That's stupid.

What I built: Quick Save

# Test AND save in one command
404ping request {{host}}/api/login -X POST \
  -d '{"email":"test@test.com"}' \
  --save myapp.login
Enter fullscreen mode Exit fullscreen mode

One flag. Done. The request you just tested is now saved. No copy-paste. No context switching.

This single feature changed how I use 404ping. Now I just add --save to everything and build my collection as I explore APIs.


Moment 2: "Why can't it just validate the response automatically?"

I was constantly doing this:

404ping run myapp:login
# Check status code manually
# Check if token exists manually
# Grep for specific values manually
Enter fullscreen mode Exit fullscreen mode

What I built: Inline Assertions

404ping request https://api.example.com/users/42 \
  --assert "status=200" \
  --assert "json.userId=42" \
  --assert "json.role=admin"
Enter fullscreen mode Exit fullscreen mode

Now 404ping tells me if something's wrong. Perfect for smoke tests or CI/CD:

404ping run myapp:login --assert "status=200" --assert "json.token!=null"
# Exit code 0 if passes, 1 if fails
Enter fullscreen mode Exit fullscreen mode

Moment 3: "Why can't it just use the token from the previous request?"

This was the big one. The workflow that made me want to rebuild everything:

Before (the painful way):

# 1. Login
404ping run myapp:login
# Response: {"token": "eyJhbGc..."}

# 2. Manually copy token
404ping set token:eyJhbGc...

# 3. Use token in next request
404ping run myapp:get-profile
Enter fullscreen mode Exit fullscreen mode

Every. Single. Time. Manual token copying. This is 2025, why am I doing this?

What I built: Request Chaining & Variable Extraction

404ping sequence auth:login users:me \
  --extract token=json.token \
  --bearer {{runtime.token}} \
  --assert "status=200"
Enter fullscreen mode Exit fullscreen mode

What happens:

  1. Runs auth:login
  2. Extracts json.token from response โ†’ stores as {{runtime.token}}
  3. Runs users:me with Authorization: Bearer {{runtime.token}} header
  4. Asserts both succeeded

No manual copying. No intermediate steps. Just declare the flow and it works.

Real-world example:

404ping sequence \
  auth:login \
  users:create \
  posts:create \
  posts:list \
  --extract token=json.auth.token \
  --extract userId=json.user.id \
  --bearer {{runtime.token}} \
  --assert "status=200"
Enter fullscreen mode Exit fullscreen mode

This is what made 404ping actually useful for real workflows.


Moment 4: "Why can't it just parse the JSON for me?"

I was piping everything to jq:

404ping run myapp:users | jq '.data.users[].email'
Enter fullscreen mode Exit fullscreen mode

What I built: Built-in JSON Filtering

404ping request https://api.example.com/posts/1 \
  --filter "json.comments[*].{author: author.name, text: body}"
Enter fullscreen mode Exit fullscreen mode

And you can extract values directly into variables:

404ping request https://api.example.com/auth \
  --extract token=json.access_token \
  --extract userId=json.user.id \
  --extract trace=header.X-Trace-Id
Enter fullscreen mode Exit fullscreen mode

Extracted values go into {{runtime.*}} and can be used in chained requests.


Moment 5: "Why can't it just switch environments easily?"

Testing across staging/production was painful:

Before:

404ping set host:https://staging-api.myapp.com
# Test stuff
404ping set host:https://api.myapp.com
# Test stuff
Enter fullscreen mode Exit fullscreen mode

What I built: Environment Profiles

Create .env.staging:

BASE_URL=https://staging-api.myapp.com
API_KEY=staging_key_123
TIMEOUT=10000
Enter fullscreen mode Exit fullscreen mode

Create .env.production:

BASE_URL=https://api.myapp.com
API_KEY=prod_key_456
TIMEOUT=5000
Enter fullscreen mode Exit fullscreen mode

Now switch with one flag:

404ping --env staging run myapp:users
404ping --env production run myapp:users
Enter fullscreen mode Exit fullscreen mode

You can even layer multiple env files:

404ping --env staging --env-file secrets/.overrides.env run billing:charge
Enter fullscreen mode Exit fullscreen mode

Moment 6: "Why can't it just work with Postman collections?"

My team uses Postman. I wanted to use 404ping. We were stuck in different worlds.

What I built: Postman Import/Export

# Import their collection
404ping postman import team-api.postman.json -c teamapi

# Work in terminal with 404ping's speed
404ping run teamapi:login
404ping run teamapi:create-user --assert "status=201"

# Export back to share updates
404ping postman export teamapi -o team-api.postman.json
Enter fullscreen mode Exit fullscreen mode

Now I can:

  • Import team's Postman collections
  • Work faster in terminal
  • Export when I need to share
  • Best of both worlds

Moment 7: "Why can't it just run scripts before/after requests?"

Sometimes you need custom logic โ€” dynamic signatures, complex validation, custom logging.

What I built: Hooks & Scripts

404ping request https://api.example.com/orders \
  --pre-script @scripts/add-signature.js \
  --post-script "console.log('Order created:', response.data.orderId)"
Enter fullscreen mode Exit fullscreen mode

Pre-scripts can modify the request before it's sent.
Post-scripts can validate, log, or extract data from responses.


Moment 8: "Why can't it just benchmark this endpoint?"

Before: Install ab or wrk, figure out syntax, parse output...

Now:

404ping request https://api.example.com/heavy \
  --benchmark 10
Enter fullscreen mode Exit fullscreen mode

Runs it 10 times and shows:

  • Min/Median/P95 latency
  • Success rate
  • Average response size

Perfect for quick performance checks.


What 404ping Actually Is Now

Let me show you a real workflow โ€” the kind I run every day as a bug bounty hunter:

Scenario: Testing a new API target

# 1. Setup environment
404ping set host:https://api.target.com

# 2. Test and save login endpoint
404ping request {{host}}/auth/login -X POST \
  -d '{"email":"test@test.com","password":"password"}' \
  --save target.login \
  --assert "status=200"

# 3. Run a full authenticated flow
404ping sequence target:login \
  https://{{host}}/users/me \
  https://{{host}}/admin/dashboard \
  --extract token=json.access_token \
  --bearer {{runtime.token}} \
  --assert "status=200"

# 4. Found IDOR? Test multiple user IDs quickly
404ping run target:get-user -u "{{host}}/users/123" --assert "status=403"
404ping run target:get-user -u "{{host}}/users/456" --assert "status=403"
404ping run target:get-user -u "{{host}}/users/789" --assert "status=200"

# 5. Check TLS configuration
404ping request {{host}}/secure --tls

# 6. Benchmark for timing attacks
404ping request {{host}}/auth/check -d '{"user":"admin"}' --benchmark 100
Enter fullscreen mode Exit fullscreen mode

That's a complete security test workflow in 6 commands. No Postman. No bash scripts. Just 404ping.


The Complete Feature List (What You Can Do Now)

Core Functionality:

  • โœ… HTTP requests (all methods, headers, bodies)
  • โœ… Global and collection-scoped variables
  • โœ… Collections for organizing requests
  • โœ… Quick save with --save flag
  • โœ… Run saved requests with overrides

New Superpowers:

  • ๐Ÿงช Assertions โ€” Validate responses inline (--assert)
  • ๐Ÿ”— Request Chaining โ€” Run sequences with variable extraction (sequence)
  • ๐Ÿ“Š JSON Filtering โ€” Parse responses without jq (--filter)
  • ๐Ÿ’พ Variable Extraction โ€” Capture values from responses (--extract)
  • ๐ŸŒ Environment Profiles โ€” Switch environments with --env
  • ๐Ÿ”„ Postman Import/Export โ€” Work with team collections
  • ๐Ÿช Hooks & Scripts โ€” Pre/post request JavaScript (--pre-script, --post-script)
  • โšก Benchmarking โ€” Performance testing built-in (--benchmark)
  • ๐Ÿ” Advanced Output Modes โ€” --info, --connection, --tls, --debug

Security & Developer Experience:

  • ๐Ÿ›ก๏ธ SSL verification by default (explicit --insecure to bypass)
  • ๐Ÿ“ Comprehensive error messages that actually help
  • ๐Ÿ” TLS/SSL certificate inspection
  • ๐ŸŒ Connection debugging
  • โœ… Input validation and path traversal protection

How to Use It (Quick Start)

Installation

git clone https://github.com/toklas495/404ping.git
cd 404ping
npm install
npm run build
Enter fullscreen mode Exit fullscreen mode

Basic Usage

# Simple request
404ping request https://api.example.com/users

# POST with data
404ping request https://api.example.com/login -X POST \
  -d '{"email":"test@test.com","password":"secret"}'

# Save while testing
404ping request {{host}}/api/login -X POST \
  -d '{"email":"{{email}}"}' \
  --save myapp.login

# Run saved request
404ping run myapp:login

# Chain requests with token extraction
404ping sequence myapp:login myapp:profile \
  --extract token=json.token \
  --bearer {{runtime.token}}

# Switch environments
404ping --env staging run myapp:users

# Import Postman collection
404ping postman import api.postman.json -c myproject
Enter fullscreen mode Exit fullscreen mode

Real Workflow

# 1. Setup
404ping set host:https://api.myapp.com

# 2. Test & save requests as you explore
404ping request {{host}}/auth/login -X POST \
  -d '{"email":"dev@myapp.com","password":"test123"}' \
  --save myapp.login \
  --assert "status=200"

# 3. Run authenticated flows
404ping sequence myapp:login myapp:get-profile myapp:list-posts \
  --extract token=json.access_token \
  --bearer {{runtime.token}} \
  --assert "status=200"

# 4. Debug issues
404ping run myapp:slow-endpoint --info --benchmark 5

# 5. Check security
404ping request {{host}}/secure --tls
Enter fullscreen mode Exit fullscreen mode

The Numbers

Since v2:

Metric Change
Features +8 major features
Commands request, run, set, vars โ†’ Added sequence, postman
Flags +12 new flags (--assert, --extract, --filter, --benchmark, --env, etc.)
GitHub Stars 6 โ†’ Growing
Use Cases Testing โ†’ Testing + CI/CD + Security Audits + Performance Testing

What I Learned

1. Listen to your own frustration

Every feature came from me getting annoyed using my own tool. The best product feedback is your own pain.

2. "Just one more flag" compounds quickly

--save seemed small. But it changed everything. Then --extract. Then --assert. Small conveniences add up to workflows you couldn't imagine before.

3. Interoperability > Lock-in

Adding Postman import/export wasn't admitting defeat. It made 404ping more valuable. Now teams can use both.

4. Security should be the default

SSL verification on by default. Explicit --insecure flag. If you want to be insecure, you have to type it out.

5. Documentation is code

Every new feature came with examples in the README. If users can't figure it out in 30 seconds, it might as well not exist.


What's Next

I'm not done. Here's what I'm thinking about:

Maybe Soon:

  • ๐Ÿ”Œ Plugin system โ€” Let people extend 404ping however they want
  • ๐Ÿ“Š Request history โ€” See what you ran recently, replay it
  • ๐Ÿงฌ Response diffing โ€” Compare staging vs production responses
  • ๐ŸŽฏ Mock server โ€” Spin up mocks from saved responses

Ideas I'm Exploring:

  • GraphQL support
  • gRPC testing
  • WebSocket connections
  • Request templating

But I'm not rushing. I'd rather ship features that work perfectly than half-bake 10 things.


Try It

If you test APIs and you're tired of:

  • Postman eating 600MB of RAM
  • Copying tokens between requests manually
  • Writing bash scripts to chain API calls
  • Remembering curl flags

Give 404ping a shot:

git clone https://github.com/toklas495/404ping.git
cd 404ping
npm install && npm run build
404ping --help
Enter fullscreen mode Exit fullscreen mode

โญ GitHub: github.com/toklas495/404ping


Final Thoughts

v2 was "curl with memory."

Now? 404ping is actually good. It's the tool I reach for first. It's fast, it's powerful, and it gets out of my way.

If you try it and hate something, tell me. If you love it, tell your friends.

And if you have ideas? Open an issue. I'm always looking for the next "why can't it just..." moment.


Happy hunting ๐ŸŽฏ

โ€” Nimesh (toklas495)


P.S. โ€” If you're using 404ping for bug bounties or in production, I'd genuinely love to hear your story. Success stories fuel late-night coding sessions.

Top comments (0)