Version: 2.0.0
Base URL: http://localhost:3001 (Development)
Protocol: tRPC + Express REST
Last Updated: November 5, 2025
Cookie: accessToken=xxx; refreshToken=yyyAuthorization: Bearer YOUR_API_KEY1. User logs in → POST /trpc/auth.loginWithPassword
2. Server sets cookies (accessToken, refreshToken)
3. Client includes cookies in subsequent requests
4. Token expires → Auto-refresh using refreshToken
http://localhost:3001/trpc
POST /trpc/{router}.{procedure}
Content-Type: application/json
{
"input": { /* procedure input */ }
}Login with email and password
// Request
POST /trpc/auth.loginWithPassword
{
"input": {
"email": "user@example.com",
"password": "securePassword123"
}
}
// Response
{
"result": {
"data": {
"userId": "hex_user_id"
}
}
}
// Sets cookies: accessToken, refreshTokenCreate new account with email/password
// Request
POST /trpc/auth.signUpWithPassword
{
"input": {
"email": "newuser@example.com",
"password": "securePassword123",
"token": "recaptcha_token", // if DISABLE_RECAPTCHA=false
"marketingEmails": false
}
}
// Response
{
"result": {
"data": null // Success - verification email sent
}
}Note: If DISABLE_SIGN_UP=true, this will throw error
Login/Signup with Google OAuth
// Request
POST /trpc/auth.loginWithGoogle
{
"input": {
"token": "google_oauth_token",
"createAccountIfNotFound": true,
"marketingEmails": false
}
}
// Response
{
"result": {
"data": {
"userId": "hex_user_id",
"email": "user@gmail.com"
}
}
}Verify email address with token
// Request
POST /trpc/auth.verifyEmail
{
"input": {
"token": "verification_token_from_email",
"email": "user@example.com"
}
}
// Response
{
"result": {
"data": null // Success
}
}Request password reset email
// Request
POST /trpc/auth.requestPasswordReset
{
"input": {
"email": "user@example.com",
"token": "recaptcha_token"
}
}
// Response
{
"result": {
"data": null // Always succeeds (doesn't leak user existence)
}
}Reset password with token
// Request
POST /trpc/auth.resetPassword
{
"input": {
"token": "reset_token_from_email",
"email": "user@example.com",
"password": "newSecurePassword123"
}
}
// Response
{
"result": {
"data": null // Success
}
}Get current user information
// Request
POST /trpc/auth.me
{
"input": {}
}
// Response
{
"result": {
"data": {
"userId": "hex_user_id",
"email": "user@example.com",
"isPasswordAccount": true,
"marketingEmails": false,
"rsaPublicKey": "-----BEGIN PUBLIC KEY-----\n..."
}
}
}Update user preferences
// Request
POST /trpc/auth.update
{
"input": {
"marketingEmails": true
}
}
// Response
{
"result": {
"data": null // Success
}
}Delete user account permanently
// Request
POST /trpc/auth.deleteAccount
{
"input": {}
}
// Response
{
"result": {
"data": null // Success - account deleted
}
}Request password reset (authenticated users)
// Request
POST /trpc/auth.requestPasswordResetNoCaptcha
{
"input": {
"signOutAllDevices": true
}
}
// Response
{
"result": {
"data": null // Success - reset email sent
}
}Update RSA encryption keys
// Request
POST /trpc/auth.updateRsaPublicKey
{
"input": {
"rsaPublicKey": "-----BEGIN PUBLIC KEY-----\n...",
"rsaPrivateKey": "-----BEGIN PRIVATE KEY-----\n..."
}
}
// Response
{
"result": {
"data": null // Success
}
}Create new license
// Request
POST /trpc/license.create
{
"input": {
"name": "My App License",
"notes": "Production license for client XYZ",
"licenseKey": "PROD-2025-XXXX-YYYY", // Optional - auto-generated if not provided
"active": true,
"ipLimit": 5,
"licenseScope": "app1,app2",
"expirationDate": "2026-01-01T00:00:00Z", // ISO 8601
"validationLimit": 1000,
"validationPoints": 1000,
"replenishAmount": 100,
"replenishInterval": "DAY" // TEN_SECONDS | MINUTE | HOUR | DAY
}
}
// Response
{
"result": {
"data": {
"id": 123,
"licenseKey": "PROD-2025-XXXX-YYYY",
"name": "My App License",
"active": true,
"userId": 1,
"createdAt": "2025-11-05T12:00:00Z",
// ... other fields
}
}
}Get license details
// Request
POST /trpc/license.read
{
"input": {
"id": 123
}
}
// Response
{
"result": {
"data": {
"id": 123,
"licenseKey": "PROD-2025-XXXX-YYYY",
"name": "My App License",
"notes": "Production license",
"active": true,
"userId": 1,
"ipLimit": 5,
"licenseScope": "app1,app2",
"expirationDate": "2026-01-01T00:00:00Z",
"validationPoints": 850,
"validationLimit": 1000,
"replenishAmount": 100,
"replenishInterval": "DAY",
"createdAt": "2025-11-05T12:00:00Z",
"updatedAt": "2025-11-05T12:00:00Z",
"logs": [
{
"id": 1,
"result": "VALID",
"ip": "192.168.1.1",
"timestamp": "2025-11-05T13:00:00Z",
"metadata": "{\"deviceId\":\"ABC123\"}"
}
]
}
}
}Update license
// Request
POST /trpc/license.update
{
"input": {
"id": 123,
"active": false, // Deactivate license
"notes": "License suspended due to payment issue"
}
}
// Response
{
"result": {
"data": {
"id": 123,
"active": false,
// ... updated fields
}
}
}Delete license
// Request
POST /trpc/license.delete
{
"input": {
"id": 123
}
}
// Response
{
"result": {
"data": {
"id": 123,
// ... deleted license data
}
}
}List user's licenses
// Request
POST /trpc/license.list
{
"input": {
"skip": 0,
"take": 25,
"filterStatus": "active" // "all" | "active" | "inactive" | "expired"
}
}
// Response
{
"result": {
"data": [
{
"id": 123,
"licenseKey": "PROD-2025-XXXX-YYYY",
"name": "My App License",
"active": true,
// ... other fields
}
]
}
}Count active licenses
// Request
POST /trpc/license.countActive
{
"input": {}
}
// Response
{
"result": {
"data": 5 // Number of active licenses
}
}Create new API key
// Request
POST /trpc/apiKey.create
{
"input": {
"name": "Production Server API Key"
}
}
// Response
{
"result": {
"data": {
"apiKey": {
"id": 1,
"name": "Production Server API Key",
"key": "ab12...ef", // Censored version
"createdAt": "2025-11-05T12:00:00Z"
},
"uncensoredApiKey": {
"id": 1,
"name": "Production Server API Key",
"key": "ab123456-789a-bcde-f012-3456789abcde", // SAVE THIS!
"createdAt": "2025-11-05T12:00:00Z"
}
}
}
}uncensoredApiKey.key - it won't be shown again!
List API keys
// Request
POST /trpc/apiKey.list
{
"input": {}
}
// Response
{
"result": {
"data": [
{
"id": 1,
"name": "Production Server API Key",
"key": "ab12...de", // Censored
"createdAt": "2025-11-05T12:00:00Z"
}
]
}
}Delete API key
// Request
POST /trpc/apiKey.delete
{
"input": {
"id": 1
}
}
// Response
{
"result": {
"data": {
"count": 1 // Number of deleted keys
}
}
}Get quick statistics
// Request
POST /trpc/logs.quickStats
{
"input": {}
}
// Response
{
"result": {
"data": {
"activeLicenses": 5,
"successfulChecksLast7Days": 1250,
"failedChecksLast7Days": 15,
"successfulCheckPrevious7Days": 1100,
"failedCheckPrevious7Days": 20,
"lastSuccessfulCheck": "2025-11-05T13:45:00Z"
}
}
}Get validation histogram
// Request
POST /trpc/logs.histogram
{
"input": {
"interval": "day", // "minute" | "hour" | "day" | "month"
"intervalCount": 7,
"licenseId": 123 // Optional - filter by license
}
}
// Response
{
"result": {
"data": {
"histogram": [
{
"date": "2025-10-29T00:00:00Z",
"valid": 150,
"invalid": 5
},
{
"date": "2025-10-30T00:00:00Z",
"valid": 180,
"invalid": 3
}
// ... 7 days total
]
}
}
}List validation logs
// Request
POST /trpc/logs.list
{
"input": {
"filter": {
"licenseId": 123, // Optional
"result": ["VALID", "EXPIRED"] // Optional - filter by result type
},
"size": 25, // Max 100
"after": 1000, // Optional - pagination
"before": 2000 // Optional - pagination
}
}
// Response
{
"result": {
"data": [
{
"id": 1001,
"userId": 1,
"licenseId": 123,
"ip": "192.168.1.1",
"result": "VALID",
"metadata": "{\"deviceId\":\"ABC123\"}",
"timestamp": "2025-11-05T13:45:00Z",
"license": {
"name": "My App License",
"licenseKey": "PROD-2025-XXXX-YYYY"
}
}
]
}
}Validation Results:
VALID- License is validNOT_FOUND- License key not foundNOT_ACTIVE- License is inactiveEXPIRED- License has expiredLICENSE_SCOPE_FAILED- App not in allowed scopesIP_LIMIT_EXCEEDED- Too many IPs using this licenseRATE_LIMIT_EXCEEDED- Validation points exhausted
Verify email with token
// Request
POST /trpc/verification.verifyEmail
{
"input": {
"token": "a1b2c3d4e5f6..."
}
}
// Response
{
"result": {
"data": {
"success": true,
"message": "Email verified successfully! Your license has been activated.",
"userId": 123
}
}
}Resend verification email
// Request
POST /trpc/verification.resendVerification
{
"input": {
"email": "user@example.com"
}
}
// Response
{
"result": {
"data": {
"success": true,
"message": "Verification email sent successfully"
}
}
}Rate Limit: 3 requests per hour per email (recommended to implement)
Check if user email is verified
// Request
POST /trpc/verification.checkVerificationStatus
{
"input": {
"email": "user@example.com"
}
}
// Response
{
"result": {
"data": {
"exists": true,
"verified": true,
"userId": 123,
"email": "user@example.com"
}
}
}Check if verification token is valid
// Request
POST /trpc/verification.checkTokenValidity
{
"input": {
"token": "a1b2c3d4e5f6..."
}
}
// Response
{
"result": {
"data": {
"valid": true,
"expired": false,
"expiresAt": "2025-11-06T12:00:00Z",
"message": "Token is valid"
}
}
}Get current user's verification status
// Request
POST /trpc/verification.getMyVerificationStatus
{
"input": {}
}
// Response
{
"result": {
"data": {
"verified": true,
"email": "user@example.com",
"hasActiveToken": false,
"tokenExpiresAt": null
}
}
}Get verification statistics
// Request
POST /trpc/verification.getStats
{
"input": {}
}
// Response
{
"result": {
"data": {
"tokens": {
"totalTokens": 150,
"activeTokens": 25,
"expiredTokens": 100,
"usedTokens": 125
},
"users": {
"total": 500,
"verified": 450,
"unverified": 50,
"verificationRate": 90
}
}
}
}Receive Stripe payment webhooks
POST /webhooks/stripe
Content-Type: application/json
Stripe-Signature: t=xxx,v1=yyy
{
"id": "evt_xxx",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_xxx",
"amount": 9900,
"currency": "usd",
"metadata": {
"userEmail": "customer@example.com",
"planName": "Pro-Plan",
"duration": "365"
}
}
}
}Response:
{
"received": true
}Supported Events:
payment_intent.succeeded- Payment successful → Create licensecheckout.session.completed- Checkout completedpayment_intent.payment_failed- Payment failedcharge.refunded- Payment refunded → Deactivate license
Auto-Generated License Flow:
- Payment succeeds
- Find or create user by email
- Create license (inactive if new user)
- Send verification email
- User verifies → License activated
Verify license key (for your app)
POST /public/license/verify
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
{
"licenseKey": "PROD-2025-XXXX-YYYY",
"ip": "192.168.1.1",
"scope": "app1",
"metadata": {
"deviceId": "ABC123",
"version": "1.0.0"
}
}Response (Valid):
{
"valid": true,
"result": "VALID",
"license": {
"id": 123,
"active": true,
"expirationDate": "2026-01-01T00:00:00Z",
"validationPoints": 950,
"validationLimit": 1000
}
}Response (Invalid):
{
"valid": false,
"result": "EXPIRED",
"message": "License has expired"
}interface User {
id: number;
uuid: string;
email: string;
phone: string | null;
isEmailVerified: boolean;
passwordHash: string | null;
refreshSession: string | null;
createdAt: Date;
updatedAt: Date;
marketingEmails: boolean;
isAdmin: boolean;
rsaPublicKey: string;
rsaPrivateKey: string;
}interface License {
id: number;
userId: number;
licenseKey: string;
name: string;
notes: string;
active: boolean;
ipLimit: number | null;
licenseScope: string | null;
expirationDate: Date | null;
validationPoints: number | null;
validationLimit: number | null;
replenishAmount: number | null;
replenishInterval: ReplenishInterval | null;
createdAt: Date;
updatedAt: Date;
}
enum ReplenishInterval {
TEN_SECONDS = "TEN_SECONDS",
MINUTE = "MINUTE",
HOUR = "HOUR",
DAY = "DAY"
}interface WebhookEvent {
id: number;
provider: PaymentProvider; // "stripe" | "paypal" | "ziina"
eventType: string;
eventId: string;
payload: any;
status: WebhookStatus; // "pending" | "processed" | "failed"
errorMessage: string | null;
createdAt: Date;
processedAt: Date | null;
}interface PaymentLicense {
id: number;
paymentId: string;
userId: number;
licenseId: number;
amount: number;
currency: string;
provider: PaymentProvider;
metadata: any;
createdAt: Date;
}interface EmailVerificationToken {
id: number;
token: string;
userId: number;
expiresAt: Date;
used: boolean;
createdAt: Date;
}interface Log {
id: number;
userId: number;
licenseId: number;
ip: string;
result: ValidationResult;
metadata: string;
timestamp: Date;
}
enum ValidationResult {
VALID = "VALID",
NOT_FOUND = "NOT_FOUND",
NOT_ACTIVE = "NOT_ACTIVE",
EXPIRED = "EXPIRED",
LICENSE_SCOPE_FAILED = "LICENSE_SCOPE_FAILED",
IP_LIMIT_EXCEEDED = "IP_LIMIT_EXCEEDED",
RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
}{
"error": {
"message": "Error description",
"code": "ERROR_CODE",
"data": {
// Additional error details
}
}
}error.notAuthenticated- Not logged infailed-captcha- reCAPTCHA verification failedsign-up-disabled- Sign up is disableduser-not-found- User doesn't existinvalid-credentials- Wrong email/passwordlicense-not-found- License doesn't existunauthorized- No permission to access resourcevalidation-error- Input validation failedtoken-expired- Verification token expiredtoken-used- Token already used
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server/src/routers/_app';
import SuperJSON from 'superjson';
export const trpc = createTRPCProxyClient<AppRouter>({
transformer: SuperJSON,
links: [
httpBatchLink({
url: 'http://localhost:3001/trpc',
credentials: 'include', // Include cookies
}),
],
});async function login(email: string, password: string) {
try {
const result = await trpc.auth.loginWithPassword.mutate({
email,
password,
});
console.log('Logged in! User ID:', result.userId);
// Cookies are automatically set
} catch (error) {
console.error('Login failed:', error);
}
}async function createLicense() {
try {
const license = await trpc.license.create.mutate({
name: 'Pro License',
notes: 'Annual subscription',
active: true,
expirationDate: new Date('2026-11-05'),
ipLimit: 3,
});
console.log('License created:', license.licenseKey);
} catch (error) {
console.error('Failed to create license:', error);
}
}// In your verify-email page component
async function handleVerify(token: string) {
try {
const result = await trpc.verification.verifyEmail.mutate({ token });
if (result.success) {
// Show success message
alert('Email verified! Your license is now active.');
}
} catch (error) {
alert('Verification failed. Token may be expired.');
}
}import express from 'express';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
app.post(
'/webhooks/stripe',
express.raw({ type: 'application/json' }),
async (req, res) => {
const sig = req.headers['stripe-signature']!;
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
// Handle the event
await handleStripeWebhook(event);
res.json({ received: true });
} catch (err) {
res.status(400).send(`Webhook Error: ${err.message}`);
}
}
);async function verifyLicense(
licenseKey: string,
apiKey: string
): Promise<boolean> {
const response = await fetch('http://localhost:3001/public/license/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
licenseKey,
ip: await getDeviceIP(),
scope: 'mobile-app',
metadata: {
deviceId: await getDeviceId(),
platform: 'iOS',
version: '1.0.0',
},
}),
});
const data = await response.json();
return data.valid;
}1. User pays on Stripe Checkout
2. Stripe sends webhook → /webhooks/stripe
3. System creates/finds user by email
4. System creates license (inactive if new user)
5. System sends verification email
6. User clicks link → verification.verifyEmail
7. License activated ✅
1. New user signs up → auth.signUpWithPassword
2. Verification email sent automatically
3. User clicks link → verification.verifyEmail
4. Account verified ✅
1. Create license → license.create
2. View license → license.read
3. Update license → license.update
4. Deactivate/Delete → license.update/delete
5. Monitor usage → logs.list, logs.histogram
1. Create API key → apiKey.create
2. Save the uncensored key (shown once!)
3. Use in Authorization header
4. Verify licenses from your app
- Always use HTTPS in production
- Store API keys securely (environment variables)
- Validate webhook signatures (Stripe)
- Rate limit public endpoints (verification, password reset)
- Use strong passwords (min 8 chars, mixed case, numbers)
- Enable reCAPTCHA in production
- Rotate API keys periodically
- Monitor webhook events for anomalies
- Documentation: See
SETUP_COMPLETE.md - Quick Start: See
QUICKSTART_NOW.md - Implementation Guide: See
IMPLEMENTATION_GUIDE.md
Last Updated: November 5, 2025
Version: 2.0.0 - Phase 2 Complete ✅