API Reference¶
The CloviNarrate API lets you integrate CloviNarrate into your own applications and automations.
All endpoints are served over HTTPS from https://clovinarrate.clovitek.com.
Authentication¶
Authenticate every request with a Bearer token in the Authorization header. Generate a
token from your account settings on the CloviNarrate dashboard.
Keep your token secret
Treat your API token like a password. Send it only over HTTPS and never commit it to source control — load it from an environment variable instead.
Responses & errors¶
All responses are JSON. Successful calls return 2xx; client errors return 4xx with a
JSON body describing the problem. Common status codes:
| Status | Meaning |
|---|---|
200 |
Success |
400 |
Bad request — check your parameters |
401 |
Missing or invalid token |
404 |
Resource not found |
429 |
Rate limit exceeded — slow down and retry |
500 |
Server error — retry or contact support |
CloviNarrate API Reference¶
Extracted from
/root/clovinarrate/server.js— all routes verified against source code.
Port: 8969
Base URL (local):http://localhost:8969
Authentication¶
CloviNarrate uses a three-priority auth chain on every protected route (requireAuth):
| Priority | Mechanism | Secret |
|---|---|---|
| 1 | cl_session cookie (CloviTek SSO JWT) |
JWT_SECRET from master.env |
| 2 | cn_token cookie or Authorization: Bearer <token> (standalone JWT) |
CLOVINARRATE_JWT_SECRET from master.env |
| 3 | X-User-Id header + X-Internal-Secret: clovitek-internal (server-to-server only) |
hardcoded sentinel |
All three paths set req.user = { id, email, name, plan, role }.
Admin role is detected via req.isAdmin (roles: admin, super_admin, platform_admin).
All /api/* routes return JSON. Unmatched /api/* paths return 404 {"error":"Not found"}.
No Auth Required¶
GET /health¶
Returns service health. No authentication required.
Response:
Auth Routes (Standalone — no prior auth needed)¶
POST /api/auth/register¶
Register a new standalone CloviNarrate account. Sets cn_token HttpOnly cookie (30-day). Fires n8n onboarding webhook fire-and-forget.
Auth: None
Body:
email and password are required.
- full_name is optional.
Response 201:
{ "ok": true, "token": "<jwt>", "user": { "id": 1, "email": "user@example.com", "name": "Jane Doe", "plan": "free", "role": "user" } }
400 {"error":"Email and password required"}, 409 {"error":"Email already registered"}, 500
POST /api/auth/login¶
Authenticate with standalone credentials. Sets cn_token HttpOnly cookie (30-day).
Auth: None
Body:
Response 200:
{ "ok": true, "token": "<jwt>", "user": { "id": 1, "email": "user@example.com", "name": "Jane Doe", "plan": "free|pro|team", "role": "user" } }
400 {"error":"Email and password required"}, 401 {"error":"Invalid email or password"}, 500
POST /api/auth/logout¶
Clears the cn_token cookie.
Auth: None (cookie is cleared regardless)
Response:
User Routes¶
GET /api/me¶
Return the currently authenticated user's profile.
Auth: Any valid auth (cookie or Bearer)
Response:
{ "user": { "id": 1, "email": "user@example.com", "name": "Jane Doe", "plan": "free", "role": "user" } }
401 {"error":"Not authenticated"}
GET /api/ai/usage¶
Return the authenticated user's AI credit usage for this session/period (sourced from aiCredits module).
Auth: Any valid auth
Response: Shape determined by aiCredits.usage(req) — varies by implementation.
Errors: 401
Narration Routes¶
All narration routes are owner-scoped: regular users can only access their own narrations. Admin users (role: admin/super_admin/platform_admin) have elevated access as noted.
GET /api/narrations¶
List narration projects.
Auth: Any valid auth
Scope: Own narrations only. Admin: Returns all users' narrations with user_email joined.
Example:
Response:
{
"narrations": [
{ "id": 1, "user_id": 1, "title": "My Deck", "source_type": "upload", "source_ref": null,
"status": "complete", "slide_count": 5, "voice_id": "AJbGPofgjOsJCmSoV9SO",
"full_video_key": "1/full.mp4", "created_at": "...", "updated_at": "..." }
]
}
"user_email" on each narration.)Errors:
401, 500
POST /api/narrations¶
Create a new narration project.
Auth: Any valid auth
Body:
title is required.
- source_type defaults to "upload". Valid values: "upload", "clovidecks".
- source_ref is optional (e.g. CloviDecks project ID).
Response 201:
{ "narration": { "id": 2, "user_id": 1, "title": "My New Narration", "source_type": "upload", ... } }
400 {"error":"title is required"}, 401, 500
GET /api/narrations/:id¶
Get a single narration plus all its slides, ordered by slide_num ASC.
Auth: Any valid auth
Scope: Owner only (no admin bypass on this route — uses user_id check).
Response:
{
"narration": { "id": 1, "title": "...", "status": "complete", ... },
"slides": [
{ "id": 10, "narration_id": 1, "slide_num": 1, "narration_text": "Hello...",
"ssml_text": null, "image_key": "uploads/img.png", "status": "done",
"audio_key": "1/slide_1.mp3", "video_key": "1/slide_1.mp4" }
]
}
401, 404 {"error":"Not found"}
PUT /api/narrations/:id¶
Update narration metadata. At least one field must be provided.
Auth: Any valid auth
Scope: Owner only.
Body (partial):
Response:
Errors:400 {"error":"Nothing to update"}, 401, 404, 500
DELETE /api/narrations/:id¶
Delete a narration and all its slides. Also removes output/<id>/ directory from disk.
Auth: Any valid auth
Scope: Owner only. Admin: Can force-delete any user's narration.
Response:
Errors:401, 404, 500
POST /api/narrations/:id/slides¶
Add a slide to a narration. Updates slide_count on the parent record.
Auth: Any valid auth
Scope: Owner only.
Body:
{ "slide_num": 1, "narration_text": "Welcome to the presentation.", "image_key": "uploads/slide1.png" }
slide_num is required.
- narration_text defaults to "".
- image_key is optional (local absolute path, or relative key resolved under /root/clovidecks/uploads/).
Response 201:
{ "slide": { "id": 10, "narration_id": 1, "slide_num": 1, "narration_text": "...", "image_key": "...", "status": "pending", ... } }
400 {"error":"slide_num is required"}, 401, 404 {"error":"Narration not found"}, 500
PUT /api/narrations/:id/slides/:num¶
Update narration text or SSML for a specific slide (identified by slide_num). At least one field must be provided.
Auth: Any valid auth
Scope: Owner only.
Path params: :id = narration ID, :num = slide_num value.
Body (partial):
Response:
{ "slide": { "id": 10, "narration_id": 1, "slide_num": 1, "narration_text": "Updated narration.", "ssml_text": "<speak>Updated.</speak>", ... } }
"slide": null if slide not found after update.)Errors:
400 {"error":"Nothing to update"}, 401, 404 {"error":"Narration not found"}, 500
POST /api/narrations/:id/generate¶
Start async audio + video generation for all slides in the narration. Resets all slides to status='pending' before firing the job via setImmediate.
Auth: Any valid auth
Scope: Owner only.
AI Credit Gate: Reserves ElevenLabs credits ($0.03 x slide_count) via aiCredits.reserve(). Returns 402 if quota exceeded.
Generation pipeline per slide:
1. ElevenLabs TTS (eleven_multilingual_v2, uses ssml_text if set, else narration_text) → MP3
2. ffmpeg: slide image + MP3 → per-slide MP4 (falls back to black 1280x720 background if image unavailable)
3. ffmpeg concat → full.mp4
Response:
Errors:401, 402 <aiCredits gate body>, 404 {"error":"Narration not found"}, 500
GET /api/narrations/:id/status¶
Poll generation status. Use to track async generation progress.
Auth: Any valid auth
Scope: Owner only. Admin: Can monitor any user's narration (response also includes user_id).
Example:
Response:
{
"narration": { "id": 1, "status": "pending|processing|complete|error", "slide_count": 5, "full_video_key": "1/full.mp4", "updated_at": "..." },
"slide_counts": { "pending": 2, "processing": 1, "done": 2, "error": 0 },
"slides": [
{ "slide_num": 1, "status": "done", "audio_key": "1/slide_1.mp3", "video_key": "1/slide_1.mp4" }
]
}
401, 404 {"error":"Not found"}, 500
GET /api/narrations/:id/video/:num¶
Stream/download a per-slide video file from output/<id>/slide_<num>.mp4.
Auth: Any valid auth
Scope: Owner only.
Path params: :id = narration ID, :num = slide number.
Response: MP4 file stream
Errors: 401, 404 {"error":"Not found"}, 404 {"error":"Video not ready"}, 500
GET /api/narrations/:id/fullvideo¶
Stream/download the concatenated full video from output/<id>/full.mp4.
Auth: Any valid auth
Scope: Owner only.
Response: MP4 file stream
Errors: 401, 404 {"error":"Not found"}, 404 {"error":"Full video not ready"}, 404 {"error":"Video file missing"}, 500
Integration Routes¶
POST /api/import¶
Import slides from CloviDecks into a new narration project. Runs in a DB transaction; rolls back on error.
Auth: Any valid auth
Security note: body.userId is intentionally ignored — the authenticated user's ID is always used, preventing cross-user injection.
Body:
{
"projectId": "clovidecks-project-uuid",
"title": "My Imported Deck",
"slides": [
{ "slide_num": 1, "image_key": "uploads/img1.png", "narration_text": "Slide one text." },
{ "slide_num": 2, "image_key": "uploads/img2.png", "narration_text": "Slide two text." }
]
}
slides array is required.
- title defaults to "Imported from CloviDecks (<projectId>)" if omitted.
- Creates narration with source_type='clovidecks' and source_ref=projectId.
Response 201:
Errors:400 {"error":"slides array is required"}, 401, 500
Admin Routes¶
All admin routes require role of admin, super_admin, or platform_admin. Returns 403 if authenticated but not admin.
GET /api/admin/users¶
List all registered users with their narration counts. For ops visibility.
Auth: Admin only (requireAdmin middleware — requireAuth + role check)
Response:
{
"users": [
{ "id": 1, "email": "admin@clovitek.com", "full_name": "CloviTek Admin",
"plan": "team", "role": "admin", "created_at": "...", "narration_count": 5 }
]
}
401, 403 {"error":"Admin access required"}, 500
GET /api/admin/narrations¶
List all narrations across all users (latest 200, ordered by updated_at DESC). Includes user_email.
Auth: Admin only
Response:
{
"narrations": [
{ "id": 1, "user_id": 2, "user_email": "user@example.com", "title": "...", "status": "complete", ... }
]
}
401, 403 {"error":"Admin access required"}, 500
Shell / SPA Routes¶
These routes serve HTML pages (not JSON). Auth enforcement is handled client-side by the SPA shell.
| Path | Description |
|---|---|
GET /app |
Serves shell/app.html — authenticated app shell (CloviTek shared UI-kit) |
GET /login |
Serves shell/login.html — standalone login page |
GET /register |
Serves shell/login.html — standalone registration page (same shell file) |
GET /output/* |
Serves generated output files (MP3/MP4) from output/ directory. No auth at middleware level. |
GET /* (non-API) |
SPA catch-all — serves public/index.html. Returns 503 if app not yet built. |
Data Models¶
Narration (cn_narrations)¶
| Field | Type | Notes |
|---|---|---|
id |
int | Primary key |
user_id |
int | FK to users.id |
title |
varchar | Required |
source_type |
varchar | "upload" or "clovidecks" |
source_ref |
varchar | CloviDecks project ID, or null |
status |
varchar | pending / processing / complete / error |
slide_count |
int | Auto-maintained |
voice_id |
varchar | ElevenLabs voice ID |
full_video_key |
varchar | Relative path: <id>/full.mp4 |
created_at |
datetime | |
updated_at |
datetime | Updated on every change |
Slide (cn_slides)¶
| Field | Type | Notes |
|---|---|---|
id |
int | Primary key |
narration_id |
int | FK to cn_narrations.id |
slide_num |
int | 1-based |
narration_text |
text | Plain-text narration |
ssml_text |
text | SSML override (used first during generation if present) |
image_key |
varchar | Local path or CloviDecks upload key |
status |
varchar | pending / processing / done / error |
audio_key |
varchar | <narration_id>/slide_<num>.mp3 |
video_key |
varchar | <narration_id>/slide_<num>.mp4 |
User (users)¶
| Field | Type | Notes |
|---|---|---|
id |
int | Primary key |
email |
varchar | Unique |
pw_hash |
varchar | bcrypt, cost 12 |
full_name |
varchar | |
plan |
enum | free / pro / team |
role |
enum | user / admin |
created_at |
datetime |
Voice & Generation Settings¶
| Setting | Value |
|---|---|
| Default Voice ID | AJbGPofgjOsJCmSoV9SO |
| ElevenLabs Model | eleven_multilingual_v2 |
| Stability | 0.40 |
| Similarity Boost | 0.82 |
| Style | 0.10 |
| Speaker Boost | true |
| ffmpeg binary | /usr/bin/ffmpeg |
| Output directory | <server_root>/output/<narration_id>/ |
Notes on Public Developer API¶
CloviNarrate does not currently expose a public developer API with API key auth. All routes use session cookie or Bearer token auth tied to registered user accounts. A public API surface (with per-tenant API keys, rate limiting, and webhook events) is planned for a future release.