Skip to main content

Documentation Index

Fetch the complete documentation index at: https://grantmaster.dev/llms.txt

Use this file to discover all available pages before exploring further.

API Gateway & tRPC

StatusUpdatedCovered Files
🟢 Stable2026-02-22functions/src/api/gateway.ts, functions/src/api/routers/, functions/src/api/middleware/, functions/src/api/mcp/

Overview

The API Gateway is a single Firebase Cloud Function (europe-west1, 512 MiB, 60 s timeout) built on Hono. It serves three protocol namespaces under one endpoint:
PathProtocolPurpose
/api/trpc/*tRPCTyped RPC for the React frontend
/api/v1/*RESTPublic REST API for third-party integrations
/api/mcpMCP (Streamable HTTP)AI agent tool server

Request Flow

Client request

    ├── /api/health (unauthenticated health check)

    └── /api/{trpc,v1,mcp}/*

            ├── CORS middleware (origin-based)
            ├── Logger middleware (emulators only)
            ├── authenticate() → AuthContext
            ├── rateLimiter()
            ├── tenantIsolation() → OrgContext

            └── Route handler

Middleware Stack

CORS

Origin-based CORS with environment-aware configuration:
  • Production: Specific allowed origins
  • Emulators: Permissive * origin

Authentication

File: functions/src/api/middleware/authenticate.ts Three authentication paths, all resolving to a unified AuthContext:
PathTriggerFlow
Firebase JWTAuthorization: Bearer {token}Verify ID token → lookup employee → resolve role permissions → get org tier
API KeyX-API-Key: {key} or `Authorization: Bearer gc_(livetest)_…`SHA-256 hash lookup in apiKeys collection → scope-to-permission mapping → usage tracking
Portal Token?token={id} or portal_ prefixDirect lookup in portalTokens collection → limited read-only permissions
AuthContext shape:
interface AuthContext {
  userId: string;
  organizationId: string;
  role: SystemRole;
  permissions: Permission[];
  tier: SubscriptionTier;
  authMethod: 'firebase' | 'apiKey' | 'portal';
}

Rate Limiter

Token-bucket rate limiting per organization, with tier-based limits.

Tenant Isolation

Ensures all downstream data access is scoped to AuthContext.organizationId.

tRPC Layer

Router Architecture

File: functions/src/api/routers/index.ts The root router merges domain routers:
RouterDomainService LayerKey Procedures
healthSystemPublic health check
projectsProjectsCRUD, phase transitions, risk assessment
expensesExpensesServerExpenseServiceSubmission, approval, receipt scanning, reports
journalsJournalsServerJournalServiceTime entry, bulk operations, monthly submissions, reports
employeesUsersProfile management, capacity, certifications
grantsGrantsPipeline, applications, milestones, closeout
complianceComplianceAudit checklist, policy checks, risk matrix
procurementProcurementServerProcurementServiceRequests, vendors, purchase orders, approvals
missionMissionGoals, pillars, alignment metrics
auditAuditFindings, reports, audit log
riskRiskRisk register, matrix, mitigations

Procedure Tiers

// Anyone can call (used for health checks)
publicProcedure

// Requires valid authentication (any auth method)
protectedProcedure

// Requires specific RBAC permission(s)
permissionProcedure([Permission.VIEW_PROJECTS, Permission.EDIT_PROJECTS])

Client Integration

The React frontend connects via @trpc/react-query:
// src/lib/trpc.ts
const trpc = createTRPCReact<AppRouter>();

// In components:
const { data } = trpc.projects.list.useQuery({ status: 'active' });
const mutation = trpc.expenses.submit.useMutation();

Domain Client Package (packages/domain-client/)

The domain-client package provides typed tRPC hooks that wrap individual router procedures with React Query configuration. This package is consumed by both the main web app and the Expo mobile app, ensuring a single data access layer across all clients.
// packages/domain-client/src/expenses.ts
export function useExpenses(projectId?: string) {
  return trpc.expenses.list.useQuery({ page: 1, pageSize: 50, projectId });
}

export function useExpenseMutations() {
  const utils = trpc.useUtils();
  const create = trpc.expenses.create.useMutation({
    onSuccess: () => utils.expenses.list.invalidate(),
  });
  return { create };
}
Current domain-client modules: grants, expenses, journals, contacts, documents, projects, uploads, widgets. Each module exports:
  • Read hooksuse<Entity>List(), use<Entity>(id) — thin query wrappers with defaults
  • Mutation hooksuse<Entity>Mutations() — bundled mutations with automatic cache invalidation via trpc.useUtils()
Feature-level hooks (in src/features/<feature>/hooks/) can re-export or compose domain-client hooks with additional UI state (filters, pagination, loading).

REST API (v1)

Path: /api/v1/* RESTful endpoints for third-party integrations. Same middleware stack as tRPC with API Key authentication as the primary auth method.

Webhook Endpoints

EndpointSourcePurpose
/api/v1/webhooks/stripeStripePayment events, subscription changes
/api/v1/webhooks/postmarkPostmarkEmail delivery status, inbound emails
/api/v1/webhooks/koboKoboToolboxField data submission callbacks

MCP Server

Path: /api/mcp Model Context Protocol server for AI agent integration. Uses Streamable HTTP transport.

Tool Naming Convention

grantmaster_{domain}_{action}
Examples: grantmaster_projects_list, grantmaster_expenses_submit, grantmaster_grants_search

Tool Registration

File: functions/src/api/mcp/tools.ts Each tool declares:
PropertyDescription
namegrantmaster_{domain}_{action}
descriptionNatural language description (sent to AI)
inputSchemaJSON Schema for input validation
handlerAsync function with access to authenticated context
requiredPermissionsRBAC permissions enforced before execution

Permission Enforcement

function requirePermissions(ctx: AuthContext, perms: Permission[]): void {
  for (const p of perms) {
    if (!ctx.permissions.includes(p)) {
      throw new McpError(ErrorCode.InvalidRequest, `Missing permission: ${p}`);
    }
  }
}

Credit Tracking

AI-powered MCP tools (those invoking Gemini) track credit usage per invocation.

Deployment

The gateway is deployed as a single Cloud Function:
export const api = onRequest({
  region: 'europe-west1',
  memory: '512MiB',
  timeoutSeconds: 60,
}, app.fetch);

Environment Configuration

VariablePurpose
CORS_ORIGINSAllowed CORS origins (comma-separated)
RATE_LIMIT_RPMDefault rate limit (requests per minute)
MCP_ENABLEDEnable/disable MCP endpoint

Key Files Reference

FilePurpose
functions/src/api/gateway.tsHono app definition, middleware mounting, route registration
functions/src/api/routers/index.tsRoot tRPC router merging 6 domain routers
functions/src/api/middleware/authenticate.tsThree-path authentication middleware
functions/src/api/middleware/rateLimiter.tsToken-bucket rate limiting
functions/src/api/middleware/tenantIsolation.tsOrganization scoping middleware
functions/src/api/mcp/tools.tsMCP tool definitions and registration
functions/src/api/mcp/server.tsMCP server setup and transport
src/lib/trpc.tsFrontend tRPC client configuration