DevPortal: Developer Portal SDK
API key management, usage analytics, and trust registry explorer for building developer portals and self-service consoles. Cryptographically random key generation with SHA-256 hashing, scope-based access control, daily bucketed analytics, and paginated registry search. Zero npm dependencies.
Executive Summary
DevPortal provides the core building blocks for developer portals: API key lifecycle management with cryptographically secure secret generation, usage analytics with daily bucketing, and trust registry exploration with filtering and pagination.
Three function groups cover the entire surface area: API Key Management generates keys with SHA-256 hashed secrets, validates incoming requests, and supports revocation. Usage Analytics records request metadata (endpoint, latency, status code) and computes aggregate statistics by day, endpoint, and error rate. Registry Explorer searches trust registry entries with name/scope filters and paginated results.
Every API key secret is generated with crypto.getRandomValues() (32 bytes / 256 bits) and immediately SHA-256 hashed. The plaintext secret is returned exactly once and never stored. Validation uses constant-time hash comparison. Scope checking is built in.
Zero npm dependencies. Runs on any platform with Web Crypto API: Node.js 20+, Tauri v2, modern browsers, Deno, Bun. Dual ESM/CJS builds in a single package.
Developer Experience
Six structured error codes with actionable hints. Immutable type contracts prevent accidental mutation. Result-based error handling with no thrown exceptions.
interface PortalError { code: PortalErrorCode; // INVALID_KEY | KEY_EXPIRED | KEY_REVOKED | NOT_FOUND | RATE_LIMITED | UNAUTHORIZED message: string; // Human-readable description }
| Code | Cause | Action |
|---|---|---|
| INVALID_KEY | SHA-256 hash mismatch | Verify the secret was copied correctly |
| KEY_EXPIRED | Current time exceeds expiresAt | Generate a new key or extend expiry |
| KEY_REVOKED | Key was explicitly revoked | Generate a new key |
| NOT_FOUND | KeyId not in store | Check the keyId parameter |
| RATE_LIMITED | Request rejected by rate limiter | Retry after backoff period |
| UNAUTHORIZED | No valid API key provided | Include API key in Authorization header |
Immutable Types
All fields are readonly. TypeScript enforces immutability at compile time. Accidental mutation is a type error.
interface ApiKey { readonly keyId: string; readonly hashedSecret: string; readonly label: string; readonly scopes: readonly string[]; readonly createdAt: number; readonly expiresAt: number | null; readonly revokedAt: number | null; }
The Problem
Developer portals need API key management, but building it from scratch means reinventing cryptographic randomness, hash verification, scope authorization, usage tracking, and registry search. Most teams get at least one of these wrong.
Weak secret generation. Many implementations use Math.random() or insufficiently long secrets. DevPortal uses crypto.getRandomValues() with 32 bytes (256 bits) of entropy.
Plaintext secret storage. Storing API secrets in plaintext or reversible encryption creates breach risk. DevPortal immediately SHA-256 hashes secrets and never persists plaintext.
No scope enforcement. API keys often grant global access instead of scoped permissions. DevPortal includes scope-based authorization with constant-time checking.
No usage visibility. Without analytics, developers can't debug rate limits or optimize request patterns. DevPortal tracks every request with latency, status code, and endpoint bucketing.
| Property | Custom Implementation | DevPortal |
|---|---|---|
| Secret generation | Often Math.random() | crypto.getRandomValues() |
| Secret storage | Plaintext or reversible encryption | SHA-256 hashed |
| Scope enforcement | Manual implementation | Built-in hasScope() |
| Usage analytics | Custom logging infrastructure | Daily bucketing + endpoint stats |
| Registry search | Manual filtering logic | Paginated + scoped search |
| npm dependencies | Varies (often 10+) | 0 |
Real-World Use Cases
Six scenarios where DevPortal replaces custom API key infrastructure with a zero-dependency SDK.
Developers sign up, generate API keys, view usage stats, and rotate keys without support tickets. All key lifecycle events tracked and auditable.
generateApiKey() + computeStats()AI agents register via trust registry. Developers generate scoped keys for agent-to-agent communication. Usage analytics track which agents are most active.
searchRegistry() + hasScope()Billing systems track API usage per customer. Daily bucketing provides month-to-date totals. Error rate analysis identifies problematic integrations.
recordUsage() + requestsByDayKey generation, revocation, and validation events feed audit logs. Scope-based access control satisfies least-privilege requirements.
revokeApiKey() + scope enforcementEach IoT device receives a scoped API key during provisioning. Registry explorer shows active devices. Revocation immediately blocks compromised devices.
validateApiKey() + expiresInDaysCommand-line tools authenticate with API keys stored in local config. Users generate keys from the web dashboard and paste into CLI setup.
listApiKeys() + label filteringSolution Architecture
Three composable modules. Each module is independent but designed to work together.
Integration
Five functions cover 90% of developer portal needs. Start with key generation, add analytics when needed, integrate registry search for multi-tenant portals.
import { generateApiKey, validateApiKey, recordUsage, computeStats, } from '@private.me/devportal'; // In-memory key store (use persistent storage in production) const keys = new Map(); const usageRecords: UsageRecord[] = []; // Generate key const result = await generateApiKey({ label: 'Production', scopes: ['gateway:deliver', 'registry:read'], expiresInDays: 90, }); if (!result.ok) throw new Error(result.error.message); const { key, secret } = result.value; keys.set(key.keyId, key); console.log('API Key:', key.keyId); console.log('Secret (save this, shown once):', secret); // Validate incoming request const valid = await validateApiKey(receivedKeyId, receivedSecret, keys); if (!valid.ok) { return { status: 401, error: valid.error }; } // Record usage recordUsage({ keyId: key.keyId, endpoint: '/gateway/deliver', timestamp: Date.now(), responseMs: 42, statusCode: 200, }, usageRecords); // Compute stats const stats = computeStats(usageRecords, { keyId: key.keyId }); console.log('Total requests:', stats.totalRequests); console.log('Avg latency:', stats.avgResponseMs); console.log('Error rate:', stats.errorRate);
Persistent Storage
DevPortal uses in-memory Map and arrays for simplicity. Production deployments need persistent storage. The caller is responsible for serializing API keys and usage records to a database or file system.
// Load keys from database on startup const keys = new Map(); const rows = await db.query('SELECT * FROM api_keys'); for (const row of rows) { keys.set(row.keyId, row); } // After generating a new key, persist to DB const result = await generateApiKey({ label, scopes, expiresInDays }); if (result.ok) { await db.insert('api_keys', result.value.key); keys.set(result.value.key.keyId, result.value.key); }
Registry Integration
The registry explorer operates on an array of RegistrySearchResult entries. In production, these typically come from a trust registry (Xlink) or identity provider.
import { searchRegistry, getRegistryStats } from '@private.me/devportal'; // Fetch entries from trust registry const entries = await trustRegistry.listAll(); // Search by name, scope, pagination const results = searchRegistry(entries, { query: 'production', // Case-insensitive name search scope: 'gateway:deliver', // Filter to entries with this scope includeRevoked: false, // Exclude revoked entries limit: 20, // Max results per page offset: 0, // Pagination offset }); // Get aggregate stats const stats = getRegistryStats(entries); console.log('Total entries:', stats.total); console.log('Active entries:', stats.active); console.log('By scope:', stats.byScope);
Security
Four cryptographic guarantees: random secret generation, irreversible hashing, constant-time comparison, and scope-based authorization.
Hash Verification Flow
Validation checks occur in sequence: (1) key existence, (2) revocation status, (3) expiry, (4) SHA-256 hash comparison. If any check fails, validation returns an error immediately without proceeding to the next step.
admin:*), implement the logic in your authorization layer before calling hasScope().
Benchmarks
Performance measurements on Node.js 20.x, Apple M2 Pro. All operations are sub-millisecond except SHA-256 hashing (~50µs).
| Operation | Time | Notes |
|---|---|---|
| generateApiKey() | <1ms | crypto.getRandomValues() + SHA-256 + UUID |
| validateApiKey() | ~50µs | Map lookup + SHA-256 comparison |
| hasScope() | <5µs | Array.includes() — linear search |
| recordUsage() | <1µs | Array.push() — amortized O(1) |
| computeStats() | ~10µs / 1K records | Single-pass aggregation |
| searchRegistry() | ~5µs / 1K entries | Filter + slice for pagination |
computeStats() recomputes from raw records on every call. For large datasets (100K+ records), cache aggregate stats at the application layer and invalidate on new usage records. pruneOldRecords() removes records older than N days to keep the dataset manageable.
Honest Limitations
DevPortal is designed for simplicity and zero dependencies. Some features are intentionally deferred to the application layer.
In-Memory Storage Only
API keys and usage records are stored in Map and arrays. The caller is responsible for persistence to a database or file system. DevPortal provides the building blocks, not a complete storage solution.
No Rate Limiting Implementation
The RATE_LIMITED error code exists in the type system, but DevPortal does not implement rate limiting. Use @private.me/xgate or a similar rate limiter at the transport layer.
Linear Scope Checking
hasScope() uses Array.includes(), which is O(n) where n is the number of scopes. For keys with 100+ scopes, consider converting to a Set at the application layer.
No Scope Hierarchies
Scopes are literal strings. There is no support for wildcard scopes (e.g., admin:*) or hierarchical scopes (e.g., read implies read:all). Implement scope expansion logic before calling hasScope().
Stats Recomputation on Every Call
computeStats() iterates through all usage records on every call. For large datasets, cache the result at the application layer and invalidate on new records.
No Automatic Key Rotation
DevPortal provides expiry support but does not automatically generate replacement keys. The developer portal must implement rotation workflows (e.g., email notifications before expiry, automatic generation of new keys).
API Reference & Implementation Details
Complete type definitions, function signatures, error codes, and related packages.
Full API Surface
Complete function reference with signatures and descriptions.
API Key Management
Generates a new API key with a 32-byte cryptographically random secret. Returns the ApiKey record (with SHA-256 hashed secret) and the plaintext secret (shown once).
Validates an API key by checking existence, revocation status, expiry, and SHA-256 hash match.
Revokes an API key by setting its revokedAt timestamp. Mutates the keys Map in place.
Returns all API keys as a readonly array.
Checks whether an API key includes a specific scope string.
Usage Analytics
Appends a usage record to the mutable records array.
Computes aggregated statistics: total requests, average response time, error rate, requests by endpoint, and requests by day. Supports filtering by keyId and minimum timestamp.
Returns the most recent usage records sorted newest-first. Default limit: 10.
Removes records older than maxAgeDays from the mutable array. Returns the number of records pruned.
Registry Explorer
Searches trust registry entries with case-insensitive name filtering, scope filtering, revocation filtering, pagination (offset + limit), sorted by createdAt descending.
Computes aggregate registry statistics: total entries, active count, revoked count, and breakdown by scope.
Type Reference
Complete type definitions with field descriptions.
interface ApiKey { readonly keyId: string; // UUID v4 via crypto.randomUUID() readonly hashedSecret: string; // SHA-256 hash (hex-encoded) readonly label: string; // Human-readable label readonly scopes: readonly string[]; // Permission scopes readonly createdAt: number; // Unix epoch ms readonly expiresAt: number | null; // Unix epoch ms or null (non-expiring) readonly revokedAt: number | null; // Unix epoch ms or null (active) }
interface UsageRecord { readonly keyId: string; // API key ID readonly endpoint: string; // API endpoint path (e.g., /gateway/deliver) readonly timestamp: number; // Unix epoch ms readonly responseMs: number; // Response latency in milliseconds readonly statusCode: number; // HTTP status code }
interface UsageStats { readonly totalRequests: number; // Total request count readonly avgResponseMs: number; // Average latency readonly errorRate: number; // Fraction of status >= 400 (0.0 to 1.0) readonly requestsByEndpoint: Record<string, number>; // Count by endpoint readonly requestsByDay: Record<string, number>; // Count by ISO date (YYYY-MM-DD) }
interface RegistrySearchResult { readonly did: string; // Decentralized Identifier readonly name: string; // Display name readonly scopes: readonly string[]; // Permission scopes readonly createdAt: number; // Unix epoch ms readonly revoked: boolean; // Revocation status }
Error Code Taxonomy
Complete error code reference with causes and actions.
| Code | Returned By | Cause | Action |
|---|---|---|---|
| INVALID_KEY | validateApiKey | SHA-256 hash of the provided secret does not match the stored hashedSecret | Verify the secret was copied correctly. Check for leading/trailing whitespace. |
| KEY_EXPIRED | validateApiKey | Date.now() exceeds the key's expiresAt timestamp | Generate a new key or extend the expiry date. |
| KEY_REVOKED | validateApiKey, revokeApiKey | Key's revokedAt is non-null (already revoked) | Generate a new key. Revocation is permanent. |
| NOT_FOUND | validateApiKey, revokeApiKey | The provided keyId does not exist in the keys Map | Check the keyId parameter. Verify key storage is working. |
| RATE_LIMITED | Application-level | Request rejected by rate limiter (enforced at transport layer) | Retry after exponential backoff. Check rate limit headers. |
| UNAUTHORIZED | Application-level | No valid API key provided in the request | Include API key in Authorization header or query parameter. |
Deployment Options
SaaS Recommended
Fully managed infrastructure. Call our REST API, we handle scaling, updates, and operations.
- Zero infrastructure setup
- Automatic updates
- 99.9% uptime SLA
- Enterprise SLA available
SDK Integration
Embed directly in your application. Runs in your codebase with full programmatic control.
npm install @private.me/devportal- TypeScript/JavaScript SDK
- Full source access
- Enterprise support available
On-Premise Upon Request
Enterprise CLI for compliance, air-gap, or data residency requirements.
- Complete data sovereignty
- Air-gap capable deployment
- Custom SLA + dedicated support
- Professional services included
Enterprise On-Premise Deployment
While devPortal is primarily delivered as SaaS or SDK, we build dedicated on-premise infrastructure for customers with:
- Regulatory mandates — HIPAA, SOX, FedRAMP, CMMC requiring self-hosted processing
- Air-gapped environments — SCIF, classified networks, offline operations
- Data residency requirements — EU GDPR, China data laws, government mandates
- Custom integration needs — Embed in proprietary platforms, specialized workflows
Includes: Enterprise CLI, Docker/Kubernetes orchestration, RBAC, audit logging, and dedicated support.