Team Account API | Next.js Supabase SaaS Kit

Complete reference for the Team Account API in MakerKit. Manage teams, members, permissions, invitations, subscriptions, and workspace data with type-safe methods.

The Team Account API manages team accounts, members, permissions, and invitations. Use it to check user permissions, manage team subscriptions, and handle team invitations in your multi-tenant SaaS application.

Setup and initialization

Import createTeamAccountsApi from @kit/team-accounts/api and pass a Supabase server client.

import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
async function ServerComponent() {
const client = getSupabaseServerClient();
const api = createTeamAccountsApi(client);
// Use API methods
}

In Server Actions:

'use server';
import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
export async function myServerAction() {
const client = getSupabaseServerClient();
const api = createTeamAccountsApi(client);
// Use API methods
}

API Methods

getTeamAccountById

Retrieves a team account by its UUID. Also verifies the current user has access to the team.

const account = await api.getTeamAccountById(accountId);

Parameters:

ParameterTypeDescription
accountIdstringThe team account UUID

Returns:

{
id: string;
name: string;
slug: string;
picture_url: string | null;
public_data: Json | null;
primary_owner_user_id: string;
created_at: string;
updated_at: string;
} | null

Usage notes:

  • Returns null if the account doesn't exist or user lacks access
  • RLS policies ensure users only see teams they belong to
  • Use this to verify team membership before operations

getAccountWorkspace

Returns the team workspace data for a given team slug. This is the primary method for loading team context in layouts.

const workspace = await api.getAccountWorkspace(slug);

Parameters:

ParameterTypeDescription
slugstringThe team URL slug

Returns:

{
account: {
id: string;
name: string;
slug: string;
picture_url: string | null;
role: string;
role_hierarchy_level: number;
primary_owner_user_id: string;
subscription_status: 'active' | 'trialing' | 'past_due' | 'canceled' | 'unpaid' | 'incomplete' | 'incomplete_expired' | 'paused' | null;
permissions: string[];
};
accounts: Array<{
id: string;
name: string;
slug: string;
picture_url: string | null;
role: string;
}>;
}

Usage notes:

  • Called automatically in the /home/[account] layout
  • The permissions array contains all permissions for the current user in this team
  • Use role_hierarchy_level for role-based comparisons (lower = more permissions)

getSubscription

Returns the subscription data for a team account, including all line items.

const subscription = await api.getSubscription(accountId);

Parameters:

ParameterTypeDescription
accountIdstringThe team account UUID

Returns:

{
id: string;
account_id: string;
billing_provider: 'stripe' | 'lemon-squeezy' | 'paddle';
status: 'active' | 'trialing' | 'past_due' | 'canceled' | 'unpaid' | 'incomplete' | 'incomplete_expired' | 'paused';
currency: string;
cancel_at_period_end: boolean;
period_starts_at: string;
period_ends_at: string;
trial_starts_at: string | null;
trial_ends_at: string | null;
items: Array<{
id: string;
subscription_id: string;
product_id: string;
variant_id: string;
type: 'flat' | 'per_seat' | 'metered';
quantity: number;
price_amount: number;
interval: 'month' | 'year';
interval_count: number;
}>;
} | null

Example: Check per-seat limits

import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
async function canAddTeamMember(accountId: string) {
const client = getSupabaseServerClient();
const api = createTeamAccountsApi(client);
const [subscription, membersCount] = await Promise.all([
api.getSubscription(accountId),
api.getMembersCount(accountId),
]);
if (!subscription) {
// Free tier: allow up to 3 members
return membersCount < 3;
}
const perSeatItem = subscription.items.find((item) => item.type === 'per_seat');
if (perSeatItem) {
return membersCount < perSeatItem.quantity;
}
// Flat-rate plan: no seat limit
return true;
}

getOrder

Returns one-time purchase order data for team accounts using lifetime deals.

const order = await api.getOrder(accountId);

Parameters:

ParameterTypeDescription
accountIdstringThe team account UUID

Returns:

{
id: string;
account_id: string;
billing_provider: 'stripe' | 'lemon-squeezy' | 'paddle';
status: 'pending' | 'completed' | 'refunded';
currency: string;
total_amount: number;
items: Array<{
product_id: string;
variant_id: string;
quantity: number;
price_amount: number;
}>;
} | null

hasPermission

Checks if a user has a specific permission within a team account. Use this for fine-grained authorization checks.

const canManage = await api.hasPermission({
accountId: 'team-uuid',
userId: 'user-uuid',
permission: 'billing.manage',
});

Parameters:

ParameterTypeDescription
accountIdstringThe team account UUID
userIdstringThe user UUID to check
permissionstringThe permission identifier

Returns: boolean

Built-in permissions:

PermissionDescription
billing.manageManage subscription and payment methods
members.inviteInvite new team members
members.removeRemove team members
members.manageUpdate member roles
settings.manageUpdate team settings

Example: Permission-gated Server Action

'use server';
import * as z from 'zod';
import { authActionClient } from '@kit/next/safe-action';
import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
const UpdateTeamSchema = z.object({
accountId: z.string().uuid(),
name: z.string().min(2).max(50),
});
export const updateTeamSettings = authActionClient
.inputSchema(UpdateTeamSchema)
.action(async ({ parsedInput: data, ctx: { user } }) => {
const client = getSupabaseServerClient();
const api = createTeamAccountsApi(client);
const canManage = await api.hasPermission({
accountId: data.accountId,
userId: user.id,
permission: 'settings.manage',
});
if (!canManage) {
return {
success: false,
error: 'You do not have permission to update team settings',
};
}
// Update team...
return { success: true };
});

getMembersCount

Returns the total number of members in a team account.

const count = await api.getMembersCount(accountId);

Parameters:

ParameterTypeDescription
accountIdstringThe team account UUID

Returns: number | null

Example: Display team size

import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
async function TeamStats({ accountId }: { accountId: string }) {
const client = getSupabaseServerClient();
const api = createTeamAccountsApi(client);
const membersCount = await api.getMembersCount(accountId);
return (
<div>
<span className="font-medium">{membersCount}</span>
<span className="text-muted-foreground"> team members</span>
</div>
);
}

getCustomerId

Returns the billing provider customer ID for a team account.

const customerId = await api.getCustomerId(accountId);

Parameters:

ParameterTypeDescription
accountIdstringThe team account UUID

Returns: string | null


getInvitation

Retrieves invitation data from an invite token. Requires an admin client to bypass RLS for pending invitations.

const invitation = await api.getInvitation(adminClient, token);

Parameters:

ParameterTypeDescription
adminClientSupabaseClientAdmin client (bypasses RLS)
tokenstringThe invitation token

Returns:

{
id: number;
email: string;
account: {
id: string;
name: string;
slug: string;
};
role: string;
expires_at: string;
} | null

Example: Accept invitation flow

import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
async function getInvitationDetails(token: string) {
const client = getSupabaseServerClient();
const adminClient = getSupabaseServerAdminClient();
const api = createTeamAccountsApi(client);
const invitation = await api.getInvitation(adminClient, token);
if (!invitation) {
return { error: 'Invalid or expired invitation' };
}
const now = new Date();
const expiresAt = new Date(invitation.expires_at);
if (now > expiresAt) {
return { error: 'This invitation has expired' };
}
return {
teamName: invitation.account.name,
role: invitation.role,
email: invitation.email,
};
}

Real-world examples

Complete team management Server Actions

// lib/server/team-actions.ts
'use server';
import * as z from 'zod';
import { revalidatePath } from 'next/cache';
import { authActionClient } from '@kit/next/safe-action';
import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
const InviteMemberSchema = z.object({
accountId: z.string().uuid(),
email: z.string().email(),
role: z.enum(['admin', 'member']),
});
const RemoveMemberSchema = z.object({
accountId: z.string().uuid(),
userId: z.string().uuid(),
});
export const inviteMember = authActionClient
.inputSchema(InviteMemberSchema)
.action(async ({ parsedInput: data, ctx: { user } }) => {
const client = getSupabaseServerClient();
const api = createTeamAccountsApi(client);
// Check permission
const canInvite = await api.hasPermission({
accountId: data.accountId,
userId: user.id,
permission: 'members.invite',
});
if (!canInvite) {
return { success: false, error: 'Permission denied' };
}
// Check seat limits
const [subscription, membersCount] = await Promise.all([
api.getSubscription(data.accountId),
api.getMembersCount(data.accountId),
]);
if (subscription) {
const perSeatItem = subscription.items.find((i) => i.type === 'per_seat');
if (perSeatItem && membersCount >= perSeatItem.quantity) {
return {
success: false,
error: 'Team has reached maximum seats. Please upgrade your plan.',
};
}
}
// Create invitation...
const { error } = await client.from('invitations').insert({
account_id: data.accountId,
email: data.email,
role: data.role,
invited_by: user.id,
});
if (error) {
return { success: false, error: 'Failed to send invitation' };
}
revalidatePath(`/home/[account]/settings/members`, 'page');
return { success: true };
});
export const removeMember = authActionClient
.inputSchema(RemoveMemberSchema)
.action(async ({ parsedInput: data, ctx: { user } }) => {
const client = getSupabaseServerClient();
const api = createTeamAccountsApi(client);
// Cannot remove yourself
if (data.userId === user.id) {
return { success: false, error: 'You cannot remove yourself' };
}
// Check permission
const canRemove = await api.hasPermission({
accountId: data.accountId,
userId: user.id,
permission: 'members.remove',
});
if (!canRemove) {
return { success: false, error: 'Permission denied' };
}
// Check if target is owner
const account = await api.getTeamAccountById(data.accountId);
if (account?.primary_owner_user_id === data.userId) {
return { success: false, error: 'Cannot remove the team owner' };
}
// Remove member...
const { error } = await client
.from('accounts_memberships')
.delete()
.eq('account_id', data.accountId)
.eq('user_id', data.userId);
if (error) {
return { success: false, error: 'Failed to remove member' };
}
revalidatePath(`/home/[account]/settings/members`, 'page');
return { success: true };
});

Permission-based UI rendering

import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { requireUser } from '@kit/supabase/require-user';
import { redirect } from 'next/navigation';
async function TeamSettingsPage({ params }: { params: { account: string } }) {
const client = getSupabaseServerClient();
const auth = await requireUser(client);
if (auth.error) {
redirect(auth.redirectTo);
}
const api = createTeamAccountsApi(client);
const workspace = await api.getAccountWorkspace(params.account);
const permissions = {
canManageSettings: workspace.account.permissions.includes('settings.manage'),
canManageBilling: workspace.account.permissions.includes('billing.manage'),
canInviteMembers: workspace.account.permissions.includes('members.invite'),
canRemoveMembers: workspace.account.permissions.includes('members.remove'),
};
return (
<div>
<h1>Team Settings</h1>
{permissions.canManageSettings && (
<section>
<h2>General Settings</h2>
{/* Settings form */}
</section>
)}
{permissions.canManageBilling && (
<section>
<h2>Billing</h2>
{/* Billing management */}
</section>
)}
{permissions.canInviteMembers && (
<section>
<h2>Invite Members</h2>
{/* Invitation form */}
</section>
)}
{!permissions.canManageSettings &&
!permissions.canManageBilling &&
!permissions.canInviteMembers && (
<p>You don't have permission to manage this team.</p>
)}
</div>
);
}