ZapAPI Docs
Get StartedConceptsAI GatewayGuidesPayment GatewaySDKAPI ReferenceResources
Guides

App Directory

The public directory of Lightning and Nostr apps — zap-weighted ranking, boosts, and how listings work.

A public directory of Lightning/nostr apps, tools and services — ranked by a zap-weighted, boostable score. Owners list their app free, the community zaps the listings they love (organic rank), and owners can pay sats to boost (paid rank, clearly labeled, decaying). Amount alone is gameable — unique zappers anchor the score.

1. The ranking formula (the heart)

score(app, now) =
 zap_score(app) — community signal
 + boost_score(app, now) — paid signal (decays to 0)

zap_score = 10 × Σ log2(1 + sats_i) — per zap, diminishing returns
 + 25 × unique_zappers(30d) — breadth beats depth (anti-self-zap)

boost_score = Σ over active boosts of:
 sats_paid × remaining_fraction (linear decay over duration)

A boost of N sats for D days starts at score N and decays linearly to 0
at expiry. At any moment the directory is a fair mix of money-now vs.
love-forever. Boosted rows always render a "Boosted" badge.

Why log per zap: a single 100k-sat self-zap shouldn't buy the top spot. log2(1+100000) ≈ 16.6 166 points, while 10 different people zapping 1k each gives 10 × 10 × log2(1001) ≈ 996 points + 250 unique-zapper points. Community breadth dominates whale spend — by construction.

Why linear-decay boost: permanent pay-to-win kills the directory; a decaying boost is honest advertising ("promoted this week").

Default boost products (configurable): 1k sats/3d, 5k sats/7d, 20k sats/14d.


2. Entities

ListedApp
 id, ownerUserId (null = admin/seeded), name, slug (unique),
 description, url, iconUrl?, category, tags[],
 lnAddress (zap target — owner-provided OR auto-provisioned app-{slug}@zapapi.app),
 zapAddressClaimId? (our first-party LightningAddress row when auto-provisioned),
 nostrPubkey?, nostrNpub?,
 status: 'pending' | 'active' | 'rejected' | 'suspended',
 rejectedReason?, featured (admin pin), createdAt, updatedAt

AppBoost
 id, appId, buyerUserId,
 satsPaid, durationDays,
 invoice: { paymentHash, bolt11, status },
 status: 'pending' | 'active' | 'expired' | 'failed',
 startsAt (set when paid confirms), expiresAt, createdAt

Zap stats come from the existing zaps table (paid zaps only), aggregated by recipientAddress = the app's lnAddress. No new zap infrastructure needed — when a listing is auto-provisioned on zapapi.app, every zap to it flows through our existing LNURL callback recordZap settlement processor. Owner-provided addresses get zaps recorded only if they resolve to our engine (documented limitation; we show "external zaps not tracked" for those).


3. Flows

3.1 Submit an app (any signed-in user, admin-approved at launch)

  1. POST /directory/apps with body { name, description, url, category, tags, lnAddress? }
  2. If no lnAddress: auto-provision app-{slug}@zapapi.app via the existing first-party claim engine (owner = the app's owner; zaps land in THEIR wallet).
  3. Status pending admin approves active (same trust model as marketplace providers, §9.1 of the marketplace doc).

3.2 Zap a listing (anyone)

  • The listing card shows a button with total zap stats.
  • Click amount picker we resolve the app's lnAddress via LNURL (our own engine for provisioned ones) invoice pay from ZapAPI wallet or external QR.
  • Settlement existing zaps pipeline records it rank recomputed on read.

3.3 Boost a listing (owner or anyone who wants to promote it)

  1. POST /directory/apps/:id/boost with body { product: '1k/3d' | '5k/7d' | '20k/14d' }
  2. Invoice minted on the platform wallet (this is ZapAPI revenue — the one place the platform does get paid).
  3. Paid boost active, expiresAt = now + durationDays.
  4. A 5-min cron flips expired boosts; score decays automatically (§1).

3.4 Directory browse (public)

GET /directory/apps?category=&sort=score|new|zaps&q= rows with live score breakdown: zapSats, uniqueZappers, activeBoostSats and score.


4. Anti-abuse

AttackMitigation
Self-zap whalelog per zap + unique-zapper term dominates (§1)
Sockpuppet zapseach zap costs real sats (structural cost, Zap Trails argument)
Permanent pay-to-winboost decays linearly to 0
Hidden ads"Boosted" badge always rendered on boosted rows
Spam listingsadmin-approved at launch (same model as providers)
Zap farming via our free addresseszaps are counted per recipient address from OUR settlement records only — we never count unverifiable external claims

5. API surface

# Public
GET /directory/apps?category=&q=&sort=score|new|zaps&page=
GET /directory/apps/:slug
GET /directory/categories

# Authed
POST /directory/apps submit (pending)
GET /directory/my-apps my listings + stats
PATCH /directory/apps/:id edit (re-review if url changes)
POST /directory/apps/:id/boost { boost, invoice }
GET /directory/boosts my boosts
POST /directory/boosts/:id/check poll payment activate

# Admin
GET /admin/directory/apps?status=
POST /admin/directory/apps/:id/approve
POST /admin/directory/apps/:id/reject
POST /admin/directory/apps/:id/feature (pin) / unfeature

Pages: /apps (public directory), /dashboard/apps (my listings + boost), admin review under /admin/directory.

On this page