Skip to main content

Overview

Resonance uses Clerk for authentication and provides two procedure types for protected API access:
  1. authProcedure - Requires authenticated user (userId only)
  2. orgProcedure - Requires organization context (userId + orgId)

Procedure Types

Base Procedure

Unprotected procedure available to all requests.
src/trpc/init.ts
export const baseProcedure = t.procedure.use(sentryMiddleware);

Auth Procedure

Requires a valid user session. Throws UNAUTHORIZED if user is not signed in.
src/trpc/init.ts
export const authProcedure = baseProcedure.use(async ({ next }) => {
  const { userId } = await auth();

  if (!userId) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }

  return next({
    ctx: { userId },
  });
});
ctx.userId
string
required
Clerk user ID available in the procedure context

Org Procedure

Requires both user authentication and organization membership. This is used for all voice and generation operations.
src/trpc/init.ts
export const orgProcedure = baseProcedure.use(async ({ next }) => {
  const { userId, orgId } = await auth();

  if (!userId) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }

  if (!orgId) {
    throw new TRPCError({
      code: "FORBIDDEN",
      message: "Organization required",
    });
  }

  return next({ ctx: { userId, orgId } });
});
ctx.userId
string
required
Clerk user ID
ctx.orgId
string
required
Clerk organization ID (required for voice and generation operations)

Authentication Flow

1

User Signs In

User authenticates via Clerk’s sign-in flow
2

Session Created

Clerk creates a session with userId and optional orgId
3

Middleware Validation

Each tRPC procedure validates the session:
  • authProcedure checks for userId
  • orgProcedure checks for both userId and orgId
4

Context Available

Authentication data becomes available in ctx for use in the procedure

Error Responses

UNAUTHORIZED (401)

Thrown when no valid user session exists.
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "UNAUTHORIZED"
  }
}

FORBIDDEN (403)

Thrown when user is authenticated but lacks organization context.
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Organization required"
  }
}

Usage in Routers

All Resonance API routers use orgProcedure to ensure organization-scoped access:
export const voicesRouter = createTRPCRouter({
  getAll: orgProcedure
    .input(/* ... */)
    .query(async ({ ctx, input }) => {
      // ctx.userId and ctx.orgId available here
      const voices = await prisma.voice.findMany({
        where: { orgId: ctx.orgId }
      });
      return voices;
    }),
});

Monitoring

All procedures include Sentry middleware for error tracking and performance monitoring:
const sentryMiddleware = t.middleware(
  Sentry.trpcMiddleware({
    attachRpcInput: true,
  }),
);
This automatically captures:
  • RPC input parameters
  • Error stack traces
  • Request timing information

Build docs developers (and LLMs) love