Overview
The Narrative Lion API lets you programmatically access your notes, run searches, submit jobs, chat with AI, and more. Use it with Claude Code, Cursor, or any AI agent that speaks HTTP.
Base URL
https://narrativelion.comMost read/write operations are available via GraphQL at POST /graphql. Billing, audio streaming, and file exports use dedicated REST endpoints. Chat uses SSE.
AI Agent Skill
Already using Claude Code, Cursor, Codex, or another AI agent? Install the Narrative Lion skill so your agent knows how to call this API for you.
npx skills add rayjan0114/narrative-lion-skillsAuto-detects your AI tool and installs to the right location. Run the same command anytime to pull the latest version.
After install, set your API key in your shell:
export NL_API_KEY=nlk_your_api_key_hereAuthentication
All API requests require an API key sent as a Bearer token in the Authorization header.
curl https://narrativelion.com/api/export/jobs \
-H "Authorization: Bearer nlk_your_api_key_here"API keys are available on the Pro and Business plans. Create one in Settings → API Keys.
Each key has a set of scopes that control what it can access. Keys are prefixed with nlk_ for easy identification.
HTTP Client Requirements
Bot protection blocks automated User-Agent strings (403 error code: 1010). Set a custom User-Agent in every request. Authenticated requests (with Authorization header) need no other special headers. Unauthenticated requests also require Origin: https://narrativelion.com.
User-Agent: MyApp/1.0 # REQUIRED — default Python/requests UAs are blocked
Content-Type: application/json
Authorization: Bearer nlk_... # if present, no Origin neededREST Endpoints
REST endpoints handle billing, binary streaming, and file operations that are not suited for GraphQL.
/api/billing/usageGet current billing period usage and credit balance.
{
"tier": "pro",
"period": "month",
"resetsAt": "2026-05-21T00:00:00.000Z",
"credits": {
"used": 20,
"limit": 3600,
"remaining": 3580
},
"wallet": {
"balance": 0,
"enabled": false
}
}/api/audio/:noteId/:segmentIdStream a podcast audio segment for a note.
| Name | Type | Required | Description |
|---|---|---|---|
noteId | string | Required | Note ID |
segmentId | string | Required | Segment ID |
Returns an audio stream (audio/mpeg).
/api/export/requestRequest a Markdown export of all your notes. Returns a job ID for polling.
curl -X POST https://narrativelion.com/api/export/request \
-H "Authorization: Bearer nlk_your_key" \
-H "Content-Type: application/json" \
-d '{"format": "markdown"}'/api/export/jobsList your export jobs and their status.
{
"jobs": [
{
"id": "job-456",
"status": "completed",
"format": "markdown",
"createdAt": "2026-04-20T10:00:00.000Z"
}
]
}/api/export/download/:jobIdDownload a completed export as a zip file.
Returns the file as application/zip.
/api/filmwork/assets/:shotId/upload?assetKey=...Upload a Filmwork asset file to the upload URL returned by requestUploadUrl.
Requires notes:write. Do not construct the asset key yourself; request it with the GraphQL requestUploadUrl mutation, then call confirmAssetUpload after the PUT succeeds.
/api/filmwork/rolls/:shotId/upload?rollKey=...Upload a generated Filmwork roll video to the upload URL returned by requestRollUploadUrl.
Requires notes:write and a video/* content type. Use confirmRollUpload after upload to create the roll record.
/api/filmwork/assets/:assetId/contentStream a Filmwork asset file.
Requires notes:read. Asset URLs are also returned on FilmworkAsset.url.
/api/filmwork/rolls/:rollId/contentStream a Filmwork generated roll video.
Requires notes:read. Roll URLs are also returned on FilmworkRoll.url.
Filmwork Pipeline
Filmwork is Narrative Lion's shot production system — storyboard markdown alongside structured per-shot data (direction, prompts, dialogue, model config). Three creation paths:
| Path | When to use | Cost | Docs |
|---|---|---|---|
| Film Director | Concept → AI storyboard | 1-2 credits | /docs/film-director |
| Reel Coach | Notes → short-form script | 1-2 credits | /docs/reel-coach |
| Direct creation | You have a formatted storyboard | 0 credits | /docs/filmwork |
Read the docs for your creation path before starting. Film Director and Reel Coach handle formatting for you. Direct creation requires correct storyboard labels.
Filmwork data model: storyboard format, JSON schemas, GraphQL operations →
Film Director
AI chat tool that generates a storyboard from a creative concept. Uses activeTool: "film_director" via the chat stream. After generation, refine inline and persist as a Filmwork project.
GraphQL
Endpoint: POST /graphql. Send JSON with query and optional variables.
curl https://narrativelion.com/graphql \
-H "Authorization: Bearer nlk_your_key" \
-H "Content-Type: application/json" \
-d '{ "query": "{ me { id email name } }" }'Queries
# Search (semantic) — optionally scoped to a collection
search(query: String!, collectionId: String, limit: Int) → { id, videoId, title, noteMd, score }
# Full-text search (keyword matching) — optionally scoped to a collection
ftsSearch(query: String!, collectionId: String, limit: Int) → { id, videoId, noteType, snippet, rank }
# Get a single note
note(noteId: String!) → { id, videoId, noteType, lang, title, noteMd, tags, collections, starredAt, createdAt, updatedAt }
# Read a YouTube note transcript on demand
noteTranscript(noteId: String!) → { status, bytes, lang, sourceMethod, segmentCount, invalidSegmentCount, segments { startMs, endMs, text } }
# List your notes
myNotes(limit: Int) → [{ id, videoId, noteType, lang, noteMd, tags, updatedAt }]
# Browse with filters — use collectionId or uncategorized to filter
browseNotes(noteType: String, starred: Boolean, sort: String, tags: [String!], collectionId: String, uncategorized: Boolean, limit: Int) → [{ id, noteMd, starredAt, tags, collections }]
# List jobs
jobs(status: String, limit: Int) → [{ id, url, status, createdAt }]
# Collections (returns full tree with note counts)
collections → [{ id, parentId, name, icon, noteCount, totalNoteCount, children }]
# Count of notes not in any collection
uncategorizedNoteCount → IntUse noteTranscript for source transcript display. The legacyrawTranscript field remains available, but UI clients should prefer the structured transcript query to avoid loading raw storage objects during note reads.
Mutations
# Submit YouTube video for note generation
submitJob(url: String!) → { id, status, createdAt }
# Create a note (plain or podcast)
createGeneralNote(content: String!, noteType: String, skipAi: Boolean) → { id, noteMd, createdAt }
# Update a note
updateNote(noteId: String!, noteMd: String, metadata: String) → { id, updatedAt }
# Delete a note
deleteNote(noteId: String!) → Boolean
# Star / unstar
toggleStar(noteId: String!) → { id, starredAt }
# Tags
addTag(noteId: String!, tag: String!) → { id, tags }
removeTag(noteId: String!, tag: String!) → { id, tags }
# Collections (max 2 levels of nesting)
createCollection(name: String!, parentId: String, icon: String) → { id, name, parentId, icon, children }
updateCollection(id: String!, name: String, icon: String, sortOrder: Int) → Collection
deleteCollection(id: String!) → Boolean
addNoteToCollection(noteId: String!, collectionId: String!) → Note
removeNoteFromCollection(noteId: String!, collectionId: String!) → NoteFilmwork GraphQL operations
Filmwork has its own set of queries and mutations for shot management, asset uploads, roll reviews, and collaboration metadata. These are documented alongside the pipeline flow.
SSE Streaming
The chat endpoint uses Server-Sent Events (SSE) for streaming responses. Send a POST request and read the event stream.
/api/chat/streamStart a streaming chat session.
curl -N https://narrativelion.com/api/chat/stream \
-H "Authorization: Bearer nlk_your_key" \
-H "Content-Type: application/json" \
-d '{
"threadId": "thread-uuid",
"actionId": "action-uuid",
"event": {
"type": "user_text",
"payload": { "text": "Summarize my latest note" }
}
}'| Name | Type | Required | Description |
|---|---|---|---|
threadId | string | Required | Chat thread ID (UUID). Create a new one per conversation. |
actionId | string | Required | Unique action ID (UUID) for idempotency. |
event.type | string | Required | "user_text", "interview_answer", or "edit_note" |
event.payload | object | Required | Event-specific data. For user_text: { text: string } |
Event stream format
event: token
data: {"t":"Hello"}
event: token
data: {"t":" world"}
event: complete
data: {"messageId":"msg-123","usage":{"inputTokens":150,"outputTokens":42}}| Event | Description |
|---|---|
token | Incremental text token from the model |
complete | Stream finished. Includes message ID and usage stats. |
error | An error occurred during generation |
Podcast
Podcast notes store a structured IR (Intermediate Representation) in JSON. The IR defines speakers, dialogue segments with script text and timing, and voice settings. TTS audio is generated per-segment via ElevenLabs.
Create a podcast note by passing IR JSON to createGeneralNote(noteType: "podcast", skipAi: true) or via CLI: nl.py notes create --type podcast --skip-ai --content '<IR JSON>'. Export finished audio via exportPodcast(noteId, format: "mp3" | "zip", paddingMs).
Full IR schema, voice catalog, TTS, export, and editing APIs →
Reel Coach
Reel Coach generates production-ready short-form video scripts (TikTok / Reels / Shorts) from your note library. Uses activeTool: "reel_coach" via the chat stream. The AI retrieves relevant notes, generates a shot-by-shot script with timing, then annotates the draft with "Coach Notes" that explain why each section works and cite source notes. After generation, refine inline and persist as a Filmwork project.
Scopes
Each API key has a set of scopes that determine what operations it can perform. When creating a key, choose a preset or customize individual scopes.
| Scope | Preset | Access |
|---|---|---|
notes:read | Read | Read notes, browse, get note by ID, read Filmwork projects and media |
notes:write | Standard | Create, update, star, tag notes, write Filmwork shots/assets/rolls |
notes:delete | - | Delete notes |
search | Read | Semantic search and full-text search |
jobs:read | Read | View job status |
jobs:submit | Standard | Submit YouTube URLs for note generation |
chat | Standard | Chat with AI (SSE streaming) |
podcast | Standard | Generate podcast audio |
export | Standard | Request and download exports |
billing:read | Read | View usage and credit balance |
Read preset includes: notes:read, search, jobs:read, billing:read.
Standard preset includes all Read scopes plus: notes:write, jobs:submit, chat, podcast, export.
Error Codes
The API returns errors as JSON with an error field and an optional code field.
{
"error": "Credit limit reached (120/100). Resets 2026-05-21",
"code": "QUOTA_EXCEEDED"
}| HTTP | Code | Description |
|---|---|---|
401 | TOKEN_MISSING | No API key provided |
401 | TOKEN_INVALID | Invalid or revoked API key |
401 | TOKEN_EXPIRED | API key has expired |
403 | - | Scope not sufficient, or plan downgraded below Pro |
429 | RATE_LIMITED | Too many requests |
402 | QUOTA_EXCEEDED | Credit limit reached for this billing period |
Rate Limits
Rate limits are applied per account. If you exceed the limit, the API returns a 429 status.
| Endpoint | Limit |
|---|---|
| Chat | 5 requests / 10 seconds |
| Search | 20 requests / 60 seconds |
| Mutations (write operations) | 30 requests / 60 seconds |
| Job submission | 10 jobs / minute per account |