feat(socials): credit-gate both scrape endpoints (5 + posts credits) + auth on bulk

Merged
SW
4mo ago
+279-19

Summary

Implements the credit-gate item on recoupable/chat#1836, priced per the Apify cost research. Contract: recoupable/docs#259 — merge order: docs → api.

Pricing: 5 credits + 1 per requested post, per profile scraped (1 credit = $0.01). Worst-case Apify cost is YouTube at $0.003 × 2×posts items ($0.60 at posts=100) → 105 credits keeps ≥1.75× margin; default scrape (5 credits) has 3–20× margin on every platform.

LayerChange
lib/socials/getSocialScrapeCreditCost.ts5 + (posts ?? 0)
lib/socials/ensureSocialScrapeCredits.tsgate via ensureCreditsOrShortCircuit → 402 with checkoutUrl (auto-recharge path included)
lib/socials/deductSocialScrapeCredits.tsdeduct-on-success via recordCreditDeduction; never throws (mirrors research)
POST /api/socials/{id}/scrapevalidator gates after auth + artist-access; handler deducts only when the run started (no charge on unsupported platform / failed start)
POST /api/artist/socials/scrapegains auth (validateAuthContext → 401) + artist access (checkAccountArtistAccess → 403) — previously fully open (audit); gates on (5+posts) × linked profiles, deducts per profile whose scrape started

⚠️ Behavior change: the bulk endpoint now requires auth, and both endpoints charge credits. No internal callers exist (grep across chat/api/tasks/admin found only the route itself); external consumers get documented 401/402/403s per docs#259.

Tests (TDD, red → green)

All new behavior written test-first and confirmed failing (10 failures across 6 files) before implementation:

  • getSocialScrapeCreditCost: 5 / 6 / 25 / 105
  • ensureSocialScrapeCredits / deductSocialScrapeCredits: gate passthrough, 402 short-circuit, never-throws deduction
  • single endpoint: payload carries account_id; ensure called with 5 (default) / 25 (posts=20); 402 short-circuit; deduct on success only (not on unsupported platform or failed start)
  • bulk endpoint: 401 propagation, 403 no-access, ensure (5+20)×2=50, deduct per started profile ((5+20)×1=25 when one of two starts), [] with zero charge when no socials

Affected domains (lib/socials, lib/artist, lib/apify, lib/credits): 324/324 pass. Full suite passes; tsc --noEmit unchanged at 200 pre-existing errors (none new); ESLint clean.

Preview verification to follow as a PR comment.

🤖 Generated with Claude Code


Summary by cubic

Adds credit gating to both social scrape endpoints at 5 credits + 1 per requested post per profile, and adds auth + artist access checks to the bulk artist scrape. Aligns the API with the cost model and docs in recoupable/docs#259 and closes the credit-gate requirement in recoupable/chat#1836.

  • New Features

    • Upfront credit check with 402 short-circuit and checkoutUrl; cost = 5 + posts per profile.
    • POST /api/socials/{id}/scrape: validates auth and artist access, forwards posts, deducts only if the Apify run starts.
    • POST /api/artist/socials/scrape: now requires auth and artist access; gates on (5 + posts) × linked profiles; deducts per profile that starts; returns [] with no charge when no socials.
  • Migration

    • Merge order: docs → api (recoupable/docs#259).
    • External callers must include auth and handle 401/402/403 responses.