LaunchIgniter Submission API
Submit a product once and have it listed across all three directories in the LaunchIgniter network:
| Directory | Domain | What you get |
|---|---|---|
launchigniter | launchigniter.com | Product page with a dofollow backlink |
startuptrusted | startuptrusted.com | Auto-approved startup listing with a dofollow backlink |
saasgrow | saasgrow.app | Badge-verified SaaS listing with a dofollow backlink |
- Base URL:
https://api.launchigniter.com - Content type:
application/json - Access: by invitation. Contact us to have a key issued for your account.
Authentication
Every request needs the API key you were issued. Send it either way:
Authorization: Bearer lip_live_xxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: lip_live_xxxxxxxxxxxxxxxxxxxxxxxx
Your key is bound to a LaunchIgniter account and carries three things set when it was issued:
- Which directories you may submit to.
- How many submissions you get per directory.
- A validity window — after
expiresAtthe key stops working.
Keys are stored hashed. If you lose one it cannot be recovered, only rotated — ask us for a replacement.
Errors
| HTTP | error | Meaning |
|---|---|---|
| 401 | authentication_required | No key sent |
| 401 | invalid_api_key | Key is unknown, or the linked account was deleted |
| 401 | api_key_revoked | Key was revoked |
| 401 | api_key_expired | Key is past its validity window |
| 403 | directory_not_granted | You asked for a directory your key cannot use |
| 403 | no_directory_access | The key has no directories granted at all |
| 400 | invalid_request | A field failed validation, or an image could not be downloaded — read message |
| 429 | rate_limited | You are sending too fast — see Rate limits below |
| 500 | server_error | Something broke on our side; safe to retry |
| 503 | image_storage_unavailable | Our image storage was briefly unreachable; retry the request |
GET /partner/v1/me
Check what your key can do and how much quota is left. Call this first.
curl https://api.launchigniter.com/partner/v1/me \
-H "Authorization: Bearer $LAUNCHIGNITER_API_KEY"
{
"key": {
"name": "Acme Agency - production",
"mask": "lip_live_a1b2c3...9f8e",
"createdAt": "2026-09-01T10:12:00.000Z",
"expiresAt": "2027-09-01T00:00:00.000Z",
"lastUsedAt": "2026-09-03T08:41:22.000Z"
},
"account": {
"name": "Acme Agency",
"username": "acme",
"email": "[email protected]"
},
"directories": [
{ "directory": "launchigniter", "name": "LaunchIgniter", "limit": 250, "used": 12, "remaining": 238 },
{ "directory": "startuptrusted", "name": "StartupTrusted", "limit": 250, "used": 12, "remaining": 238 },
{ "directory": "saasgrow", "name": "SaaSGrow", "limit": null, "used": 12, "remaining": null }
]
}
A limit of null means unlimited.
GET /partner/v1/categories
The authoritative list of category slugs accepted by the categories field.
Categories are mapped automatically to the closest equivalent on StartupTrusted
and SaaSGrow, so you only ever send LaunchIgniter slugs.
curl https://api.launchigniter.com/partner/v1/categories \
-H "Authorization: Bearer $LAUNCHIGNITER_API_KEY"
{
"categories": [
{ "name": "Artificial Intelligence", "slug": "ai" },
{ "name": "Developer Tools", "slug": "developer-tools" },
{ "name": "SaaS", "slug": "saas" }
]
}
Commonly used slugs include ai, saas, developer-tools, marketing,
productivity, design, ecommerce, finance, analytics-tool,
chrome-extension, mobile-app, newsletter, no-code, seo-tools,
project-management and website-builder. Always fetch the live list rather
than hardcoding it.
POST /partner/v1/listings
Submit one product to every directory your key can reach.
curl -X POST https://api.launchigniter.com/partner/v1/listings \
-H "Authorization: Bearer $LAUNCHIGNITER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme AI",
"tagline": "Ship your SaaS in a weekend",
"description": "Acme AI turns a product brief into a deployable Next.js app with auth, billing and a dashboard already wired up.",
"websiteUrl": "https://acme.ai",
"logoUrl": "https://acme.ai/logo.png",
"categories": ["ai", "developer-tools"],
"demoVideoUrl": "https://www.youtube.com/watch?v=xxxxxxxxxxx",
"screenshotUrls": [
"https://acme.ai/shots/dashboard.png",
"https://acme.ai/shots/editor.png"
],
"isOpenSource": false,
"isVibeCoded": true,
"maker": {
"email": "[email protected]",
"name": "Acme Founder"
},
"directories": ["launchigniter", "startuptrusted", "saasgrow"]
}'
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Max 80 characters. Truncated to 28 on StartupTrusted and SaaSGrow, which have shorter title limits. |
tagline | string | yes | Max 200 characters. One line — this is the listing subtitle on LaunchIgniter. |
description | string | yes | Max 5000 characters. Carried in full to all three directories and shown on the listing page. StartupTrusted and SaaSGrow also shorten it at a word boundary for their card blurb and meta description. |
websiteUrl | string | yes | Must be http(s). Used for duplicate detection. |
logoUrl | string | yes | Must be http(s) and point at a publicly reachable image. Copied to our CDN on submission — see Images below. Every directory shows the listing with its logo. |
categories | string[] | yes | 1–3 slugs from GET /partner/v1/categories. The first one drives the category on the other two directories. |
demoVideoUrl | string | no | YouTube or Loom URL. LaunchIgniter only. |
screenshotUrls | string[] | yes | 1–6 image URLs, kept in the order given. Copied to our CDN on submission — see Images below. Shown on the LaunchIgniter listing only. |
isOpenSource | boolean | no | Defaults to false. LaunchIgniter only. |
isVibeCoded | boolean | no | Defaults to false. LaunchIgniter only. |
maker.email | string | no | The account the StartupTrusted and SaaSGrow listings are created under, so your client can claim and edit them. Defaults to the email on your key. An account is created there if none exists. |
maker.name | string | no | Display name for that account. |
directories | string[] | no | Subset of the directories your key is granted. Defaults to all of them. |
The LaunchIgniter product itself is always owned by the account your key belongs
to. maker only affects the two partner directories.
Response
Each directory is reported independently, so a failure on one never blocks the others.
| HTTP | status | Meaning |
|---|---|---|
| 201 | completed | Every requested directory succeeded |
| 207 | partial | Some directories succeeded, some did not |
| 422 | failed | No directory accepted the listing |
| 200 | — | Idempotent replay of an earlier submission |
{
"status": "completed",
"createdAt": "2026-09-03T09:15:04.221Z",
"listing": {
"name": "Acme AI",
"tagline": "Ship your SaaS in a weekend",
"websiteUrl": "https://acme.ai",
"categories": ["ai", "developer-tools"],
"makerEmail": "[email protected]"
},
"results": [
{
"directory": "launchigniter",
"name": "LaunchIgniter",
"status": "success",
"listingSlug": "acme-ai",
"listingUrl": "https://launchigniter.com/product/acme-ai"
},
{
"directory": "startuptrusted",
"name": "StartupTrusted",
"status": "success",
"listingSlug": "acme-ai",
"listingUrl": "https://startuptrusted.com/startup/acme-ai"
},
{
"directory": "saasgrow",
"name": "SaaSGrow",
"status": "success",
"listingSlug": "acme-ai",
"listingUrl": "https://saasgrow.app/saas/acme-ai/timeline"
}
],
"quota": [
{ "directory": "launchigniter", "name": "LaunchIgniter", "limit": 250, "used": 13, "remaining": 237 },
{ "directory": "startuptrusted", "name": "StartupTrusted", "limit": 250, "used": 13, "remaining": 237 },
{ "directory": "saasgrow", "name": "SaaSGrow", "limit": null, "used": 13, "remaining": null }
]
}
Per-directory result status
| Status | Meaning | Consumes quota |
|---|---|---|
success | A new listing was created | yes |
duplicate | The product was already listed there. The existing listing is brought live — approved and badge-verified — and listingUrl points at it | no |
failed | The directory rejected the listing; see error | no |
skipped | Your submission allowance for that directory is used up; the result also carries "quotaExceeded": true | no |
Only newly created listings count against your quota, so retries after a partial failure are free.
An existing listing is never a failure. If any of the three directories already
carries the product — whether it came from an earlier API call, a Pro launch, or
a submission made directly on that site — the listing is approved and
badge-verified so it goes live, and the directory reports duplicate with the
existing listingUrl. Ownership of an existing listing is never transferred; it
stays with whoever created it.
Common failed reasons:
A product with this name or website was rejected on LaunchIgniter— the product was turned down by moderation. Resubmitting will not override that.Integration not configured— a temporary outage on our side. Safe to retry.
Rate limits
Limits are per API key, not per IP, so your traffic is never affected by another partner.
| Scope | Limit |
|---|---|
| All endpoints | 120 requests per 15 minutes |
POST /partner/v1/listings | 20 submissions per hour |
Submissions are held to a tighter limit because one call fans out to three directories and downloads every image you reference.
Every response carries RateLimit-Limit, RateLimit-Remaining and
RateLimit-Reset. Going over returns HTTP 429 with "error": "rate_limited";
wait for the window in RateLimit-Reset and retry. A 429 never creates a
listing, so retrying is safe.
If your volume needs a higher ceiling, ask and we will raise it on your key.
Images
logoUrl and screenshotUrls may point at any public host. We do not
hotlink them: each image is downloaded once, at submission time, and stored on
our own CDN. The listing then uses our copy, so it keeps rendering even if you
later move or delete the original.
That means the URLs in the response are ours, not the ones you sent:
"logoUrl": "https://minio-l408owcw08k844gg4wk4ck8g.173.249.12.9.sslip.io/launch-igniter/images/1717171717-3f9c2a1b.png"
Store whatever we return rather than hardcoding the host — the storage host can change.
Requirements for every image URL:
- Reachable over
http(s)from the public internet at the moment you submit — no auth, no signed URLs that have already expired, no internal hosts. - Served with an image content type: PNG, JPEG, GIF, WebP, AVIF, SVG, BMP or ICO.
- 10MB or smaller.
If any image fails these checks the whole submission is rejected with a 400 and
a message naming the offending URL — nothing is listed. Fix the URL and call
the endpoint again.
Idempotency
There is nothing to send. Retries are safe by default, and we deduplicate on
your behalf using your API key and the submitted websiteUrl.
If you already completed a submission for that site, we return the original
result with HTTP 200 and "idempotentReplay": true instead of submitting
again.
{
"status": "completed",
"idempotentReplay": true,
"results": [ ... ]
}
A partial or failed submission is never replayed — call the endpoint again
with the same body and it genuinely retries. Directories that already accepted
the product report duplicate and are not charged, while the failed ones are
attempted again.
GET /partner/v1/listings
Everything submitted with your key, newest first.
Query parameters
| Parameter | Default | Notes |
|---|---|---|
page | 1 | |
limit | 20 | Max 100 |
status | — | Omit to get everything. Narrows to completed, partial or failed |
curl "https://api.launchigniter.com/partner/v1/listings?limit=50" \
-H "Authorization: Bearer $LAUNCHIGNITER_API_KEY"
{
"submissions": [ { "status": "completed", "createdAt": "...", "listing": {}, "results": [] } ],
"pagination": { "page": 1, "limit": 50, "total": 3, "totalPages": 1 }
}
Add status only when you want a subset - here, the submissions where every
directory succeeded:
curl "https://api.launchigniter.com/partner/v1/listings?status=completed&limit=50" \
-H "Authorization: Bearer $LAUNCHIGNITER_API_KEY"
?status=partial and ?status=failed narrow to the submissions that need
attention, so an empty list from those is the healthy result, not a lost
submission.
GET /partner/v1/listings/{website}
The most recent submission your key made for a site, in the same shape as the
POST response. Submissions are addressed by the website they were submitted
for — pass the domain, with or without a scheme or www..
curl https://api.launchigniter.com/partner/v1/listings/acme.ai \
-H "Authorization: Bearer $LAUNCHIGNITER_API_KEY"
Returns 404 with {"error": "not_found"} if your key has not submitted that
site.
Review and publication
- LaunchIgniter — partner submissions publish immediately when your key has
auto-approval enabled, which is the default. Otherwise they enter the normal
moderation queue and go live once reviewed.
GET /partner/v1/listingsreflects the submission, not the moderation outcome. The product page links out dofollow. - StartupTrusted and SaaSGrow — listings are auto-approved and live right away. StartupTrusted links out dofollow on every listing; SaaSGrow listings are created badge-verified, which is what makes the backlink dofollow.
Please only submit real, working products. Keys that submit spam, dead links or duplicate domains get revoked.
Working example
A small Node script that checks quota, submits a batch and reports what landed where:
const API = 'https://api.launchigniter.com';
const KEY = process.env.LAUNCHIGNITER_API_KEY;
const call = async (path, options = {}) => {
const res = await fetch(`${API}${path}`, {
...options,
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
...options.headers,
},
});
const body = await res.json();
if (!res.ok && res.status !== 207) {
throw new Error(`${res.status} ${body.error}: ${body.message ?? ''}`);
}
return body;
};
const me = await call('/partner/v1/me');
console.log('Quota:', me.directories);
for (const product of products) {
const result = await call('/partner/v1/listings', {
method: 'POST',
body: JSON.stringify({
name: product.name,
tagline: product.tagline,
description: product.description,
websiteUrl: product.url,
logoUrl: product.logo,
screenshotUrls: product.screenshots,
categories: [product.category],
maker: { email: product.founderEmail, name: product.founderName },
}),
});
for (const r of result.results) {
console.log(`${product.name} -> ${r.directory}: ${r.status} ${r.listingUrl ?? r.error ?? ''}`);
}
}
Support
Questions, a higher allowance, or a key rotation: email
[email protected] with the masked key
(lip_live_a1b2c3...) — never the full key.