feat: tiered API plans (Free/Bronze/Gold/Platinum)
- Free: $0, 30 req/min, market data only - Bronze: $29/mo, 100 req/min, + companies & search - Gold: $99/mo, 500 req/min, + filings & historical - Platinum: $299/mo, 2000 req/min, all features + priority - Plan-aware rate limiting per API key (or per IP for free) - Usage tracking with daily aggregation - GET /api/v1/plans — plan listing - POST /api/v1/plans/register — instant free API key - GET /api/v1/plans/usage — usage stats - /pricing — dark-themed HTML pricing page - X-RateLimit-* and X-Plan headers on every response - Restricted endpoints return upgrade prompt - Updated OpenAPI spec with security scheme - 53 handlers, compiles clean
This commit is contained in:
152
internal/api/handlers/pricing.go
Normal file
152
internal/api/handlers/pricing.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package handlers
|
||||
|
||||
import "github.com/gofiber/fiber/v2"
|
||||
|
||||
func (h *Handler) PricingPage(c *fiber.Ctx) error {
|
||||
c.Set("Content-Type", "text/html")
|
||||
return c.SendString(pricingHTML)
|
||||
}
|
||||
|
||||
const pricingHTML = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Sentinela API - Pricing</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0a0e17;color:#e1e5ee;min-height:100vh}
|
||||
.container{max-width:1200px;margin:0 auto;padding:40px 20px}
|
||||
h1{text-align:center;font-size:2.5rem;margin-bottom:8px;background:linear-gradient(135deg,#00d4aa,#7c3aed);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.subtitle{text-align:center;color:#8892a4;font-size:1.1rem;margin-bottom:48px}
|
||||
.plans{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:24px;margin-bottom:64px}
|
||||
.plan{background:#131a2b;border:1px solid #1e293b;border-radius:16px;padding:32px 24px;position:relative;transition:transform .2s,border-color .2s}
|
||||
.plan:hover{transform:translateY(-4px);border-color:#7c3aed}
|
||||
.plan.featured{border-color:#00d4aa;background:linear-gradient(180deg,#0f1a2e 0%,#131a2b 100%)}
|
||||
.plan.featured::before{content:"MOST POPULAR";position:absolute;top:-12px;left:50%;transform:translateX(-50%);background:linear-gradient(135deg,#00d4aa,#059669);color:#fff;font-size:.7rem;font-weight:700;padding:4px 16px;border-radius:20px;letter-spacing:1px}
|
||||
.plan-name{font-size:1.3rem;font-weight:700;margin-bottom:4px}
|
||||
.plan-price{font-size:2.8rem;font-weight:800;margin:16px 0 4px}
|
||||
.plan-price span{font-size:.9rem;font-weight:400;color:#8892a4}
|
||||
.plan-billing{color:#8892a4;font-size:.85rem;margin-bottom:24px}
|
||||
.plan-rate{background:#1e293b;border-radius:8px;padding:8px 12px;margin-bottom:20px;font-size:.9rem;color:#00d4aa;text-align:center}
|
||||
.features{list-style:none;margin-bottom:28px}
|
||||
.features li{padding:6px 0;font-size:.9rem;color:#c1c9d6}
|
||||
.features li::before{content:"✓ ";color:#00d4aa;font-weight:700}
|
||||
.features li.no::before{content:"✗ ";color:#4a5568}
|
||||
.features li.no{color:#4a5568}
|
||||
.btn{display:block;width:100%;padding:12px;border:none;border-radius:10px;font-size:1rem;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:opacity .2s}
|
||||
.btn-free{background:#1e293b;color:#e1e5ee}
|
||||
.btn-primary{background:linear-gradient(135deg,#00d4aa,#059669);color:#fff}
|
||||
.btn-gold{background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff}
|
||||
.btn-plat{background:linear-gradient(135deg,#7c3aed,#6d28d9);color:#fff}
|
||||
.btn:hover{opacity:.85}
|
||||
table{width:100%;border-collapse:collapse;margin-top:16px}
|
||||
th,td{padding:12px 16px;text-align:center;border-bottom:1px solid #1e293b;font-size:.9rem}
|
||||
th{color:#8892a4;font-weight:600}
|
||||
th:first-child,td:first-child{text-align:left}
|
||||
td.yes{color:#00d4aa}
|
||||
td.no{color:#4a5568}
|
||||
.matrix-section{margin-top:64px}
|
||||
.matrix-section h2{text-align:center;font-size:1.8rem;margin-bottom:8px}
|
||||
.matrix-sub{text-align:center;color:#8892a4;margin-bottom:24px}
|
||||
footer{text-align:center;color:#4a5568;font-size:.85rem;margin-top:64px;padding-bottom:32px}
|
||||
footer a{color:#00d4aa;text-decoration:none}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Sentinela API Plans</h1>
|
||||
<p class="subtitle">Brazilian financial data API — real-time rates, companies, and filings</p>
|
||||
|
||||
<div class="plans">
|
||||
<div class="plan">
|
||||
<div class="plan-name">Free</div>
|
||||
<div class="plan-price">$0<span></span></div>
|
||||
<div class="plan-billing">Free forever</div>
|
||||
<div class="plan-rate">30 requests/minute</div>
|
||||
<ul class="features">
|
||||
<li>Selic, CDI, IPCA rates</li>
|
||||
<li>FX rates (USD/BRL)</li>
|
||||
<li>Market overview</li>
|
||||
<li class="no">Company data</li>
|
||||
<li class="no">Filing data</li>
|
||||
<li class="no">Search</li>
|
||||
</ul>
|
||||
<a href="/api/v1/plans/register" class="btn btn-free">Get Started</a>
|
||||
</div>
|
||||
|
||||
<div class="plan">
|
||||
<div class="plan-name">Bronze</div>
|
||||
<div class="plan-price">$29<span>/mo</span></div>
|
||||
<div class="plan-billing">Billed monthly</div>
|
||||
<div class="plan-rate">100 requests/minute</div>
|
||||
<ul class="features">
|
||||
<li>All market data</li>
|
||||
<li>Company data</li>
|
||||
<li>Company search (FTS)</li>
|
||||
<li>API key auth</li>
|
||||
<li class="no">Filing data</li>
|
||||
<li class="no">Bulk export</li>
|
||||
</ul>
|
||||
<a href="mailto:api@sentinela.dev?subject=Bronze Plan" class="btn btn-primary">Contact Us</a>
|
||||
</div>
|
||||
|
||||
<div class="plan featured">
|
||||
<div class="plan-name">Gold</div>
|
||||
<div class="plan-price">$99<span>/mo</span></div>
|
||||
<div class="plan-billing">Billed monthly</div>
|
||||
<div class="plan-rate">500 requests/minute</div>
|
||||
<ul class="features">
|
||||
<li>All market data</li>
|
||||
<li>Company data & search</li>
|
||||
<li>Filing data</li>
|
||||
<li>Historical data</li>
|
||||
<li>Bulk export</li>
|
||||
<li class="no">Webhooks</li>
|
||||
</ul>
|
||||
<a href="mailto:api@sentinela.dev?subject=Gold Plan" class="btn btn-gold">Contact Us</a>
|
||||
</div>
|
||||
|
||||
<div class="plan">
|
||||
<div class="plan-name">Platinum</div>
|
||||
<div class="plan-price">$299<span>/mo</span></div>
|
||||
<div class="plan-billing">Billed monthly</div>
|
||||
<div class="plan-rate">2,000 requests/minute</div>
|
||||
<ul class="features">
|
||||
<li>Everything in Gold</li>
|
||||
<li>Webhooks</li>
|
||||
<li>Priority support</li>
|
||||
<li>Custom date ranges</li>
|
||||
<li>Raw CSV export</li>
|
||||
<li>Dedicated capacity</li>
|
||||
</ul>
|
||||
<a href="mailto:api@sentinela.dev?subject=Platinum Plan" class="btn btn-plat">Contact Us</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="matrix-section">
|
||||
<h2>Feature Comparison</h2>
|
||||
<p class="matrix-sub">Detailed breakdown of what's included in each plan</p>
|
||||
<table>
|
||||
<tr><th>Feature</th><th>Free</th><th>Bronze</th><th>Gold</th><th>Platinum</th></tr>
|
||||
<tr><td>Selic / CDI / IPCA</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
||||
<tr><td>FX Rates</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
||||
<tr><td>Market Overview</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
||||
<tr><td>Company Data</td><td class="no">✗</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
||||
<tr><td>Company Search</td><td class="no">✗</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
||||
<tr><td>Filing Data</td><td class="no">✗</td><td class="no">✗</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
||||
<tr><td>Historical Data</td><td class="no">✗</td><td class="no">✗</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
||||
<tr><td>Bulk Export</td><td class="no">✗</td><td class="no">✗</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
||||
<tr><td>Webhooks</td><td class="no">✗</td><td class="no">✗</td><td class="no">✗</td><td class="yes">✓</td></tr>
|
||||
<tr><td>CSV Export</td><td class="no">✗</td><td class="no">✗</td><td class="no">✗</td><td class="yes">✓</td></tr>
|
||||
<tr><td>Priority Support</td><td class="no">✗</td><td class="no">✗</td><td class="no">✗</td><td class="yes">✓</td></tr>
|
||||
<tr><td>Rate Limit</td><td>30/min</td><td>100/min</td><td>500/min</td><td>2,000/min</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>Sentinela API — <a href="/docs">API Documentation</a> · <a href="/api/v1/plans">Plans API</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
Reference in New Issue
Block a user