Loading...
private.me Docs
Get DevPortal
PRIVATE.ME · Technical White Paper

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.

v0.1.0 Zero npm deps TypeScript strict SHA-256 hashed secrets
Section 01

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.

Section 02

Developer Experience

Six structured error codes with actionable hints. Immutable type contracts prevent accidental mutation. Result-based error handling with no thrown exceptions.

Error detail structure
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.

Immutable type example
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;
}
Section 03

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
Section 04

Real-World Use Cases

Six scenarios where DevPortal replaces custom API key infrastructure with a zero-dependency SDK.

🌐
Platform
Self-Service API Portal

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 / ML
Agent Marketplace

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()
📊
SaaS
Usage Metering

Billing systems track API usage per customer. Daily bucketing provides month-to-date totals. Error rate analysis identifies problematic integrations.

recordUsage() + requestsByDay
🔒
Enterprise
Audit & Compliance

Key generation, revocation, and validation events feed audit logs. Scope-based access control satisfies least-privilege requirements.

revokeApiKey() + scope enforcement
📡
IoT
Device Authentication

Each IoT device receives a scoped API key during provisioning. Registry explorer shows active devices. Revocation immediately blocks compromised devices.

validateApiKey() + expiresInDays
💻
Open Source
CLI Tool Authentication

Command-line tools authenticate with API keys stored in local config. Users generate keys from the web dashboard and paste into CLI setup.

listApiKeys() + label filtering
Section 05

Solution Architecture

Three composable modules. Each module is independent but designed to work together.

Usage Analytics
Bucketed
Per-request recording (latency, status, endpoint)
Daily bucketing (YYYY-MM-DD)
Aggregate stats (avg latency, error rate)
Pruning (remove old records)
Registry Explorer
Searchable
Name filtering (case-insensitive substring)
Scope filtering (exact match)
Revocation filtering (include/exclude)
Pagination (offset + limit)
Developer Requests key Generate 32-byte secret Hash SHA-256 Store Hashed only Secret Shown once API KEY GENERATION FLOW Generate → Hash → Store (hashed) → Return (plaintext once)
Section 06

Integration

Five functions cover 90% of developer portal needs. Start with key generation, add analytics when needed, integrate registry search for multi-tenant portals.

Basic integration
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.

Persistence example (pseudocode)
// 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.

Registry search example
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);
Section 07

Security

Four cryptographic guarantees: random secret generation, irreversible hashing, constant-time comparison, and scope-based authorization.

Secret Storage
SHA-256
Immediate hashing on generation
Hex-encoded digest (64 chars)
Irreversible
Plaintext returned once
Validation
Constant-time
SHA-256 hash comparison
Timing-safe equality check
Expiry checked before hash
Revocation checked before hash
Authorization
Scoped
Scope array per key
hasScope() helper function
Least-privilege enforcement
Immutable scope list

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.

Timing-safe comparison
SHA-256 hash comparison uses constant-time equality to prevent timing attacks. An attacker cannot learn information about the hash by measuring comparison duration.
Scope inheritance
DevPortal does not implement scope hierarchies or wildcard scopes. Every scope is a literal string. If you need hierarchical scopes (e.g., admin:*), implement the logic in your authorization layer before calling hasScope().
Section 08

Benchmarks

Performance measurements on Node.js 20.x, Apple M2 Pro. All operations are sub-millisecond except SHA-256 hashing (~50µs).

<1ms
Key generation
~50µs
SHA-256 hashing
<10µs
Validation (cache hit)
<5µs
Scope checking
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
Scaling considerations
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.
Section 09

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).

Production readiness
DevPortal is a building block SDK, not a complete developer portal. You must add: persistent storage, rate limiting, key rotation workflows, email notifications, audit logging, and access control. DevPortal handles the cryptographic primitives and data structures.
Advanced Topics

API Reference & Implementation Details

Complete type definitions, function signatures, error codes, and related packages.

Appendix A1

Full API Surface

Complete function reference with signatures and descriptions.

API Key Management

generateApiKey(options: ApiKeyCreateOptions): Promise<Result<{key: ApiKey; secret: string}, PortalError>>

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).

validateApiKey(keyId: string, secret: string, keys: Map<string, ApiKey>): Promise<Result<ApiKey, PortalError>>

Validates an API key by checking existence, revocation status, expiry, and SHA-256 hash match.

revokeApiKey(keyId: string, keys: Map<string, ApiKey>): Result<ApiKey, PortalError>

Revokes an API key by setting its revokedAt timestamp. Mutates the keys Map in place.

listApiKeys(keys: Map<string, ApiKey>): readonly ApiKey[]

Returns all API keys as a readonly array.

hasScope(key: ApiKey, scope: string): boolean

Checks whether an API key includes a specific scope string.

Usage Analytics

recordUsage(record: UsageRecord, records: UsageRecord[]): void

Appends a usage record to the mutable records array.

computeStats(records: readonly UsageRecord[], options?: {keyId?: string; since?: number}): UsageStats

Computes aggregated statistics: total requests, average response time, error rate, requests by endpoint, and requests by day. Supports filtering by keyId and minimum timestamp.

getRecentUsage(records: readonly UsageRecord[], limit?: number): readonly UsageRecord[]

Returns the most recent usage records sorted newest-first. Default limit: 10.

pruneOldRecords(records: UsageRecord[], maxAgeDays: number): number

Removes records older than maxAgeDays from the mutable array. Returns the number of records pruned.

Registry Explorer

searchRegistry(entries: readonly RegistrySearchResult[], options: RegistrySearchOptions): readonly RegistrySearchResult[]

Searches trust registry entries with case-insensitive name filtering, scope filtering, revocation filtering, pagination (offset + limit), sorted by createdAt descending.

getRegistryStats(entries: readonly RegistrySearchResult[]): {total, active, revoked, byScope}

Computes aggregate registry statistics: total entries, active count, revoked count, and breakdown by scope.

Appendix A2

Type Reference

Complete type definitions with field descriptions.

ApiKey type
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)
}
UsageRecord type
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
}
UsageStats type
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)
}
RegistrySearchResult type
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
}
Appendix A3

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

📦

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
Get Started →
🏢

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
Request Quote →

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.

Contact sales for assessment and pricing →