📡

API Documentation

Complete REST API reference — Fastify 5 · Drizzle ORM · Lucia Auth · Zod validation

v7.0 · April 2026

⚙️ Tech Stack & Conventions

Framework
Fastify 5
Type-safe plugins, built-in rate limiting, schema serialisation
ORM
Drizzle ORM
Type-safe SQL, PostgreSQL dialect, schema-first migrations
Auth
Lucia Auth (session-based)
HTTP-only session cookies + opaque Bearer token fallback
Validation
Zod (@teezu/types)
Shared Zod schemas from the @teezu/types package; auto-compiled to JSON Schema for Fastify
Rate Limiting
@fastify/rate-limit
Per-route limits; Redis store in production. Default: 100 req / 1 min per IP
Realtime
Socket.IO 4 (over Fastify)
Chat rooms, stream events, WebRTC signalling

🚀 Quick Start

Base URL

https://api.teezu.online/api/v1

Authentication — Bearer token (REST)

Authorization: Bearer <session_token>

Authentication — Session cookie (browser)

Cookie: teezu_session=<opaque_session_id>

Content-Type

Content-Type: application/json

Example — fetch user profile

curl -X GET https://api.teezu.online/api/v1/users/usr_abc123 \
  -H "Authorization: Bearer <session_token>"

🔐 Auth

Session managed by Lucia Auth · cookies + Bearer fallback · Zod-validated bodies

POST /auth/register public

Register a new user account. Validates with RegisterSchema from @teezu/types.

// Request body
{
  "email": "user@example.com",
  "username": "john_doe",
  "password": "secureT33zu!123",
  "role": "viewer" // "viewer" | "creator" | "provider"
}

// 201 Created
{
  "userId": "usr_abc123",
  "sessionToken": "sess_xyz...",
  "user": { "id": "usr_abc123", "email": "user@example.com", "username": "john_doe", "role": "viewer" }
}
POST /auth/login public

Authenticate and create a new Lucia session. Returns opaque session token + sets teezu_session cookie.

// Request body
{
  "email": "user@example.com",
  "password": "secureT33zu!123"
}

// 200 OK
{
  "sessionToken": "sess_xyz...",
  "expiresAt": "2026-05-01T12:00:00Z",
  "user": { "id": "usr_abc123", "username": "john_doe", "role": "viewer" }
}
POST /auth/logout auth required

Invalidate the current session. Clears the session cookie server-side.

// No request body required

// 204 No Content
POST /auth/refresh auth required

Extend the current Lucia session. Returns a new expiresAt timestamp.

// 200 OK
{
  "sessionToken": "sess_new...",
  "expiresAt": "2026-05-15T12:00:00Z"
}

👤 Users

User records stored in users table via Drizzle ORM · PATCH uses partial Zod schema

GET /users/:id auth required

Fetch a user by ID. Public fields only unless the requester is the owner or admin.

// 200 OK
{
  "id": "usr_abc123",
  "username": "john_doe",
  "displayName": "John Doe",
  "avatarUrl": "https://cdn.teezu.online/avatars/...",
  "role": "viewer",
  "createdAt": "2026-01-10T08:00:00Z"
}
PATCH /users/:id owner / admin

Partial update of user fields. Validated with UpdateUserSchema.

// Request body (all fields optional)
{
  "displayName": "Johnny",
  "bio": "Content creator 🔥",
  "avatarUrl": "https://..."
}

// 200 OK — returns updated user object
GET /users/:id/profile auth required

Extended public profile: stats, recent content, subscription tiers and social links.

📸 Content

Media stored in S3/Cloudinary · metadata in content table · AI moderation on upload

GET /feed auth required

Personalized content feed. Supports cursor-based pagination.

// Query params
?limit=20&cursor=crs_abc&filter=all|premium|free

// 200 OK
{
  "items": [ { "id": "cnt_001", "type": "image", "url": "...", "isPremium": false, ... } ],
  "nextCursor": "crs_xyz",
  "hasMore": true
}
POST /content creator / provider

Create new content entry. Accepts multipart/form-data for file uploads via Multer pipeline (S3 → thumbnail → AI check → DB).

// multipart/form-data fields
caption        string
type           "image" | "video" | "audio"
isPremium      boolean
tierRequired   "free" | "fan" | "superfan"
file           File (binary)

// 201 Created
{
  "contentId": "cnt_abc123",
  "url": "https://cdn.teezu.online/...",
  "thumbnailUrl": "https://cdn.teezu.online/thumbs/...",
  "moderationStatus": "approved"
}
GET /content/:id auth required

Fetch a single content item with metadata, stats, and presigned CDN URL.

DELETE /content/:id owner / admin

Soft-delete content. Removes from feed; CDN asset scheduled for purge.

🎥 Livestream

WebRTC-based streams · Socket.IO signalling · state machine: idle → live → ended

POST /streams creator / provider

Create a new stream session. Returns stream credentials for WebRTC.

// Request body
{
  "title": "Evening show 🔥",
  "isPremium": true,
  "tokenPrice": 10
}

// 201 Created
{
  "streamId": "str_abc123",
  "state": "idle",
  "iceServers": [ { "urls": "stun:stun.teezu.online:3478" } ],
  "socketRoom": "stream:str_abc123"
}
GET /streams/:id auth required

Get current stream metadata, state, viewer count and host info.

PATCH /streams/:id/state host only

Transition stream state. Valid transitions: idle→live, live→frozen, live→ended, frozen→live.

// Request body
{
  "state": "live" // "idle" | "live" | "frozen" | "ended"
}

// 200 OK
{
  "streamId": "str_abc123",
  "state": "live",
  "startedAt": "2026-04-15T20:00:00Z"
}

💬 Chat

REST for history · Socket.IO for realtime delivery · messages stored in messages table

GET /conversations auth required

List all conversations for the authenticated user, ordered by last message time.

// 200 OK
{
  "conversations": [
    {
      "id": "conv_abc",
      "participant": { "id": "usr_xyz", "username": "jane", "avatarUrl": "..." },
      "lastMessage": { "content": "Hey!", "sentAt": "2026-04-14T18:30:00Z" },
      "unreadCount": 2
    }
  ]
}
POST /conversations/:id/messages auth required

Send a message. Persisted to DB and broadcast via Socket.IO send_message event.

// Request body
{
  "content": "Hello! 👋",
  "type": "text" // "text" | "image" | "tip" | "unlock"
}

// 201 Created
{
  "messageId": "msg_001",
  "conversationId": "conv_abc",
  "content": "Hello! 👋",
  "type": "text",
  "sentAt": "2026-04-15T09:10:00Z"
}

🪙 Tokens

In-app token economy · wallet balances in wallets table · escrow via WalletService

GET /tokens/balance auth required

Get the authenticated user's current token balance.

// 200 OK
{
  "balance": 4250,
  "held": 200,        // tokens in escrow (pending bookings)
  "available": 4050,
  "currency": "TZT"
}
POST /tokens/tip auth required

Send tokens as a tip to a creator or during a stream. Triggers Socket.IO tip event.

// Request body
{
  "recipientId": "usr_creator42",
  "amount": 100,
  "context": "stream",      // "stream" | "chat" | "content"
  "contextId": "str_abc123" // optional reference ID
}

// 200 OK
{
  "transactionId": "txn_tip_001",
  "newBalance": 4150,
  "recipient": { "id": "usr_creator42", "username": "creator42" }
}
POST /tokens/purchase auth required

Initiate a token purchase via payment gateway. Returns a checkout URL.

// Request body
{
  "packageId": "pkg_500",   // predefined token package
  "paymentMethod": "card"   // "card" | "crypto" | "ideal"
}

// 200 OK
{
  "checkoutUrl": "https://checkout.teezu.online/pay/sess_xyz",
  "expiresAt": "2026-04-15T10:00:00Z"
}

🤖 AI

AI module routes — mood detection, matching engine, generative responses

POST /ai/mood auth required

Detect mood / sentiment from a text or media input. Used to personalise feed and recommendations.

// Request body
{
  "input": "Feeling adventurous tonight 🔥",
  "type": "text" // "text" | "image_url"
}

// 200 OK
{
  "mood": "adventurous",
  "confidence": 0.91,
  "tags": ["energetic", "social", "bold"]
}
POST /ai/match auth required

Run the matching engine for the current user. Returns ranked creator / provider suggestions.

// Request body (all optional)
{
  "limit": 10,
  "filters": { "role": "provider", "tags": ["fitness"] }
}

// 200 OK
{
  "matches": [
    { "userId": "usr_xyz", "score": 0.97, "reasons": ["shared interests", "high rating"] }
  ]
}
POST /ai/generate creator / provider

Generate AI-assisted content: captions, bio suggestions, chat openers.

// Request body
{
  "type": "caption",     // "caption" | "bio" | "opener"
  "context": "sunset photo on the beach",
  "tone": "playful"
}

// 200 OK
{
  "generated": "Golden hour hits different when you're living your best life 🌅🔥",
  "alternatives": ["Sun, sea, and good vibes only ✨", "The beach called — I answered 🐚"]
}

🗓️ Providers & Booking

Provider profiles + booking flow · availability check → escrow → confirmation

GET /providers auth required

List available providers. Supports filters and sorting.

// Query params
?category=fitness&minRating=4&sort=popularity&limit=20

// 200 OK
{
  "providers": [
    { "id": "usr_prov1", "username": "pro_jane", "category": "fitness", "rating": 4.8, "tokenRate": 50 }
  ],
  "total": 142
}
GET /providers/:id auth required

Full provider profile: bio, services, availability slots, reviews, token rate.

POST /providers/:id/book auth required

Book a provider session. Pipeline: availability check → wallet balance check → escrow (WalletService.holdFunds()) → booking record (pending).

// Request body
{
  "slotId": "slot_2026041520",   // from provider availability
  "durationMinutes": 30,
  "note": "Looking forward to it!"
}

// 201 Created
{
  "bookingId": "bkg_abc123",
  "status": "pending",
  "provider": { "id": "usr_prov1", "username": "pro_jane" },
  "slot": { "start": "2026-04-15T20:00:00Z", "end": "2026-04-15T20:30:00Z" },
  "tokensHeld": 1500,
  "confirmBy": "2026-04-15T18:00:00Z"
}

⚠️ Error Responses

All errors follow the Fastify error shape with a code field for programmatic handling.

// Standard error body
{
  "statusCode": 422,
  "error": "Unprocessable Entity",
  "code": "VALIDATION_ERROR",
  "message": "email: Invalid email address",
  "details": [ { "field": "email", "message": "Invalid email address" } ]
}
400 Bad Request
Malformed JSON body or missing required fields
401 Unauthorized
Missing, expired, or invalid session token
403 Forbidden
Authenticated but insufficient role / ownership
404 Not Found
Resource does not exist or is soft-deleted
409 Conflict
Duplicate resource (e.g. username already taken)
422 Unprocessable Entity
Zod schema validation failed — check details[]
429 Too Many Requests
Rate limit exceeded — see Retry-After header
500 Internal Server Error
Unexpected server error — correlate with X-Request-Id header

🚦 Rate Limiting

Implemented via @fastify/rate-limit with a Redis store in production. Limits are per authenticated user (by session) or per IP for public routes.

100
requests / min
Global default
10
requests / min
Auth routes (login / register)
20
requests / min
AI endpoints
// Rate limit response headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1744900800
Retry-After: 38   // only on 429