API Reference for Next.js Supabase SaaS Kit
Complete API reference for Makerkit's service layer. Learn how to use Account, Team Account, Authentication, Workspace, and Policy APIs to build multi-tenant SaaS applications.
Makerkit provides a service layer that abstracts database operations into clean, type-safe APIs. These services handle common SaaS operations like account management, team collaboration, authentication, and billing.
API architecture
The service layer follows a consistent pattern across all APIs:
- Create a Supabase client using
getSupabaseServerClient() - Initialize the API by passing the client to a factory function
- Call methods that return typed responses with proper error handling
import { createAccountsApi } from '@kit/accounts/api';import { getSupabaseServerClient } from '@kit/supabase/server-client';async function example() { const client = getSupabaseServerClient(); const api = createAccountsApi(client); const workspace = await api.getAccountWorkspace();}This pattern works in Server Components, Server Actions, and API Route Handlers.
Available APIs
| API | Package | Purpose |
|---|---|---|
| Account API | @kit/accounts/api | Personal account data and subscriptions |
| Team Account API | @kit/team-accounts/api | Team management, members, permissions |
| Authentication API | @kit/supabase/require-user | User authentication and session handling |
| User Workspace API | @kit/accounts/api | Personal workspace data for layouts |
| Team Workspace API | @kit/team-accounts/api | Team workspace data for layouts |
| Policies API | @kit/policies | Declarative business rules and validation |
| OTP API | @kit/otp | One-time password generation and verification |
Account API
The Account API handles personal user accounts, including subscriptions and billing.
Setup
import { createAccountsApi } from '@kit/accounts/api';import { getSupabaseServerClient } from '@kit/supabase/server-client';const client = getSupabaseServerClient();const api = createAccountsApi(client);Methods
| Method | Description |
|---|---|
getAccountWorkspace() | Get the user's personal workspace data |
loadUserAccounts() | Load all accounts the user belongs to |
getSubscription(accountId) | Get subscription data for an account |
getCustomerId(accountId) | Get the billing customer ID |
Example: Check subscription status
import { createAccountsApi } from '@kit/accounts/api';import { getSupabaseServerClient } from '@kit/supabase/server-client';async function checkSubscription(accountId: string) { const client = getSupabaseServerClient(); const api = createAccountsApi(client); const subscription = await api.getSubscription(accountId); if (!subscription || subscription.status !== 'active') { return { hasAccess: false, reason: 'No active subscription' }; } return { hasAccess: true, plan: subscription.planId };}Team Account API
The Team Account API manages team accounts, including members, invitations, and permissions.
Setup
import { createTeamAccountsApi } from '@kit/team-accounts/api';import { getSupabaseServerClient } from '@kit/supabase/server-client';const client = getSupabaseServerClient();const api = createTeamAccountsApi(client);Methods
| Method | Description |
|---|---|
getTeamAccountById(id) | Get team account by ID |
getAccountWorkspace(slug) | Get team workspace data |
getSubscription(accountId) | Get team subscription |
getOrder(accountId) | Get one-time purchase orders |
hasPermission(params) | Check user permission in team |
getMembersCount(accountId) | Get number of team members |
getCustomerId(accountId) | Get billing customer ID |
getInvitation(adminClient, token) | Retrieve invitation by token |
Example: Check team permissions
import { createTeamAccountsApi } from '@kit/team-accounts/api';import { getSupabaseServerClient } from '@kit/supabase/server-client';async function canManageBilling(accountId: string, userId: string) { const client = getSupabaseServerClient(); const api = createTeamAccountsApi(client); const hasPermission = await api.hasPermission({ accountId, userId, permission: 'billing.manage', }); return hasPermission;}Authentication API
The Authentication API verifies user identity and handles session management.
Checking authentication
Use requireUser to verify the user is authenticated:
import { redirect } from 'next/navigation';import { requireUser } from '@kit/supabase/require-user';import { getSupabaseServerClient } from '@kit/supabase/server-client';async function ProtectedPage() { const client = getSupabaseServerClient(); const auth = await requireUser(client); if (auth.error) { redirect(auth.redirectTo); } const user = auth.data; return <div>Welcome, {user.email}</div>;}The requireUser function:
- Returns user data if authenticated
- Provides a
redirectTopath for unauthenticated users - Handles MFA verification redirects automatically
Client-side authentication
Use the useUser hook in client components:
'use client';import { useUser } from '@kit/supabase/hooks/use-user';function UserProfile() { const user = useUser(); if (!user) { return <div>Loading...</div>; } return <div>{user.email}</div>;}Workspace APIs
Workspace APIs load layout data for personal and team contexts.
User Workspace
The User Workspace API provides data for personal account layouts:
import { loadUserWorkspace } from '@kit/accounts/api';import { getSupabaseServerClient } from '@kit/supabase/server-client';async function PersonalLayout({ children }) { const client = getSupabaseServerClient(); const workspace = await loadUserWorkspace(client); return ( <WorkspaceContext value={workspace}> {children} </WorkspaceContext> );}Team Workspace
The Team Workspace API provides data for team account layouts:
import { loadTeamWorkspace } from '@kit/team-accounts/api';import { getSupabaseServerClient } from '@kit/supabase/server-client';async function TeamLayout({ params, children }) { const client = getSupabaseServerClient(); const { account } = await params; const workspace = await loadTeamWorkspace(client, account); return ( <TeamWorkspaceContext value={workspace}> {children} </TeamWorkspaceContext> );}Policies API
The Policies API provides a declarative framework for business rules and validation logic.
Why use policies?
Instead of scattering validation logic across your codebase, policies centralize rules in a testable, extensible format:
import { createPolicyRegistry, definePolicy, allow, deny } from '@kit/policies';const invitationRegistry = createPolicyRegistry();invitationRegistry.registerPolicy( definePolicy({ id: 'max-team-size', stages: ['submission'], evaluate: async (context) => { if (context.team.memberCount >= 10) { return deny({ code: 'MAX_MEMBERS_REACHED', message: 'Team has reached maximum capacity', remediation: 'Upgrade your plan for more seats', }); } return allow(); }, }));Evaluating policies
import { createPoliciesEvaluator } from '@kit/policies';async function validateInvitation(context) { const evaluator = createPoliciesEvaluator(); const result = await evaluator.evaluate( invitationRegistry, context, 'ALL', // All policies must pass 'submission' ); if (!result.allowed) { throw new Error(result.reasons.join(', ')); }}OTP API
The OTP API generates and verifies one-time passwords for secure operations.
Generate a token
import { createNonce } from '@kit/otp/server';async function generateEmailVerificationToken(userId: string) { const result = await createNonce({ userId, purpose: 'email-verification', expiresInSeconds: 3600, // 1 hour }); return result.token;}Verify a token
import { verifyNonce } from '@kit/otp/server';async function verifyEmailToken(token: string, userId: string) { const result = await verifyNonce({ token, purpose: 'email-verification', userId, }); if (!result.valid) { throw new Error('Invalid or expired token'); } return result;}Using APIs in Server Actions
All APIs work seamlessly in Server Actions with the enhanceAction utility:
'use server';import { enhanceAction } from '@kit/next/actions';import { createTeamAccountsApi } from '@kit/team-accounts/api';import { getSupabaseServerClient } from '@kit/supabase/server-client';import { z } from 'zod';const InviteSchema = z.object({ teamId: z.string().uuid(), email: z.string().email(), role: z.enum(['admin', 'member']),});export const inviteMemberAction = enhanceAction( async (data, user) => { const client = getSupabaseServerClient(); const api = createTeamAccountsApi(client); // Check permission const canInvite = await api.hasPermission({ accountId: data.teamId, userId: user.id, permission: 'members.invite', }); if (!canInvite) { throw new Error('Not authorized to invite members'); } // Process invitation... return { success: true }; }, { schema: InviteSchema, auth: true, });Error handling
All APIs throw errors that should be caught and handled appropriately:
import { createAccountsApi } from '@kit/accounts/api';import { getSupabaseServerClient } from '@kit/supabase/server-client';async function getSubscriptionSafely(accountId: string) { try { const client = getSupabaseServerClient(); const api = createAccountsApi(client); const subscription = await api.getSubscription(accountId); return { data: subscription, error: null }; } catch (error) { console.error('Failed to fetch subscription:', error); return { data: null, error: 'Failed to load subscription' }; }}Best practices
1. Create APIs in the request context
Always create API instances within the request handler, not at module scope:
// Good: Created in request contextasync function handler() { const client = getSupabaseServerClient(); const api = createAccountsApi(client);}// Bad: Module-level singletonconst client = getSupabaseServerClient(); // Won't workconst api = createAccountsApi(client);2. Use RLS for authorization
APIs respect Row Level Security policies. Ensure your tables have appropriate RLS:
create policy "Users can view own accounts" on accounts for select to authenticated using (auth.uid() = id);3. Combine with data loaders
For complex pages, combine API calls efficiently:
async function DashboardPage({ params }) { const client = getSupabaseServerClient(); const { account } = await params; // Parallel data fetching const [workspace, subscription, members] = await Promise.all([ loadTeamWorkspace(client, account), createTeamAccountsApi(client).getSubscription(account), loadTeamMembers(client, account), ]); return <Dashboard data={{ workspace, subscription, members }} />;}API documentation
Explore detailed documentation for each API:
- Account API — Personal account operations
- Team Account API — Team management
- Authentication API — User authentication
- User Workspace API — Personal workspace data
- Team Workspace API — Team workspace data
- Policies API — Business rule validation
- OTP API — One-time password handling
- Registry API — Service registry patterns