Use whenever the user wants to find, rank, or shortlist directories, listing sites, or launch platforms where they can submit a software product, SaaS, app,...
---
name: find-product-directories
description: Use whenever the user wants to find, rank, or shortlist directories, listing sites, or launch platforms where they can submit a software product, SaaS, app, tool, or startup — to get backlinks, referral traffic, and launch-day reach. Triggers on "where can I submit my SaaS for launch", "list of Product Hunt alternatives", "directories to get backlinks for our app", "high-authority software listing sites", "where should I list my startup", or "pull submission details for these 8 directory domains", even when described indirectly (we're launching next week, where do we post; how do we get backlinks by listing in directories). Drives the ServiceGraph API (api.servicegraph.co) — a catalog of 1,000+ product directories enriched with Domain Rating, backlinks, and organic traffic. Defer to find-mcp-directories for MCP-server listings and find-ai-directories for AI-tool / AI-agent / agent-skill listings. Skip finding a firm/agency to hire (use the find-* agency skills), finding products *inside* a directory ("recommend the best CRM"), building a directory site (do-the-work), local/business directories for brick-and-mortar listings, and link-building *services*.
version: "0.1.0"
metadata:
api_base: https://api.servicegraph.co
dataset_id: product_directory
niche: product_saas_launch
---
# find-product-directories
Drive the **ServiceGraph API** (`https://api.servicegraph.co`) to find and
rank **directories where a founder can submit a software product** — SaaS
review sites (Capterra, G2, SaaSHub), launch platforms (Product Hunt and its
alternatives), app/startup listings, and general software directories — via
the `product_directory` dataset. The catalog has **1,000+ directories**, each
enriched with **Domain Rating (`dr`)**, backlink counts, and **organic
traffic**, so you can rank by *real SEO value and reach* instead of guessing.
**This is a "where to launch / where to get backlinks" skill, not a "who to
hire" skill.** Each row is a *directory you submit to*, not a firm and not a
product. The payoff for the user is twofold: a **backlink** from a
high-authority domain (SEO) and **launch-day referral traffic**.
Any HTTP client works (curl, fetch, requests). Examples below use curl.
## Sibling skills — defer when the niche is specific
If the user's product is specifically one of these, defer to the dedicated
skill (its catalog slice and recipes are tuned for it):
- **MCP servers** ("where do I list my MCP server", "MCP directories") →
`find-mcp-directories`
- **AI tools / AI agents / agent skills** ("directories for my AI app",
"where to list my AI agent", "agent-skills directories") →
`find-ai-directories`
This skill is the **umbrella** for everything else founders launch — SaaS,
web apps, mobile apps, dev tools, startups in general — and is the right pick
when the ask spans niches or names none.
## MCP server (preferred for authed calls)
If your harness has the ServiceGraph MCP server loaded (recognizable by tool
names containing `servicegraph`), prefer those tools — the harness handles
credentials in its own sandbox via OAuth 2.1 + PKCE, so no token enters LLM
context. Otherwise use the REST flow below.
## API surface (dataset id: `product_directory`)
Every endpoint requires the bearer (`Authorization: Bearer vk_…`). There is
no anonymous tier.
| Endpoint | Cost | Use it for |
|---|---|---|
| `GET /v1/datasets/product_directory/fields[?include_values=1&q=]` | free | Filter-field catalog + DSL grammar. Call first per session. |
| `GET /v1/datasets/product_directory/values/:field[?q=&limit=]` | free | Enumerate values for one field (e.g. `industry`, `has`). |
| `GET /v1/datasets/product_directory/check?filter=…` | free | Validate a filter. Returns `{valid, normalized}` or `{valid:false, error}`. |
| `POST /v1/datasets/product_directory/translate-intent` | free | `{intent}` → LLM-generated DSL filter + sanity count. |
| `GET /v1/datasets/product_directory/search?filter=…&limit=&offset=` | free | Brief directory cards (incl. `dr`) + per-row `unlock` hint + `total`. |
| `GET /v1/datasets/product_directory/:apex` | free | Single row brief; **gated fields only if unlocked**. |
| `POST /v1/datasets/product_directory/unlocks` | **10 credits / row** | `{apexes:[...]}` ≤100. Atomic batch; 30-day TTL; `was_cached:true` rows free. |
| `GET /v1/me/credits` | free | Balance. |
**Cost model.** Discovery / validation / search / brief reads are free —
including the `dr` ranking signal. Unlocking a row costs **10 credits** and
lasts **30 days**; it reveals the **gated fields**: `editor_note` (the
hand-written submission note — *how* to submit and *whether the listing gives
a backlink*), `organic_traffic`, and `total_visits`. Re-fetching an unlocked
row within TTL is free.
## Auth
Tokens are `vk_*` API keys minted in the dashboard.
**Keep the token out of the LLM context** — never read `.env*` into your
context; dispatch every authed call through a shell wrapper.
1. **Just try the call** through a shell wrapper that sources `.env.local`:
```bash
( set -a; [ -f .env.local ] && . ./.env.local; set +a;
curl -sS -H "Authorization: Bearer $SERVICEGRAPH_API_KEY" \
'https://api.servicegraph.co/v1/datasets/product_directory/fields' )
```
2. **On `401 unauthorized`**, prompt the user (don't accept the key in chat):
> "Open **https://servicegraph.co/profile/api-keys**, sign in, click
> **Create key**, and copy the `vk_…` value. Then add
> `SERVICEGRAPH_API_KEY=vk_…` to `.env.local` here (or export it in your
> shell). Tell me when done. Please don't paste the key into chat."
3. **Retry** the same call after the user signals ready. A later 401 means the
key was rotated/revoked — re-prompt.
## Filter DSL
GitHub-search-style.
```
filter := orExpr
orExpr := andExpr ("OR" andExpr)*
andExpr := notExpr (("AND")? notExpr)* # whitespace = implicit AND
notExpr := ("NOT" | "-") notExpr | atom
atom := "(" filter ")" | predicate
predicate:= IDENT op valueOrList | bareword
op := ":" | "=" | ">=" | "<=" | ">" | "<"
```
**Four rules that bite:**
1. **AND binds tighter than OR.** `a OR b c` parses as `a OR (b AND c)`. Use parens.
2. **Comma list = OR within one predicate.** `industry:software_saas,fintech` = either.
3. **Negation is `-x` or `NOT x`.** Use `saas -crm`, not `keyword:saas,-crm`.
4. **Bareword = keyword search.** Free-text substring across the directory's
name, title, description, listed metadata, **and the dir-catalog `niche`
tag** (so `saas` matches dirs whose niche is "SaaS products"). Multiple
barewords AND. Wrap multi-word phrases in double quotes (`"product launch"`).
## Fields that matter here
This dataset ranks directories by *SEO value and reach*, so the numeric
signals are the point:
| Field | Free in brief? | Use it for |
|---|---|---|
| `dr` | **yes** | Ahrefs-style Domain Rating 0–100. The **primary authority filter** — a backlink from `dr>=70` is worth far more than from `dr<30`. Briefs are returned **sorted by `dr` descending**, so the strongest domains come first. |
| `referring_main_domains` | yes (when populated) | Distinct apexes linking in — "is this a real backlink source?". `>=100` decent, `>=1000` strong. |
| `backlinks` | yes (when populated) | Total inbound links; a sanity floor, long-tailed. |
| `organic_keywords` | yes (when populated) | Distinct US Google keywords ranked for. |
| `organic_traffic` | **gated** | Est. monthly US organic visits — the real-reach metric. Filterable even while hidden (`organic_traffic>=10000`), but the value shows only after unlock. |
| `total_visits` | **gated** | Est. total monthly visits. |
| `editor_note` | **gated** | Hand-written submission note: how to submit, and **whether you get a backlink**. |
| `industry` | yes | High-level vertical of the directory (e.g. `software_saas`, `fintech`, `design_creative`). A coarse refiner; keyword on `niche` is usually sharper. |
| `has` | yes | Presence of a structured field / third-party listing (`has:pricing`, `has:g2`, …). |
Because `dr` is free and briefs are pre-sorted by it, **you can rank a
shortlist by authority without spending a single credit** — unlock only the
ones the user wants submission instructions for.
## Identifying rows — `apex`
Directories are keyed by **apex domain** (`producthunt.com`, not
`www.producthunt.com/posts`). Strip user-supplied URLs to the apex before
calling `:apex` endpoints or building unlock batches.
## Recipes
### A. Launch platforms (Product Hunt alternatives)
User: *"Where can I launch my SaaS next week besides Product Hunt?"*
```
GET /v1/datasets/product_directory/search?filter=launch&limit=15
# → cards sorted by dr desc; producthunt.com, uneed.best, peerpush.net, tinylaunch.com, …
```
Present the top N by `dr` (free). Tighten to high-authority only:
```
GET /v1/datasets/product_directory/search?filter=launch+dr>=60&limit=15
```
### B. SaaS review / listing directories for backlinks
User: *"High-authority directories to list our B2B SaaS for SEO."*
```
GET /v1/datasets/product_directory/search?filter=saas+dr>=60&limit=20
# → saashub.com, saasworthy.com, financesonline.com, capterra.com (via 'saas' niche), …
```
Software in general (broader than SaaS):
```
GET /v1/datasets/product_directory/search?filter=software+dr>=60&limit=20
```
### C. Rank by real reach (organic traffic)
User: *"Which directories actually send traffic, not just a backlink?"*
`organic_traffic` is gated but **filterable** — gate on it to surface
high-reach dirs, then unlock to see the numbers:
```
GET /v1/datasets/product_directory/search?filter=saas+organic_traffic>=50000&limit=15
# → small set of high-traffic dirs. Unlock the user's picks to reveal traffic + how to submit.
```
### D. Vertical / niche product
User: *"Directories for our fintech app."* / *"design-tool listing sites."*
Combine a keyword (matched against the `niche` tag) with the `industry`
refiner, or just lead with the keyword:
```
GET /v1/datasets/product_directory/search?filter=fintech&limit=15
GET /v1/datasets/product_directory/search?filter=design+dr>=50&limit=15
```
If a keyword over-narrows, drop it and use `industry:` (e.g.
`industry:software_saas`, `industry:fintech`) plus `dr>=`.
### E. Intent translator (let the API draft the filter)
User: *"Where do I get our new dev tool in front of developers and pick up backlinks?"*
```
POST /v1/datasets/product_directory/translate-intent
{ "intent": "directories to list a developer tool for backlinks and traffic" }
# → {filter, normalized, count}. Sanity-check the count, then search.
```
### F. Unlock submission instructions for the picks
User has a shortlist and wants to actually submit:
```
# Present briefs ranked by dr (free). "Unlocking 6 = 60 credits, 30-day TTL —
# this reveals each one's submission note (how to submit, backlink yes/no) and traffic."
POST /v1/datasets/product_directory/unlocks
{ "apexes": ["producthunt.com", "saashub.com", "uneed.best", "..."] }
# → editor_note + organic_traffic + total_visits for each.
```
The `editor_note` is the operational payoff — e.g. *"Listing doesn't give a
backlink but seeds several other directories; submit via CLI, see …"*. Surface
it verbatim so the user knows the effort and the SEO return before submitting.
### G. BYO apex list — score directories I already have
User pastes a list of directory domains:
1. `GET /v1/datasets/product_directory/:apex` per domain — free brief with
`dr` (404 = not in catalog, no charge). Flag misses and rank the hits by `dr`.
2. User picks N to fully enrich. `POST /unlocks` with all of them = **10×N
credits**, single atomic charge; reveals submission notes + traffic.
3. Within 30-day TTL, repeated unlock POSTs are free.
## Gotchas
- **Rows are directories, not firms or products.** If the user wants to *hire*
an agency, route to a `find-*` agency skill. If they want to *find a product*
listed somewhere ("best CRM for us"), this dataset can't answer that — it
lists the directories, not their contents.
- **`dr` is free and briefs are pre-sorted by it** — rank and shortlist for
zero credits; spend only to reveal submission notes + traffic.
- **Gated fields can still be filtered.** `organic_traffic>=10000` narrows the
set even though the value stays hidden until unlock.
- **Catalog is global, not US-only, and not B2B-only.** Don't refuse non-US or
consumer-product asks the way the agency skills do — a directory is a
directory regardless of who launches.
- **Local / brick-and-mortar business directories are a different dataset**
(`business_directory`). If the user wants to list a restaurant, clinic, or
local service, this isn't it — point them at the branded `servicegraph` skill.
- **Multi-word phrases must be split or quoted.** `product launch` parses as
two AND'd keywords; `"product launch"` is one phrase.
- **Unlock is atomic.** `POST /unlocks` with 6 apexes either charges (up to) 60
credits or leaves balance untouched on 402. Plan the batch.
- **Within-TTL re-views are free** (`was_cached:true`).
## Errors
JSON envelope: `{"error": {"code": "...", "message": "..."}}`.
| Status | Code | What to do |
|---|---|---|
| 400 | `filter_parse_error` | `position` included; fix and re-validate with `/check`. |
| 400 | `kind_in_filter` | Strip any `kind:` from filter — URL is authoritative. |
| 400 | `field_not_in_dataset` | Field isn't allowed on `product_directory`; drop it. |
| 400 | `invalid_apex` | Re-normalize to apex. |
| 401 | `unauthorized` / `invalid_audience` | Re-prompt for a fresh `vk_…`. |
| 402 | `insufficient_credits` | `needed` and `balance` in payload; nothing charged. |
| 404 | `not_found` / `not_in_dataset` | Apex isn't in this dataset. Skip; not charged. |
| 429 | `rate_limited` | Honor `Retry-After`. |
## End-to-end example
User: *"We're launching our B2B SaaS in two weeks. Find the highest-authority
directories to submit to for backlinks, and tell me how to submit to the top
five."*
```
# 1. Discover (once per session)
GET /v1/datasets/product_directory/fields?include_values=1
# 2. Validate + scope (free)
GET /v1/datasets/product_directory/check?filter=saas+dr>=60
# 3. Search briefs (free) — already sorted by dr desc
GET /v1/datasets/product_directory/search?filter=saas+dr>=60&limit=20
# → present ranked by dr; note which look like launch platforms vs review sites
# 4. User picks 5. "Unlocking 5 = 50 credits, 30-day TTL — reveals each one's
# submission note (how to submit + backlink yes/no) and traffic."
# 5. Atomic unlock (charges 50 credits)
POST /v1/datasets/product_directory/unlocks
{ "apexes": ["saashub.com", "saasworthy.com", "producthunt.com", "uneed.best", "financesonline.com"] }
# 6. Surface each editor_note verbatim + traffic so the user can prioritize.
```
don't have the plugin yet? install it then click "run inline in claude" again.
added intent, inputs, explicit 6-step procedure with decision points for sibling skills and filtering edge cases, structured output contract with data format and file guidance, outcome signals, and comprehensive edge-case handling for auth, rate limits, gated fields, and domain normalization.
use this skill whenever a founder, maker, or startup wants to find, rank, or shortlist directories, listing sites, or launch platforms where they can submit a software product, SaaS, web app, mobile app, dev tool, or startup. the payoff is twofold: a backlink from a high-authority domain (SEO value) and referral traffic on launch day. the servicegraph API catalogs 1,000+ product directories enriched with domain rating, backlink counts, and organic traffic estimates, so you rank by real authority and reach instead of guessing. defer to find-mcp-directories for MCP servers, find-ai-directories for AI tools/agents, and find-*-agency skills for hiring firms. skip local business directories, finding products inside a directory ("best CRM"), building a directory site, and link-building services.
Required
vk_* token minted at https://servicegraph.co/profile/api-keys. store as SERVICEGRAPH_API_KEY in .env.local. keep the token out of LLM context; always dispatch authed calls through a shell wrapper that sources .env.local.Optional
producthunt.com, not www.producthunt.com/posts).retrieve the field catalog (free). call GET /v1/datasets/product_directory/fields?include_values=1 to see all filterable fields, their types, and value enums. cache the result per session; it changes rarely. if auth fails (401), prompt the user to create an API key at https://servicegraph.co/profile/api-keys, add SERVICEGRAPH_API_KEY=vk_… to .env.local, and signal when done. retry the call after they confirm.
translate or validate user intent (free). either (a) call POST /v1/datasets/product_directory/translate-intent with {"intent": "<user's ask>"} to let the API draft a filter and return a sanity count, or (b) manually compose a filter (GitHub-search style: saas dr>=60, launch, fintech -crm, etc.) and validate it with GET /v1/datasets/product_directory/check?filter=<filter>. if validation returns valid:false, read the error field, fix the filter, and re-validate. and binds tighter than or; use parens to group. comma lists within one predicate are OR. bareword searches (e.g. saas) match directory name, description, niche tags, and metadata; wrap multi-word phrases in double quotes.
search for directory briefs (free). call GET /v1/datasets/product_directory/search?filter=<filter>&limit=<N>&offset=0. the API returns up to N rows (max 100 per call), pre-sorted by domain rating (dr) descending. each brief includes: apex (domain), dr (0-100), referring_main_domains, backlinks, organic_keywords, industry, has tags, unlock hint, and a total count. the gated fields (editor_note, organic_traffic, total_visits) show only after unlock; do not call the :apex endpoint for each row (wasteful). present the briefs ranked by dr. for paginated results, increment offset by N and re-call to fetch the next batch.
user picks a shortlist (free so far). based on the ranked briefs, ask the user which directories they want submission instructions for. note: you can already rank and shortlist by authority (dr) and keyword match for zero credits. only unlock if they ask for submission details, traffic numbers, or you need gated data to filter further.
unlock submission notes and traffic (10 credits per row, atomic batch, 30-day TTL). if the user wants to know how to submit or see traffic numbers, call POST /v1/datasets/product_directory/unlocks with {"apexes": ["apex1", "apex2", ...]} (batch ≤100 rows). the API charges (up to) 10×N credits if all rows are fresh; if any row was cached within the 30-day TTL, it's free (was_cached:true). on success, the response includes the full row for each apex: editor_note (hand-written submission instructions and whether you get a backlink), organic_traffic (est. monthly US organic visits), and total_visits (est. total monthly visits). on 402 insufficient_credits, nothing is charged; notify the user of the cost and retry after they top up.
surface results. present the submission notes (editor_note) verbatim so the user knows the effort and SEO return before submitting. rank by dr or organic_traffic depending on their priority (authority vs reach). if they pasted a list of domains to score, call GET /v1/datasets/product_directory/:apex for each (free brief, 404 if not in catalog). flag any misses, rank hits by dr, and offer to unlock the shortlist.
business_directory (a different dataset), not product_directory. point them to the branded servicegraph skill.GET /:apex for each (free). rank the hits by dr (free) and offer to unlock the shortlist for submission notes + traffic (10 credits per row)..env.local, and retry. do not accept the key pasted into chat.dr threshold, broaden keywords, remove niche filters), validate again, and re-search.organic_traffic>=X but sees no value in briefs, remind them that organic_traffic is gated but filterable. unlock the hits to reveal the numbers.needed and balance. do not charge the user; inform them and retry after they fund their account.Retry-After header and retry after that delay.success is:
editor_note (verbatim submission instructions + backlink yes/no) and organic_traffic and total_visits (est. monthly, est. total). formatting: plaintext (easy to copy for submission), no markdown bloat.data format:
{"data": [...], "total": N} for search, {"apexes": {...}} for unlocks). parse and present as a human-readable ranked list (spreadsheet-friendly, or markdown table if the user asks). never dump raw JSON.file location:
directories-shortlist.csv with columns: apex, dr, referring_main_domains, organic_traffic, editor_note). confirm file path before writing.the user knows the skill worked when:
editor_note after unlock).editor_note).total:0, the filter is too tight. suggest loosening constraints (lower dr threshold, drop niche keywords, use bareword instead of field:value) and re-search.organic_traffic>=50000) to narrow the set. the values stay hidden until unlock. this is by design; use it to pre-filter high-reach directories before spending credits.product launch parses as two AND'd keywords. to search for the phrase literally, use "product launch" (with quotes).www.producthunt.com/posts?x=1 becomes producthunt.com. if the user gives a subdomain (e.g. mobile.producthunt.com), check if it's a main directory or a section; the API uses apex domains, so extract carefully.POST /unlocks with 6 apexes either charges (up to) 60 credits or leaves the balance untouched. plan the batch; if you're unsure of the cost, call GET /me/credits first to confirm balance.was_cached:true for cached rows (free charge) and was_cached:false for fresh fetches (10 credits).credit: original skill authored by nostrband (clawhub). enriched and rewritten to implexa standards.