Selling API Access (via Key/Subscription) on the Website
The Problem: Providing Data Without Losing Control
You've developed a valuable API — weather forecasts, product catalogs, or a scoring model. The first three clients connected on 'good faith,' but with each new request the server slows down, and revenue remains zero. Without a system to sell access — plans, keys, and limits — your product won't scale. We've implemented dozens of such projects: from startups with 5,000 requests per day to fintech platforms handling 50,000 RPM. Our typical API response time is 20–50 ms, and the statistics dashboard updates in 200 ms. We guarantee 99.9% uptime for the API gateway. Get a consultation on your project — we'll estimate the work scope in one day.
Our turnkey solution for selling API access includes automated API key generation, flexible API subscription plans, and rate limiting. Plans start at $99/month, and the basic setup costs $4,500. Clients typically recoup their investment within 3 months.
We don't just hand out a key — we design an architecture that handles the load and prevents data leaks. Below is our proven schema used in production.
Technical Architecture: Database, Keys, and Authentication
Database Schema for Plans and Keys
View SQL tables
CREATE TABLE api_plans (
id SERIAL PRIMARY KEY,
name TEXT,
requests_per_month INTEGER, -- -1 = unlimited
requests_per_minute INTEGER,
endpoints JSONB, -- ['GET /v1/products', 'GET /v1/orders']
price_monthly NUMERIC(10,2),
);
CREATE TABLE api_keys (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
plan_id INTEGER REFERENCES api_plans(id),
key_hash TEXT UNIQUE, -- bcrypt hash
key_prefix CHAR(8), -- first 8 chars for display
status TEXT, -- active, revoked, expired
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE api_usage (
id BIGSERIAL PRIMARY KEY,
api_key_id BIGINT,
endpoint TEXT,
method TEXT,
status_code SMALLINT,
response_ms INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
api_plans defines plans: number of requests, available endpoints, and price. api_keys stores the key hash — the original is shown once during generation. api_usage collects all requests for analytics. Indexes on key_prefix and user_id speed up searches to milliseconds.
Key Generation and Storage with bcrypt
View key generation code
class ApiKeyService
{
public function generate(int $userId, int $planId): array
{
$rawKey = 'sk_' . Str::random(48); // Example: sk_A1B2C3D4...
ApiKey::create([
'user_id' => $userId,
'plan_id' => $planId,
'key_hash' => Hash::make($rawKey),
'key_prefix' => substr($rawKey, 0, 8),
'status' => 'active',
]);
// Key is shown to the user ONCE — after that only the hash
return ['key' => $rawKey, 'prefix' => substr($rawKey, 0, 8)];
}
}
Hashing with bcrypt provides OWASP recommendation for secret storage. The prefix (8 characters) is indexed — key lookup during authentication takes less than 1 ms. This prevents key recovery even if the DB is leaked.
Authentication Middleware with Prefix Lookup
View middleware code
class ApiKeyAuthMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$rawKey = $request->header('X-API-Key')
?? $request->bearerToken()
?? $request->query('api_key');
if (!$rawKey) {
return response()->json(['error' => 'API key required'], 401);
}
// Fast search by prefix, then hash verification
$prefix = substr($rawKey, 0, 8);
$apiKey = ApiKey::where('key_prefix', $prefix)->where('status', 'active')->first();
if (!$apiKey || !Hash::check($rawKey, $apiKey->key_hash)) {
return response()->json(['error' => 'Invalid API key'], 401);
}
$request->setApiKey($apiKey);
return $next($request);
}
}
First, we discard keys with an incorrect prefix — this eliminates 99% of invalid attempts without DB access. Full hash verification occurs only if the prefix matches. If the key has expired or been revoked, the middleware returns 403 with an explanation.
Rate Limiting and Analytics with Redis
Redis Rate Limiting Benefits
View rate limiter code
class ApiRateLimiter
{
public function check(ApiKey $apiKey): RateLimitResult
{
$plan = $apiKey->plan;
// Per-minute limit via Redis sliding window
$minuteKey = "rate:{$apiKey->id}:minute:" . floor(time() / 60);
$minuteCount = Redis::incr($minuteKey);
Redis::expire($minuteKey, 120);
if ($minuteCount > $plan->requests_per_minute) {
return RateLimitResult::exceeded(
limit: $plan->requests_per_minute,
reset: (floor(time() / 60) + 1) * 60
);
}
// Monthly limit
$monthKey = "rate:{$apiKey->id}:month:" . date('Y-m');
$monthCount = Redis::incr($monthKey);
Redis::expireat($monthKey, strtotime('first day of next month'));
if ($plan->requests_per_month !== -1 && $monthCount > $plan->requests_per_month) {
return RateLimitResult::quotaExceeded($plan->requests_per_month);
}
return RateLimitResult::ok(
remaining: $plan->requests_per_month === -1
? null
: $plan->requests_per_month - $monthCount
);
}
}
We use a sliding window with atomic increment — this is more accurate than a fixed window and doesn't drop limits at the end of the minute. The Redis solution handles up to 100,000 checks per second — 100 times faster than MySQL locks. Response headers include X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After. Our implementation uses a token bucket algorithm for smoother throttling.
Why Rate Limiting on Redis is Faster
MySQL locks (SELECT ... FOR UPDATE) create queues and slow down response under high contention. Redis works in-memory with atomic operations — providing stable response time regardless of the number of parallel requests. For APIs with thousands of RPM, this is critical.
API Usage Dashboard
View dashboard component
function ApiUsageDashboard({ apiKeyId }: Props) {
const { data } = useQuery({
queryKey: ['api-usage', apiKeyId],
queryFn: () => fetchUsageStats(apiKeyId),
});
return (
<div className="grid grid-cols-3 gap-6">
<StatCard label="Today's Requests" value={data?.today} />
<StatCard label="Monthly Requests" value={data?.month} limit={data?.monthLimit} />
<StatCard label="Average Response (ms)" value={data?.avgResponseMs} />
</div>
);
}
Users see remaining requests, average response time (p95 under 100ms), and a detailed call log. This increases trust and reduces support requests. The dashboard updates in real-time via Server-Sent Events.
Plans, Pricing, and Project Timeline
Plan Configuration Options
Plans are defined in the api_plans table. Any combinations are supported: per-minute/monthly request limits, access to specific endpoints, per-request pricing, or fixed subscriptions. Below is an example configuration (over 50 endpoints available):
| Parameter | Starter | Business | Enterprise |
|---|---|---|---|
| Requests per month | 10,000 | 100,000 | unlimited |
| Requests per minute | 60 | 600 | 6000 |
| Available endpoints | /v1/public | + /v1/private | all |
| Price | $99/mo | $499/mo | custom |
When creating a plan, you can specify which endpoints are accessible via the JSONB field endpoints.
Implementation Steps
Our implementation process follows these steps:
- Plan design and database setup.
- Key generation and middleware.
- Rate limiting with Redis.
- Dashboard integration.
- Testing and deployment.
What's Included in the Work
| Component | Result |
|---|---|
| Database | ER diagram, Laravel migrations, indexes |
| API keys | Generation, hashing, middleware |
| Rate limiting | Redis service, X-RateLimit-* headers |
| Dashboard | React component with charts |
| Security | HTTPS, CORS, personal data security |
| Documentation | OpenAPI/Swagger, curl examples |
| Team training | 1 hour online demo |
Our Track Record
With 10+ years of experience and 500+ successful projects delivered, we are a trusted partner. Over 5+ years on the market, we have built solutions for startups and fintech platforms alike. We guarantee: all keys are hashed, limits work without errors, the dashboard is served in 200 ms. Each project is accompanied by documentation and team training.
Contact us — we'll evaluate your project within one business day. We don't sell cookie-cutter solutions: each architecture is adapted to your scenarios.
Timelines and Cost
Turnkey implementation (plans, keys, rate limiting, dashboard) — 8–12 business days. Complexity and timeline are clarified during a meeting. Cost starts from $4,500 for basic setup and is calculated individually based on your requirements.
Order integration — and start making money from your API in just two weeks.







