> ## 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 a service integration

> Connect to external services with platform-managed credentials

Synthetiq apps integrate with external services through pre-built service clients. The platform manages credentials, authentication, and connection lifecycle — app code simply imports a client and calls methods.

The task tracker should send a Slack message to the team channel whenever a task is marked as completed. To do this, we'll create a Slack service and use it in the `updateTask` procedure.

## Create a Slack service and client

First, create a Slack service and generate a typed client using the [Services Framework](/docs/platform-docs/services-framework/overview). See [Defining a service](/docs/platform-docs/services-framework/defining-a-service) and [Using a service](/docs/platform-docs/services-framework/using-a-service).

```bash theme={null}
synthetiq service create slack
synthetiq service client slack
```

## Add the client to the app

Install the generated client in the task tracker:

```bash theme={null}
pnpm add @synthetiq/services-slack-client
```

## Use it in a procedure

When a user marks a task as completed, send a notification to Slack. Update the `updateTask` procedure in `src/server/routes/tasks.ts`:

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

export const tasksRouter = router({
  // ... other procedures

  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 }) => {
      const task = await ctx.db.task.update({
        where: { id: input.id, userId: ctx.userId },
        data: input,
      });

      if (input.status === 'completed') {
        const slack = new SlackClient();
        await slack.sendMessage({
          channel: "#task-updates",
          text: `Task completed: ${task.title}`,
        });
      }

      return task;
    }),
});
```

The client resolves credentials automatically at call time — no API keys or tokens appear in app code. Admins configure Slack credentials once via the admin UI at `/admin/services`, and the framework handles the rest.

For service access control configuration, credential models, and build-time validation details, see the [Access control reference](/docs/platform-docs/app-framework/reference/access-control).
