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

# Agents API

> Internal reference for the Agents API — covering CRUD operations, tool management, datastore connections, configuration update, and all sub-resources.

> **Internal documentation for developers.** This page documents the agents API endpoints, auth requirements, Prisma query patterns, and implementation notes. Not intended for end users.

***

## Route Map

| Method           | Endpoint                                   | Handler File                                           |
| ---------------- | ------------------------------------------ | ------------------------------------------------------ |
| `GET`            | `/api/agents`                              | `app/api/agents/route.ts`                              |
| `POST`           | `/api/agents`                              | `app/api/agents/route.ts`                              |
| `GET`            | `/api/agents/[id]`                         | `app/api/agents/[id]/route.ts`                         |
| `PATCH`          | `/api/agents/[id]`                         | `app/api/agents/[id]/route.ts`                         |
| `DELETE`         | `/api/agents/[id]`                         | `app/api/agents/[id]/route.ts`                         |
| `GET/POST`       | `/api/agents/[id]/skills`                  | `app/api/agents/[id]/skills/route.ts`                  |
| `PATCH`          | `/api/agents/[id]/skills/[skillId]`        | `app/api/agents/[id]/skills/[skillId]/route.ts`        |
| `GET`            | `/api/agents/[id]/skills/packs`            | `app/api/agents/[id]/skills/packs/route.ts`            |
| `POST`           | `/api/agents/[id]/skills/packs/apply`      | `app/api/agents/[id]/skills/packs/apply/route.ts`      |
| `POST`           | `/api/agents/[id]/skills/propose`          | `app/api/agents/[id]/skills/propose/route.ts`          |
| `POST`           | `/api/agents/[id]/skills/propose-from-rag` | `app/api/agents/[id]/skills/propose-from-rag/route.ts` |
| `POST`           | `/api/agents/[id]/skills/optimize`         | `app/api/agents/[id]/skills/optimize/route.ts`         |
| `GET/PATCH`      | `/api/agents/[id]/skills/cortex-mode`      | `app/api/agents/[id]/skills/cortex-mode/route.ts`      |
| `GET/POST/PUT`   | `/api/agents/[id]/specialists`             | `app/api/agents/[id]/specialists/route.ts`             |
| `GET`            | `/api/agents/[id]/tools`                   | `app/api/agents/[id]/tools/route.ts`                   |
| `PATCH`          | `/api/agents/[id]/tools/[toolId]`          | `app/api/agents/[id]/tools/[toolId]/route.ts`          |
| `GET/PATCH`      | `/api/agents/[id]/datastores`              | `app/api/agents/[id]/datastores/route.ts`              |
| `GET/POST/PATCH` | `/api/agents/[id]/datasources`             | `app/api/agents/[id]/datasources/route.ts`             |
| `PATCH`          | `/api/agents/[id]/visibility`              | `app/api/agents/[id]/visibility/route.ts`              |
| `POST`           | `/api/agents/[id]/reset-security`          | `app/api/agents/[id]/reset-security/route.ts`          |
| `POST`           | `/api/agents/onboarding/generate-prompt`   | `app/api/agents/onboarding/generate-prompt/route.ts`   |

***

## Auth Pattern

All agent endpoints use `withPermissionRoute` from `@zappway/lib/create-route-handler`.

```ts theme={null}
withPermissionRoute(
  request,
  {
    anyPermission: ['agents.read'],  // or 'agents.write' for mutations
    authMode: 'lightweight',         // read endpoints use lightweight
  },
  async (req: AppRouteRequest) => { ... }
)
```

**READ endpoints:** `anyPermission: ['agents.read']` + `authMode: 'lightweight'`

**WRITE endpoints:** `anyPermission: ['agents.write']` (no authMode — uses full session)

**Org isolation:** All queries must scope to `req.session.organization.id`.

***

## Core CRUD

### List Agents

```ts theme={null}
// GET /api/agents
const agents = await prisma.agent.findMany({
  where: { organizationId },
  orderBy: { updatedAt: 'desc' },
});
```

### Create Agent

```ts theme={null}
// POST /api/agents
const agent = await prisma.agent.create({
  data: {
    organizationId,
    name: data.name,
    description: data.description,
    // ...other fields
  },
});
```

### Get Agent by ID

```ts theme={null}
// GET /api/agents/[id]
const agent = await prisma.agent.findFirst({
  where: { id: agentId, organizationId },
});
if (!agent) return NextResponse.json({ error: 'Not found' }, { status: 404 });
```

***

## Skills Sub-API

### Key Library Functions

```ts theme={null}
import {
  getAvailableAgentSkillSlug,
  assertAgentBelongsToOrganization,
  normalizeAgentSkillSlug,
  AGENT_SKILL_STATUS_VALUES,
  AGENT_SKILL_ACTION_TYPES,
  listSkillPacksForAgent,
  applySkillPack,
  createAgentSkillOptimizationApprovals,
} from '@zappway/lib/agent-skills';
```

### Skill Scope Logic

```ts theme={null}
// scope: 'agent' → agentId = params.id
// scope: 'organization' → agentId = null
agentId: data.scope === 'organization' ? null : agentId,
```

### Slug Uniqueness Constraint

```
UniqueConstraint: organizationId_slug
```

When creating/updating a slug, always check for conflicts:

```ts theme={null}
const conflict = await prisma.agentSkill.findUnique({
  where: { organizationId_slug: { organizationId, slug: nextSlug } },
  select: { id: true },
});
if (conflict && conflict.id !== current.id) return 409;
```

### List Skills Response Type

```ts theme={null}
import type { AgentSkillsResponse } from '@zappway/lib/types/agent-skills';
// Response: { skills: AgentSkill[], approvals: ActionApproval[] }
```

***

## Specialists Sub-API

### Cycle Detection

The `PUT /specialists` handler detects cycles at depth-1 before creating a delegation:

```ts theme={null}
const reverseTools = await tx.tool.findMany({
  where: { agentId: targetAgentId, type: 'agent' },
  select: { config: true },
});
const hasCycle = reverseTools.some(
  (t) => (t.config as Record<string, unknown> | null)?.targetAgentId === parentAgentId
);
if (hasCycle) return 400 'CYCLE_BLOCKED';
```

### specialistRule Config Structure

```ts theme={null}
// Stored in tool.config (Prisma.InputJsonObject)
{
  targetAgentId: string,
  specialistRule: {
    toolId: string,
    targetAgentId: string,
    reason: string,
    confidence: number,
    signalsDetected: string[],
    matchedDocuments: string[],
    status: 'active',
    createdAt: string, // ISO
  }
}
```

### Deactivate: Removes specialistRule

```ts theme={null}
if (data.action === 'deactivate') {
  delete config.specialistRule;
  await prisma.tool.update({ where: { id: data.toolId }, data: { config } });
}
```

***

## Known Issues / Gotchas

* **`assertAgentBelongsToOrganization`** — Always call this before any agent-specific DB operation to prevent cross-org data leaks. It throws if the agent doesn't match the org.
* **`propose-from-rag`** uses a `skipped` counter for proposals that closely match existing skills (by slug). The threshold is defined in `@zappway/lib/agent-skills`.
* **Specialists `PUT`** is IDEMPOTENT but NOT STRICTLY ATOMIC at the DB level (see inline comment in code: `IDEMPOTENT_BUT_NOT_ATOMIC`). Under high concurrency, two parallel requests could both create a duplicate delegation. A DB-level unique index on `(agent_id, type, config->>'targetAgentId')` would fix this.
* **Status `lastReviewedAt`** is only updated when status changes to `active` or `archived`, not on general `PATCH` updates.
* **Onboarding prompt generation** — `POST /api/agents/onboarding/generate-prompt` calls the seeded RAG Builder agent first and uses the OpenRouter backup only after a no-tick timeout or a hard agent error. The backup request must use the current OpenAI-compatible output-token parameter and typed JSON/error handling.

***

## Prisma Indexes Used

| Table            | Index / Constraint                    | Used by                 |
| ---------------- | ------------------------------------- | ----------------------- |
| `AgentSkill`     | `organizationId_slug` (unique)        | Slug conflict check     |
| `AgentSkill`     | `organizationId, agentId`             | List skills per agent   |
| `Tool`           | `agentId, type`                       | Specialist tools lookup |
| `ActionApproval` | `organizationId, agentId, actionType` | Pending approvals       |

***

## Error Reference

| Error                                | Status | Cause                                              |
| ------------------------------------ | ------ | -------------------------------------------------- |
| `Agent not found for organization`   | 404    | `assertAgentBelongsToOrganization` threw           |
| `Skill not found`                    | 404    | Skill doesn't belong to agent/org                  |
| `Skill slug already exists`          | 409    | `organizationId_slug` unique constraint            |
| `An agent cannot delegate to itself` | 400    | Self-loop in `PUT /specialists`                    |
| `Delegation cycle detected`          | 400    | Depth-1 cycle in `PUT /specialists`                |
| `Tool not found or invalid`          | 404    | Tool doesn't belong to agent or `type !== 'agent'` |
