Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api-reference/content-agent/callback.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
title: 'Task Callback'
openapi: 'POST /api/content-agent/callback'
---
4 changes: 4 additions & 0 deletions api-reference/content-agent/webhook.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
title: 'Slack Webhook'
openapi: 'POST /api/content-agent/{platform}'
---
140 changes: 140 additions & 0 deletions api-reference/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -4500,6 +4500,146 @@
}
}
}
},
"/api/content-agent/{platform}": {
"post": {
"description": "Webhook endpoint for the Recoup Content Agent Slack bot. Receives @mention events from Slack and triggers content generation for the mentioned artist. The bot parses the mention text for `<artist_account_id> [template] [batch=N] [lipsync]`, validates the artist, calls POST /api/content/create, and starts a background polling task that reports results back to the Slack thread.\n\nFor Slack, also handles `url_verification` challenges during app setup.",
"parameters": [
{
"name": "platform",
"in": "path",
"description": "Chat platform identifier. Currently supports `slack`.",
"required": true,
"schema": {
"type": "string",
"enum": ["slack"]
}
}
],
"requestBody": {
"description": "Slack Events API payload (app_mention event or url_verification challenge)",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "Slack Events API envelope — the shape depends on the event type"
}
}
}
},
"responses": {
"200": {
"description": "Event processed successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
}
}
}
}
}
},
Comment on lines +4531 to +4546
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Slack Events API url_verification response format (what exact JSON/body is required for challenge acknowledgement?)

💡 Result:

The Slack Events API sends a url_verification challenge during Request URL configuration. Respond with HTTP 200 OK and the exact challenge value from the incoming JSON payload. Supported response formats (any one works): 1. Plaintext (recommended): Content-Type: text/plain Body: 3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P 2. Form-urlencoded: Content-Type: application/x-www-form-urlencoded Body: challenge=3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P 3. JSON: Content-Type: application/json Body: {"challenge":"3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"} Incoming payload example: { "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl", "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P", "type": "url_verification" } Respond immediately with the challenge value (no JSON wrapper required). Verify request authenticity using signing secrets for production events.

Citations:


🏁 Script executed:

# First, let's see the broader context of the endpoint around lines 4531-4546
sed -n '4500,4580p' api-reference/openapi.json | cat -n

Repository: recoupable/docs

Length of output: 3964


🏁 Script executed:

# Also search for the endpoint name/path to understand what this endpoint is
grep -n "url_verification\|/events\|challenge" api-reference/openapi.json | head -30

Repository: recoupable/docs

Length of output: 651


Response schema must document the url_verification challenge response.

The endpoint explicitly handles Slack url_verification challenges during app setup (per the description), but the 200 response schema only documents { ok: boolean }. According to Slack's Events API, url_verification requests require responding with the challenge value in JSON format: {"challenge": "<value>"}.

Update the response schema to use oneOf to cover both response cases: the challenge response for url_verification events and the ok response for regular events.

Proposed fix
 "200": {
   "description": "Event processed successfully",
   "content": {
     "application/json": {
       "schema": {
-        "type": "object",
-        "properties": {
-          "ok": {
-            "type": "boolean"
-          }
-        }
+        "oneOf": [
+          {
+            "type": "object",
+            "required": ["challenge"],
+            "properties": {
+              "challenge": { "type": "string" }
+            }
+          },
+          {
+            "type": "object",
+            "properties": {
+              "ok": { "type": "boolean" }
+            }
+          }
+        ]
       }
     }
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"responses": {
"200": {
"description": "Event processed successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Event processed successfully",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"type": "object",
"required": ["challenge"],
"properties": {
"challenge": { "type": "string" }
}
},
{
"type": "object",
"properties": {
"ok": { "type": "boolean" }
}
}
]
}
}
}
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api-reference/openapi.json` around lines 4531 - 4546, The 200 response schema
currently only documents { ok: boolean } but must also allow the Slack
url_verification challenge response; update the "responses" -> "200" ->
"content" -> "application/json" -> "schema" to use a oneOf that includes two
schemas: one object with a required "challenge" (string) property (for
url_verification) and one object with an "ok" (boolean) property (for normal
event ACKs). Ensure the oneOf entries clearly define the "challenge" and "ok"
properties and required fields so both response shapes (challenge JSON and {
"ok": true/false }) are valid.

"404": {
"description": "Unknown platform"
}
}
}
},
Comment on lines +4504 to +4552
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the structure of the openapi.json file and find the global security definition
head -100 api-reference/openapi.json | grep -A 10 '"security"' || echo "Checking file structure..."

Repository: recoupable/docs

Length of output: 441


🏁 Script executed:

# Get the full context around lines 4504-4552 to see the endpoint and any security override
sed -n '4490,4560p' api-reference/openapi.json

Repository: recoupable/docs

Length of output: 2833


🏁 Script executed:

# Search for the top-level security definition to confirm global apiKeyAuth
rg '"security"\s*:' api-reference/openapi.json -B 5 -A 10 | head -80

Repository: recoupable/docs

Length of output: 2850


🏁 Script executed:

# Check if there are other webhook or public endpoints with "security": [] override
rg '"security"\s*:\s*\[\]' api-reference/openapi.json -B 15 -A 5

Repository: recoupable/docs

Length of output: 41


Webhook endpoint incorrectly inherits global apiKeyAuth requirement.

The /api/content-agent/{platform} endpoint lacks an explicit security override and therefore requires x-api-key authentication. Slack webhooks are initiated by Slack's servers and cannot include custom API key headers—they rely on signature verification instead. This endpoint must override the global security with "security": [] to function as a webhook.

Required fix
 "/api/content-agent/{platform}": {
   "post": {
+    "security": [],
     "description": "Webhook endpoint for the Recoup Content Agent Slack bot. Receives `@mention` events from Slack and triggers content generation for the mentioned artist. The bot parses the mention text for `<artist_account_id> [template] [batch=N] [lipsync]`, validates the artist, calls POST /api/content/create, and starts a background polling task that reports results back to the Slack thread.\n\nFor Slack, also handles `url_verification` challenges during app setup.",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api-reference/openapi.json` around lines 4504 - 4552, The POST operation for
the "/api/content-agent/{platform}" endpoint currently inherits the global
apiKeyAuth requirement; update the operation object for the POST (the entry
under "/api/content-agent/{platform}" -> "post") to explicitly override security
by adding "security": [] so the Slack webhook can be called without x-api-key
headers while leaving the rest of the operation intact (description, parameters,
requestBody, responses).

"/api/content-agent/callback": {
"post": {
"description": "Internal callback endpoint for the `poll-content-run` Trigger.dev task. Receives content generation results and posts them back to the originating Slack thread. Authenticated via the `x-callback-secret` header.\n\nThis endpoint is not intended for external use — it is called automatically by the polling task when content runs complete, fail, or time out.",
"requestBody": {
"description": "Content generation results from the polling task",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["threadId", "status"],
"properties": {
"threadId": {
"type": "string",
"description": "Chat SDK thread identifier for the originating Slack thread"
},
"status": {
"type": "string",
"enum": ["completed", "failed", "timeout"],
"description": "Overall status of the content generation batch"
},
"results": {
"type": "array",
"description": "Per-run results",
"items": {
"type": "object",
"required": ["runId", "status"],
"properties": {
"runId": {
"type": "string",
"description": "Trigger.dev run ID"
},
"status": {
"type": "string",
"enum": ["completed", "failed", "timeout"]
},
"videoUrl": {
"type": "string",
"description": "URL of the generated video (when completed)"
},
"captionText": {
"type": "string",
"description": "Generated caption text (when completed)"
},
"error": {
"type": "string",
"description": "Error message (when failed)"
}
}
}
},
"message": {
"type": "string",
"description": "Optional human-readable message"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Callback processed and results posted to Slack",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "ok"
}
}
}
}
}
},
"401": {
"description": "Missing or invalid callback secret"
},
"400": {
"description": "Invalid request body"
}
},
"security": [
{
"callbackSecret": []
}
]
Comment on lines +4637 to +4641
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import json, sys

with open("api-reference/openapi.json", "r", encoding="utf-8") as f:
    doc = json.load(f)

schemes = set(doc.get("components", {}).get("securitySchemes", {}).keys())
missing = []

for path, item in doc.get("paths", {}).items():
    for method, op in item.items():
        if method.lower() not in {"get","post","put","patch","delete","head","options","trace"}:
            continue
        for req in op.get("security", []):
            for name in req.keys():
                if name not in schemes:
                    missing.append((path, method.upper(), name))

if missing:
    print("Missing security scheme definitions:")
    for m in missing:
        print(f"- {m[1]} {m[0]} -> {m[2]}")
    sys.exit(1)

print("All referenced security schemes are defined.")
PY

Repository: recoupable/docs

Length of output: 148


Add missing callbackSecret security scheme definition to OpenAPI spec.

The callbackSecret security scheme is referenced at POST /api/content-agent/callback but not defined in securitySchemes, making the OpenAPI document invalid for tooling that resolves security references.

🔧 Proposed fix
@@
   "securitySchemes": {
+    "callbackSecret": {
+      "type": "apiKey",
+      "in": "header",
+      "name": "x-callback-secret",
+      "description": "Shared secret for internal content-agent callback authentication."
+    },
     "bearerAuth": {
       "type": "http",
       "scheme": "bearer"
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api-reference/openapi.json` around lines 4637 - 4641, The OpenAPI document
references a security requirement named "callbackSecret" for POST
/api/content-agent/callback but lacks a corresponding entry in
components.securitySchemes; add a new security scheme named callbackSecret under
components.securitySchemes (e.g., type: apiKey, in: header, name:
X-Callback-Secret or whichever header/query form your implementation expects) so
the reference resolves and the spec validates; update the components object in
api-reference/openapi.json to include this callbackSecret scheme matching how
the service verifies the secret.

}
}
},
"components": {
Expand Down
129 changes: 129 additions & 0 deletions content-agent.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
title: 'Content Agent'
description: 'Generate artist videos from Slack with the Recoup Content Agent bot'
---

## Overview

The **Recoup Content Agent** is a Slack bot that generates social-ready artist videos on @mention. It plugs into the existing [content creation pipeline](/api-reference/content/create) and delivers results directly in your Slack thread.

## How It Works

```
User mentions bot in Slack
@RecoupContentAgent <artist_account_id> [template] [batch=N] [lipsync]
Bot validates the artist and sends an immediate acknowledgment
Triggers the content creation pipeline (POST /api/content/create)
Background task polls for completion (~5-10 min)
Bot replies in-thread with the generated video URL(s)
```

### @Mention Syntax

```
@RecoupContentAgent <artist_account_id> [template] [batch=N] [lipsync]
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `artist_account_id` | Yes | UUID of the artist account |
| `template` | No | Content template name (defaults to `artist-caption-bedroom`) |
| `batch=N` | No | Number of videos to generate (1-30, default 1) |
| `lipsync` | No | Enable lipsync mode (audio baked into video) |

### Examples

**Basic — single video with default template:**
```
@RecoupContentAgent abc-123-uuid
```

**Custom template:**
```
@RecoupContentAgent abc-123-uuid artist-caption-bedroom
```

**Batch with lipsync:**
```
@RecoupContentAgent abc-123-uuid batch=3 lipsync
```
Comment on lines +26 to +54
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider documenting all ContentCreateRequest parameters.

The @mention syntax currently documents artist_account_id, template, batch, and lipsync, but the ContentCreateRequest schema also includes caption_length (short/medium/long) and upscale (boolean) parameters.

If these parameters are intentionally omitted from the bot interface, consider adding a note explaining that the bot provides a simplified interface. Otherwise, document how users can pass these additional options.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@content-agent.mdx` around lines 26 - 54, The `@RecoupContentAgent` mention docs
list only artist_account_id, template, batch and lipsync but omit
ContentCreateRequest fields caption_length and upscale; update the documentation
for the `@RecoupContentAgent` syntax to either (a) add parameters for
caption_length (short/medium/long) and upscale (boolean) with descriptions and
example usages (e.g., include "caption_length=short" and "upscale" tokens) or
(b) add a single-line note under the syntax indicating the bot exposes a
simplified interface and that additional ContentCreateRequest options
(caption_length, upscale) are not supported via mention and must be set via the
full API; reference the `@RecoupContentAgent` mention and the ContentCreateRequest
schema when making the change.


## Architecture

The content agent follows the same pattern as the coding agent bot:

| Component | Location | Purpose |
|-----------|----------|---------|
| Slack webhook | `POST /api/content-agent/slack` | Receives @mention events |
| Callback endpoint | `POST /api/content-agent/callback` | Receives polling results |
| Bot singleton | `lib/content-agent/bot.ts` | Chat SDK with Slack adapter + Redis state |
| Mention handler | `lib/content-agent/handlers/` | Parses args, validates artist, triggers pipeline |
| Poll task | `poll-content-run` (Trigger.dev) | Monitors content runs, posts results via callback |
Comment on lines +56 to +66
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align webhook endpoint with OpenAPI path parameter.

The Architecture table shows POST /api/content-agent/slack, but the OpenAPI spec defines this as POST /api/content-agent/{platform} with platform as a path parameter. Consider using either:

  • The parameterized form: POST /api/content-agent/{platform}
  • A note clarifying: POST /api/content-agent/slack (where platform=slack)

This ensures consistency between the guide and the API reference.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@content-agent.mdx` around lines 56 - 66, The Architecture docs currently list
the webhook as "POST /api/content-agent/slack" which conflicts with the OpenAPI
path parameter; update the table entry to use the parameterized path "POST
/api/content-agent/{platform}" (or alternately keep "POST
/api/content-agent/slack" and add a clarifying parenthetical "where
platform=slack") so the doc matches the OpenAPI spec; ensure this change is
applied to the Architecture table row for "Slack webhook" so readers see the
same path format as the OpenAPI definition.


### Data Flow

1. **Slack event** → `POST /api/content-agent/slack` handles the webhook
2. **Mention handler** parses the command, calls [`GET /api/content/validate`](/api-reference/content/validate) to check artist readiness
3. **Content creation** triggered via [`POST /api/content/create`](/api-reference/content/create) — returns `runIds`
4. **Poll task** (`poll-content-run`) monitors the Trigger.dev runs every 30 seconds (up to 30 minutes)
5. **Callback** → [`POST /api/content-agent/callback`](/api-reference/content-agent/callback) receives results and posts video URLs back to the Slack thread

### API Endpoints Used

| Endpoint | When | Purpose |
|----------|------|---------|
| [`GET /api/content/validate`](/api-reference/content/validate) | Before triggering | Checks if the artist has required assets |
| [`POST /api/content/create`](/api-reference/content/create) | On mention | Triggers the video generation pipeline |
| [`GET /api/content/templates`](/api-reference/content/templates) | Reference | Lists available content templates |
| [`POST /api/content-agent/callback`](/api-reference/content-agent/callback) | After completion | Internal — receives poll results |

## Setup

### 1. Create a Slack App

1. Go to [api.slack.com/apps](https://api.slack.com/apps) and create a new app
2. Under **OAuth & Permissions**, add bot scopes:
- `chat:write` — post messages
- `app_mentions:read` — receive @mention events
3. Under **Event Subscriptions**:
- Enable events
- Set the request URL to `https://recoup-api.vercel.app/api/content-agent/slack`
- Subscribe to `app_mention` bot event
4. Install the app to your workspace

### 2. Configure Environment Variables
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is recoup Api key not listed here?


| Variable | Where | Description |
|----------|-------|-------------|
| `SLACK_CONTENT_BOT_TOKEN` | API (Vercel) | Bot OAuth token (`xoxb-...`) from Slack app |
| `SLACK_CONTENT_SIGNING_SECRET` | API (Vercel) | Signing secret from Slack app **Basic Information** |
| `CONTENT_AGENT_CALLBACK_SECRET` | API + Tasks | Shared secret for callback authentication (generate a random string) |
| `RECOUP_API_KEY` | API + Tasks | Recoup API key for authenticating pipeline requests |
| `RECOUP_API_BASE_URL` | Tasks (Trigger.dev) | API base URL (e.g., `https://recoup-api.vercel.app`) |

### 3. Verify

Mention the bot in any Slack channel where it's been added:

```
@RecoupContentAgent <your-artist-account-id>
```

You should see:
1. An immediate acknowledgment message
2. A video URL reply in the thread after ~5-10 minutes

## Troubleshooting

| Issue | Cause | Fix |
|-------|-------|-----|
| No response from bot | Event subscription URL not configured | Check Slack app Event Subscriptions |
| "Artist not found" | Invalid `artist_account_id` | Verify the UUID exists in the platform |
| "No GitHub repository found" | Artist missing repo config | Ensure the artist account has a linked GitHub repo |
| Timeout after 30 min | Pipeline took too long | Check Trigger.dev dashboard for the failed run |
| "Unsupported template" | Invalid template name | Use [`GET /api/content/templates`](/api-reference/content/templates) to list available templates |
13 changes: 13 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
"authentication",
"api-reference/sandboxes/create"
]
},
{
"group": "Agents",
"pages": [
"content-agent"
]
}
]
},
Expand Down Expand Up @@ -243,6 +249,13 @@
"api-reference/content/validate",
"api-reference/content/estimate"
]
},
{
"group": "Content Agent",
"pages": [
"api-reference/content-agent/webhook",
"api-reference/content-agent/callback"
]
}
]
}
Expand Down