> ## Documentation Index
> Fetch the complete documentation index at: https://synthetiq.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Add backend routes

> Type-safe tRPC procedures with declarative access control and input validation

The backend API is built with [tRPC](https://trpc.io/), providing end-to-end type safety from server to client. Every procedure is a typed function with input validation, access control, and metadata — making the API self-documenting and available as an AI agent tool.

## Task routes

The task tracker needs routes to create, list, and update tasks. Create `src/server/routes/tasks.ts`:

```typescript theme={null}
import { router, scopedProcedure } from '@synthetiq/app-framework/server';
import { z } from 'zod';

export const tasksRouter = router({
  getMyTasks: scopedProcedure([])
    .meta({ description: "Get current user's tasks" })
    .query(async ({ ctx }) => {
      return ctx.db.task.findMany({
        where: { userId: ctx.userId },
        orderBy: { createdAt: 'desc' },
      });
    }),

  getAllTasks: scopedProcedure(['tasks:viewAll'])
    .meta({ description: 'Get all tasks across all users (admin)' })
    .query(async ({ ctx }) => {
      return ctx.db.task.findMany({
        include: { user: { select: { id: true, name: true } } },
        orderBy: { createdAt: 'desc' },
      });
    }),

  createTask: scopedProcedure([])
    .meta({ description: 'Create a new task' })
    .input(z.object({
      title: z.string().min(1),
      priority: z.number().min(1).max(5).optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.task.create({
        data: { title: input.title, priority: input.priority, userId: ctx.userId },
      });
    }),

  updateTask: scopedProcedure([])
    .meta({ description: 'Update a task' })
    .input(z.object({
      id: z.string(),
      title: z.string().min(1).optional(),
      status: z.enum(['active', 'completed', 'archived']).optional(),
      priority: z.number().min(1).max(5).optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.task.update({
        where: { id: input.id, userId: ctx.userId },
        data: input,
      });
    }),
});
```

Key points:

* `scopedProcedure([])` requires authentication — any logged-in user can call the procedure
* `scopedProcedure(['tasks:viewAll'])` additionally requires the `tasks:viewAll` scope
* Every procedure must include `.meta({ description })` — the build fails without it. The description is used by the AI agent, HTTP API docs, MCP server, and built-in docs pages.
* Input is validated with [Zod](https://zod.dev/) — invalid input returns a 400 error before the handler executes
* `ctx.userId` identifies the authenticated user — never accept user identity as input

## Registering routers

Register the new `tasksRouter` with the app in `src/server/router.ts`:

```typescript theme={null}
// src/server/router.ts
import { setAppRouter } from "./init";
import { router, adminRouter, userRouter, utilsRouter, aiAgentRouter,
         docsRouter, servicesRouter, oauthAdminRouter, oauthAppsRouter,
         workflowAdminRouter, logsRouter, metricsRouter } from "@synthetiq/app-framework/server";
import { tasksRouter } from "./routes/tasks";

export const appRouter = router({
  utils: utilsRouter,
  user: userRouter,
  ai: aiAgentRouter,
  admin: adminRouter,
  docs: docsRouter,
  services: servicesRouter,
  oauthAdmin: oauthAdminRouter,
  oauthApps: oauthAppsRouter,
  workflowAdmin: workflowAdminRouter,
  logs: logsRouter,
  metrics: metricsRouter,
  tasks: tasksRouter,
});

setAppRouter(appRouter);

export type AppRouter = typeof appRouter;
```

<Note>
  Always include all system routers. The built-in admin pages, docs pages, and monitoring pages depend on them.
</Note>

For the full context object, procedure type variants, streaming procedures, and MCP exclusion, see the [Procedures reference](/docs/platform-docs/app-framework/reference/procedures).
