---
name: connect-service
description: Connects an external service (Supabase, HubSpot, Google Sheets, etc.) through a Vercel API proxy route, a typed frontend service, and react-query hooks. Use when the user says "conectar servicio", "integrar API", "agregar Supabase/HubSpot/Sheets", or needs to consume any third-party API.
metadata:
  category: Implementation
---

# Skill: Connect External Service

Adds a secure integration with an external service by creating a Vercel API proxy route (server-side), a typed frontend service, and react-query hooks. API keys never reach the browser.

## When to use

- User wants to connect to an external service (Supabase, HubSpot, Google Sheets, Coefficient, Airtable, etc.)
- User says "conectar servicio", "integrar con X", "necesito datos de X"
- User needs to call a third-party API from the sandbox

## Prerequisites

- Project must exist (created via `/bootstrap-react-project`)
- **Super admin login is required** — this skill MUST NOT run on a project without it (see step 0 below)
- Read `references/CONVENTIONS.md` and `references/PATTERNS.md` before writing code

## Steps

### 0. Require super admin login (security gate — STOP if missing)

External services can expose sensitive data. This skill only runs on projects that already have the super admin login in place (Google SSO via Janus — installed by `/add-super-admin-login`).

Check for the super admin login marker:

```bash
test -f api/janus/token.ts && echo "has-super-admin" || echo "no-super-admin"
```

If the command returns `no-super-admin`, **stop immediately** and tell the user (do not proceed, do not gather requirements, do not touch any file):

> Conectar servicios externos puede exponer datos sensibles. Para mitigar el riesgo, este proyecto necesita el super admin login antes de integrar cualquier servicio.
>
> Corré `/add-super-admin-login` primero. Cuando esté listo, volvé a correr `/connect-service`.
>
> Nota: si este es un proyecto de revenue (ligado a una comunidad), `/connect-service` no aplica — el flujo de revenue usa `/add-login` + `/connect-to-humand-api` y los client IDs no son intercambiables.

Only proceed to step 1 if the check returns `has-super-admin`.

### 1. Gather requirements

Ask the user:
- **Which service?** (e.g. Supabase, HubSpot, Google Sheets)
- **What operations?** Read data, write data, or both?
- **Which specific endpoints/resources?** (e.g. "leer contactos de HubSpot", "escribir filas en Google Sheets")

> Solo voy a exponer las operaciones que necesitás, no toda la API del servicio. Esto es por seguridad.

Confirm the list of operations before proceeding.

### 2. Ensure react-query is installed

Check if `@tanstack/react-query` is already in the project:

```bash
ls node_modules/@tanstack/react-query/package.json 2>/dev/null
```

If missing, install and configure it:

```bash
bun add @tanstack/react-query
```

Then check if `QueryClientProvider` is already set up in `src/main.tsx` or `src/App.tsx`. If not, create `src/providers/QueryProvider.tsx` using `templates/QueryProvider.tsx` as the source.

Wrap the app with the provider in `src/main.tsx` (outermost provider, before Router and Theme).

### 3. Create the Vercel API proxy route

Create the file at `api/<service-name>/[...path].ts`. This is a Vercel serverless function that proxies requests to the external service.

**Pick the proxy pattern based on the service:**

| Pattern | When to use | Examples |
|---|---|---|
| **fetch-based** (default) | Service exposes a REST API and has no official Node SDK, or the SDK is unwieldy. | HubSpot v3, Google Sheets REST, Airtable, Notion REST |
| **SDK-based** | Service ships an official Node-compatible SDK that handles auth, retries, typing. | Supabase (`@supabase/supabase-js`), Stripe, Firebase Admin |

For SDK-based, import the SDK at the top of the proxy, initialize the client **once at module scope** (reused across warm invocations), and read credentials from `process.env`. The `ROUTES` table and security rules below still apply — the only difference is that each handler calls `client.xxx()` instead of `fetch(...)`.

**Security rules — enforce ALL of these:**

| Rule | Implementation |
|---|---|
| Server-side secrets only | Read API keys from `process.env.<SERVICE>_API_KEY` — NEVER from `VITE_` prefixed vars |
| Scoped endpoints | Only proxy the specific operations listed in step 1 — reject all other paths |
| Input validation | Validate request body with Zod before forwarding |
| Error sanitization | Catch external errors, return generic messages — never leak stack traces or service internals |
| Timeouts | Set a reasonable timeout (10s default) on external calls using `AbortController` |
| Response shaping | Only return the fields the frontend needs — strip metadata, tokens, internal IDs |

Use `templates/proxy.ts` as the base. Create the file at `api/<service-name>/[...path].ts` and adapt it to the specific service — each operation from step 1 becomes one entry in `ROUTES` with its own handler function.

### 4. Configure environment variables

**First check if another skill already prepared `.env.example` for this service** (e.g. `/setup-supabase` writes the Supabase block). If a `# <Service Name> integration` section already exists, validate the required vars are there and skip to step 5 — do NOT duplicate the block.

Otherwise, create or update `.env.example` at the project root using `templates/env.example` as the base (no real values).

Tell the user to add the real values:

> Para que funcione, necesitás agregar tus credenciales. Creá un archivo `.env.local` en la raíz del proyecto y agregá:
> ```
> <SERVICE>_API_KEY=tu-clave-acá
> ```
> Este archivo no se sube a git, así que tus claves están seguras.

Verify `.env.local` is in `.gitignore`. If not, add it.

### 5. Create the frontend service

Create `src/services/<service-name>.ts` with typed functions that call the proxy. These functions use axios (from the project's configured instance) and hit `/api/<service-name>/...`, NOT the external service directly.

**Types in the frontend — generalizable rule:**

If the service (or a pre-setup skill, e.g. `/setup-supabase`) generates a types file, it lives at `src/types/<service-name>.ts` (per project convention — types go in `src/types`, not alongside services). The frontend may import **types only** from there. Importing the actual SDK into `src/` is forbidden — the SDK stays server-side in the proxy. This keeps the FE bundle small and keeps credentials out of the browser.

```typescript
// ✅ Allowed in src/
import type { Database } from '../types/supabase';

// ❌ Forbidden in src/
import { createClient } from '@supabase/supabase-js';
```

Use `templates/service.ts` as the base. Create `src/services/<service-name>.ts` and adapt types and functions to the actual operations from step 1.

### 6. Create react-query hooks

Create `src/services/<service-name>.hooks.ts` with query and mutation hooks following `humand-react` conventions:

Use `templates/service.hooks.ts` as the base. Create `src/services/<service-name>.hooks.ts` and adapt query keys, hooks, and mutations to the actual operations from step 1.

### 7. Verify

Run type check and lint:

```bash
npx tsc --noEmit
bun run lint
```

Ignore TS2688 and TS5101. Fix any other errors.

### 8. Tell the user what was created

Summarize in plain language:

> Listo, conecté el servicio de [nombre]. Esto es lo que hice:
>
> - **Proxy seguro** en `/api/<service-name>/` — las credenciales quedan en el servidor, nunca llegan al navegador
> - **Servicio tipado** en `src/services/<service-name>.ts` — funciones para cada operación
> - **Hooks de react-query** en `src/services/<service-name>.hooks.ts` — listos para usar en tus componentes
>
> Para usarlo en una pantalla:
> ```tsx
> import { useGetContacts } from '../../services/<service-name>.hooks';
>
> const { contacts, isLoading } = useGetContacts();
> ```
>
> Recordá agregar tus credenciales en `.env.local` antes de probarlo.

### 9. Commit

```
Agent({
  model: "haiku",
  prompt: `
    In {projectPath}:

    1. Stage all changes: git add .
    2. Commit with message: "Integración con <ServiceName> conectada"
       Include co-author line:
       Co-Authored-By: Claude <noreply@anthropic.com>

    Report the commit hash when done.
  `
})
```

---

## Security checklist (verify before marking done)

- [ ] API keys are in `process.env`, NOT in any `VITE_` variable
- [ ] `.env.local` is in `.gitignore`
- [ ] `.env.example` has variable names but no real values
- [ ] Proxy only exposes the specific operations requested — no open passthrough
- [ ] Request bodies are validated with Zod before forwarding
- [ ] Error responses don't leak external service details
- [ ] External calls have a timeout configured
- [ ] Frontend service calls `/api/...` proxy, NOT the external URL directly
- [ ] All hooks use single-object syntax (`useQuery({ queryKey, queryFn })`)
