Day 23: Feature requests from the pilot
Day 23 of 30. Today we respond to real user feedback.
Day 23 of 30. Today we respond to real user feedback.
Our pilot has been going on now for 3 days. They’ve captured 847 screenshots and have feedback. This is absolutely great, and the moment we’ve been waiting for. These are real users telling us what they actually need.
Now, of course we’re not going to say yes to everything, and we don’t want to turn this system into a solution which is only suitable for one customer. But getting direct feedback is amazing, and certainly something we’ll take on board.
Interactive feature request board
See how we prioritized and addressed the feature requests:
"Wow, that was fast! We'll test these today. The thumbnail option alone will save us a lot of bandwidth. Getting closer to switching over fully."
The feedback
We’ve received a detailed email (slightly changed to fit the format of our blog):
The API works well for basic cases. A few things we’d love:
Faster response for repeated URLs - We often screenshot the same pages. Would be nice if there was some caching.
Custom viewport sizes - We need 1200x630 for Open Graph images specifically.
Wait for specific element - Some of our pages load content dynamically. The “network idle” isn’t enough.
Thumbnail option - We don’t always need full-resolution. A smaller image would be faster.
Bulk endpoint - We sometimes need to capture 10-20 URLs at once. Individual calls incur overhead, and a faster solution would be appreciated.
Fair feedback! Let’s see what we can ship quickly.
Feature 1: Custom viewport sizes
This is easy. We already support width/height, but we weren’t advertising it well.
Updated the API:
{
"url": "https://example.com",
"width": 1200,
"height": 630
}
This works with any device type. If you specify width/height, they override the device defaults.
Time to implement: Already done, just needed docs update.
Feature 2: Wait for selector
This is genuinely useful. Instead of waiting for network idle, wait for a specific element:
data class ScreenshotRequest(
val url: String,
val waitForSelector: String? = null, // NEW
// ... other fields
)
Implementation:
if (request.waitForSelector != null) {
page.waitForSelector(request.waitForSelector,
Page.WaitForSelectorOptions()
.setTimeout(10000.0)
.setState(WaitForSelectorState.VISIBLE)
)
}
API usage:
{
"url": "https://example.com",
"wait_for_selector": ".main-content"
}
Now you can wait for your SPA to render specific content.
Time to implement: 30 minutes, plus time for testing, documentation and deployment.
Feature 3: Thumbnail/resize option
Returning a 3MB full-resolution image when you need a 100KB thumbnail is a little wasteful. To address this, we’ve added a resize option:
data class ScreenshotRequest(
// ...
val resize: ResizeOptions? = null
)
data class ResizeOptions(
val width: Int,
val height: Int? = null, // Maintain aspect ratio if null
val fit: String = "cover" // cover, contain, fill
)
API usage:
{
"url": "https://example.com",
"resize": {
"width": 400
}
}
This now returns a 400px wide thumbnail. The response size drops from ~1MB to ~50KB.
Time to implement: 1 hour.
Feature 4: Caching
This is more complex. (There are 2 things hard in programming…)
- Cache key: Do we use the URL only? Or the URL + screenshot options, which is more likely.
- Cache duration: How long? A day? A week? A year?
- Cache invalidation: How do users bypass cache?
- Storage: Where do cached images live?
For now, we’re adding a cache parameter that returns cached results if available:
{
"url": "https://example.com",
"cache_ttl": 3600
}
If we captured this URL in the last hour, we return the cached result. Otherwise, we capture a fresh screenshot.
Simple implementation:
fun findCachedScreenshot(url: String, options: ScreenshotOptions, ttl: Int): Screenshot? {
val cutoff = Instant.now().minusSeconds(ttl.toLong())
return screenshotRepository.findByUrlAndOptionsAfter(url, options.hashCode(), cutoff)
}
Time to implement: 60 minutes, but more work will be needed in this space
Feature 5: Bulk endpoint (deferred)
Our users want to submit 10-20 URLs at once. But why stop at 20, and not go for 200, or 2000, or more? We see currently several options:
A. Sync bulk: We accept an array of URLs, process them all, and then return them all. There is a potential challenge in this, which is that it could take 60+ seconds depending on the URLs.
B. Async bulk: We also accept the array of URLs, but instead return a job ID. The client polls or uses a webhook. This is basically the async mode we designed on Day 3.
We’re deferring this for now, since it’s the trigger to build async mode properly. We told our client:
Bulk processing is on our roadmap. For now, it’s possible to parallelize on your end - our API handles concurrent requests fine. We’ll have a proper bulk endpoint in the next week.
Our client was okay with this, which is a great situation to be in!
Updated API documentation
We updated the docs with new parameters:
## Request Parameters
| Parameter | Type | Description |
|-------------------|---------|---------------------------|
| url | string | URL to capture (required) |
| device | string | desktop, mobile, tablet |
| width | integer | Custom viewport width |
| height | integer | Custom viewport height |
| full_page | boolean | Capture entire page |
| format | string | png or jpeg |
| wait_for_selector | string | CSS selector to wait for |
| resize.width | integer | Resize output width |
| resize.height | integer | Resize output height |
| cache_ttl | integer | Cache duration in seconds |
Client response
We emailed our client the updates:
Good news! We shipped three of your requests:
- Custom viewport: Use width/height params
- Wait for selector: Use wait_for_selector param
- Thumbnails: Use resize param
Caching is live too. Set cache_ttl for repeated URLs.
Bulk endpoint is coming - for now, parallel requests work well.
Their reply:
Wow, that was fast! We’ll test these today. The thumbnail option alone will save us a lot of bandwidth. Getting closer to switching over fully.
This is the feedback loop working. We’re dealing with real users, real problems, and real solutions.
What we built today
- Custom viewport documentation
- Wait for selector feature
- Image resizing/thumbnails
- Basic response caching
- Updated documentation
All shipped to production within one day.
Tomorrow: first paying customer?
On to day 24. Our client’s team has been testing for almost a week. Time to get our final feedback!
Book of the day
Inspired by Marty Cagan
Cagan runs the Silicon Valley Product Group and has worked with hundreds of product teams. This book is about building products customers actually want.
Key insight for today: the best product insights come from users, not brainstorming. Sarah’s email contained more actionable ideas than a week of us guessing what people want.
The chapter on continuous discovery - constantly talking to users and iterating - perfectly describes what we did today. Ship something, get feedback, improve, repeat.
Essential reading for anyone building products.