Narrative Lion

API Reference

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.com

Most 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.

Install (or update)
npx skills add rayjan0114/narrative-lion-skills

Auto-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_here

Source: github.com/rayjan0114/narrative-lion-skills

Authentication

All API requests require an API key sent as a Bearer token in the Authorization header.

Example request
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 needed

REST Endpoints

REST endpoints handle billing, binary streaming, and file operations that are not suited for GraphQL.

GET/api/billing/usage

Get current billing period usage and credit balance.

Response
{
  "tier": "pro",
  "period": "month",
  "resetsAt": "2026-05-21T00:00:00.000Z",
  "credits": {
    "used": 20,
    "limit": 3600,
    "remaining": 3580
  },
  "wallet": {
    "balance": 0,
    "enabled": false
  }
}
GET/api/audio/:noteId/:segmentId

Stream a podcast audio segment for a note.

NameTypeRequiredDescription
noteIdstringRequiredNote ID
segmentIdstringRequiredSegment ID

Returns an audio stream (audio/mpeg).

POST/api/export/request

Request a Markdown export of all your notes. Returns a job ID for polling.

Request
curl -X POST https://narrativelion.com/api/export/request \
  -H "Authorization: Bearer nlk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"format": "markdown"}'
GET/api/export/jobs

List your export jobs and their status.

Response
{
  "jobs": [
    {
      "id": "job-456",
      "status": "completed",
      "format": "markdown",
      "createdAt": "2026-04-20T10:00:00.000Z"
    }
  ]
}
GET/api/export/download/:jobId

Download a completed export as a zip file.

Returns the file as application/zip.

PUT/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.

PUT/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.

GET/api/filmwork/assets/:assetId/content

Stream a Filmwork asset file.

Requires notes:read. Asset URLs are also returned on FilmworkAsset.url.

GET/api/filmwork/rolls/:rollId/content

Stream 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:

PathWhen to useCostDocs
Film DirectorConcept → AI storyboard1-2 credits/docs/film-director
Reel CoachNotes → short-form script1-2 credits/docs/reel-coach
Direct creationYou have a formatted storyboard0 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.

Full flow, payloads, setup schema, and persist API →

GraphQL

Endpoint: POST /graphql. Send JSON with query and optional variables.

Basic request
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 → Int

Use 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!) → Note

Filmwork 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.

See Filmwork docs for all filmwork GraphQL operations →

SSE Streaming

The chat endpoint uses Server-Sent Events (SSE) for streaming responses. Send a POST request and read the event stream.

POST/api/chat/stream

Start a streaming chat session.

Request
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" }
    }
  }'
NameTypeRequiredDescription
threadIdstringRequiredChat thread ID (UUID). Create a new one per conversation.
actionIdstringRequiredUnique action ID (UUID) for idempotency.
event.typestringRequired"user_text", "interview_answer", or "edit_note"
event.payloadobjectRequiredEvent-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}}
EventDescription
tokenIncremental text token from the model
completeStream finished. Includes message ID and usage stats.
errorAn 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.

Full flow, payloads, refinement, and persist API →

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.

ScopePresetAccess
notes:readReadRead notes, browse, get note by ID, read Filmwork projects and media
notes:writeStandardCreate, update, star, tag notes, write Filmwork shots/assets/rolls
notes:delete-Delete notes
searchReadSemantic search and full-text search
jobs:readReadView job status
jobs:submitStandardSubmit YouTube URLs for note generation
chatStandardChat with AI (SSE streaming)
podcastStandardGenerate podcast audio
exportStandardRequest and download exports
billing:readReadView 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 response
{
  "error": "Credit limit reached (120/100). Resets 2026-05-21",
  "code": "QUOTA_EXCEEDED"
}
HTTPCodeDescription
401TOKEN_MISSINGNo API key provided
401TOKEN_INVALIDInvalid or revoked API key
401TOKEN_EXPIREDAPI key has expired
403-Scope not sufficient, or plan downgraded below Pro
429RATE_LIMITEDToo many requests
402QUOTA_EXCEEDEDCredit 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.

EndpointLimit
Chat5 requests / 10 seconds
Search20 requests / 60 seconds
Mutations (write operations)30 requests / 60 seconds
Job submission10 jobs / minute per account