CursorPool
← 返回首页

slide generation layerproof

agent-skill-layerproof plugin for Cursor

cursor.directory·0
Skill

exports

Public API export (X-API-KEY). Export PNG ZIP, PPTX, or video (async), get status, cancel. PublicApiExportController.

# Skill: Export Slides

## Description

Export presentations as PNG ZIP, PPTX, or **narrated video** (Pro / credits). This skill documents the **public API** at `/api/v2/projects/{projectId}/exports` (PublicApiExportController). Exports are async: POST returns `export_id` (same id used for job-style polling); poll GET `.../exports/{export_id}` for status; when COMPLETED, use `download_url` (presigned, ~1 hour). Authenticate with `X-API-KEY` header.

---

## TypeScript types (request / response)

Mirrors `PublicApiExportController` data classes.

```typescript
// --- Export PNG / PPTX / Video (POST) — 202 ---
type PublicExportStartedResponse = {
  export_id: string;  // UUID — poll GET .../exports/{export_id}
  status?: string;   // default "PENDING"
};

// --- Video export body (POST .../exports/video) — optional ---
type PublicVideoExportRequest = {
  quality?: string | null;       // export quality enum as returned by API
  include_notes?: boolean;      // default false
  voice?: string | null;         // default "Puck"
  language_code?: string | null; // default "en-US"
};

// --- Get Export Status (GET) ---
type PublicExportProgress = { current: number; total: number };
type PublicExportStatusData = {
  export_id: string;
  status: string;
  format: string;
  download_url?: string | null;
  expires_at?: string | null;
  file_size_bytes?: number | null;
  progress?: PublicExportProgress | null;
  error_message?: string | null;
};
type PublicExportStatusResponse = { data: PublicExportStatusData };

// --- Cancel Export (POST) ---
type PublicCancelExportResponse = {
  export_id: string;
  status: string;  // "CANCELLED" or "ALREADY_TERMINAL"
};
```

---

## Export PNG ZIP (async)

Response (202): `PublicExportStartedResponse`. Poll GET .../exports/{exportId} for status.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/exports/png" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Export PPTX (async)

Response (202): `PublicExportStartedResponse`. Poll GET .../exports/{exportId} for status.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/exports/pptx" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Export Video (async)

Narrated video with TTS. May return **402** (quota) or **403** (Pro plan required). Optional JSON body: `PublicVideoExportRequest`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/exports/video" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"include_notes":false,"voice":"Puck","language_code":"en-US"}'
```

---

## Get Export Status

Response: `PublicExportStatusResponse`. When status is COMPLETED, `data.downloadUrl` and `data.expiresAt` are set.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/exports/<export_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Cancel Export

Response: `PublicCancelExportResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/exports/<export_id>/cancel" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Agent behavior

When the user asks to export a project (PNG ZIP or PPTX) or check/cancel an export, do the following.

### 1. Choose the right endpoint

Base path: `/api/v2/projects/{projectId}/exports`. All require `projectId`.

| User intent | Endpoint | Method |
|-------------|----------|--------|
| Export as PNG ZIP | `.../exports/png` | POST |
| Export as PPTX | `.../exports/pptx` | POST |
| Export as video (TTS) | `.../exports/video` | POST |
| Get export status / download URL | `.../exports/{exportId}` | GET |
| Cancel in-progress export | `.../exports/{exportId}/cancel` | POST |

### 2. Build and run

- **Auth**: Include `X-API-KEY: $LAYERPROOF_API_KEY`. Read env vars; if missing, tell the user.
- **Path**: Resolve projectId from context; exportId from the 202 response of export PNG/PPTX.
- Run curl and show the result.

### 3. After starting export

- Response contains `export_id`. Tell the user to poll GET .../exports/{export_id} until status is COMPLETED (or FAILED). When COMPLETED, use `data.download_url` (valid ~1 hour).

### 4. Response handling

- Always show raw JSON in a code block.
- If `download_url` is present, show the URL and mention the file can be downloaded.
- On 410, download URL expired; suggest triggering a new export.

### 5. Example workflows

**Workflow A — User**: "Export my project as PPTX and give me the download link."

1. Resolve projectId. POST `.../projects/{projectId}/exports/pptx`; capture `export_id` from 202 response.
2. Poll GET `.../projects/{projectId}/exports/{export_id}` until `data.status` is COMPLETED or FAILED. If FAILED, show `data.error_message` and suggest retry or checking project/slides.
3. When COMPLETED, show `data.download_url` and `data.expires_at`; remind user the URL is temporary (~1 hour).

**Workflow B — User**: "Export the same project as both PNG ZIP and PPTX; if one fails, still get the other."

1. POST `.../exports/png`; get `export_id_png`. POST `.../exports/pptx`; get `export_id_pptx`.
2. Poll both: GET `.../exports/{export_id_png}` and GET `.../exports/{export_id_pptx}` (e.g. in sequence or inform user to poll). For each: when COMPLETED, show `download_url`; when FAILED, show `error_message` and which format failed.
3. Optionally: if user wants to cancel the slower one, POST `.../exports/{export_id}/cancel` for the relevant export id.

**Workflow C — User**: "Start a PPTX export; I might cancel it if it takes too long."

1. POST `.../exports/pptx`; get `export_id`. Tell user polling has started.
2. If user says "cancel the export" before COMPLETED: POST `.../exports/{export_id}/cancel`; show response (status CANCELLED or ALREADY_TERMINAL).
3. If not canceled: poll until COMPLETED and show `download_url`, or FAILED and show `error_message`.

---

## Response format (required)

- (if response contains url to show image) please show image and show json response instead of table
- Always show the **raw JSON response** (verbatim) in a JSON code block.
- If the response contains a URL for an image, **render/show the image** and also show the **JSON response** (do not convert to a table).
Skill

jobs

Public API job status polling (X-API-KEY). Poll async operations (outline, slides, exports, etc.) by activityId. Types follow PublicApiJobController (/api/v2/jobs/{activityId}).

# Skill: Job Monitoring

## Description

Poll the status of asynchronous operations. This skill documents the **public API** at `/api/v2/jobs/{activityId}` (PublicApiJobController). The `activityId` is returned when you start async operations (e.g. outline/generate, batch-generate, export PNG/PPTX). Authenticate with `X-API-KEY` header.

---

## TypeScript types (request / response)

Mirrors `PublicApiJobController` response.

```typescript
// --- Get Job Status (GET) ---
type JobStatusResponse = {
  activity_id: string;
  status: 'SCHEDULED' | 'RUNNING' | 'PENDING' | 'DONE' | 'CANCELED';
  message: string;
  created_at?: string | null;   // ISO 8601
  updated_at?: string | null;   // ISO 8601
  live_object_id?: string | null;  // when derivable from activity input
  output?: Record<string, unknown> | null;  // structured map when DONE
  failure_reason?: string | null;
};
```

---

## Get Job Status

Response: `JobStatusResponse`.

Poll until `status` is `DONE` or `CANCELED`. When DONE, use `output` (object map when present) for results; when failed, use `failure_reason`.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/jobs/<activity_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Agent behavior

When the user asks to check job status or poll an async operation, do the following.

### 1. Endpoint

| User intent | Endpoint | Method |
|-------------|----------|--------|
| Check/poll job status | `/api/v2/jobs/{activityId}` | GET |

### 2. Build and run

- **Auth**: Include `X-API-KEY: $LAYERPROOF_API_KEY`. Read env vars; if missing, tell the user.
- **Path**: Use the `activity_id` returned from a previous async call (outline generate, batch generate, export, etc.). Run curl and show the result.

### 3. After response

- If status is SCHEDULED/RUNNING/PENDING, suggest polling again after a few seconds.
- If DONE, use `output` (object) if present and summarize the result.
- If CANCELED or DONE with `failure_reason`, report the outcome.

### 4. Response handling

- Always show the **raw JSON response** in a JSON code block.
- On 404, job not found or user does not own the associated project.

### 5. Example workflows

**Workflow A — User**: "Check status of job [activity_id]."

1. GET `/api/v2/jobs/{activityId}`; show JSON. If SCHEDULED/RUNNING/PENDING, suggest re-polling in a few seconds. If DONE, show `output` (and summarize). If CANCELED or failure_reason, report and stop.

**Workflow B — User**: "I started an outline generate and a batch generate; poll both and tell me when they’re done or if either failed."

1. User provides two activity IDs (or they are in context from prior slide-deck calls). Poll GET `/api/v2/jobs/{activityId1}` and GET `/api/v2/jobs/{activityId2}`.
2. For each: if DONE, report success and optionally parse `output`; if CANCELED or DONE with `failure_reason`, report which job failed and the reason.
3. If both still in progress, suggest polling again; if one is DONE and the other not, report current state and continue polling the other until terminal.

**Workflow C — User**: "Wait for this job and then get the slide deck / campaign / export result."

1. Poll GET `/api/v2/jobs/{activityId}` until status is DONE or CANCELED. If failed, report and stop.
2. When DONE: depending on workflow type (from initial trigger), hand off to the right skill — e.g. slide-decks GET deck, social-campaigns GET campaign, exports GET export status — using IDs from context or from `output` (e.g. project_id, slide_deck_id, campaign_id, export_id) and show the resulting resource or download URL.

---

## Response format (required)

- (if response contains url to show image) please show image and show json response instead of table
- Always show the **raw JSON response** (verbatim) in a JSON code block.
- If the response contains a URL for an image, **render/show the image** and also show the **JSON response** (do not convert to a table).
Skill

project-files

Public API project file management (X-API-KEY). Prepare upload/update, confirm, AI files, subdirectories, resolve assets/paths, preview URL, get, download, delete. PublicApiProjectFileController.

# Skill: Project Files

## Description

Manage files in project directories. This skill documents the **public API** at `/api/v2/projects/{projectId}` (PublicApiProjectFileController). Upload flow: 1) POST prepare with directoryId; 2) PUT file to `upload_url`; 3) POST confirm with `file_id`. All paths require `projectId`; prepare/prepare-update also need `directoryId`. Authenticate with `X-API-KEY` header.

---

## TypeScript types (request / response)

Mirrors `PublicApiProjectFileController` data classes.

```typescript
// --- Prepare upload (POST) — 201 ---
type PrepareFileUploadRequest = {
  path: string;
  file_name: string;
  mime_type: string;
  size: number;
};
type PrepareFileUploadResponse = {
  upload_url: string;
  file_id: string;
  s3_key: string;
  expires_at: string;
};

// --- Prepare update (POST) ---
type PrepareUpdateFileRequest = {
  path: string;
  mime_type?: string | null;
  size?: number | null;
};
type PrepareUpdateFileResponse = {
  upload_url: string;
  file_id: string;
  s3_key: string;
  expires_at: string;
};

// --- Confirm (POST) ---
type ConfirmFileUploadRequest = { metadata?: Record<string, unknown> | null };
type FileResponse = {
  id: string;
  name: string;
  file_type: string;
  mime_type: string;
  s3_key: string;
  size: number;
  uploaded_at: string;
  uploaded_by: string;
  status: string;
  metadata: Record<string, unknown> | null;
};

// --- Download URL (GET), Get file (GET), Delete (DELETE) ---
type DownloadUrlResponse = {
  download_url: string;
  expires_at: string;
};

// --- Preview URL (POST .../preview-url) ---
type PreviewUrlResponse = {
  preview_url: string;
  expires_at: string;
};

// --- Create subdirectory (POST .../subdirectories) ---
type CreateDirectoryRequest = { name: string };
type CreateDirectoryResponse = { id: string; name: string };

// --- Resolve assets (POST .../resolve-assets) ---
type ResolveAssetsRequest = { paths: string[] };
type ResolvedAsset = {
  original_path: string;
  presigned_url: string | null;
  error: string | null;
};
type ResolveAssetsResponse = { assets: ResolvedAsset[] };

// --- Resolve paths to IDs (POST .../resolve-paths-to-ids) ---
type ResolvePathsToIdsRequest = { paths: string[] };
type ResolvedPathToId = {
  original_path: string;
  file_id: string | null;
  error: string | null;
};
type ResolvePathsToIdsResponse = { resolved: ResolvedPathToId[] };

// --- AI file (POST .../ai-files) — 201 ---
type CreateAiFileRequest = {
  filename: string;
  path: string;
  workflow_type: string;
  project_type_id?: string | null;
  form_input_data?: Record<string, unknown> | null;
};
type CreateAiFileResponse = {
  file_id: string;
  filename: string;
  workflow_type: string;
};

// --- Trigger / cancel AI file ---
type TriggerAiFileRequest = { form_input_data?: Record<string, unknown> | null };
type TriggerAiFileResponse = {
  activity_id: string;
  workflow_type: string;
  live_object_id: string;
};
type CancelAiFileResponse = {
  live_object_id: string;
  cancelled_children: number;
};
```

---

## Prepare Project File Upload

Request body: `PrepareFileUploadRequest`. Response (201): `PrepareFileUploadResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/directories/<directory_id>/files/prepare" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"path":"/","file_name":"doc.pdf","mime_type":"application/pdf","size":2048}'
```

---

## Prepare Project File Update

Request body: `PrepareUpdateFileRequest`. Response: `PrepareUpdateFileResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/directories/<directory_id>/files/prepare-update" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"path":"/doc.pdf","mime_type":"application/pdf","size":2048}'
```

---

## Confirm Project File Upload

Request body: `ConfirmFileUploadRequest`. Response: `FileResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/files/<file_id>/confirm" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"metadata":{}}'
```

---

## Get File Download URL

Response: `DownloadUrlResponse`.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/files/<file_id>/download-url" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Get File Details

Response: `FileResponse`.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/files/<file_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Delete Project File

Response: 204 No Content.

```bash
curl -X DELETE "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/files/<file_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Create Subdirectory

Creates a child directory under `directory_id`. Response (201): `CreateDirectoryResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/directories/<directory_id>/subdirectories" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"assets"}'
```

---

## Preview URL (HTML)

For HTML files: processes assets and returns a presigned preview URL.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/directories/<directory_id>/files/<file_id>/preview-url" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Resolve Asset Paths to URLs

Body: `{ "paths": ["./img.png", "..."] }`. Response: `ResolveAssetsResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/directories/<directory_id>/resolve-assets" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"paths":["/relative/path.png"]}'
```

---

## Resolve Paths to Live Object IDs

Body: `{ "paths": ["~/file.txt", "/abs/path"] }`. Response: `ResolvePathsToIdsResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/directories/<directory_id>/resolve-paths-to-ids" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"paths":["/notes.txt"]}'
```

---

## Create AI File

Creates an `.ai` configuration file in the directory. Response (201): `CreateAiFileResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/directories/<directory_id>/ai-files" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"filename":"workflow.ai","path":"/","workflow_type":"YOUR_WORKFLOW"}'
```

---

## Trigger AI File Workflow

Poll `GET /api/v2/jobs/{activity_id}` after.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/ai-files/<ai_file_id>/trigger" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{}'
```

---

## Cancel AI File Workflow

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/ai-files/<ai_file_id>/cancel" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Agent behavior

When the user asks to manage project files (upload, update, confirm, download, delete), do the following.

### 1. Choose the right endpoint

Base path: `/api/v2/projects/{projectId}`. Replace `project_id`, `directory_id`, `file_id` as needed.

| User intent | Endpoint | Method |
|-------------|----------|--------|
| Get upload URL for new file | `.../directories/{directoryId}/files/prepare` | POST |
| Get upload URL to update file | `.../directories/{directoryId}/files/prepare-update` | POST |
| Confirm upload after PUT | `.../files/{fileId}/confirm` | POST |
| Get download URL | `.../files/{fileId}/download-url` | GET |
| Get file metadata | `.../files/{fileId}` | GET |
| Delete file | `.../files/{fileId}` | DELETE |
| Create subdirectory | `.../directories/{directoryId}/subdirectories` | POST |
| HTML preview URL | `.../directories/{directoryId}/files/{fileId}/preview-url` | POST |
| Resolve paths → presigned URLs | `.../directories/{directoryId}/resolve-assets` | POST |
| Resolve paths → file IDs | `.../directories/{directoryId}/resolve-paths-to-ids` | POST |
| Create AI file | `.../directories/{directoryId}/ai-files` | POST |
| Trigger AI file | `.../ai-files/{aiFileId}/trigger` | POST |
| Cancel AI file | `.../ai-files/{aiFileId}/cancel` | POST |

### 2. Build and run the request

- **Auth**: Include `X-API-KEY: $LAYERPROOF_API_KEY`. Read env vars; if missing, tell the user.
- **Path**: Resolve projectId, directoryId, fileId from context or user input.
- **POST**: Build JSON body from types above; run curl and show result.
- **GET/DELETE**: Build path only; run curl and show result.

### 3. Upload flow

Prepare → PUT file to `upload_url` → confirm with `file_id`. Then use `file_id` for get/download/delete.

### 4. Response handling

- Always show raw JSON in a code block; show image + JSON if image URL present.
- On error, show body and status code.

### 5. Example workflows

**Workflow A — User**: "Upload a PDF to project X in the root directory."

1. Resolve projectId and directoryId (e.g. root directory from project or list directories). POST `.../directories/{directoryId}/files/prepare` with `{"path":"/","file_name":"brief.pdf","mime_type":"application/pdf","size":<bytes>}`; get `upload_url`, `file_id`, `s3_key`.
2. Tell user to PUT the file to `upload_url` with `Content-Type: application/pdf`. Then POST `.../files/{file_id}/confirm` with `{}` (or `{"metadata":{}}`).
3. Show JSON; use `file_id` for later get/download/delete.

**Workflow B — User**: "Upload two reference docs to the project, then use them when generating the slide deck outline."

1. For each file: prepare with path (e.g. `/ref1.pdf`, `/ref2.docx`) → user uploads to `upload_url` → confirm. Capture both `file_id` and `s3_key` (if returned) for each.
2. If outline generation accepts project file references: pass the project file identifiers (e.g. s3_keys or file_ids) into the slide-deck outline/generate request (e.g. `file_s3_keys` or equivalent). Otherwise use public-files for outline references and keep project files for project-scoped use only.
3. Hand off to slide-decks: POST outline/generate with the resolved keys; poll job; get deck.

**Workflow C — User**: "Update an existing file in the project (new version) and get a download URL after."

1. Resolve projectId, directoryId, and existing file path. POST `.../directories/{directoryId}/files/prepare-update` with `{"path":"/brief.pdf","mime_type":"application/pdf","size":<new_bytes>}`; get `upload_url`, `file_id`.
2. User PUTs new content to `upload_url`; then POST `.../files/{file_id}/confirm`. GET `.../files/{file_id}/download-url` to show the user a temporary download link.
3. On error (e.g. path not found), show response body and suggest verifying path and project/directory IDs.

---

## Response format (required)

- (if response contains url to show image) please show image and show json response instead of table
- Always show the **raw JSON response** (verbatim) in a JSON code block.
- If the response contains a URL for an image, **render/show the image** and also show the **JSON response** (do not convert to a table).
Skill

projects

Public API project management (X-API-KEY). Create, list, get, update, delete, clone, vote, visibility, recent/deleted/restore. Cursor pagination. Types follow PublicApiProjectController (/api/v2/projects).

# Skill: Projects Management

## Description

Manage Layerproof projects (create, list, get, update, delete, clone, vote, public list, visibility, recent, soft-delete restore). This skill documents the **public API** at `/api/v2/projects` (PublicApiProjectController). Authenticate with `X-API-KEY` header. **List** uses cursor pagination (`limit`, `cursor`). **List deleted** uses offset pagination (`page`, `page_size`).

---

## Requirements

Environment variables:

- `LAYERPROOF_BASE_URL`
- `LAYERPROOF_API_KEY`

All requests must include:

- `X-API-KEY: $LAYERPROOF_API_KEY`

---

## TypeScript types (request / response)

Mirrors `PublicApiProjectController` data classes.

```typescript
// --- Create (POST) — 201 ---
type PublicApiCreateProjectRequest = {
  name: string;                   // required, 1–255 chars
  description?: string | null;    // max 10000 chars
  status?: string | null;         // e.g. "DRAFT"; defaults to DRAFT if omitted
  tags?: string[] | null;
  metadata?: Record<string, any> | null;
  project_kind?: "SLIDE_DECK" | "GENERIC" | "MINDMAP" | "THEME" | "BLOG_POST" | "SOCIAL_CAMPAIGN";  // default: SLIDE_DECK
  workspace_id?: string | null;   // UUID
  theme_id?: string | null;       // UUID; optional theme for slide deck
  aspect_ratio?: string | null;   // e.g. "16:9"
};

// --- Update (PUT) ---
type PublicApiUpdateProjectRequest = {
  name?: string | null;           // 1–255 chars
  description?: string | null;    // max 10000 chars
  status?: string | null;
  tags?: string[] | null;
  metadata?: Record<string, any> | null;
  workspace_id?: string | null;   // UUID
  is_public?: boolean | null;
};

// --- Clone (POST /{projectId}/clone) — 201 ---
type CloneProjectRequest = {
  name?: string | null;           // defaults to "Copy of <source name>"
  workspace_id?: string | null;   // UUID; if null, auto-creates hidden workspace
};

// --- Project response ---
type PublicApiProjectResponse = {
  id: string;
  name: string;
  description: string | null;
  status: string;                 // e.g. "ACTIVE", "DRAFT"
  project_kind: string;           // e.g. "SLIDE_DECK"
  metadata: Record<string, any>;
  slide_deck_id: string | null;
  workspace_id?: string | null;   // UUID
  thumbnail_url?: string | null;  // optional preview image URL
  slide_deck_type?: string | null;  // e.g. "PRESENTATION", "SOCIAL_POST", "WEBPAGE", "VIDEO"
  tags: string[];
  created_at: string;             // ISO 8601
  updated_at: string;             // ISO 8601
  is_public: boolean;
  vote_count: number;
  has_voted?: boolean | null;
};

// --- List response (cursor-paginated) ---
type PublicApiProjectListResponse = {
  data: PublicApiProjectResponse[];
  next_cursor?: string | null;     // pass as `cursor` param for next page
  has_more?: boolean;
};

// --- Recent projects (GET /list/recent) ---
type PublicApiRecentProjectsResponse = {
  data: PublicApiProjectResponse[];
};

// --- Update visibility only (PUT /{project_id}/visibility) ---
type PublicApiUpdateProjectVisibilityRequest = {
  is_public: boolean;  // required
};
```

---

## List Projects

Query params: `limit` (default 20, max 100), `cursor` (from previous `next_cursor`).

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/projects?limit=20" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

Example JSON response:

```json
{"data":[{"id":"2b12d232-eca6-4161-96df-9f954fbbb36f","name":"Exit Strategy Overview","description":"...","status":"ACTIVE","project_kind":"SLIDE_DECK","metadata":{},"slide_deck_id":"ab2c64a5-0b59-45ec-baef-1d5b68dbf5fe","created_at":"2026-03-09T03:15:51.548959Z","updated_at":"2026-03-09T03:16:17.069810Z","is_public":false,"vote_count":0,"has_voted":null}],"next_cursor":null,"has_more":false}
```

---

## Get Project

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

Example JSON response:

```json
{"id":"2b12d232-eca6-4161-96df-9f954fbbb36f","name":"Exit Strategy Overview","description":"...","status":"ACTIVE","project_kind":"SLIDE_DECK","metadata":{"original_prompt":"Create an exit strategy overview"},"slide_deck_id":"ab2c64a5-0b59-45ec-baef-1d5b68dbf5fe","created_at":"2026-03-09T03:15:51.548959Z","updated_at":"2026-03-09T03:16:17.069810Z","is_public":false,"vote_count":0,"has_voted":false}
```

---

## Create Project

Request body: `PublicApiCreateProjectRequest`. Response (201): `PublicApiProjectResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{
    "name": "<project_name>",
    "description": "<description>",
    "project_kind": "SLIDE_DECK"
  }'
```

Example JSON response:

```json
{"id":"68f8270f-f36a-4817-8d0b-2c05efcd1c9a","name":"tmp-skill-project","description":"tmp","status":"DRAFT","project_kind":"SLIDE_DECK","metadata":{},"slide_deck_id":"a877d0c0-9514-40cc-8d27-cee3a1e73934","created_at":"2026-03-10T07:07:46.211172Z","updated_at":"2026-03-10T07:07:46.211176Z","is_public":false,"vote_count":0,"has_voted":null}
```

---

## Update Project

Request body: `PublicApiUpdateProjectRequest`. Only provided fields are updated. Response: `PublicApiProjectResponse`.

```bash
curl -X PUT "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"<new_name>","description":"<new_description>","is_public":true}'
```

---

## Delete Project

Soft-deletes the project and all associated resources. Response: 204 No Content.

```bash
curl -X DELETE "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## List Public Projects

Query params: `limit` (default 20, max 100), `cursor`, `slide_deck_type` (one of `PRESENTATION`, `SOCIAL_POST`, `WEBPAGE`, `VIDEO`; optional).

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/projects/public?limit=20" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Vote for a Project

Records a vote on a public project. Response: 204 No Content.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/vote" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Remove Vote from a Project

Removes a vote from a public project. Response: 204 No Content.

```bash
curl -X DELETE "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/vote" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Clone Project

Clones an owned or public project into a new private project. Request body: `CloneProjectRequest`. Response (201): `PublicApiProjectResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/clone" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"<cloned_project_name>","workspace_id":"<workspace_id>"}'
```

---

## Update Project Visibility

Sets public or private without sending a full project update. Request body: `PublicApiUpdateProjectVisibilityRequest`. Response: `PublicApiProjectResponse`.

```bash
curl -X PUT "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/visibility" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"is_public":true}'
```

---

## List Recent Projects

Query: `limit` (default 4, max 100). Response: `PublicApiRecentProjectsResponse`.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/projects/list/recent?limit=8" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## List Deleted Projects

Soft-deleted projects for the current user. Query: `page` (default 0), `page_size` (default 20). Response: `PublicApiProjectListResponse` (cursor fields may be absent; use `has_more`).

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/projects/deleted?page=0&page_size=20" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Restore Deleted Project

Restores a soft-deleted project. Response (200): `{"restored": true}`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/restore" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Permanently Delete Project

Hard-deletes a project that is already soft-deleted. Response: 204 No Content.

```bash
curl -X DELETE "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/permanently" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Agent behavior

When the user asks to manage projects, do the following.

### 1. Choose the right endpoint

| User intent | Endpoint | Method |
|-------------|----------|--------|
| List own projects | `/api/v2/projects?limit=20` | GET |
| Get project by ID | `/api/v2/projects/{projectId}` | GET |
| Create project | `/api/v2/projects` | POST |
| Update project | `/api/v2/projects/{projectId}` | PUT |
| Delete project | `/api/v2/projects/{projectId}` | DELETE |
| List public projects | `/api/v2/projects/public?limit=20` | GET |
| Vote for a project | `/api/v2/projects/{projectId}/vote` | POST |
| Remove vote | `/api/v2/projects/{projectId}/vote` | DELETE |
| Clone a project | `/api/v2/projects/{projectId}/clone` | POST |
| Set visibility only | `/api/v2/projects/{projectId}/visibility` | PUT |
| Recent projects | `/api/v2/projects/list/recent?limit=8` | GET |
| List deleted projects | `/api/v2/projects/deleted?page=0&page_size=20` | GET |
| Restore deleted project | `/api/v2/projects/{projectId}/restore` | POST |
| Permanently delete (after soft delete) | `/api/v2/projects/{projectId}/permanently` | DELETE |

### 2. Build and run the request

- **Auth**: Include `X-API-KEY: $LAYERPROOF_API_KEY`. Read `LAYERPROOF_BASE_URL` and `LAYERPROOF_API_KEY` from the environment; if missing, tell the user to set them.
- **GET list**: Use `limit` (default 20, max 100) and `cursor` for pagination. Pass `next_cursor` from a previous response as `cursor` to fetch the next page.
- **POST/PUT**: Build JSON body from the appropriate request type. Run curl and show result.
- **DELETE project / vote**: Build path; run curl. Response is 204 with no body.

### 3. Response handling

- Always show the **raw JSON response** in a JSON code block.
- For 204 responses, indicate success and no body.
- On error, show the response body and status code.

### 4. Example workflows

**Workflow A — User**: "Create a project called Q3 Roadmap."

1. Choose POST `/api/v2/projects`.
2. Body: `{"name":"Q3 Roadmap","project_kind":"SLIDE_DECK"}`.
3. Run curl; show JSON. The returned `id` is the project ID; `slide_deck_id` is the associated slide deck.

**Workflow B — User**: "Create a slide deck project in workspace W, then generate an outline for it."

1. Resolve `workspace_id` (e.g. from GET workspaces or user). POST `/api/v2/projects` with `{"name":"...", "project_kind":"SLIDE_DECK", "workspace_id":"<workspace_id>"}`; capture `id` and `slide_deck_id`.
2. Hand off to slide-decks skill: use `projectId = id` and `slideDeckId = slide_deck_id`; POST `.../projects/{projectId}/slide-deck/{slideDeckId}/outline/generate` with prompt and slide_count; poll jobs; get deck or update outline as needed.

**Workflow C — User**: "List my projects, find the one named 'Launch Deck', export it as PPTX, and if there’s a clone or vote option show me how."

1. GET `/api/v2/projects` with `limit` and optional `cursor`; paginate if needed. Locate project where `name` matches "Launch Deck"; capture `id`.
2. Hand off to exports skill: POST `.../projects/{id}/exports/pptx`; poll GET `.../exports/{exportId}` until COMPLETED; show `downloadUrl`.
3. Optionally: POST `.../projects/{id}/clone` to clone, or POST/DELETE `.../projects/{id}/vote` per API; show response and explain usage.

---

## Response format (required)

- (if response contains url to show image) please show image and show json response instead of table
- Always show the **raw JSON response** (verbatim) in a JSON code block.
- If the response contains a URL for an image, **render/show the image** and also show the **JSON response** (do not convert to a table).
Skill

public-files

Public API reference files (X-API-KEY). Prepare upload, confirm, delete, get download URL. Use s3_key from prepare in outline generation. Types follow PublicApiFileController (/api/v2/files).

# Skill: Public Files

## Description

Upload reference files (images, documents) for use in outline generation. This skill documents the **public API** at `/api/v2/files` (PublicApiFileController). Flow: 1) POST /prepare → get `upload_url` and `s3_key`; 2) PUT file to `upload_url`; 3) POST /confirm; use `s3_key` in `file_s3_keys` when calling slide-deck outline/generate. Authenticate with `X-API-KEY` header. Max 20 MB; allowed types: PDF, DOCX, PPTX, TXT, MD, PNG, JPEG, WebP, SVG, BMP, TIFF, HEIC, HEIF, AVIF, ICO.

---

## TypeScript types (request / response)

Mirrors `PublicApiFileController` (/api/v2/files) data classes.

```typescript
// --- Prepare (POST) ---
type PrepareFileUploadRequest = {
  file_name: string;   // max 255 chars
  mime_type: string;
  size: number;        // 1 to 20*1024*1024
};
type PrepareFileUploadResponse = {
  upload_url: string;
  s3_key: string;
  expires_at: string;  // ISO 8601, ~10 min
};

// --- Confirm (POST) ---
type PublicApiConfirmFileUploadRequest = { s3_key: string };
type PublicApiConfirmFileUploadResponse = { s3_key: string; uploaded: boolean };

// --- Delete (POST) ---
type PublicApiDeleteFileRequest = { s3_key: string };
type PublicApiDeleteFileResponse = { success: boolean };

// --- Download URL (POST) ---
type PublicApiDownloadUrlRequest = {
  s3_key: string;
  expiry_seconds?: number;  // 60–604800, default 3600
};
type PublicApiDownloadUrlResponse = {
  download_url: string;
  expires_at: string;
};
```

---

## Prepare Public File Upload

Request body: `PrepareFileUploadRequest`. Response: `PrepareFileUploadResponse`. Then PUT file to `upload_url` with matching Content-Type.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/files/prepare" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"file_name":"doc.pdf","mime_type":"application/pdf","size":1024}'
```

---

## Confirm Public File Upload

Request body: `PublicApiConfirmFileUploadRequest`. Response: `PublicApiConfirmFileUploadResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/files/confirm" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"s3_key":"<s3_key_from_prepare>"}'
```

---

## Delete Public File

Request body: `PublicApiDeleteFileRequest`. Response: `PublicApiDeleteFileResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/files/delete" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"s3_key":"<s3_key>"}'
```

---

## Get Public File Download URL

Request body: `PublicApiDownloadUrlRequest`. Response: `PublicApiDownloadUrlResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/files/download-url" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"s3_key":"<s3_key>","expiry_seconds":3600}'
```

---

## Agent behavior

When the user asks to upload or manage reference files (for outlines), do the following.

### 1. Choose the right endpoint

| User intent | Endpoint | Method |
|-------------|----------|--------|
| Get upload URL for a file | `/api/v2/files/prepare` | POST |
| Confirm file uploaded to S3 | `/api/v2/files/confirm` | POST |
| Delete a file by s3_key | `/api/v2/files/delete` | POST |
| Get download URL for a file | `/api/v2/files/download-url` | POST |

### 2. Build and run the request

- **Auth**: Include `X-API-KEY: $LAYERPROOF_API_KEY`. Read `LAYERPROOF_BASE_URL` and `LAYERPROOF_API_KEY` from the environment; if missing, tell the user to set them.
- **Body**: All endpoints use JSON body (`file_name`, `mime_type`, `size` for prepare; `s3_key` for confirm/delete/download-url). Run curl and show the result.

### 3. Upload flow

After prepare: tell the user to PUT the file to `upload_url` with `Content-Type: <mime_type>`, then call confirm with the returned `s3_key`. Use that `s3_key` in `file_s3_keys` when calling slide-deck outline/generate.

### 4. Response handling

- Always show the **raw JSON response** in a JSON code block.
- If the response contains a URL for an image, show the image and the JSON.
- On error, show the response body and status code.

### 5. Example workflows

**Workflow A — User**: "Upload a reference PDF for outline generation."

1. POST `/api/v2/files/prepare` with `{"file_name":"brief.pdf","mime_type":"application/pdf","size":<bytes>}`; get `upload_url`, `s3_key`, `expires_at`.
2. User PUTs file to `upload_url` with `Content-Type: application/pdf`. POST `/api/v2/files/confirm` with `{"s3_key":"<s3_key>"}`.
3. Use this `s3_key` in slide-deck POST `.../outline/generate` body as `file_s3_keys: ["<s3_key>"]`.

**Workflow B — User**: "Upload two reference files (PDF + DOCX), confirm both, then generate an outline that uses them."

1. For first file: prepare → user uploads → confirm; capture `s3_key_1`. For second: prepare → upload → confirm; capture `s3_key_2`.
2. Hand off to slide-decks: resolve projectId and slideDeckId. POST `.../outline/generate` with `{"prompt":"...", "slide_count":5, "file_s3_keys":["<s3_key_1>","<s3_key_2>"]}`.
3. Poll jobs for `activity_id`; when DONE, GET deck to show outline that was informed by the references.

**Workflow C — User**: "Get a download URL for a file I uploaded earlier (I have the s3_key), then delete it when done."

1. POST `/api/v2/files/download-url` with `{"s3_key":"<s3_key>","expiry_seconds":3600}`; show `download_url` and `expires_at`.
2. When user is done: POST `/api/v2/files/delete` with `{"s3_key":"<s3_key>"}`; show success response. Warn that delete is irreversible.

---

## Response format (required)

- (if response contains url to show image) please show image and show json response instead of table
- Always show the **raw JSON response** (verbatim) in a JSON code block.
- If the response contains a URL for an image, **render/show the image** and also show the **JSON response** (do not convert to a table).
Skill

slide-decks

Public API slide deck operations (X-API-KEY). Generate outlines, update outline, get deck, batch generate slides, generate transcript/image/content, cancel, theme, transcript, duplicate section. Types follow PublicApiSlideDeckController (/api/v2/projects/{projectId}/slide-deck/{slideDeckId}).

# Skill: Slide Deck Generation

## Description

Manage AI-generated slide decks and outlines. This skill documents the **public API** at `/api/v2/projects/{projectId}/slide-deck/{slideDeckId}` (PublicApiSlideDeckController). All endpoints require `projectId` and `slideDeckId` in the path. Authenticate with `X-API-KEY` header. Poll async operations via `GET /api/v2/jobs/{activityId}`.

---

## TypeScript types (request / response)

Mirrors `PublicApiSlideDeckController` data classes.

```typescript
// --- Outline section (used in UpdateOutlineRequest and responses) ---
type PublicApiSlideIconAsset = { query: string; slot?: string | null };
type PublicApiSlideImageAsset = { prompt: string; slot?: string | null };
type PublicApiOutlineSection = {
  id: string;
  section_title: string;   // 1–500 chars
  content?: string | null;  // max 5000
  key_points?: string[] | null;
  visual_suggestion?: string | null;  // max 500
  speaker_notes?: string | null;       // max 2000
  url_references?: string | null;
  layout?: string | null;
  icon_assets?: PublicApiSlideIconAsset[];
  image_assets?: PublicApiSlideImageAsset[];
  reference_image_paths?: string[];
  slide_intent?: Record<string, unknown> | null;
};

// --- Generate Outline (POST) — async ---
type GenerateOutlineRequest = {
  prompt: string;               // 3–2000 chars, required
  slide_count?: number;         // default 5, min 1
  language?: string;            // 2–10 chars, e.g. "en"
  file_s3_keys?: string[];      // from POST /api/v2/files/prepare
  web_search_enabled?: boolean;
  text_detail_level?: string | null;
  tone?: string | null;
};
type GenerateOutlineResponse = {
  activity_id: string;
  status: string;
  message: string;
  estimated_completion_seconds?: number;
};

// --- Update Outline (PUT) ---
type UpdateOutlineRequest = {
  title: string;                // 1–500 chars
  sections: PublicApiOutlineSection[];  // at least one required
};
type PublicApiOutline = {
  id: string;
  title: string;
  sections: PublicApiOutlineSection[];
  total_sections: number;
  suggested_slide_count?: number | null;
  updated_at?: string;
};
type UpdateOutlineResponse = { outline: PublicApiOutline };

// --- Get Deck (GET) ---
type PublicApiSlideDeck = {
  id: string;
  project_id: string;
  title: string;
  description?: string | null;
  deck_type?: string | null;
  slide_numbers?: number | null;
  aspect_ratio: string;
  theme?: string | null;
  outline_generation_live_object_id?: string | null;
  created_at: string;
  updated_at: string;
};
type PublicApiSlide = {
  id: string;
  index: number;
  section_id: string;
  section_title: string;
  content?: string | null;
  key_points?: string[] | null;
  visual_suggestion?: string | null;
  speaker_notes?: string | null;
  transcript?: string | null;
  image_url?: string | null;
  image_expires_at?: string | null;
  generation_status: string;
  error_message?: string | null;
  created_at: string;
  updated_at: string;
};
type PublicApiDeckMetadata = {
  total_slides: number;
  completed_slides: number;
  pending_slides: number;
  overall_progress: number;
};
type GetDeckResponse = {
  slide_deck: PublicApiSlideDeck;
  outline: PublicApiOutline;
  slides: PublicApiSlide[];
  metadata: PublicApiDeckMetadata;
};

// --- Batch Generate Slides (POST) — async ---
type BatchGenerateSlidesRequest = {
  generation_type?: string;          // default "BOTH"
  speaking_style?: string | null;
  target_duration_minutes?: number | null;  // min 1
  transcript_tone?: string | null;
  aspect_ratio?: string | null;
  output_language?: string | null;   // 2–10 chars
  text_detail_level?: string | null;
  tone?: string | null;
};
type BatchGenerateSlidesResponse = {
  activity_id: string;
  total_slides: number;
  status: string;
  message: string;
  estimated_completion_seconds: number;
};

// --- Generate Slide Content / Transcript / Image (POST) — async ---
type GenerateSlideContentRequest = {
  slide_section_id: string;
  speaking_style?: string | null;
  target_duration_minutes?: number | null;  // min 1
  transcript_tone?: string | null;
  generation_type?: string | null;   // default "TRANSCRIPT_AND_IMAGE"
};
type GenerateSlideContentResponse = {
  activity_id: string;
  slide_section_id: string;
  transcript_gen_live_object_id?: string | null;
  image_gen_live_object_id?: string | null;
  status: string;
  message: string;
};

type GenerateSlideTranscriptRequest = {
  slide_section_id: string;
  speaking_style?: string | null;
  target_duration_minutes?: number | null;  // min 1
  transcript_tone?: string | null;
};
type GenerateSlideTranscriptResponse = {
  activity_id: string;
  slide_section_id: string;
  transcript_gen_live_object_id: string;
  status: string;
  message: string;
  estimated_completion_seconds?: number;
};

type GenerateSlideImageRequest = {
  slide_section_id: string;
  aspect_ratio?: string;  // "16:9" | "4:3" | "1:1", default "16:9"
};
type GenerateSlideImageResponse = {
  activity_id: string;
  slide_section_id: string;
  image_gen_live_object_id: string;
  status: string;
  message: string;
  estimated_completion_seconds?: number;
};

// --- Cancel Generation (POST) ---
type CancelGenerationRequest = { live_object_ids?: string[] | null };
type CancelGenerationResponse = {
  cancelled_live_objects: string[];
  failed_to_cancel_live_objects: string[];
  total_activities_cancelled: number;
  message: string;
};

// --- Generate Theme (POST) — async ---
type GenerateThemeRequest = {
  prompt: string;                      // 10–2000 chars
  reference_image_paths?: string[] | null;
};
type GenerateThemeResponse = {
  activity_id: string;
  status: string;
  message: string;
  estimated_completion_seconds: number;
};

// --- Update Transcript (PUT) ---
type UpdateTranscriptRequest = { transcript: string };
type UpdateTranscriptResponse = {
  slide_section_id: string;
  version_id: string;
  version_number: number;
  transcript_gen_live_object_id: string;
  transcript: string;
  updated_at: string;
};

// --- Duplicate Section (POST) ---
type DuplicateSectionRequest = {
  copy_content?: boolean;
  insert_after?: boolean;
};
type DuplicateSectionResponse = {
  new_section_id: string;
  new_section: PublicApiOutlineSection;
  transcript_gen_live_object_id?: string | null;
  image_gen_live_object_id?: string | null;
  message: string;
};
```

---

## Generate Outline (async)

Request body: `GenerateOutlineRequest`. Response (202): `GenerateOutlineResponse`.

Starts outline generation from a prompt. Use `file_s3_keys` from files uploaded via `/api/v2/files/prepare` and confirm. Poll `GET /api/v2/jobs/{activityId}` for status.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/outline/generate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"prompt":"Create a product launch deck","slide_count":6}'
```

With reference files and language:

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/outline/generate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"prompt":"Q4 strategy","slide_count":5,"language":"en","file_s3_keys":["public-api/.../file.pdf"],"web_search_enabled":true}'
```

---

## Update Outline

Request body: `UpdateOutlineRequest`. Response: `UpdateOutlineResponse`.

Updates outline title and sections (add, remove, reorder). At least one section required.

```bash
curl -X PUT "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/outline" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"title":"My Deck","sections":[{"id":"section-uuid","section_title":"Intro","content":"...","speaker_notes":"..."}]}'
```

---

## Get Full Slide Deck

Query: `include_images` (default true), `image_expiry_seconds` (default 3600). Response: `GetDeckResponse`.

Returns deck, outline, slides with presigned image URLs, and metadata.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/?include_images=true&image_expiry_seconds=3600" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Batch Generate Slides (async)

Request body: `BatchGenerateSlidesRequest`. Response (202): `BatchGenerateSlidesResponse`.

Generate transcript and/or images for all outline sections. Requires an outline first. Poll jobs with `activity_id`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/slides/batch-generate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"generation_type":"BOTH"}'
```

---

## Generate Slide Content (async)

Request body: `GenerateSlideContentRequest`. Response (202): `GenerateSlideContentResponse`.

Generate transcript and image for one section. Poll `GET /api/v2/jobs/{activityId}`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/slides/generate-content" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"slide_section_id":"<section_uuid>"}'
```

---

## Generate Slide Transcript (async)

Request body: `GenerateSlideTranscriptRequest`. Response (202): `GenerateSlideTranscriptResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/slides/generate-transcript" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"slide_section_id":"<section_uuid>"}'
```

---

## Generate Slide Image (async)

Request body: `GenerateSlideImageRequest`. Response (202): `GenerateSlideImageResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/slides/generate-image" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"slide_section_id":"<section_uuid>","aspect_ratio":"16:9"}'
```

---

## Cancel Generation

Request body: `CancelGenerationRequest`. Response: `CancelGenerationResponse`.

Provide `live_object_ids` from active generation responses to cancel.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/cancel" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"live_object_ids":["<live_object_uuid>"]}'
```

---

## Generate Deck Theme (async)

Request body: `GenerateThemeRequest`. Response (202): `GenerateThemeResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/theme/generate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"prompt":"Minimal corporate blue"}'
```

---

## Update Slide Transcript

Request body: `UpdateTranscriptRequest`. Response: `UpdateTranscriptResponse`.

Path includes `slideSectionId`: `PUT .../slides/{slideSectionId}/transcript`.

```bash
curl -X PUT "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/slides/<slide_section_id>/transcript" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"transcript":"Updated speaker notes."}'
```

---

## Duplicate Slide Section

Request body: `DuplicateSectionRequest`. Response: `DuplicateSectionResponse`.

Path includes `slideSectionId`: `POST .../slides/{slideSectionId}/duplicate`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slide-deck/<slide_deck_id>/slides/<slide_section_id>/duplicate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"copy_content":true,"insert_after":true}'
```

---

## Agent behavior

When the user asks to work with slide decks (outline, deck, slides, theme, transcript), do the following.

### 1. Choose the right endpoint

Base path: `/api/v2/projects/{projectId}/slide-deck/{slideDeckId}`. All require `projectId` and `slideDeckId`.

| User intent | Endpoint | Method |
|-------------|----------|--------|
| Generate outline from prompt | `.../outline/generate` | POST |
| Update outline structure | `.../outline` | PUT |
| Get full deck (outline + slides + URLs) | `.../` (with optional query) | GET |
| Batch generate all slides | `.../slides/batch-generate` | POST |
| Generate one slide content | `.../slides/generate-content` | POST |
| Generate one slide transcript | `.../slides/generate-transcript` | POST |
| Generate one slide image | `.../slides/generate-image` | POST |
| Cancel generations | `.../cancel` | POST |
| Generate deck theme | `.../theme/generate` | POST |
| Update slide transcript | `.../slides/{slideSectionId}/transcript` | PUT |
| Duplicate section | `.../slides/{slideSectionId}/duplicate` | POST |
| Improve section (AI) | `.../slides/improve` | POST |
| Manual slide generation | `.../slides/generate-manual` | POST |
| Patch visual style description | `.../theme/visual-style-description` | PATCH |
| List / restore transcript versions | `.../slides/{id}/transcript/versions` (GET), `.../versions/{version_id}/restore` (POST) | GET / POST |
| Mark transcript / improvement / live object read | `.../mark-transcript-read`, `.../mark-improvement-read`, `.../live-objects/{id}/mark-read` | PATCH |
| Text-to-speech | `.../slides/{id}/generate-tts` (POST), `.../slides/{audio_id}/audio/download-url` (GET) | POST / GET |
| Update deck settings (aspect ratio, etc.) | `.../` (slide-deck root) | PATCH |
| Tone settings | `.../tone-settings` | GET / PUT |
| Batch Konva layout | `.../slides/batch-generate-layout` | POST |
| PPTX import | `.../import/prepare-upload`, `.../import` | POST |
| Citations | `.../citations`, `.../citations/slide/{index}`, `.../citations/{citation_id}` | GET |

### 2. Build and run the request

- **Auth**: Every request must include `X-API-KEY: $LAYERPROOF_API_KEY`. Read `LAYERPROOF_BASE_URL` and `LAYERPROOF_API_KEY` from the environment; if missing, tell the user to set them.
- **Path**: Resolve `projectId` and `slideDeckId` (and `slideSectionId` where needed) from context or user input. If missing, ask.
- **GET**: Build curl with path and query params (`include_images`, `image_expiry_seconds` for get deck). Run and show result.
- **POST/PUT**: Build JSON body from the types above. Use `-X POST` or `-X PUT`, `-H "Content-Type: application/json"`, and `-d '...'`. Run and show result.

### 3. After async endpoints

- Responses include `activity_id`. Tell the user the job was started and suggest polling `GET $LAYERPROOF_BASE_URL/api/v2/jobs/{activityId}` until `status` is `DONE` or `CANCELED`.
- Typical flow: generate outline → poll until DONE → get deck or update outline → batch generate slides or generate single slide content/transcript/image → poll jobs.

### 4. Response handling

- Always show the **raw JSON response** in a JSON code block; do not convert to a table.
- If the response contains image URLs (e.g. in get deck `slides[].image_url`), show images and the JSON.
- On error (4xx/5xx), show the response body and status code; suggest fixing API key, projectId/slideDeckId/sectionId, or request body.

### 5. Example workflows

**Workflow A — User**: "Generate an outline for a product launch deck in project X."

1. Resolve projectId and slideDeckId (e.g. from GET projects, then `project.slide_deck_id`).
2. Choose `POST .../outline/generate`.
3. Build body: `{"prompt":"Product launch deck","slide_count":5}`.
4. Run curl; show JSON. Tell user to poll `GET /api/v2/jobs/{activityId}` and then call get deck or update outline as needed.

**Workflow B — User**: "Full deck: outline from a prompt and reference PDF, then tweak the outline, apply a theme, batch generate slides, and fix one slide’s image."

1. Resolve projectId and slideDeckId. Get `file_s3_keys` from public-files (prepare → upload → confirm) or project-files if the API accepts them for outline.
2. POST `.../outline/generate` with `{"prompt":"Product launch with pricing","slide_count":6,"file_s3_keys":["<s3_key>"],"language":"en"}`; capture `activity_id`.
3. Poll `GET /api/v2/jobs/{activity_id}` until DONE. On failure, report and stop.
4. GET deck; from `outline.sections` identify a section to change. PUT `.../outline` with `title` and updated `sections` (e.g. edit `section_title`, `key_points`, `visual_suggestion` for one section).
5. If user wants a theme: use themes skill — **POST `/api/v2/themes/apply`** with `slide_deck_id` and `theme_id` (set `regenerate_slides` if images should be regenerated); poll job when `activity_id` is returned.
6. POST `.../batch-generate` with optional `generation_type`, `aspect_ratio`, `speaking_style`; capture `activity_id`.
7. Poll `GET /api/v2/jobs/{activity_id}` until DONE.
8. GET deck; check `metadata.completed_slides` and `slides[].generation_status`. If one slide’s image is wrong, POST `.../slides/{sectionId}/generate-image` (or generate-content) with section id; poll that job until DONE; GET deck again to show result.

**Workflow C — User**: "Duplicate a section in the outline and regenerate slides for the new section only."

1. GET deck; from `outline.sections` pick a `section_id` (UUID string) to duplicate.
2. POST `.../slides/{slide_section_id}/duplicate` with `{"copy_content":true,"insert_after":true}`; read `new_section_id` from the response.
3. PUT `.../outline` with updated `title` and `sections` array (merge/reorder the duplicated section as needed).
4. POST `.../slides/generate-transcript` | `generate-image` | `generate-content` with the new section id; poll `GET /api/v2/jobs/{activity_id}`; GET deck to confirm.

---

## Response format (required)

- (if response contains url to show image) please show image and show json response instead of table
- Always show the **raw JSON response** (verbatim) in a JSON code block.
- If the response contains a URL for an image, **render/show the image** and also show the **JSON response** (do not convert to a table).
Skill

slides

Public API slide editing (X-API-KEY). Edit slide images with AI, accept/revert edits, object removal, text extraction, save Konva nodes. Types follow PublicApiSlideController (/api/v2/projects/{projectId}/slides).

# Skill: Slide Editing

## Description

Modify slides after generation. This skill documents the **public API** at `/api/v2/projects/{projectId}/slides` (PublicApiSlideController). All slide endpoints require `projectId` and `slideId` in the path. Authenticate with `X-API-KEY` header.

---

## TypeScript types (request / response)

Mirrors `PublicApiSlideController` (/api/v2/projects/{projectId}/slides) data classes.

```typescript
// Shared: cropped region for region-based editing. Must lie fully within image bounds.
// Constraints: x >= 0, y >= 0, width > 0, height > 0; x + width <= image width, y + height <= image height.
type CroppedRegion = {
  x: number;
  y: number;
  width: number;
  height: number;
};

// --- Edit Slide Image (POST) — async, poll jobs ---
type SlideImageEditRequest = {
  /** AI instruction for the edit (required, non-blank) */
  instruction: string;
  /** Path to input image in project working dir (required, non-blank) */
  input_image_path: string;
  /** Optional extra reference image paths */
  other_reference_image_paths?: string[];
  /** Optional region to edit; only this region is modified */
  cropped_region?: CroppedRegion;
};

// --- Accept Image Edit (POST) ---
type AcceptImageEditRequest = {
  live_object_id: string;   // UUID from image-edit or object-removal response (required)
  target_node_id?: string;
  override_image_path?: string;  // Optional path to use instead of workflow output
};

// --- Revert Slide (POST) ---
type RevertSlideRequest = {
  history_entry_id: string;  // UUID from slide history (required)
  node_id?: string;
};

// --- Object Removal (POST) — async, poll jobs ---
type ObjectRemovalRequest = {
  input_image_path: string;  // required, non-blank
  mask_path?: string;
  cropped_region?: CroppedRegion;
};

// --- Extract Text (POST) — async, poll jobs ---
type ExtractTextRequest = {
  node_id?: string;
  node_image_path?: string;
};

// --- Save Konva Nodes (PUT) ---
type SaveKonvaNodesRequest = {
  konva_nodes: Record<string, unknown>;  // required
  konva_order: string[];                  // required
  base_snapshot_id?: string;  // UUID, optional
};

// --- Responses ---
type TriggerWorkflowResponse = {
  activity_id: string;   // UUID – poll GET /api/v2/jobs/{activityId}
  workflow_type: string;
  live_object_id: string; // UUID – pass to accept-image-edit when done
};

type AcceptImageEditResponse = {
  id: string;             // slide UUID
  slide_section_id: string;
  image_path: string | null;
  status: string;
};
```

---

## Edit Slide Image (async)

Request body: `SlideImageEditRequest`. Response: `TriggerWorkflowResponse`.

Triggers AI-powered slide image editing. Upload input image first, then call with `instruction` and `input_image_path`. Poll `GET /api/v2/jobs/{activityId}` for status; when DONE, call accept-image-edit with `live_object_id`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slides/<slide_id>/image-edit" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"instruction":"Make the background blue","input_image_path":"/slides/slide-1.png"}'
```

With optional region and reference images:

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slides/<slide_id>/image-edit" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"instruction":"Replace this area","input_image_path":"/slides/slide-1.png","cropped_region":{"x":0,"y":0,"width":200,"height":100},"other_reference_image_paths":["/ref.png"]}'
```

---

## Accept Image Edit

Request body: `AcceptImageEditRequest`. Response: `AcceptImageEditResponse`.

Call after the image-edit or object-removal workflow completes successfully. Use `live_object_id` from the trigger response.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slides/<slide_id>/accept-image-edit" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"live_object_id":"<live_object_uuid>"}'
```

---

## Revert Slide

Request body: `RevertSlideRequest`. Response: `AcceptImageEditResponse`.

Reverts the slide to a previous version using `history_entry_id` from slide history.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slides/<slide_id>/revert" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"history_entry_id":"<history_entry_uuid>"}'
```

---

## Remove Objects (async)

Request body: `ObjectRemovalRequest`. Response: `TriggerWorkflowResponse`.

Triggers AI-powered object removal. Optional `mask_path` for mask-based removal. Poll jobs; when DONE, call accept-image-edit with `live_object_id`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slides/<slide_id>/object-removal" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"input_image_path":"/slides/slide-1.png"}'
```

With mask:

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slides/<slide_id>/object-removal" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"input_image_path":"/slides/slide-1.png","mask_path":"/masks/mask.png"}'
```

---

## Extract Text (async)

Request body: `ExtractTextRequest` (optional). Response: `TriggerWorkflowResponse`.

Performs OCR, removes text from image via inpainting, creates editable Konva text nodes. Poll `GET /api/v2/jobs/{activityId}` for status.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slides/<slide_id>/extract-text" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{}'
```

With optional node:

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slides/<slide_id>/extract-text" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"node_id":"node-1","node_image_path":"/nodes/node1.png"}'
```

---

## Save Konva Nodes

Request body: `SaveKonvaNodesRequest`. Response: 200, no body.

Persists Konva canvas nodes and order (positions, layer order, flattened image path).

```bash
curl -X PUT "$LAYERPROOF_BASE_URL/api/v2/projects/<project_id>/slides/<slide_id>/konva-nodes" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"konva_nodes":{},"konva_order":[]}'
```

---

## Agent behavior

When the user asks to edit slides (image edit, accept edit, revert, remove objects, extract text, save canvas), do the following.

### 1. Choose the right endpoint

| User intent | Endpoint | Method |
|-------------|----------|--------|
| Edit slide image with AI instruction | `/api/v2/projects/{projectId}/slides/{slideId}/image-edit` | POST |
| Accept edited image after workflow completes | `/api/v2/projects/{projectId}/slides/{slideId}/accept-image-edit` | POST |
| Revert slide to previous version | `/api/v2/projects/{projectId}/slides/{slideId}/revert` | POST |
| Remove objects from slide image (AI) | `/api/v2/projects/{projectId}/slides/{slideId}/object-removal` | POST |
| Extract text from slide (OCR + inpainting) | `/api/v2/projects/{projectId}/slides/{slideId}/extract-text` | POST |
| Save Konva canvas nodes/order | `/api/v2/projects/{projectId}/slides/{slideId}/konva-nodes` | PUT |

All paths require `projectId` and `slideId` (replace placeholders with actual UUIDs).

### 2. Build and run the request

- **Auth**: Every request must include `X-API-KEY: $LAYERPROOF_API_KEY`. Read `LAYERPROOF_BASE_URL` and `LAYERPROOF_API_KEY` from the environment; if missing, tell the user to set them.
- **Path**: Resolve `project_id` and `slide_id` from context (e.g. from project/slide-deck list or user input). If missing, ask the user.
- **POST/PUT**: Build JSON body from the types above. Use `-X POST` or `-X PUT`, `-H "Content-Type: application/json"`, and `-d '...'`. Run the curl and show the result.

### 3. After async endpoints (image-edit, object-removal, extract-text)

- Response includes `activity_id` and `live_object_id`. Tell the user the workflow was started.
- Suggest polling `GET $LAYERPROOF_BASE_URL/api/v2/jobs/<activity_id>` until `status` is `DONE` or `CANCELED`.
- For image-edit and object-removal: when DONE, tell the user to call **accept-image-edit** with `live_object_id` to apply the result.

### 4. Response handling

- Always show the **raw JSON response** in a JSON code block; do not convert to a table.
- If the response contains a URL for an image (e.g. `image_path`), show the image and the JSON.
- On error (4xx/5xx), show the response body and status code; suggest fixing API key, projectId/slideId, or request body.

### 5. Example workflows

**Workflow A — User**: "Edit slide image in project X, slide Y: make the background darker."

1. Choose `POST /api/v2/projects/{projectId}/slides/{slideId}/image-edit`.
2. Resolve projectId and slideId (from user or ask).
3. Build body: `{"instruction":"Make the background darker","input_image_path":"/path/to/slide/image"}` (user may need to provide image path).
4. Run curl; show JSON. Mention polling jobs with `activity_id` and calling accept-image-edit with `live_object_id` when done.

**Workflow B — User**: "Edit two slides: darker background on slide 1, remove the logo from slide 3; then revert slide 1 if I don’t like it."

1. Resolve projectId; get slide ids from slide-deck GET deck (e.g. `slides[0].id`, `slides[2].id`).
2. For slide 1: POST `.../slides/{slideId1}/image-edit` with `{"instruction":"Make the background darker"}`; capture `activity_id_1` and `live_object_id_1`. For slide 3: POST `.../slides/{slideId3}/object-removal` with region/instruction; capture `activity_id_3` and `live_object_id_3`.
3. Poll `GET /api/v2/jobs/{activity_id_1}` and `.../jobs/{activity_id_3}` until both DONE. If either fails, report which slide and reason.
4. POST `.../slides/{slideId1}/accept-image-edit` with `{"live_object_id":"<live_object_id_1>"}`; POST `.../slides/{slideId3}/accept-image-edit` with `{"live_object_id":"<live_object_id_3>"}`. Show updated slide images (GET deck or slide detail).
5. If user says "revert slide 1": GET slide history (if available) for slideId1 to get `history_entry_id`; POST `.../slides/{slideId1}/revert` with `{"history_entry_id":"<id>"}`. Confirm with GET deck.

**Workflow C — User**: "Extract text from slide 2 and replace the old text (OCR + inpainting)."

1. Resolve projectId and slideId for slide 2. POST `.../slides/{slideId}/extract-text` (body per API); capture `activity_id`.
2. Poll `GET /api/v2/jobs/{activity_id}` until DONE. When DONE, `output` may contain extracted text or updated asset reference.
3. If a follow-up accept or apply step is required (e.g. accept-image-edit), use the returned `live_object_id`; otherwise show the result (e.g. updated slide or transcript) from GET deck.

---

## Response format (required)

- (if response contains url to show image) please show image and show json response instead of table
- Always show the **raw JSON response** (verbatim) in a JSON code block.
- If the response contains a URL for an image, **render/show the image** and also show the **JSON response** (do not convert to a table).
Skill

social-campaigns

Public API social campaigns (X-API-KEY). CRUD campaigns, generate, confirm outline, topics, variations, captions, exports, theme, citations. PublicApiSocialCampaignController (/api/v2/social-campaigns).

# Skill: Social Campaigns

## Description

**Social campaigns** are projects with `project_kind` **SOCIAL_CAMPAIGN**. This skill documents **PublicApiSocialCampaignController** at `/api/v2/social-campaigns`. Authenticate with `X-API-KEY`.

- **Campaign generation** returns `activity_id` — poll **`GET /api/v2/jobs/{activity_id}`** (same as slide workflows).
- **ZIP exports** return `export_id` — poll **`GET /api/v2/social-campaigns/{campaign_id}/exports/{export_id}`** (not the jobs endpoint).

---

## TypeScript types (selected)

```typescript
// --- Create / list / get ---
type PublicApiCreateCampaignRequest = {
  name: string;
  description?: string | null;
  status?: string | null;
  tags?: string[] | null;
  metadata?: Record<string, unknown> | null;
  workspace_id?: string | null;
  theme_id?: string | null;
  aspect_ratio?: string | null;
};
type PublicApiCampaignResponse = {
  campaign_id: string;
  name: string;
  description?: string | null;
  campaign_live_object_id: string;
  created_at: string;
  updated_at: string;
};
type PublicApiCampaignListResponse = {
  data: PublicApiCampaignResponse[];
  next_cursor?: string | null;
  has_more: boolean;
};

// --- Generate campaign ---
type PublicApiTrendSnapshot = {
  key: string;
  label: string;
  platform: string;
  region?: string | null;
  score?: number | null;
  // ... additional optional analytics fields
};
type PublicApiGenerateCampaignRequest = {
  prompt: string;
  theme_id?: string | null;
  tone_config?: {
    output_language?: string | null;
    text_detail_level?: string | null;
    tone?: string | null;
    voice?: string | null;
    audience?: string | null;
  } | null;
  reference_file_paths?: string[] | null;
  web_search_enabled?: boolean;
  target_topic_count?: number | null;
  trend_snapshot?: PublicApiTrendSnapshot | null;
  trend_snapshots?: PublicApiTrendSnapshot[] | null;
  auto_select?: boolean;  // default false — if true, skips outline confirmation
};
type PublicApiGenerateCampaignResponse = {
  campaign_live_object_id: string;
  activity_id: string;
  workflow_type: string;
};

// --- Confirm outline (after outline step when auto_select is false) ---
type PublicApiConfirmOutlineRequest = {
  selections: { topic_index: number; option_index: number }[];
};

// --- Topics (“posts” in paths) ---
type PublicApiCreateTopicRequest = {
  topic?: string | null;
  core_message?: string | null;
  key_points?: string[] | null;
  visual_suggestion?: string | null;
};
type PublicApiTopicResponse = {
  topic_id: string;
  live_object_id: string;
  status: string;
  topic?: string | null;
  core_message?: string | null;
  key_points?: string[] | null;
  visual_suggestion?: string | null;
  version: number;
  created_at: string;
  updated_at: string;
};

// --- Export ---
type PublicApiCampaignExportStartedResponse = {
  export_id: string;
  status: string;
};
type PublicApiCampaignExportStatusResponse = {
  export_id: string;
  status: 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';
  download_url?: string | null;
  expires_at?: string | null;
  file_size_bytes?: number | null;
  error_message?: string | null;
};
```

---

## Campaign CRUD

| Action | Method | Path |
|--------|--------|------|
| Create | POST | `/api/v2/social-campaigns` |
| List (cursor) | GET | `/api/v2/social-campaigns?limit=20&cursor=` |
| Get + posts | GET | `/api/v2/social-campaigns/{campaign_id}` |
| Update | PUT | `/api/v2/social-campaigns/{campaign_id}` |
| Update settings (theme) | PUT | `/api/v2/social-campaigns/{campaign_id}/settings` |
| Delete | DELETE | `/api/v2/social-campaigns/{campaign_id}` |

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/social-campaigns" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"Q1 Launch","workspace_id":"<workspace_uuid>"}'
```

---

## Generate & confirm

| Action | Method | Path |
|--------|--------|------|
| Generate from prompt | POST | `/api/v2/social-campaigns/{campaign_id}/generate` |
| Confirm outline picks | POST | `/api/v2/social-campaigns/{campaign_id}/confirm-outline` |

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/social-campaigns/<campaign_id>/generate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"prompt":"Holiday campaign","target_topic_count":5,"web_search_enabled":true,"auto_select":false}'
```

Poll **`GET /api/v2/jobs/{activity_id}`**. If `auto_select` is false, user picks outline options, then **POST confirm-outline** with `selections`.

---

## Topics (paths use `/posts`)

| Action | Method | Path |
|--------|--------|------|
| Create topic | POST | `/api/v2/social-campaigns/{campaign_id}/posts` |
| Update topic | PUT | `/api/v2/social-campaigns/{campaign_id}/posts/{post_id}` |
| Delete topic | DELETE | `/api/v2/social-campaigns/{campaign_id}/posts/{post_id}` |
| Reorder | PUT | `/api/v2/social-campaigns/{campaign_id}/reorder` body `{"topic_ids":["uuid",...]}` |

---

## Variations & images

| Action | Method | Path |
|--------|--------|------|
| Generate variations | POST | `.../posts/{post_id}/generate-variations` |
| More variations | POST | `.../posts/{post_id}/generate-more-variations` |
| Delete variation | DELETE | `.../posts/{post_id}/variations/{variation_id}` |
| Retry variation | POST | `.../posts/{post_id}/variations/{variation_id}/retry` |
| Aspect ratio variant | POST | `.../posts/{post_id}/variations/{variation_id}/generate-aspect-ratio` |
| Edit variation image | POST | `.../posts/{post_id}/variations/{variation_id}/edit-image` |
| Accept edit (if needed) | POST | `.../posts/{post_id}/variations/{variation_id}/accept-edit` |

Poll **`GET /api/v2/jobs/{activity_id}`** for async variation work.

---

## Captions

| Action | Method | Path |
|--------|--------|------|
| Update caption / hashtags | PUT | `.../posts/{post_id}/caption` |
| Generate caption (async) | POST | `.../posts/{post_id}/generate-caption` |

---

## Theme

| Action | Method | Path |
|--------|--------|------|
| Generate campaign theme (async) | POST | `/api/v2/social-campaigns/{campaign_id}/generate-theme` |

Poll jobs with returned activity id.

---

## Exports (ZIP)

| Action | Method | Path |
|--------|--------|------|
| Export full campaign ZIP | POST | `/api/v2/social-campaigns/{campaign_id}/exports/zip` |
| Export one topic ZIP | POST | `/api/v2/social-campaigns/{campaign_id}/posts/{post_id}/exports/zip` |
| Poll export | GET | `/api/v2/social-campaigns/{campaign_id}/exports/{export_id}` |

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/social-campaigns/<campaign_id>/exports/zip" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Citations & history

| Action | Method | Path |
|--------|--------|------|
| Campaign citations | GET | `/api/v2/social-campaigns/{campaign_id}/citations` |
| Post citations | GET | `.../posts/{post_id}/citations` |
| Platform post citations | GET | `.../posts/{post_id}/platforms/{platform}/citations` |
| Platform generation history | GET | `.../posts/{post_id}/platforms/{platform}/history` |
| Platform edit history | GET | `.../posts/{post_id}/platforms/{platform}/edits` |

---

## Agent behavior

1. Resolve **`campaign_id`** (create or list). **`campaign_live_object_id`** in responses is the campaign root live object.
2. **Generate** → poll **jobs** until `DONE` / handle `failure_reason`.
3. **Exports** → poll **`.../social-campaigns/{id}/exports/{export_id}`** until `COMPLETED` or `FAILED`.
4. **Topics** are listed under **`posts`** in API paths; treat `post_id` as **topic id**.
5. There is **no** `generate-platform` or per-platform image-edit in this public controller; platform-specific data appears in **get** campaign and **citations/history** endpoints when present.

---

## Response format (required)

- When responses include **image URLs** for posts or variations, **render previews** and show raw JSON.
- Always show **verbatim JSON** in a code block.
Skill

themes

Public API theme management (X-API-KEY). List, get, save, update, delete, generate, regenerate, apply, unapply. PublicThemeController (/api/v2/themes).

# Skill: Theme Management

## Description

Themes define visual styling for slides. This skill documents the **public API** at `/api/v2/themes` (PublicThemeController). Authenticate with `X-API-KEY` header.

---

## TypeScript types (request / response)

Mirrors `PublicThemeController` (/api/v2/themes) data classes.

```typescript
// --- List Themes (GET) ---
// Query: offset (default 0), limit (default 20), search (optional)
type PublicThemeListResponse = {
  data: PublicThemeResponse[];
  total: number;
  offset: number;
  limit: number;
};

// --- Get Theme By ID (GET) ---
type PublicThemeResponse = {
  id: string;
  name: string;
  description: string | null;
  visibility: string;       // "PRIVATE" | "SYSTEM" | "SHARED"
  preview_url: string;
  created_at: string;       // ISO 8601
  updated_at: string;      // ISO 8601
};

// --- Generate Theme (POST) — async ---
type PublicGenerateThemeRequest = {
  prompt: string;           // required, max 5000 chars
  project_id?: string | null;  // optional THEME project UUID; if omitted, a project is created automatically
};
type PublicGenerateThemeResponse = {
  activity_id: string;      // UUID – poll GET /api/v2/jobs/{activity_id}
  theme_id: string;        // UUID
};

// --- Apply Theme (POST) ---
type PublicApplyThemeRequest = {
  slide_deck_id: string;   // UUID, required
  theme_id: string;        // UUID, required
  regenerate_slides?: boolean;  // default false; if true, triggers batch image regeneration – poll activity_id
};
type PublicApplyThemeResponse = {
  theme_id: string;
  theme_name: string;
  slide_deck_id: string;
  applied: boolean;        // true when applied successfully
  activity_id: string | null;  // set when regenerate_slides is true – poll GET /api/v2/jobs/{activity_id}
};

// --- Save theme (POST /api/v2/themes) — 201 ---
type PublicSaveThemeRequest = {
  name: string;                              // required, max 256
  visual_style_description: string;         // required, max 5000
  preview_s3_key: string;                    // required — S3 key of preview image
  description?: string | null;              // max 1000
  source_prompt?: string | null;
  source_project_id?: string | null;
  tags?: string[] | null;
};

// --- Update theme (PUT /{theme_id}) ---
type PublicUpdateThemeRequest = {
  name?: string | null;
  description?: string | null;
  tags?: string[] | null;
  visual_style_description?: string | null;
};

// --- Regenerate theme (POST /{theme_id}/regenerate) — 202 ---
type PublicRegenerateThemeRequest = {
  project_id: string;  // UUID — project whose working dir is used
  adjustment_prompt: string;
  reference_image_paths?: string[] | null;
  preview_only?: boolean;
};
type PublicRegenerateThemeResponse = {
  activity_id: string;
  theme_id: string;
  live_object_id: string;
};

// --- Unapply (POST /unapply) ---
type PublicUnapplyThemeRequest = {
  project_id: string;
  slide_deck_id: string;
};
```

---

## List Themes

Query: `offset` (default 0), `limit` (default 20), `search` (optional). Response: `PublicThemeListResponse`.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/themes?offset=0&limit=20" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Get Theme By ID

Response: `PublicThemeResponse`.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/themes/<theme_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Generate Theme

Request body: `PublicGenerateThemeRequest`. Response (202): `PublicGenerateThemeResponse`.

Only `prompt` is required. Optional `project_id` uses an existing THEME project; if omitted, one is created automatically. Poll `GET /api/v2/jobs/{activityId}` for status.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/themes/generate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"prompt":"Clean, minimal style with SF Pro"}'
```

With optional `project_id`:

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/themes/generate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"prompt":"Clean, minimal style","project_id":"<theme_project_uuid>"}'
```

---

## Apply Theme

Request body: `PublicApplyThemeRequest`. Response: `PublicApplyThemeResponse`.

Theme and slide deck are specified in the body. When `regenerate_slides` is true, poll `GET /api/v2/jobs/{activityId}` for batch image regeneration status.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/themes/apply" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"slide_deck_id":"<slide_deck_uuid>","theme_id":"<theme_uuid>"}'
```

With slide regeneration:

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/themes/apply" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"slide_deck_id":"<slide_deck_uuid>","theme_id":"<theme_uuid>","regenerate_slides":true}'
```

---

## Save Theme (manual)

Creates a saved private theme. Requires `visual_style_description` and `preview_s3_key` (upload preview via project/public files first). Response (201): `PublicThemeResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/themes" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"My brand","visual_style_description":"Minimal dark UI, rounded cards","preview_s3_key":"<s3_key>"}'
```

---

## Update Theme

Request body: `PublicUpdateThemeRequest`. Response: `PublicThemeResponse`.

```bash
curl -X PUT "$LAYERPROOF_BASE_URL/api/v2/themes/<theme_id>" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"Renamed theme"}'
```

---

## Delete Theme

Soft-deletes a user-owned theme. Response: 204 No Content.

```bash
curl -X DELETE "$LAYERPROOF_BASE_URL/api/v2/themes/<theme_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Regenerate Theme (async)

Request body: `PublicRegenerateThemeRequest`. Response (202): `PublicRegenerateThemeResponse`. Poll `GET /api/v2/jobs/{activity_id}`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/themes/<theme_id>/regenerate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"project_id":"<project_uuid>","adjustment_prompt":"More contrast, warmer palette"}'
```

---

## Unapply Theme from Slide Deck

Removes applied theme from a deck. Request body: `PublicUnapplyThemeRequest`. Response: 204 No Content.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/themes/unapply" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"project_id":"<project_uuid>","slide_deck_id":"<slide_deck_uuid>"}'
```

---

## List My Themes (by user)

Query: `offset`, `limit`, optional `visibility` (`PRIVATE` \| `SYSTEM` \| `SHARED`), optional `search`. Response: `PublicThemeListResponse`.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/themes/by-user-id?offset=0&limit=20" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Agent behavior

When the user asks to work with themes (list, get, generate, apply), do the following.

### 1. Choose the right endpoint

| User intent | Endpoint | Method |
|-------------|----------|--------|
| List/browse themes, search themes | `/api/v2/themes` | GET |
| Get one theme by ID | `/api/v2/themes/<theme_id>` | GET |
| Save a theme manually | `/api/v2/themes` | POST |
| Update theme metadata | `/api/v2/themes/<theme_id>` | PUT |
| Delete theme | `/api/v2/themes/<theme_id>` | DELETE |
| Generate theme from prompt (async) | `/api/v2/themes/generate` | POST |
| Regenerate / adjust theme (async) | `/api/v2/themes/<theme_id>/regenerate` | POST |
| Apply a theme to a slide deck | `/api/v2/themes/apply` | POST |
| Unapply theme from deck | `/api/v2/themes/unapply` | POST |
| List current user’s themes | `/api/v2/themes/by-user-id` | GET |

### 2. Build and run the request

- **Auth**: Every request must include `X-API-KEY: $LAYERPROOF_API_KEY`. Read `LAYERPROOF_BASE_URL` and `LAYERPROOF_API_KEY` from the environment; if missing, tell the user to set them.
- **GET**: Build `curl` with the chosen path and query params (`offset`, `limit`, `search` for list). Run the curl and show the result.
- **POST**: Build a JSON body from the user's input (prompt, theme ID, slide deck ID, etc.). Use `-X POST`, `-H "Content-Type: application/json"`, and `-d '...'`. Run the curl and show the result.

### 3. After generate or apply (with regeneration)

- **Generate theme**: Response includes `activity_id` and `theme_id`. Tell the user the theme was started and give `theme_id`. Optionally poll `GET $LAYERPROOF_BASE_URL/api/v2/jobs/<activity_id>` until `status` is `DONE` or `CANCELED`, then report outcome.
- **Apply theme with `regenerate_slides: true`**: Response may include `activity_id`. If present, tell the user regeneration was started and optionally poll `GET .../api/v2/jobs/<activity_id>` for status.

### 4. Response handling

- Always show the **raw JSON response** in a JSON code block; do not convert to a table.
- If the response contains a URL for an image (e.g. `preview_url`), show the image and the JSON.
- On error (4xx/5xx), show the response body and status code; suggest fixing missing/invalid API key, IDs, or request body.

### 5. Example workflows

**Workflow A — User**: "Generate a theme with prompt: minimal dark mode."

1. Choose `POST /api/v2/themes/generate`.
2. Build body: `{"prompt":"minimal dark mode"}`.
3. Run: `curl -X POST "$LAYERPROOF_BASE_URL/api/v2/themes/generate" -H "Content-Type: application/json" -H "X-API-KEY: $LAYERPROOF_API_KEY" -d '{"prompt":"minimal dark mode"}'`.
4. Show the JSON response; if it contains `activity_id`, mention they can poll `/api/v2/jobs/{activityId}` for status and use `theme_id` once done.

**Workflow B — User**: "List themes, generate a new 'corporate blue' theme, wait for it to finish, then apply it to my slide deck and regenerate slides."

1. GET `/api/v2/themes` with optional `limit`, `offset`, `search`; show list. User may pick existing or request new.
2. POST `/api/v2/themes/generate` with `{"prompt":"corporate blue"}`; capture `activity_id` and `theme_id`.
3. Poll `GET /api/v2/jobs/{activity_id}` until status is DONE (or CANCELED). If DONE, theme is ready; if failed, report `failure_reason`.
4. Resolve projectId and slideDeckId (projects + slide-deck). POST or PUT the slide-deck theme/settings endpoint with `theme_id` (e.g. PUT `.../slide-deck/.../settings` with `{"theme_id":"<theme_id>"}`).
5. Use **POST `/api/v2/themes/apply`** with `regenerate_slides: true` if you want slide images regenerated; capture `activity_id` and poll jobs until DONE. Otherwise apply with `regenerate_slides: false` or omit.

**Workflow C — User**: "I have a theme ID; apply it to deck X and only update the look (no slide regeneration)."

1. **POST `/api/v2/themes/apply`** with `{"slide_deck_id":"<slide_deck_uuid>","theme_id":"<theme_uuid>"}` (omit `regenerate_slides` or set `false`).
2. Confirm with GET deck; no job polling needed unless `regenerate_slides` was true.

---

## Response format (required)

- (if response contains url to show image) please show image and show json response instead of table
- Always show the **raw JSON response** (verbatim) in a JSON code block.
- If the response contains a URL for an image, **render/show the image** and also show the **JSON response** (do not convert to a table).
Skill

tones

Public API tone presets (X-API-KEY). List, create, get, update, delete, duplicate, apply to slide deck, save-as-preset. PublicApiToneController (/api/v2/tones).

# Skill: Tone Presets

## Description

Manage **tone presets** (voice, audience, detail level) and apply them to a slide deck. This skill documents **PublicApiToneController** at `/api/v2/tones`. Slide decks also expose **per-deck** tone settings under `.../slide-deck/{id}/tone-settings` (see **slide-decks** skill); this skill covers the **shared preset library** endpoints.

Authenticate with `X-API-KEY`.

---

## TypeScript types (request / response)

Mirrors `PublicApiToneController` / client tone DTOs (field names follow API JSON; use snake_case if your client sends snake_case per server config).

```typescript
// --- List (GET) ---
// Query: offset (default 0), limit (default 20), search (optional),
// visibility (optional, repeat or array: PRIVATE, SYSTEM)
type TonePresetListResponse = {
  data: TonePresetResponse[];
  total: number;
  offset: number;
  limit: number;
};

type TonePresetResponse = {
  id: string;
  name: string;
  description?: string | null;
  visibility?: string | null;
  output_language?: string | null;
  text_detail_level?: string | null;
  tone?: string | null;
  voice?: string | null;
  audience?: string | null;
  created_at?: string | null;
  updated_at?: string | null;
};

// --- Create (POST) ---
type CreateTonePresetRequest = {
  name: string;
  description?: string | null;
  output_language?: string | null;
  text_detail_level?: string | null;
  tone?: string | null;
  voice?: string | null;
  audience?: string | null;
};

// --- Update (PUT /{tone_id}) ---
type UpdateTonePresetRequest = {
  name?: string | null;
  description?: string | null;
  output_language?: string | null;
  text_detail_level?: string | null;
  tone?: string | null;
  voice?: string | null;
  audience?: string | null;
};

// --- Apply (POST /{tone_id}/apply) ---
type ApplyTonePresetRequest = {
  project_id: string;
  slide_deck_id: string;
};
type ApplyTonePresetResponse = TonePresetResponse; // applied settings snapshot

// --- Save as preset from deck (POST /save-as-preset) — 201 ---
type SaveAsPresetRequest = {
  slide_deck_id: string;
  name: string;
  description?: string | null;
};

// --- Duplicate (POST /{tone_id}/duplicate) — 201 ---
type DuplicateTonePresetRequest = {
  name: string;
};
```

---

## List Tone Presets

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/tones?offset=0&limit=20" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Get Tone Preset

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/tones/<tone_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Create Tone Preset

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/tones" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"Boardroom","output_language":"en-US","tone":"Professional"}'
```

---

## Update Tone Preset

```bash
curl -X PUT "$LAYERPROOF_BASE_URL/api/v2/tones/<tone_id>" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"description":"Stricter pacing"}'
```

---

## Delete Tone Preset

Response: 204 No Content (cannot delete system presets — 403).

```bash
curl -X DELETE "$LAYERPROOF_BASE_URL/api/v2/tones/<tone_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Apply Preset to Slide Deck

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/tones/<tone_id>/apply" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"project_id":"<project_uuid>","slide_deck_id":"<slide_deck_uuid>"}'
```

---

## Save Current Deck Settings as Preset

Persists the slide deck’s current tone settings as a new preset. Response (201): `TonePresetResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/tones/save-as-preset" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"slide_deck_id":"<slide_deck_uuid>","name":"From deck settings"}'
```

---

## Duplicate Preset

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/tones/<tone_id>/duplicate" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"Copy of Boardroom"}'
```

---

## Agent behavior

| User intent | Endpoint | Method |
|-------------|----------|--------|
| List presets | `/api/v2/tones` | GET |
| Get preset | `/api/v2/tones/{toneId}` | GET |
| Create preset | `/api/v2/tones` | POST |
| Update preset | `/api/v2/tones/{toneId}` | PUT |
| Delete preset | `/api/v2/tones/{toneId}` | DELETE |
| Apply to slide deck | `/api/v2/tones/{toneId}/apply` | POST |
| Save deck settings as preset | `/api/v2/tones/save-as-preset` | POST |
| Duplicate preset | `/api/v2/tones/{toneId}/duplicate` | POST |

**Note:** Per-deck tone fields without creating a preset are under **GET/PUT** `.../slide-deck/{slideDeckId}/tone-settings` (slide-decks skill).

---

## Response format (required)

- Always show the **raw JSON response** in a JSON code block.
- If the response contains image URLs, show images and the JSON.
Skill

workspaces

Public API workspace management (X-API-KEY). Create, list, get, update, delete workspaces. Types follow PublicApiWorkspaceController (/api/v2/workspaces).

# Skill: Workspace Management

## Description

Manage workspaces. This skill documents the **public API** at `/api/v2/workspaces` (PublicApiWorkspaceController). Authenticate with `X-API-KEY` header. List uses `page` and `page_size` (`page_size` default 20, max 100). **For the first page of results, pass `page=0` explicitly.**

---

## TypeScript types (request / response)

Mirrors `PublicApiWorkspaceController` data classes.

```typescript
// --- Create (POST) — 201 ---
type PublicApiCreateWorkspaceRequest = {
  name: string;           // required, 1–255 chars
  description?: string | null;
  default_theme_id?: string | null;  // optional theme UUID
};
type PublicApiWorkspaceResponse = {
  id: string;
  user_id: string;
  name: string;
  description: string | null;
  default_theme_id: string | null;
  shared_working_dir_live_object_id: string;
  project_count: number;
  created_at: string;     // ISO 8601
  updated_at: string;    // ISO 8601
  thumbnail_url?: string | null;
};

// --- List (GET) ---
// Query: page (use 0 for first page), page_size (default 20, max 100)
type PublicApiWorkspaceListResponse = {
  data: PublicApiWorkspaceResponse[];
  total: number;
  page: number;
  page_size: number;
};

// --- Restore (POST /{workspace_id}/restore) ---
type PublicApiRestoreWorkspaceRequest = {
  restore_projects?: boolean;  // default true
};
type PublicApiRestoreWorkspaceResponse = {
  workspace_restored: boolean;
  projects_restored: number;
};

// --- List files (GET /{workspace_id}/files) ---
// Optional query: type (file classification filter, if supported by server)
type PublicApiWorkspaceFileEntry = {
  id: string;
  name: string;
  path: string;
  file_type: string;
  mime_type: string;
  size: number;
  uploaded_at: string;
  uploaded_by: string;
  status: string;
  presigned_url?: string | null;
  url_expires_at?: string | null;
};
type PublicApiWorkspaceFilesResponse = {
  files: PublicApiWorkspaceFileEntry[];
  total: number;
};

// --- Update (PUT) ---
type PublicApiUpdateWorkspaceRequest = {
  name?: string | null;   // max 255 if provided
  description?: string | null;
  default_theme_id?: string | null;  // optional theme UUID
};
```

---

## Create Workspace

Request body: `PublicApiCreateWorkspaceRequest`. Response (201): `PublicApiWorkspaceResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/workspaces" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"<workspace_name>","description":"<description>"}'
```

---

## List Workspaces

Query: `page` (use `0` for the first page), `page_size` (default 20, max 100). Response: `PublicApiWorkspaceListResponse`.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/workspaces?page=0&page_size=20" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## List Deleted Workspaces

Soft-deleted workspaces for the current user. Query: `page` (default 0), `page_size` (default 20, max 100).

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/workspaces/deleted?page=0&page_size=20" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Restore Workspace

Body optional: `{"restore_projects": true}` (default). Response: `PublicApiRestoreWorkspaceResponse`.

```bash
curl -X POST "$LAYERPROOF_BASE_URL/api/v2/workspaces/<workspace_id>/restore" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"restore_projects":true}'
```

---

## Permanently Delete Workspace

Hard-deletes a workspace that is already soft-deleted. Response: 204 No Content.

```bash
curl -X DELETE "$LAYERPROOF_BASE_URL/api/v2/workspaces/<workspace_id>/permanently" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## List Files in Workspace

Lists files in the workspace shared working directory. Optional query: `type` (file classification).

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/workspaces/<workspace_id>/files" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Get Workspace

Response: `PublicApiWorkspaceResponse`.

```bash
curl "$LAYERPROOF_BASE_URL/api/v2/workspaces/<workspace_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Update Workspace

Request body: `PublicApiUpdateWorkspaceRequest`. Response: `PublicApiWorkspaceResponse`.

```bash
curl -X PUT "$LAYERPROOF_BASE_URL/api/v2/workspaces/<workspace_id>" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY" \
  -d '{"name":"New name","description":"New description"}'
```

---

## Delete Workspace

Response: 204 No Content.

```bash
curl -X DELETE "$LAYERPROOF_BASE_URL/api/v2/workspaces/<workspace_id>" \
  -H "X-API-KEY: $LAYERPROOF_API_KEY"
```

---

## Agent behavior

When the user asks to manage workspaces (create, list, get, update, delete), do the following.

### 1. Choose the right endpoint

| User intent | Endpoint | Method |
|-------------|----------|--------|
| Create workspace | `/api/v2/workspaces` | POST |
| List workspaces | `/api/v2/workspaces?page=0&page_size=20` | GET |
| List deleted workspaces | `/api/v2/workspaces/deleted?page=0&page_size=20` | GET |
| Restore deleted workspace | `/api/v2/workspaces/{workspaceId}/restore` | POST |
| Permanently delete (after soft delete) | `/api/v2/workspaces/{workspaceId}/permanently` | DELETE |
| List files in workspace | `/api/v2/workspaces/{workspaceId}/files` | GET |
| Get workspace by ID | `/api/v2/workspaces/{workspaceId}` | GET |
| Update workspace | `/api/v2/workspaces/{workspaceId}` | PUT |
| Delete workspace | `/api/v2/workspaces/{workspaceId}` | DELETE |

### 2. Build and run

- **Auth**: Include `X-API-KEY: $LAYERPROOF_API_KEY`. Read `LAYERPROOF_BASE_URL` and `LAYERPROOF_API_KEY` from the environment; if missing, tell the user to set them.
- **GET**: Build path and query params (`page`, `page_size` for list). Run curl and show result.
- **POST/PUT**: Build JSON body (name, description for create; name/description optional for update). Run curl and show result.
- **DELETE**: Build path; run curl. Response is 204 with no body.

### 3. Response handling

- Always show the **raw JSON response** in a JSON code block.
- For 204 delete, indicate success and no body.
- On error, show response body and status code.

### 4. Example workflows

**Workflow A — User**: "Create workspace Marketing."

1. Choose POST /api/v2/workspaces.
2. Body: `{"name":"Marketing"}` (or with description).
3. Run curl; show JSON. Returned `id` is the workspace ID for get/update/delete.

**Workflow B — User**: "Set up a workspace for Q2, create it if it doesn’t exist, then list my workspaces and show the one I’ll use for new projects."

1. GET `/api/v2/workspaces` with `page`, `page_size`; inspect list for a workspace named "Q2" or similar.
2. If not found: POST `/api/v2/workspaces` with `{"name":"Q2","description":"Q2 campaigns and decks"}`; capture `id` as `workspace_id`.
3. If found: use that workspace’s `id`. Optionally GET `/api/v2/workspaces/{id}` to show full details.
4. Tell user: "Use workspace_id <id> when creating projects (e.g. POST /api/v2/projects with workspace_id in body)."

**Workflow C — User**: "Rename workspace X to 'Marketing 2025' and add a description."

1. Resolve workspace ID (from list or user). PUT `/api/v2/workspaces/{workspaceId}` with `{"name":"Marketing 2025","description":"..."}`.
2. Run curl; show JSON. Optionally GET the workspace again to confirm name and description.

---

## Response format (required)

- (if response contains url to show image) please show image and show json response instead of table
- Always show the **raw JSON response** (verbatim) in a JSON code block.
- If the response contains a URL for an image, **render/show the image** and also show the **JSON response** (do not convert to a table).

来源:https://github.com/compilet-dev/agent-skill-layerproof