Inspiration
We used to scroll through Instagram and LinkedIn and wonder why our posts weren't getting engagement, while our competitors, and honestly even our friends, were pulling far more than us. Maybe we were lacking somewhere. Maybe there was some algorithm we just weren't understanding.
Sooner or later we figured it out. We weren't consistent.
And that's the frustrating part. These are small things. Everyone knows, somewhere in the back of their mind, exactly what they're lacking. They just forget. It becomes a blind spot. You can't see what's quietly degrading your own engagement, because you're too close to it.
So we wanted a system that could find those gaps. The exploitable gaps in our own posts, so we could fix them, and in our competitors' posts, so we could use their blind spots to our leverage.
What it does
You log a competitor's LinkedIn profile, and add whatever other platforms you want to track. Hit Spy Now, and SpyGlass reads through everything they posted in the last day and reports it straight into Slack.
Want to go deeper? Run the dossier. It searches their entire content history, decodes the exploitable gaps, and shows you the method they're actually running, so you can make your own content better and surpass theirs.
And it turns the lens around. It audits your account too, and tells you the truth about what's holding you back.
How we built it
It's a Slack app in Python running in Socket Mode, so there's no public webhook and nothing exposed to the internet. It's deployed on Render, holding a persistent socket to Slack.
Slack does everything. Slash commands, buttons, modals, the Assistant pane. No dashboard, no second tab. Just the bot, where you already work.
- Apify and Firecrawl scrape competitor posts across LinkedIn, Instagram and X, and pick up new articles from their websites.
- Supabase is the database. Competitors, their accounts, every post, and engagement over time.
- MiniMax M3 via OpenRouter is the model. One tight, single shot call per analysis instead of a chatty agent loop. Fast and predictable.
But the decision that actually shaped everything was about honesty. Every number is computed in Python, from real rows in the database. The model never counts. It only interprets. Because a model handed a pile of scraped posts will invent statistics if you let it.
Challenges we ran into
We deleted our flashiest feature. I'd built a "predict what they'll post next", not literally predicting their post, but predicting which algorithm they were chasing. The AI was making things up. And eventually I had to admit the obvious. You can't really predict that. So I deleted it.
Then we deleted another one. I built a growth over time chart to show competitors' week by week growth. It was drawing confident lines through noise. Cut.
Deleting features you've already finished is miserable. Shipping a tool that gives people wrong data is much worse.
Render kept killing our bot. The free tier spins a service down after 15 minutes, which silently kills the Slack socket. So I added a keep alive that pings every 5 minutes. Except the pinger sends a HEAD request and my health endpoint only answered GET, so it returned a 501 and I thought the whole deploy was broken. Once it accepted HEAD, Render stayed up 24/7.
Scraped posts break JSON. Real posts are full of quotes and line breaks. When the model embeds that text into a JSON string it doesn't always escape it, and the parser dies with "Expecting ',' delimiter". The insulting part is that I'd already written repair logic for exactly this, and a bug meant it could never actually run. Now it escalates properly. Strict parse, then lenient, then structural repair.
LinkedIn gave us comments disguised as posts. The scraper returns comments and posts in the same list. So our early runs saved strangers' comments as competitor posts and analysed them, which quietly corrupted every engagement number downstream. We had to split them apart.
Also, the apify and firecrawl data, which are coming as JSON, sometimes break JSON, like realboost with a full of codes and line breaks. Sometimes it breaks the JSON, and the model that embeds the text into a JSON string doesn't always escape it. The parser dies on expecting something, or worse. I had written the same exact logic for exactly this, and a big min it could never run, so now finally it was fixed, thanks as well.
Accomplishments that we're proud of
That we built something true. SpyGlass doesn't make things up. It analyses real scraped content and gives you real numbers. In a space full of AI tools confidently inventing statistics, we built one that refuses to, and we verified it by hand, checking every claim in our own audit against the raw data. Every number was correct.
What we learned
The whole purpose of this tool was always to give out true numbers. And the moment a system invents one statistic, just one, the entire thing becomes worthless. Nobody can trust the other numbers after that.
We also learned that the honest version of a feature is almost always smaller than the impressive one. We shipped fewer features than we designed. The tool is better for it. Better to deliver less, and have it be real.
What's next for SpyGlass
-A weekly digest. Everything that moved across every competitor, in one message on Monday morning. -Breakout alerts. Right now you find out the next day. We want SpyGlass to tell you the moment a competitor's post takes off, while it's still worth reacting to. -Two sided comparison. Today it tells you why they're winning. Next it should tell you what they're bad at, the gaps you're already exploiting without realising it. -More platforms. YouTube and TikTok, so the whole content footprint is covered, not just the text first ones. -Team memory. Right now it audits an account. Next it should learn what worked for your team specifically, and stop suggesting things you've already tried.
Built With
- apify
- block-kit
- firecrawl
- minimax
- openrouter
- postgresql
- python
- python-docx
- render
- slack
- slack-api
- slack-bolt
- socket-mode
- supabase
Log in or sign up for Devpost to join the conversation.