# TSKR Platform API Documentation

**Version:** 2.0.0
**Base URL:** `https://api.tskr.ai`

## Overview

The TSKR Platform API provides enterprise-grade REST APIs for building AI-powered workflows. This documentation is designed to be both human and agent-readable.

## Authentication

### Certificate-based JWT

The API uses RSA certificate-based JWT authentication. Your account manager will provide you with a private key certificate that you'll use to sign JWTs.

**How it works:**
1. Certificates are created via the TSKR platform UI
2. Your account manager shares the private key certificate with you
3. The public key is stored on TSKR servers for JWT verification
4. You sign JWTs with your private key to authenticate API requests

**Header Format:**
```
Authorization: Bearer <jwt-token>
```

**JWT Header:**
- `alg`: "RS256" (required)
- `kid`: Your key ID (provided by your account manager)

### Super Admin Access

Certain administrative endpoints require super admin privileges. Super admin access is granted automatically to users with `@skej.com` email addresses. These endpoints are used for managing skills, skill categories, task templates, and agents.

### API Types & JWT Requirements

The API is divided into two categories based on JWT payload requirements:

#### Management APIs
Account-level operations that don't require user context (Assistants, Users).

**JWT Payload:**
```json
{
  "iat": 1234567890,
  "exp": 1234571490
}
```

**Example:**
```javascript
import * as jose from 'jose';

const privateKey = await jose.importPKCS8(privateKeyPem, 'RS256');

const jwt = await new jose.SignJWT({})
  .setProtectedHeader({ alg: 'RS256', kid: 'your-key-id' })
  .setIssuedAt()
  .setExpirationTime('1h')
  .sign(privateKey);

// Use in Management API requests
fetch('https://api.tskr.ai/assistants', {
  headers: { 'Authorization': `Bearer ${jwt}` }
});
```

#### Delegated APIs
User-context operations that require identifying which user's data to operate on (Chats, Tasks).

**JWT Payload:**
```json
{
  "email": "user@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "iat": 1234567890,
  "exp": 1234571490
}
```

**Fields:**
- `email`: User email address (required)
- `firstName`: User first name (optional)
- `lastName`: User last name (optional)
- `iat`: Issued at timestamp (auto-generated)
- `exp`: Expiration timestamp (recommended: 1 hour)

**Example:**
```javascript
import * as jose from 'jose';

const privateKey = await jose.importPKCS8(privateKeyPem, 'RS256');

const jwt = await new jose.SignJWT({
  email: 'user@example.com',
  firstName: 'John',
  lastName: 'Doe'
})
  .setProtectedHeader({ alg: 'RS256', kid: 'your-key-id' })
  .setIssuedAt()
  .setExpirationTime('1h')
  .sign(privateKey);

// Use in Delegated API requests
fetch('https://api.tskr.ai/chats', {
  headers: { 'Authorization': `Bearer ${jwt}` }
});
```

**Auto-provisioning:** Users are automatically created from JWT claims if they don't exist (Delegated APIs only).

## Response Format

The API uses standardized response formats for all endpoints:

### List Responses (with pagination)
```json
{
  "ok": true,
  "resourceName": [...],
  "page": 1,
  "perPage": 20,
  "total": 100
}
```

### List Responses (without pagination)
```json
{
  "ok": true,
  "resourceName": [...]
}
```

### Single Resource (show/create/update)
```json
{
  "ok": true,
  "resourceName": { ... }
}
```

### Delete Response
```json
{
  "ok": true,
  "resourceName": {
    "id": "...",
    "message": "..."
  }
}
```

### Error Response
```json
{
  "error": "Error message",
  "error_code": "error_type"
}
```

**Note:** `resourceName` varies by endpoint (e.g., `chats`, `chat`, `tasks`, `task`, `users`, `user`, etc.)

## HTTP Status Codes

- `200` - OK
- `201` - Created
- `400` - Bad Request
- `401` - Unauthorized
- `403` - Forbidden
- `404` - Not Found
- `409` - Conflict (e.g., resource already processing)
- `500` - Internal Server Error

---

# Delegated APIs

Delegated APIs require user context (`email` in JWT payload). These APIs operate at the account level, returning data for all users in the account. Individual endpoints may support filtering by user when needed.

---

## Chats API

Manage conversations between users and AI assistants.

### List Chats

**Endpoint:** `GET /chats`

**Description:** List all chats for the account (without messages). Returns chats across all users in the account.

**Query Parameters:**
- `status` (string, optional) - Filter by status: active, archived (default: active)
- `assistantId` (string, optional) - Filter by assistant ID
- `metadata.*` (string, optional) - Filter by metadata fields (e.g., metadata.source=dashboard-chat)
- `page` (integer, optional) - Page number (default: 1)
- `perPage` (integer, optional) - Items per page (default: 20, max: 100)

**Response:**
```json
{
  "ok": true,
  "chats": [
    {
      "id": "chat_123",
      "title": "Dashboard Chat",
      "status": "active",
      "messageCount": 5,
      "assistant": {
        "id": "asst_456",
        "name": "AI Assistant",
        "email": "ai@example.com"
      },
      "metadata": { "source": "dashboard-chat" },
      "lastMessageAt": "2026-02-11T10:00:00Z",
      "createdAt": "2026-02-10T09:00:00Z"
    }
  ]
}
```

### Create Chat

**Endpoint:** `POST /chats`

**Description:** Create a new chat with an AI assistant.

**Request Body:**
```json
{
  "assistantId": "asst_456",
  "title": "New Chat",
  "metadata": { "source": "dashboard-chat" }
}
```

**Parameters:**
- `assistantId` (string, required) - ID of the assistant
- `title` (string, optional) - Chat title (auto-generated if omitted)
- `metadata` (object, optional) - Custom metadata

**Response:**
```json
{
  "ok": true,
  "chat": {
    "id": "chat_789",
    "title": "New Chat",
    "status": "active",
    "messageCount": 0,
    "createdAt": "2026-02-11T10:00:00Z"
  }
}
```

### Get Chat

**Endpoint:** `GET /chats/:chatId`

**Description:** Get chat details (without messages - use Chat Messages endpoints).

**Path Parameters:**
- `chatId` (string, required) - The chat ID

**Response:**
```json
{
  "ok": true,
  "chat": {
    "id": "chat_123",
    "title": "Dashboard Chat",
    "status": "active",
    "messageCount": 5,
    "assistant": {
      "id": "asst_456",
      "name": "AI Assistant",
      "email": "ai@example.com"
    },
    "metadata": { "source": "dashboard-chat" },
    "lastMessageAt": "2026-02-11T10:00:00Z",
    "createdAt": "2026-02-10T09:00:00Z"
  }
}
```

### Process Chat

**Endpoint:** `POST /chats/:chatId/process`

**Description:** Process chat and generate AI assistant response. Uses Redis distributed locks (30s TTL) to prevent concurrent processing.

**Smart Processing Features:**
- **Ignore Check:** In group conversations (audience > 1), determines if message is directed at assistant. Directly addressed messages always processed.
- **Status-based CC:** Clarification requests (`needs_clarification` status) exclude CC recipients for privacy
- **Platform-aware:** Email responses include to/cc/subject fields; other platforms (SMS, WhatsApp, Slack, Teams, Chat) use simplified structure
- **Message Suppression:** Email responses with tasks are suppressed (`message: null`) - tasks handle communication

**Path Parameters:**
- `chatId` (string, required) - The chat ID

**Response (Email Platform):**
```json
{
  "ok": true,
  "message": {
    "to": ["sender@example.com"],
    "cc": ["cc1@example.com", "cc2@example.com"],
    "subject": "Re: Original Subject",
    "content": "AI response here...",
    "platform": "email"
  },
  "taskIds": ["task_123"]
}
```

**Response (Non-Email Platform - SMS, WhatsApp, Slack, Teams, Chat):**
```json
{
  "ok": true,
  "message": {
    "to": ["sender@example.com", "cc1@example.com"],
    "content": "AI response here...",
    "platform": "sms"
  },
  "taskIds": ["task_123"]
}
```

**Response (Message Ignored):**
```json
{
  "ok": true,
  "message": null,
  "taskIds": [],
  "ignored": true
}
```

**Response (Message Suppressed - Email Platform with Tasks):**
```json
{
  "ok": true,
  "message": null,
  "taskIds": ["task_123", "task_456"],
  "suppressed": true
}
```

**Error Response (409 - Already Processing):**
```json
{
  "error": "Chat is already being processed",
  "error_code": "chat_processing"
}
```

---

## Chat Messages API

Full CRUD operations on chat messages.

### List Messages

**Endpoint:** `GET /chats/:chatId/messages`

**Description:** List all messages in a chat.

**Path Parameters:**
- `chatId` (string, required) - The chat ID

**Query Parameters:**
- `page` (integer, optional) - Page number (default: 1)
- `perPage` (integer, optional) - Items per page (default: 20, max: 100)

**Response:**
```json
{
  "ok": true,
  "messages": [
    {
      "id": "msg_123",
      "role": "user",
      "content": "Hello!",
      "files": [],
      "timestamp": "2026-02-11T10:00:00Z",
      "metadata": {}
    }
  ],
  "total": 1,
  "page": 1,
  "perPage": 20
}
```

### Add Message

**Endpoint:** `POST /chats/:chatId/messages`

**Description:** Add a message to a chat.

**Path Parameters:**
- `chatId` (string, required) - The chat ID

**Request Body:**
```json
{
  "content": "Hello!",
  "role": "user",
  "files": []
}
```

**Parameters:**
- `content` (string, required) - Message content
- `role` (string, optional) - Message role: user, assistant (default: user)
- `platform` (string, optional) - Platform: email, sms, whatsapp, slack, teams, chat
- `files` (array, optional) - File attachments
- `metadata` (object, optional) - Additional metadata

**Response:**
```json
{
  "ok": true,
  "message": {
    "id": "msg_123",
    "role": "user",
    "content": "Hello!",
    "files": [],
    "timestamp": "2026-02-11T10:00:00Z",
    "metadata": {}
  }
}
```

### Get Message

**Endpoint:** `GET /chats/:chatId/messages/:messageId`

**Description:** Get a specific message.

**Path Parameters:**
- `chatId` (string, required) - The chat ID
- `messageId` (string, required) - The message ID

**Response:**
```json
{
  "ok": true,
  "message": {
    "id": "msg_123",
    "role": "user",
    "content": "Hello!",
    "files": [],
    "timestamp": "2026-02-11T10:00:00Z",
    "metadata": {}
  }
}
```

### Update Message

**Endpoint:** `PUT /chats/:chatId/messages/:messageId`

**Description:** Update a message.

**Path Parameters:**
- `chatId` (string, required) - The chat ID
- `messageId` (string, required) - The message ID

**Request Body:**
```json
{
  "content": "Updated content",
  "files": [],
  "metadata": {}
}
```

**Parameters:**
- `content` (string, optional) - Updated message content
- `files` (array, optional) - Updated file attachments
- `metadata` (object, optional) - Updated metadata (merged with existing)

**Response:**
```json
{
  "ok": true,
  "message": {
    "id": "msg_123",
    "role": "user",
    "content": "Updated content",
    "files": [],
    "timestamp": "2026-02-11T10:00:00Z",
    "metadata": {}
  }
}
```

### Delete Message

**Endpoint:** `DELETE /chats/:chatId/messages/:messageId`

**Description:** Delete a specific message.

**Path Parameters:**
- `chatId` (string, required) - The chat ID
- `messageId` (string, required) - The message ID

**Response:**
```json
{
  "ok": true,
  "message": {
    "id": "msg_123",
    "message": "Message deleted successfully"
  }
}
```

### Clear All Messages

**Endpoint:** `DELETE /chats/:chatId/messages`

**Description:** Clear all messages in a chat (bulk delete).

**Path Parameters:**
- `chatId` (string, required) - The chat ID

**Response:**
```json
{
  "ok": true,
  "messages": {
    "id": "chat_123",
    "message": "All messages cleared successfully"
  }
}
```

---

## Tasks API

Manage tasks created by AI assistants.

### List Tasks

**Endpoint:** `GET /tasks`

**Description:** List all tasks for the account. Returns tasks across all users in the account by default. Use `?mine=true` to filter by tasks where the authenticated user is owner or follower.

**Query Parameters:**
- `status` (string, optional) - Filter by status: pending, in_progress, completed, failed, on_hold, needs_clarification
- `type` (string, optional) - Filter by task type
- `mine` (boolean, optional) - Filter to tasks where user is owner/follower (default: false)
- `page` (integer, optional) - Page number (default: 1)
- `perPage` (integer, optional) - Items per page (default: 20, max: 100)

**Response:**
```json
{
  "ok": true,
  "tasks": [
    {
      "id": "task_123",
      "title": "Schedule team meeting",
      "description": "Set up a weekly sync with the engineering team",
      "status": "pending",
      "type": "task",
      "createdAt": "2026-02-03T10:00:00.000Z",
      "updatedAt": "2026-02-03T10:00:00.000Z"
    }
  ],
  "page": 1,
  "perPage": 20,
  "total": 1
}
```

### Get Task

**Endpoint:** `GET /tasks/:taskId`

**Description:** Get detailed information about a specific task.

**Path Parameters:**
- `taskId` (string, required) - The task ID

**Response:**
```json
{
  "ok": true,
  "task": {
    "id": "task_123",
    "title": "Schedule team meeting",
    "description": "Set up a weekly sync with the engineering team",
    "status": "pending",
    "type": "task",
    "subtasks": [],
    "activities": [
      {
        "id": "activity_1",
        "type": "status_change",
        "content": "Task created",
        "createdAt": "2026-02-03T10:00:00.000Z"
      }
    ],
    "meetings": [],
    "createdAt": "2026-02-03T10:00:00.000Z",
    "updatedAt": "2026-02-03T10:00:00.000Z"
  }
}
```

### Create Task

**Endpoint:** `POST /tasks`

**Description:** Create a new task.

**Request Body:**
```json
{
  "title": "New task",
  "description": "Task description",
  "type": "task",
  "status": "pending"
}
```

**Response:**
```json
{
  "ok": true,
  "task": {
    "id": "task_456",
    "title": "New task",
    "description": "Task description",
    "status": "pending",
    "type": "task",
    "createdAt": "2026-02-11T10:00:00Z"
  }
}
```

### Update Task

**Endpoint:** `PUT /tasks/:taskId`

**Description:** Update task properties.

**Path Parameters:**
- `taskId` (string, required) - The task ID

**Request Body:**
```json
{
  "title": "Updated title",
  "description": "Updated description",
  "status": "completed",
  "summary": "Task summary"
}
```

**Parameters:**
- `title` (string, optional) - Task title
- `description` (string, optional) - Task description
- `status` (string, optional) - Task status
- `summary` (string, optional) - Task summary

**Response:**
```json
{
  "ok": true,
  "task": {
    "id": "task_123",
    "title": "Updated title",
    "status": "completed",
    "summary": "Task summary",
    "updatedAt": "2026-02-11T10:00:00Z"
  }
}
```

### Process Task

**Endpoint:** `POST /tasks/:taskId/process`

**Description:** Add a message to a task and run the AI agent to process it.

**Path Parameters:**
- `taskId` (string, required) - The task ID

**Request Body:**
```json
{
  "message": "Please complete this task",
  "files": []
}
```

**Parameters:**
- `message` (string, optional) - User message to add to the task
- `files` (array, optional) - File attachments (images)

**Response:**
```json
{
  "ok": true,
  "task": {
    "id": "task_123",
    "message": "I'll work on completing this task right away."
  }
}
```

### Delete Task

**Endpoint:** `DELETE /tasks/:taskId`

**Description:** Delete a task and all associated data permanently.

**Path Parameters:**
- `taskId` (string, required) - The task ID

**Response:**
```json
{
  "ok": true,
  "task": {
    "id": "task_123",
    "message": "Task deleted successfully"
  }
}
```

---

## Memories API

Store and retrieve user memories, preferences, facts, and contextual information.

### List Memories

**Endpoint:** `GET /memories`

**Description:** List all memories for the authenticated user.

**Query Parameters:**
- `type` (string, optional) - Filter by memory type: note, preference, fact, context
- `tags` (string, optional) - Filter by tags (comma-separated)
- `search` (string, optional) - Search in title and content
- `page` (integer, optional) - Page number (default: 1)
- `perPage` (integer, optional) - Items per page (default: 20, max: 100)

**Response:**
```json
{
  "ok": true,
  "memories": [
    {
      "id": "mem_123",
      "title": "Preferred Communication Time",
      "content": "User prefers to be contacted between 9 AM - 5 PM EST",
      "type": "preference",
      "tags": ["communication", "scheduling"],
      "createdAt": "2026-02-01T10:00:00Z",
      "updatedAt": "2026-02-01T10:00:00Z"
    }
  ],
  "page": 1,
  "perPage": 20,
  "total": 1
}
```

### Create Memory

**Endpoint:** `POST /memories`

**Description:** Create a new memory.

**Request Body:**
```json
{
  "title": "Meeting Notes",
  "content": "Discussed Q1 objectives and team goals",
  "type": "note",
  "tags": ["meeting", "Q1"]
}
```

**Parameters:**
- `title` (string, required) - Memory title
- `content` (string, required) - Memory content/details
- `type` (string, optional) - Memory type (default: note)
- `tags` (array, optional) - Tags for categorization
- `metadata` (object, optional) - Additional metadata

**Response:**
```json
{
  "ok": true,
  "memory": {
    "id": "mem_456",
    "title": "Meeting Notes",
    "content": "Discussed Q1 objectives and team goals",
    "type": "note",
    "tags": ["meeting", "Q1"],
    "createdAt": "2026-02-11T10:00:00Z"
  }
}
```

### Get Memory

**Endpoint:** `GET /memories/:memoryId`

**Description:** Get details of a specific memory.

**Path Parameters:**
- `memoryId` (string, required) - The memory ID

**Response:**
```json
{
  "ok": true,
  "memory": {
    "id": "mem_123",
    "title": "Preferred Communication Time",
    "content": "User prefers to be contacted between 9 AM - 5 PM EST",
    "type": "preference",
    "tags": ["communication", "scheduling"],
    "metadata": {},
    "createdAt": "2026-02-01T10:00:00Z",
    "updatedAt": "2026-02-01T10:00:00Z"
  }
}
```

### Update Memory

**Endpoint:** `PUT /memories/:memoryId`

**Description:** Update memory properties.

**Path Parameters:**
- `memoryId` (string, required) - The memory ID

**Request Body:**
```json
{
  "content": "User prefers to be contacted between 10 AM - 6 PM EST",
  "tags": ["communication", "scheduling", "updated"]
}
```

**Parameters:**
- `title` (string, optional) - Memory title
- `content` (string, optional) - Memory content
- `type` (string, optional) - Memory type
- `tags` (array, optional) - Tags array
- `metadata` (object, optional) - Additional metadata

**Response:**
```json
{
  "ok": true,
  "memory": {
    "id": "mem_123",
    "title": "Preferred Communication Time",
    "content": "User prefers to be contacted between 10 AM - 6 PM EST",
    "type": "preference",
    "tags": ["communication", "scheduling", "updated"],
    "updatedAt": "2026-02-11T10:00:00Z"
  }
}
```

### Delete Memory

**Endpoint:** `DELETE /memories/:memoryId`

**Description:** Delete a memory permanently.

**Path Parameters:**
- `memoryId` (string, required) - The memory ID

**Response:**
```json
{
  "ok": true,
  "memory": {
    "id": "mem_123",
    "message": "Memory deleted successfully"
  }
}
```

---

## Contacts API

Manage user contacts with comprehensive contact information.

### List Contacts

**Endpoint:** `GET /contacts`

**Description:** List all contacts for the authenticated user.

**Query Parameters:**
- `search` (string, optional) - Search in name, email, company
- `tags` (string, optional) - Filter by tags (comma-separated)
- `company` (string, optional) - Filter by company
- `page` (integer, optional) - Page number (default: 1)
- `perPage` (integer, optional) - Items per page (default: 20, max: 100)

**Response:**
```json
{
  "ok": true,
  "contacts": [
    {
      "id": "contact_123",
      "name": "John Doe",
      "email": "john@acme.com",
      "phone": "+1-555-0123",
      "company": "Acme Corp",
      "title": "Engineering Manager",
      "tags": ["client", "engineering"],
      "createdAt": "2026-02-01T10:00:00Z"
    }
  ],
  "page": 1,
  "perPage": 20,
  "total": 1
}
```

### Create Contact

**Endpoint:** `POST /contacts`

**Description:** Create a new contact.

**Request Body:**
```json
{
  "name": "Jane Smith",
  "email": "jane@example.com",
  "phone": "+1-555-0456",
  "company": "Tech Co",
  "title": "Product Manager",
  "tags": ["partner", "product"]
}
```

**Parameters:**
- `name` (string, required) - Contact full name
- `email` (string, optional) - Contact email
- `phone` (string, optional) - Contact phone
- `company` (string, optional) - Company name
- `title` (string, optional) - Job title
- `notes` (string, optional) - Notes
- `tags` (array, optional) - Tags array
- `metadata` (object, optional) - Additional metadata

**Response:**
```json
{
  "ok": true,
  "contact": {
    "id": "contact_456",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+1-555-0456",
    "company": "Tech Co",
    "title": "Product Manager",
    "tags": ["partner", "product"],
    "createdAt": "2026-02-11T10:00:00Z"
  }
}
```

### Bulk Import Contacts

**Endpoint:** `POST /contacts/bulk-import`

**Description:** Import up to 1000 contacts in a single request. Automatically creates new contacts and updates existing ones (matched by email).

**Request Body:**
```json
{
  "contacts": [
    {
      "email": "john@example.com",
      "name": "John Doe",
      "phone": "+1234567890",
      "company": "Acme Corp",
      "title": "CEO",
      "timezone": "America/New_York",
      "emails": ["john@example.com", "john.doe@acme.com"]
    },
    {
      "email": "jane@example.com",
      "name": "Jane Smith",
      "company": "Tech Co"
    }
  ]
}
```

**Parameters:**
- `contacts` (array, required) - Array of contact objects (max 1000)
  - `email` (string, required) - Contact email (used for uniqueness check)
  - `name` (string, optional) - Contact name (defaults to email)
  - `phone` (string, optional) - Phone number
  - `company` (string, optional) - Company name
  - `title` (string, optional) - Job title
  - `timezone` (string, optional) - Timezone
  - `emails` (array, optional) - Additional email addresses

**Response:**
```json
{
  "ok": true,
  "contacts": {
    "total": 100,
    "created": 85,
    "updated": 10,
    "skipped": 5,
    "errors": [
      {
        "index": 42,
        "email": "invalid",
        "error": "Email is required"
      }
    ]
  }
}
```

**Features:**
- Validates up to 1000 contacts per request
- Detects duplicates within the batch
- Updates existing contacts (by email)
- Creates new contacts
- Links to registered users automatically
- Uses bulk operations for performance
- Returns detailed results with error handling

### Get Contact

**Endpoint:** `GET /contacts/:contactId`

**Description:** Get details of a specific contact.

**Path Parameters:**
- `contactId` (string, required) - The contact ID

**Response:**
```json
{
  "ok": true,
  "contact": {
    "id": "contact_123",
    "name": "John Doe",
    "email": "john@acme.com",
    "phone": "+1-555-0123",
    "company": "Acme Corp",
    "title": "Engineering Manager",
    "notes": "Key decision maker for technical purchases",
    "tags": ["client", "engineering"],
    "metadata": {},
    "createdAt": "2026-02-01T10:00:00Z",
    "updatedAt": "2026-02-01T10:00:00Z"
  }
}
```

### Update Contact

**Endpoint:** `PUT /contacts/:contactId`

**Description:** Update contact properties.

**Path Parameters:**
- `contactId` (string, required) - The contact ID

**Request Body:**
```json
{
  "title": "Senior Engineering Manager",
  "notes": "Promoted last quarter. Key decision maker.",
  "tags": ["client", "engineering", "senior"]
}
```

**Parameters:**
- `name` (string, optional) - Contact full name
- `email` (string, optional) - Contact email
- `phone` (string, optional) - Contact phone
- `company` (string, optional) - Company name
- `title` (string, optional) - Job title
- `notes` (string, optional) - Notes
- `tags` (array, optional) - Tags array
- `metadata` (object, optional) - Additional metadata

**Response:**
```json
{
  "ok": true,
  "contact": {
    "id": "contact_123",
    "name": "John Doe",
    "email": "john@acme.com",
    "title": "Senior Engineering Manager",
    "notes": "Promoted last quarter. Key decision maker.",
    "tags": ["client", "engineering", "senior"],
    "updatedAt": "2026-02-11T10:00:00Z"
  }
}
```

### Delete Contact

**Endpoint:** `DELETE /contacts/:contactId`

**Description:** Delete a contact permanently.

**Path Parameters:**
- `contactId` (string, required) - The contact ID

**Response:**
```json
{
  "ok": true,
  "contact": {
    "id": "contact_123",
    "message": "Contact deleted successfully"
  }
}
```

---

## Calendars API

Manage user calendars synced from OAuth providers (Google Calendar, Microsoft Azure).

### Calendar Architecture

Calendars are linked to users through a multi-step process:
1. **Calendar** - The actual calendar object synced from Google/Microsoft
2. **UserCalendar** - Join table linking User + Calendar + UserIdentifier with access roles
3. **Flow**: Create identifier → Set `platforms.{google|azure}.accessGranted` → Call `sync-resources` → Calendars linked to user

### Calendar Schema

**Calendar Fields:**
- `id` (string) - Unique calendar identifier
- `account` (string) - Account ID this calendar belongs to
- `platform` (string) - Calendar platform: google, azure
- `name` (string) - Calendar display name
- `timezone` (string) - Calendar timezone (IANA format, e.g., America/New_York)
- `referenceId` (string) - Platform-specific calendar ID (unique across all calendars)
- `backgroundColor` (string) - Calendar color in hex format
- `conferenceSolutions` (array) - Available conference solutions (Google Meet, Zoom, Teams)
- `lastSyncedAt` (date) - Timestamp of last successful sync
- `lastSyncToken` (string) - Sync token for incremental syncs
- `syncError` (boolean) - Flag indicating sync issues
- `eventCount` (number) - Number of events in this calendar
- `role` (string) - User access role: owner, writer, reader (from UserCalendar)
- `isPrimary` (boolean) - Whether this is the user's primary calendar (from UserCalendar)
- `userIdentifier` (string) - ID of the identifier that connected this calendar (from UserCalendar)
- `createdAt` (date) - Calendar creation timestamp
- `updatedAt` (date) - Last update timestamp

**Access Roles:**
- `owner` - Full control (can share, delete, modify settings)
- `writer` - Can create/edit/delete events
- `reader` - Can only view events

### List Calendars

**Endpoint:** `GET /calendars`

**Description:** List all calendars for the authenticated user. This is a delegated API that uses the user context from the JWT token.

**How Calendar Sync Works:**
1. Create an identifier with `platforms.google.accessGranted: ["calendars.read", "calendars.write"]` or `platforms.azure.accessGranted: ["calendars.read", "calendars.write"]`
2. Call `POST /identifiers/:id/sync-resources` to pull calendars from Google/Microsoft
3. Calendars are created and linked to the user via UserCalendar records
4. Use this endpoint to list all calendars for the authenticated user

**Response:**
```json
{
  "ok": true,
  "calendars": [
    {
      "id": "cal_123",
      "account": "acc_456",
      "platform": "google",
      "name": "Work Calendar",
      "timezone": "America/New_York",
      "referenceId": "primary",
      "backgroundColor": "#9E69AF",
      "lastSyncedAt": "2026-02-16T10:00:00Z",
      "syncError": false,
      "eventCount": 42,
      "createdAt": "2026-02-01T10:00:00Z",
      "updatedAt": "2026-02-16T10:00:00Z",
      "role": "owner",
      "isPrimary": true,
      "userIdentifier": "id_789"
    }
  ]
}
```

**Response Fields:**
- `role` - User's access level (owner, writer, or reader)
- `isPrimary` - Whether this is the user's primary calendar
- `userIdentifier` - The identifier that connected this calendar

---

## Identifiers API

Manage user identifiers for various platforms (email, phone, Slack, Teams, etc.).

### List Identifiers

**Endpoint:** `GET /identifiers`

**Description:** List all identifiers in the account.

**Query Parameters:**
- `type` (string, optional) - Filter by identifier type (email, phone, external_id, etc.)
- `userId` (string, optional) - Filter by user ID

**Response:**
```json
{
  "ok": true,
  "identifiers": [
    {
      "id": "id_123",
      "account": "acc_456",
      "user": {
        "id": "user_789",
        "name": "John Doe",
        "status": "active"
      },
      "type": "email",
      "value": "user@example.com",
      "verified": true,
      "primary": false,
      "metadata": {},
      "createdAt": "2026-02-01T10:00:00Z",
      "updatedAt": "2026-02-01T10:00:00Z"
    }
  ]
}
```

### Create Identifier

**Endpoint:** `POST /identifiers`

**Description:** Create a new identifier.

**Request Body:**
```json
{
  "type": "phone",
  "value": "+1-555-0789",
  "label": "Mobile Phone",
  "verified": true,
  "primary": false
}
```

**Parameters:**
- `type` (string, required) - Identifier type (email, phone, slack, teams)
- `value` (string, required) - Identifier value
- `label` (string, optional) - Human-readable label
- `verified` (boolean, optional) - Verification status (default: false)
- `primary` (boolean, optional) - Primary status (default: false)
- `metadata` (object, optional) - Additional metadata

**Response:**
```json
{
  "ok": true,
  "identifier": {
    "id": "id_456",
    "type": "phone",
    "value": "+1-555-0789",
    "label": "Mobile Phone",
    "verified": true,
    "primary": false,
    "createdAt": "2026-02-11T10:00:00Z"
  }
}
```

### Get Identifier

**Endpoint:** `GET /identifiers/:identifierId`

**Description:** Get details of a specific identifier.

**Path Parameters:**
- `identifierId` (string, required) - The identifier ID

**Response:**
```json
{
  "ok": true,
  "identifier": {
    "id": "id_123",
    "type": "email",
    "value": "user@example.com",
    "label": "Work Email",
    "verified": true,
    "primary": true,
    "metadata": {},
    "createdAt": "2026-02-01T10:00:00Z",
    "updatedAt": "2026-02-01T10:00:00Z"
  }
}
```

### Update Identifier

**Endpoint:** `PUT /identifiers/:identifierId`

**Description:** Update identifier properties.

**Path Parameters:**
- `identifierId` (string, required) - The identifier ID

**Request Body:**
```json
{
  "label": "Primary Work Email",
  "verified": true,
  "primary": true
}
```

**Parameters:**
- `label` (string, optional) - Human-readable label
- `verified` (boolean, optional) - Verification status
- `primary` (boolean, optional) - Primary status
- `metadata` (object, optional) - Additional metadata

**Response:**
```json
{
  "ok": true,
  "identifier": {
    "id": "id_123",
    "type": "email",
    "value": "user@example.com",
    "label": "Primary Work Email",
    "verified": true,
    "primary": true,
    "updatedAt": "2026-02-11T10:00:00Z"
  }
}
```

### Make Identifier Primary

**Endpoint:** `POST /identifiers/:identifierId/make-primary`

**Description:** Make an email identifier the primary email for its user. This updates the user's `email` field to match the identifier's value.

**Path Parameters:**
- `identifierId` (string, required) - The identifier ID

**Requirements:**
- Identifier must be of type `email`
- Identifier must be linked to a user

**Response:**
```json
{
  "ok": true,
  "user": {
    "id": "user_123",
    "email": "newemail@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "status": "active"
  },
  "identifier": {
    "id": "id_456",
    "type": "email",
    "value": "newemail@example.com",
    "verified": true,
    "primary": false
  }
}
```

### Delete Identifier

**Endpoint:** `DELETE /identifiers/:identifierId`

**Description:** Delete an identifier permanently. Automatically cascades to delete associated preferences, calendars, and timelines.

**Path Parameters:**
- `identifierId` (string, required) - The identifier ID

**Response:**
```json
{
  "ok": true,
  "identifier": {
    "id": "id_123",
    "message": "Identifier deleted successfully"
  }
}
```

### List Identifier Calendars

**Endpoint:** `GET /identifiers/:identifierId/calendars`

**Description:** List all calendars linked to a specific identifier. Calendars are linked by matching the calendar's email field with the identifier's value (email address).

**Path Parameters:**
- `identifierId` (string, required) - The identifier ID

**Response:**
```json
{
  "ok": true,
  "calendars": [
    {
      "id": "cal_123",
      "account": "acc_456",
      "platform": "google",
      "name": "Work Calendar",
      "timezone": "America/New_York",
      "referenceId": "primary",
      "backgroundColor": "#9E69AF",
      "lastSyncedAt": "2026-02-11T10:00:00Z",
      "syncError": false,
      "eventCount": 42,
      "createdAt": "2026-02-01T10:00:00Z",
      "updatedAt": "2026-02-11T10:00:00Z"
    }
  ]
}
```

### Sync Identifier Calendars

**Endpoint:** `POST /identifiers/:identifierId/calendars/sync`

**Description:** Sync calendars from OAuth provider (Google Calendar or Microsoft Azure). This fetches the list of calendars from the provider and creates/updates calendar records in the database. Requires the user to have OAuth access configured in skej.

**Path Parameters:**
- `identifierId` (string, required) - The identifier ID

**Request Body:**
```json
{
  "platform": "google"
}
```

**Parameters:**
- `platform` (string, required) - Platform to sync from: google, azure

**Response:**
```json
{
  "ok": true,
  "calendars": [
    {
      "id": "cal_123",
      "platform": "google",
      "name": "Work Calendar",
      "referenceId": "primary"
    },
    {
      "id": "cal_456",
      "platform": "google",
      "name": "Personal",
      "referenceId": "personal@example.com"
    }
  ]
}
```

**Error Response:**
```json
{
  "error": "No OAuth access found for this user"
}
```

### Sync Identifier Resources

**Endpoint:** `POST /identifiers/:identifierId/sync-resources`

**Description:** Trigger on-demand sync of calendars and contacts from all OAuth providers for this identifier. This is the recommended way to sync user data after setting up OAuth access. Syncs calendars from Google/Azure and contacts from providers with contact access scopes.

**Path Parameters:**
- `identifierId` (string, required) - The identifier ID

**Prerequisites:**
1. Identifier must have `platforms.{google|azure}.accessGranted` set (e.g., `platforms.google.accessGranted: ["calendars.read", "calendars.write", "contacts.read"]`)
2. User must have OAuth tokens configured in skej

**Response (Local/Development):**
```json
{
  "ok": true,
  "syncResources": {
    "message": "Resource sync completed",
    "identifierId": "id_123",
    "userId": "user_456",
    "status": "completed",
    "result": {
      "calendarsSynced": 3,
      "contactsSynced": 42,
      "errors": []
    }
  }
}
```

**Response (Production/Queued):**
```json
{
  "ok": true,
  "syncResources": {
    "message": "Resource sync triggered successfully",
    "identifierId": "id_123",
    "userId": "user_456",
    "status": "queued"
  }
}
```

**Notes:**
- In local/development environments, runs synchronously and returns results immediately
- In production/staging, queues to SQS worker and returns immediately
- Syncs all platforms with active OAuth tokens (Google, Azure)
- Creates Calendar and Contact records linked to the user
- Use `GET /calendars` to list synced calendars afterwards

---

## Preferences API

Manage user preferences with typed skill-specific schemas. Preferences are stored per user identifier and organized by skill type (scheduling, reminders, followups, travel).

### Get All Preferences

**Endpoint:** `GET /identifiers/:identifierId/preferences`

**Description:** Get all preferences for a user identifier.

**Path Parameters:**
- `identifierId` (string, required) - The user identifier ID

**Response:**
```json
{
  "ok": true,
  "preferences": {
    "id": "pref_123",
    "account": "acc_456",
    "userIdentifier": "id_123",
    "preferences": {
      "scheduling": {
        "availability": {
          "workingHours": {
            "MO": [{ "start": "09:00", "end": "17:00" }],
            "TU": [{ "start": "09:00", "end": "17:00" }],
            "WE": [{ "start": "09:00", "end": "17:00" }],
            "TH": [{ "start": "09:00", "end": "17:00" }],
            "FR": [{ "start": "09:00", "end": "17:00" }],
            "SA": [],
            "SU": []
          },
          "workingHolidays": ["2026-12-25", "2026-01-01"]
        },
        "defaults": {
          "virtual": {
            "duration": 30,
            "defaultMeetingLink": "https://meet.example.com/my-room",
            "conferenceSolution": "zoom"
          },
          "inPerson": {
            "duration": 60,
            "defaultLocation": "Office - Conference Room A"
          },
          "supervision": "supervised"
        },
        "calendars": {
          "read": ["cal_123", "cal_456"],
          "write": ["cal_123"]
        },
        "buffers": {
          "bufferType": "meeting",
          "bufferLength": 15,
          "bufferBlockLength": 0,
          "dailyMeetingLimit": 360
        }
      },
      "reminders": {
        "level": "all-events",
        "email": {
          "enabled": true,
          "inPersonMinutes": 30,
          "virtualMinutes": 10
        },
        "slack": {
          "enabled": true,
          "inPersonMinutes": 30,
          "virtualMinutes": 2
        },
        "teams": {
          "enabled": false,
          "inPersonMinutes": 30,
          "virtualMinutes": 2
        }
      },
      "followups": {
        "enabled": true,
        "count": 2,
        "interval": 4320
      },
      "travel": {
        "seatPreference": "aisle",
        "cabinClass": "economy",
        "mealPreference": "vegetarian",
        "airlinePreferences": ["UA", "AA"]
      }
    },
    "createdAt": "2026-02-01T10:00:00Z",
    "updatedAt": "2026-02-11T10:00:00Z"
  }
}
```

### Get Skill Preferences

**Endpoint:** `GET /identifiers/:identifierId/preferences/:skillName`

**Description:** Get preferences for a specific skill.

**Path Parameters:**
- `identifierId` (string, required) - The user identifier ID
- `skillName` (string, required) - The skill name (scheduling, reminders, followups, travel)

**Response:**
```json
{
  "ok": true,
  "skill": "scheduling",
  "preferences": {
    "availability": {
      "workingHours": {
        "MO": [{ "start": "09:00", "end": "17:00" }],
        "TU": [{ "start": "09:00", "end": "17:00" }]
      },
      "workingHolidays": []
    },
    "defaults": {
      "virtual": { "duration": 30 },
      "inPerson": { "duration": 60 },
      "supervision": "supervised"
    },
    "calendars": {
      "read": [],
      "write": []
    },
    "buffers": {
      "bufferType": "none",
      "bufferLength": 0,
      "bufferBlockLength": 0,
      "dailyMeetingLimit": 0
    }
  }
}
```

### Set Skill Preferences

**Endpoint:** `PUT /identifiers/:identifierId/preferences/:skillName`

**Description:** Set or update preferences for a specific skill.

**Path Parameters:**
- `identifierId` (string, required) - The user identifier ID
- `skillName` (string, required) - The skill name (scheduling, reminders, followups, travel)

**Request Body:**
```json
{
  "preferences": {
    "level": "all-events",
    "email": {
      "enabled": true,
      "inPersonMinutes": 30,
      "virtualMinutes": 10
    },
    "slack": {
      "enabled": true,
      "inPersonMinutes": 30,
      "virtualMinutes": 2
    }
  }
}
```

**Response:**
```json
{
  "ok": true,
  "skill": "reminders",
  "preferences": {
    "level": "all-events",
    "email": {
      "enabled": true,
      "inPersonMinutes": 30,
      "virtualMinutes": 10
    },
    "slack": {
      "enabled": true,
      "inPersonMinutes": 30,
      "virtualMinutes": 2
    },
    "teams": {
      "enabled": false,
      "inPersonMinutes": 30,
      "virtualMinutes": 2
    }
  }
}
```

### Preference Schemas

#### Scheduling Preferences
- `availability.workingHours` - Object with MO-SU keys, each containing array of {start, end} time ranges
- `availability.workingHolidays` - Array of date strings (YYYY-MM-DD)
- `defaults.virtual.duration` - Default virtual meeting duration (minutes)
- `defaults.virtual.defaultMeetingLink` - Default meeting link
- `defaults.virtual.conferenceSolution` - Conference solution (zoom, meet, teams)
- `defaults.inPerson.duration` - Default in-person meeting duration (minutes)
- `defaults.inPerson.defaultLocation` - Default location string
- `defaults.supervision` - "supervised" | "unsupervised"
- `calendars.read` - Array of calendar referenceIds to read from (Google Calendar ID, Azure Calendar ID, etc.)
- `calendars.write` - Single calendar referenceId to write to
- `buffers.bufferType` - "none" | "meeting" | "timeBlock"
- `buffers.bufferLength` - Buffer length (minutes)
- `buffers.bufferBlockLength` - Time block length (minutes)
- `buffers.dailyMeetingLimit` - Daily meeting limit (minutes, 0 = no limit)

#### Reminders Preferences
- `level` - "all-events" | "tskr-events" | "none"
- `email.enabled` - Email reminders enabled
- `email.inPersonMinutes` - Minutes before in-person event
- `email.virtualMinutes` - Minutes before virtual event
- `slack.*` - Same structure as email
- `teams.*` - Same structure as email

#### Followups Preferences
- `enabled` - Follow-ups enabled (boolean)
- `count` - Number of follow-up attempts (0-5)
- `interval` - Interval between follow-ups (minutes)

#### Travel Preferences
- `seatPreference` - "aisle" | "window" | "middle"
- `cabinClass` - "economy" | "premium_economy" | "business" | "first"
- `mealPreference` - Meal preference string
- `airlinePreferences` - Array of airline codes

---

# Management APIs

Management APIs operate at the account level and don't require user context in JWT payload.

---

## Assistants API

Manage AI assistants for your account.

### List Assistants

**Endpoint:** `GET /assistants`

**Description:** List all assistants for the authenticated account.

**Query Parameters:**
- `email` (string, optional) - Filter by assistant email

**Response:**
```json
{
  "ok": true,
  "assistants": [
    {
      "id": "asst_123",
      "name": "AI Assistant",
      "email": "ai@example.com",
      "personality": "Professional and helpful",
      "status": "active",
      "writingStyle": {
        "professionalism": 8,
        "humor": 3,
        "emojiUse": 2
      }
    }
  ]
}
```

### Create Assistant

**Endpoint:** `POST /assistants`

**Description:** Create a new assistant for your account.

**Request Body:**
```json
{
  "name": "AI Assistant",
  "email": "assistant@example.com",
  "personality": "Professional and helpful",
  "signature": "Best regards,\nAI Assistant",
  "writingStyle": {
    "professionalism": 8,
    "humor": 3,
    "emojiUse": 2
  }
}
```

**Parameters:**
- `name` (string, required) - Assistant name
- `email` (string, required) - Assistant email (must be unique)
- `personality` (string, optional) - Personality description
- `signature` (string, optional) - Email signature
- `writingStyle` (object, optional) - Writing style preferences
  - `professionalism` (number) - Professionalism level (0-10)
  - `humor` (number) - Humor level (0-10)
  - `emojiUse` (number) - Emoji usage level (0-10)

**Response:**
```json
{
  "ok": true,
  "assistant": {
    "id": "asst_456",
    "name": "AI Assistant",
    "email": "assistant@example.com",
    "status": "active"
  }
}
```

### Get Assistant

**Endpoint:** `GET /assistants/:assistantId`

**Description:** Get details of a specific assistant.

**Path Parameters:**
- `assistantId` (string, required) - The assistant ID

**Response:**
```json
{
  "ok": true,
  "assistant": {
    "id": "asst_123",
    "name": "AI Assistant",
    "email": "assistant@example.com",
    "personality": "Professional and helpful",
    "status": "active",
    "writingStyle": {
      "professionalism": 8,
      "humor": 3,
      "emojiUse": 2
    }
  }
}
```

### Update Assistant

**Endpoint:** `PUT /assistants/:assistantId`

**Description:** Update an assistant's properties.

**Path Parameters:**
- `assistantId` (string, required) - The assistant ID

**Request Body:**
```json
{
  "name": "Updated Assistant Name",
  "personality": "Updated personality",
  "writingStyle": {
    "professionalism": 9,
    "humor": 2
  }
}
```

**Parameters:**
- `name` (string, optional) - Assistant name
- `email` (string, optional) - Assistant email
- `personality` (string, optional) - Personality description
- `signature` (string, optional) - Email signature
- `writingStyle` (object, optional) - Writing style preferences
- `status` (string, optional) - Assistant status

**Response:**
```json
{
  "ok": true,
  "assistant": {
    "id": "asst_123",
    "name": "Updated Assistant Name",
    "personality": "Updated personality",
    "status": "active"
  }
}
```

### Delete Assistant

**Endpoint:** `DELETE /assistants/:assistantId`

**Description:** Delete an assistant permanently.

**Path Parameters:**
- `assistantId` (string, required) - The assistant ID

**Response:**
```json
{
  "ok": true,
  "message": "Assistant deleted successfully"
}
```

---

## Users API

Manage users for your account.

### List Users

**Endpoint:** `GET /users`

**Description:** List all users for the authenticated account with pagination and filtering.

**Query Parameters:**
- `page` (integer, optional) - Page number (default: 1)
- `perPage` (integer, optional) - Items per page (default: 50, max: 100)
- `status` (string, optional) - Filter by status (active, inactive)
- `email` (string, optional) - Filter by exact email (returns 0 or 1 user)
- `externalId` (string, optional) - Filter by external ID from external system (e.g., Skej user ID)

**Response:**
```json
{
  "ok": true,
  "users": [
    {
      "id": "user_123",
      "email": "user@example.com",
      "externalId": "skej_user_abc123",
      "firstName": "John",
      "lastName": "Doe",
      "avatar": "https://example.com/avatar.jpg",
      "timezone": "America/New_York",
      "status": "active",
      "createdAt": "2026-02-01T10:00:00Z"
    }
  ],
  "page": 1,
  "perPage": 50,
  "total": 150
}
```

### Get User

**Endpoint:** `GET /users/:userId`

**Description:** Get details of a specific user.

**Path Parameters:**
- `userId` (string, required) - The user ID

**Response:**
```json
{
  "ok": true,
  "user": {
    "id": "user_123",
    "email": "user@example.com",
    "externalId": "skej_user_abc123",
    "firstName": "John",
    "lastName": "Doe",
    "avatar": "https://example.com/avatar.jpg",
    "timezone": "America/New_York",
    "status": "active",
    "createdAt": "2026-02-01T10:00:00Z",
    "updatedAt": "2026-02-01T10:00:00Z"
  }
}
```

### Update User

**Endpoint:** `PUT /users/:userId`

**Description:** Update user information.

**Path Parameters:**
- `userId` (string, required) - The user ID

**Request Body:**
```json
{
  "firstName": "Jane",
  "lastName": "Smith",
  "avatar": "https://example.com/new-avatar.jpg",
  "timezone": "Europe/London",
  "externalId": "skej_user_xyz789"
}
```

**Parameters:**
- `firstName` (string, optional) - User first name
- `lastName` (string, optional) - User last name
- `avatar` (string, optional) - Avatar image URL
- `timezone` (string, optional) - User timezone
- `externalId` (string, optional) - External user ID from external system (must be unique within account)

**Response:**
```json
{
  "ok": true,
  "user": {
    "id": "user_123",
    "email": "user@example.com",
    "externalId": "skej_user_xyz789",
    "firstName": "Jane",
    "lastName": "Smith",
    "avatar": "https://example.com/new-avatar.jpg",
    "timezone": "Europe/London",
    "updatedAt": "2026-02-11T10:00:00Z"
  }
}
```

### Delete User

**Endpoint:** `DELETE /users/:userId`

**Description:** Delete a user permanently.

**Path Parameters:**
- `userId` (string, required) - The user ID

**Response:**
```json
{
  "ok": true,
  "user": {
    "id": "user_123",
    "message": "User deleted successfully"
  }
}
```


---

# Admin APIs

Admin APIs require super admin privileges. Access is automatically granted to users with `@skej.com` email addresses.

---

## Skills API (Admin)

Manage skills for the platform. Skills define capabilities that AI agents can perform.

### Create Skill

**Endpoint:** `POST /admin/skills`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Create a new skill.

**Request Body:**
```json
{
  "name": "scheduling",
  "displayName": "Scheduling",
  "icon": "Calendar",
  "color": "#3B82F6",
  "description": "Schedule meetings and manage calendars",
  "category": "productivity",
  "status": "available"
}
```

**Response:**
```json
{
  "ok": true,
  "skill": {
    "id": "skill_123",
    "name": "scheduling",
    "displayName": "Scheduling",
    "status": "available"
  }
}
```

### Update Skill

**Endpoint:** `PUT /admin/skills/:skillId`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Update a skill's properties.

### Delete Skill

**Endpoint:** `DELETE /admin/skills/:skillId`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Delete a skill permanently.

---

## Skill Categories API (Admin)

Manage skill categories for organizing skills.

### Create Skill Category

**Endpoint:** `POST /admin/skill-categories`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Create a new skill category.

### Update Skill Category

**Endpoint:** `PUT /admin/skill-categories/:categoryId`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Update a skill category.

### Delete Skill Category

**Endpoint:** `DELETE /admin/skill-categories/:categoryId`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Delete a skill category permanently.

### Reorder Skill Categories

**Endpoint:** `POST /admin/skill-categories/reorder`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Reorder skill categories by updating sortOrder.

---

## Task Templates API (Admin)

Manage task templates for creating standardized tasks.

### Create Task Template

**Endpoint:** `POST /admin/task-templates`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Create a new task template.

### Update Task Template

**Endpoint:** `PUT /admin/task-templates/:templateId`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Update a task template.

### Delete Task Template

**Endpoint:** `DELETE /admin/task-templates/:templateId`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Delete a task template permanently.

---

## Agents API (Admin)

Manage AI agents that power assistants.

### List Agents

**Endpoint:** `GET /admin/agents`

**Authentication:** Requires super admin (@skej.com email)

**Description:** List all agents in the system.

### Get Agent

**Endpoint:** `GET /admin/agents/:agentId`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Get details of a specific agent.

### Create Agent

**Endpoint:** `POST /admin/agents`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Create a new agent.

### Update Agent

**Endpoint:** `PUT /admin/agents/:agentId`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Update an agent's properties.

### Delete Agent

**Endpoint:** `DELETE /admin/agents/:agentId`

**Authentication:** Requires super admin (@skej.com email)

**Description:** Delete an agent permanently.

---

## Rate Limits

- Standard rate limit: 1000 requests per minute
- Chat processing: 1 concurrent request per chat (30-second lock)

## Special Features

### Redis Distributed Locks

The chat processing endpoint uses Redis distributed locks to prevent concurrent processing:
- Lock TTL: 30 seconds
- Returns 409 Conflict if chat is already being processed
- Auto-releases on success, error, or timeout

### Auto-provisioning

Users are automatically created from JWT claims when using certificate-based authentication:
- User created from: `email`, `firstName`, `lastName`
- UserIdentifier created linking email to user
- Only happens with certificate-based JWT (not system JWT)

### Message Architecture

- Chat endpoints do NOT include messages by default
- Messages must be fetched separately via `/chats/:chatId/messages`
- Improves performance and allows independent polling

---

## Support

For API support or questions, contact: support@tskr.ai

## Changelog

### Version 2.0.0 (Current)
- Full CRUD operations on chats and messages
- Separated message endpoints from chat endpoints
- Added Redis-based distributed locks for chat processing
- Auto-provisioning of users from JWT claims
- Account creation restricted to authorized users
