Resumen
Encuentras huecos de SaaS validados buscando en subreddits concretos frases de demanda como "hay alguna app que haga X" y "alguien conoce una herramienta para", extrayendo los hilos completos con sus comentarios y agrupando las quejas recurrentes en ideas de producto candidatas. La API de Reddit de Scavio te da dos endpoints para esto: /api/v1/reddit/search cuesta 1 crédito y devuelve los hilos que coinciden, y /api/v1/reddit/post cuesta 2 créditos porque incluye el árbol de comentarios, que es donde está la frustración real. Esto reemplaza un scraper casero frágil que se rompe cada vez que Reddit cambia su HTML o limita tu IP. Un límite honesto desde el principio: Reddit te da evidencia cualitativa de demanda, no un tamaño de mercado. Un hilo donde cuarenta personas se quejan de una función ausente de Shopify te dice que el dolor es real; no te dice cuánta gente pagaría. Valida el tamaño aparte con volumen de búsqueda antes de construir.
Desencadenador
Ejecución de descubrimiento bajo demanda, o cron semanal
Programación
Bajo demanda / semanal
Pasos del flujo de trabajo
Elige subreddits y frases de demanda
Elige los subreddits donde tus usuarios objetivo se quejan en voz alta. Para huecos de SaaS y apps eso suele ser r/SaaS, r/indiehackers, r/Entrepreneur, mas la comunidad de nicho a la que sirves, como r/shopify o r/ecommerce. Luego escribe una lista corta de frases de demanda que la gente escribe de verdad cuando quiere una herramienta que no existe: "hay alguna app", "alguien conoce una herramienta", "por que no hay", "ojala existiera una forma de". Estas frases son el corazon del flujo porque captan intencion, no solo tema. Manten la lista en cinco o seis; una lista ajustada hace predecible el gasto de creditos y baja el ruido.
Busca cada frase con /api/v1/reddit/search
Recorre tus frases de demanda con POST /api/v1/reddit/search y un cuerpo como {"query": frase, "sort": "relevance"}. Cada llamada cuesta 1 credito y devuelve los hilos que coinciden con su titulo, puntuacion, subreddit y permalink. Reune cada permalink en un conjunto para no traer el mismo post dos veces cuando dos frases caen en el mismo hilo. En esta fase solo pagas 1 credito por frase, asi que lanzar una red amplia aqui es barato; la parte cara viene despues.
Filtra los hilos que vale la pena abrir
No toda coincidencia merece una extraccion completa. Puntua los hilos candidatos por votos y numero de comentarios, y queda con los que superen un suelo que tu fijes, digamos cinco comentarios o una puntuacion mayor que diez. Un hilo con un comentario es una persona; uno con treinta es un patron. Este filtro importa porque el siguiente paso cuesta 2 creditos por hilo, asi que quieres gastarlos en hilos que de verdad lleven discusion.
Extrae hilos completos con /api/v1/reddit/post
Para cada hilo que pase el filtro, haz POST a /api/v1/reddit/post con la URL del hilo. Esta llamada cuesta 2 creditos, no 1, porque devuelve el cuerpo del post mas los comentarios en arbol. Los comentarios son la carga util aqui: ahi alguien nombra el flujo exacto que odia, la herramienta que probo y dejo, y el precio que pagaria. Reune el cuerpo del post y el texto aplanado de los comentarios en un bloque por hilo.
Extrae frases de dolor y agrupalas
Pasa el texto de comentarios reunido por una agrupacion simple. Saca las frases que nombran una capacidad ausente o una queja sobre una herramienta existente, y luego agrupalas por el problema que describen. Puedes hacerlo por palabras clave, o entregar el bloque a un LLM con un prompt que le pida agrupar las quejas en huecos de producto distintos. Cada grupo con varias voces independientes es una idea candidata; un grupo de uno es una opinion suelta.
Ordena candidatos y valida el tamano aparte
Ordena los grupos por cuantos hilos distintos y usuarios distintos los mencionan, no por total de comentarios, para que un hilo ruidoso no pese mas que cinco mas tranquilos. Los primeros grupos son tus huecos validados. Luego haz lo que Reddit no puede hacer por ti: lleva la frase nucleo de cada candidato a una herramienta de palabras clave o al endpoint de Google de Scavio y comprueba si existe volumen de busqueda real. Reddit prueba que el dolor es real; el volumen de busqueda te dice si suficiente gente busca la solucion como para que sea negocio.
Programalo y vigila la tendencia
Ejecuta el pipeline una vez para una foto fija, o ponlo en un cron semanal para ver que huecos crecen. Una queja que aparece en tres hilos este mes y nueve el que viene es un hueco que empeora, que es justo cuando una herramienta se vende. Scavio es una capa de datos y no programa nada por si misma, asi que el cron o tu agente controla el ritmo. Cachea entre ejecuciones los permalinks ya extraidos para no pagar 2 creditos dos veces por el mismo post.
Implementacion en Python
"""
Mine Reddit for validated SaaS gaps.
Search target subreddits for demand phrases (1 credit each),
fetch the meaty threads with their comments (2 credits each),
and collect the comment text for clustering into product ideas.
Reddit is qualitative demand evidence, not market sizing.
Validate volume separately before you build.
Requires: pip install requests
Set SCAVIO_API_KEY in your environment.
"""
import os
import time
import requests
API_KEY = os.environ["SCAVIO_API_KEY"]
BASE = "https://api.scavio.dev/api/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
# Phrases people type when they want a tool that does not exist yet.
DEMAND_PHRASES = [
"is there an app",
"anyone know a tool",
"why is there no",
"wish there was a way to",
"looking for a tool that",
]
# Only open threads with real discussion (each open costs 2 credits).
MIN_COMMENTS = 5
MIN_SCORE = 10
def post(path, body):
resp = requests.post(f"{BASE}{path}", headers=HEADERS, json=body, timeout=60)
resp.raise_for_status()
return resp.json()
def search_demand(phrase):
# 1 credit per call.
data = post("/reddit/search", {"query": phrase, "sort": "relevance"})
return data.get("threads", [])
def fetch_thread(url):
# 2 credits: returns the post body plus the comment tree.
return post("/reddit/post", {"url": url})
def collect_candidate_threads():
seen = {}
for phrase in DEMAND_PHRASES:
for t in search_demand(phrase):
url = t.get("url") or t.get("permalink")
if not url or url in seen:
continue
comments = t.get("num_comments", 0)
score = t.get("score", 0)
if comments >= MIN_COMMENTS and score >= MIN_SCORE:
seen[url] = t
time.sleep(0.5)
return list(seen.values())
def comment_text(thread):
blob = [thread.get("selftext", "")]
for c in thread.get("comments", []):
body = c.get("body")
if body:
blob.append(body)
for reply in c.get("replies", []):
rbody = reply.get("body")
if rbody:
blob.append(rbody)
return "\n".join(b for b in blob if b)
def mine_gaps():
candidates = collect_candidate_threads()
print(f"{len(candidates)} threads worth opening")
corpus = []
for t in candidates:
url = t.get("url") or t.get("permalink")
thread = fetch_thread(url)
corpus.append({
"title": t.get("title"),
"subreddit": t.get("subreddit"),
"score": t.get("score"),
"source_url": url,
"text": comment_text(thread),
})
time.sleep(0.5)
# Hand `corpus` to your clustering step (keyword buckets or an LLM)
# to group recurring complaints into candidate product gaps.
return corpus
if __name__ == "__main__":
gaps = mine_gaps()
for row in gaps:
print(f"[{row['subreddit']}] {row['title']} ({row['score']})")
print(row["source_url"])
Implementacion en JavaScript
/**
* Mine Reddit for validated SaaS gaps.
* Search target subreddits for demand phrases (1 credit each),
* fetch the meaty threads with their comments (2 credits each),
* and collect the comment text for clustering into product ideas.
*
* Reddit is qualitative demand evidence, not market sizing.
* Validate volume separately before you build.
*
* Node 18+ (built-in fetch). Set SCAVIO_API_KEY in your environment.
*/
const API_KEY = process.env.SCAVIO_API_KEY;
const BASE = "https://api.scavio.dev/api/v1";
const HEADERS = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
// Phrases people type when they want a tool that does not exist yet.
const DEMAND_PHRASES = [
"is there an app",
"anyone know a tool",
"why is there no",
"wish there was a way to",
"looking for a tool that",
];
// Only open threads with real discussion (each open costs 2 credits).
const MIN_COMMENTS = 5;
const MIN_SCORE = 10;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function post(path, body) {
const resp = await fetch(`${BASE}${path}`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(body),
});
if (!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`);
return resp.json();
}
// 1 credit per call.
async function searchDemand(phrase) {
const data = await post("/reddit/search", { query: phrase, sort: "relevance" });
return data.threads || [];
}
// 2 credits: returns the post body plus the comment tree.
async function fetchThread(url) {
return post("/reddit/post", { url });
}
async function collectCandidateThreads() {
const seen = new Map();
for (const phrase of DEMAND_PHRASES) {
const threads = await searchDemand(phrase);
for (const t of threads) {
const url = t.url || t.permalink;
if (!url || seen.has(url)) continue;
const comments = t.num_comments ?? 0;
const score = t.score ?? 0;
if (comments >= MIN_COMMENTS && score >= MIN_SCORE) seen.set(url, t);
}
await sleep(500);
}
return [...seen.values()];
}
function commentText(thread) {
const blob = [thread.selftext || ""];
for (const c of thread.comments || []) {
if (c.body) blob.push(c.body);
for (const reply of c.replies || []) {
if (reply.body) blob.push(reply.body);
}
}
return blob.filter(Boolean).join("\n");
}
async function mineGaps() {
const candidates = await collectCandidateThreads();
console.log(`${candidates.length} threads worth opening`);
const corpus = [];
for (const t of candidates) {
const url = t.url || t.permalink;
const thread = await fetchThread(url);
corpus.push({
title: t.title,
subreddit: t.subreddit,
score: t.score,
source_url: url,
text: commentText(thread),
});
await sleep(500);
}
// Hand `corpus` to your clustering step (keyword buckets or an LLM)
// to group recurring complaints into candidate product gaps.
return corpus;
}
mineGaps()
.then((gaps) => {
for (const row of gaps) {
console.log(`[${row.subreddit}] ${row.title} (${row.score})`);
console.log(row.source_url);
}
})
.catch((err) => {
console.error(err);
process.exit(1);
});