Skip to main content

Architecture

Bounty uses tRPC for type-safe API communication between the frontend and backend. This provides end-to-end type safety without code generation, ensuring that your client code is always in sync with your API.

Why tRPC?

  • Type Safety: Full TypeScript type inference from server to client
  • No Code Generation: Types are shared directly via TypeScript
  • Developer Experience: Autocomplete and type checking in your IDE
  • Runtime Safety: Request validation with Zod schemas
  • Performance: Optimized batching and caching

Core Concepts

Procedures

The Bounty API defines two types of procedures:

Queries

Used for reading data. Queries are idempotent and cacheable.
const healthCheck = await trpc.healthCheck.useQuery();
// Returns: { message: 'IM ALIVE!!!!', timestamp: string, status: 'healthy' }

Mutations

Used for creating, updating, or deleting data. Mutations have side effects.
const createBounty = await trpc.bounties.create.useMutation({
  title: 'Fix authentication bug',
  amount: 500,
  // ... other fields
});

Middleware

The API uses middleware to handle cross-cutting concerns. See packages/api/src/trpc.ts:1 for implementation details.

Available Procedure Types

No authentication required. Available to all users.Use for: Public data, health checks, unauthenticated endpoints
Requires authentication. User must have a valid session.Use for: User-specific data, authenticated operationsThrows: UNAUTHORIZED if no session exists
Requires admin role. Prevents access during impersonation.Use for: Admin panel operations, system managementThrows: FORBIDDEN if user is not admin or is impersonating
Requires early access or admin role.Use for: Beta features, early access functionalityThrows: FORBIDDEN if user lacks early access
Requires active organization membership. Provides ctx.org and ctx.orgMembership.Use for: Team-scoped operationsThrows: BAD_REQUEST if no active organization, FORBIDDEN if not a member
Requires organization owner role. Extends orgProcedure.Use for: Organization settings, billing, member managementThrows: FORBIDDEN if user is not the organization owner

Available Routers

The Bounty API is organized into domain-specific routers. See packages/api/src/routers/index.ts:1 for the complete router definition.

bounties

Bounty creation, management, applications, and submissions

user

User profile, settings, and preferences

organization

Team management, invitations, and settings

profiles

Public user profiles and reputation

notifications

User notifications and preferences

repository

GitHub repository integration and management

githubInstallation

GitHub App installation and permissions

linear

Linear integration for issue tracking

discord

Discord integration and notifications

connect

Third-party service connections

emails

Email preferences and notifications

onboarding

New user onboarding flow

featureVotes

Feature request voting system

earlyAccess

Early access program management

Utility Endpoints

The root router includes utility endpoints for health monitoring:
// Health check endpoint
trpc.healthCheck.useQuery()
// Returns: { message: 'IM ALIVE!!!!', timestamp: string, status: 'healthy' }

// Ping endpoint
trpc.ping.useQuery()
// Returns: { message: 'pong', timestamp: string, status: 'healthy' }

// Protected data example
trpc.privateData.useQuery()
// Returns: { message: 'This is private' }
// Requires authentication

Rate Limiting

Bounty implements distributed rate limiting using Upstash Redis with a sliding window algorithm. See packages/api/src/lib/ratelimiter.ts:1 for configuration.

Rate Limit Middleware

Rate-limited procedures are available for all authorization levels:
  • rateLimitedPublicProcedure(operation) - For public endpoints
  • rateLimitedProtectedProcedure(operation) - For authenticated endpoints
  • rateLimitedAdminProcedure(operation) - For admin endpoints
  • rateLimitedOrgProcedure(operation) - For organization-scoped endpoints

Operation Types

'read:default'  // 100 requests/minute
'read:list'     // 60 requests/minute
'read:detail'   // 120 requests/minute
'write:default' // 30 requests/minute
'write:create'  // 10 requests/minute
'write:update'  // 20 requests/minute
'write:delete'  // 10 requests/minute
'bounty:create'  // 5 requests/minute
'bounty:comment' // 15 requests/minute
'bounty:vote'    // 30 requests/minute
'bounty:apply'   // 10 requests/minute
'payment:verify' // 10 requests/minute
'payment:create' // 5 requests/minute

Rate Limit Response

When rate limited, the API returns a TOO_MANY_REQUESTS error with retry information:
{
  code: 'TOO_MANY_REQUESTS',
  message: 'Rate limit exceeded. Try again in 45 seconds.',
  data: {
    reason: 'rate_limited',
    retryAfter: 45,
    limit: 100,
    reset: 1234567890
  }
}

Rate Limit Headers

Successful requests include rate limit information in the context:
ctx.rateLimit = {
  remaining: 95,  // Requests remaining in window
  limit: 100,     // Total requests allowed
  reset: 1234567890  // Timestamp when limit resets
}

Error Handling

The API uses custom error formatting to provide detailed error information. See packages/api/src/trpc.ts:14 for implementation.

Error Response Format

{
  code: 'UNAUTHORIZED' | 'FORBIDDEN' | 'BAD_REQUEST' | 'TOO_MANY_REQUESTS' | ...,
  message: 'Human-readable error message',
  data: {
    reason: 'unauthenticated' | 'forbidden' | 'rate_limited' | ...,
    // Additional context based on error type
  }
}

Common Error Codes

  • UNAUTHORIZED - Authentication required but not provided
  • FORBIDDEN - User lacks permission for the requested resource
  • BAD_REQUEST - Invalid input or missing required parameters
  • TOO_MANY_REQUESTS - Rate limit exceeded
  • NOT_FOUND - Requested resource does not exist
  • INTERNAL_SERVER_ERROR - Unexpected server error

Context

Every procedure receives a context object with request metadata. See packages/api/src/context.ts:1 for implementation.
interface Context {
  session: Session | null;  // Better Auth session
  clientIP: string;         // Client IP address
  requestId: string;        // Unique request identifier
  req: NextRequest;         // Next.js request object
  db: Database;             // Drizzle ORM instance
}

IP Detection

The context extracts the client IP from multiple sources:
  1. x-forwarded-for header
  2. x-real-ip header
  3. cf-connecting-ip header (Cloudflare)
  4. Fallback to 'unknown'

Request Tracing

Each request receives a unique requestId for distributed tracing:
  • Uses existing headers from upstream (x-request-id, x-vercel-id, cf-ray)
  • Generates new ID if not present: req_{timestamp}_{random}

Type Exports

The API exports TypeScript types for client usage:
import type { AppRouter } from '@bounty/api';

// Use with tRPC client
const trpc = createTRPCClient<AppRouter>({
  // ... configuration
});

Next Steps

Authentication

Learn about Better Auth integration and session management

Router Reference

Explore individual router endpoints and schemas

Build docs developers (and LLMs) love