One prompt. A finished book.

Open Nellie Sign in

API Overview

Generate professional novels, comics, manga, and more using the Nellie AI generation API.

API Version: v1
Base URL: https://api.nelliewriter.com/v1

Table of Contents

Authentication

All API requests require an API key obtained from your Nellie account.

📖 See also: Authentication Guide for detailed security best practices and key management.

Getting Your API Key

  1. Log into the Nellie app
  2. Navigate to Settings → API Management on mobile, or https://app.nelliewriter.com/api on web
  3. Click “Create Key” and save it securely
  4. Include the key in all requests via the X-API-Key header
  5. Treat the key like a password, only embed it in trusted backend services. If you must call the API directly from a browser or desktop app you control, keep the key in local secure storage and rotate it frequently.

Authentication Example

curl -X POST https://api.nelliewriter.com/v1/book \
  -H "X-API-Key: nel_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{...}'

Endpoints

📖 Detailed endpoint documentation: See the endpoints/ folder for comprehensive guides on each endpoint.

1. Generate Book (Async)

POST /v1/book: Full Reference

Starts an asynchronous book generation job. Returns immediately with a request ID that you can poll for status.

Headers:

X-API-Key: your_api_key
Content-Type: application/json

Request Body:

{
  "prompt": "A thriller about a detective solving a mystery in Tokyo",
  "style": "thriller",
  "type": "novel",
  "images": true,
  "author": "Nellie",
  "custom_tone": "Steady suspense, cinematic pacing",
  "model": "2.0",
  "output_format": "epub",
  "webhook_url": "https://mysite.com/api/nellie-callback"
}

Response (202 Accepted):

{
  "success": true,
  "requestId": "uuid-of-request",
  "status": "queued",
  "statusUrl": "https://api.nelliewriter.com/v1/status/uuid-of-request",
  "message": "Job queued successfully"
}

2. Check Status (Polling)

GET /v1/status/{requestId}: Full Reference

Check the progress and results of a generation request.

Headers: None required (public endpoint)

Response (In Progress):

{
  "requestId": "uuid",
  "status": "processing",
  "progress": 45,
  "messages": [
    "Processing..."
  ],
  "createdAt": "2025-11-17T10:30:00Z",
  "completedAt": null,
  "creditsUsed": 0
}

Response (Completed):

{
  "requestId": "uuid",
  "status": "completed",
  "progress": 100,
  "messages": [
    "Request received, starting job...",
    "Generation completed successfully"
  ],
  "createdAt": "2025-11-17T10:30:00Z",
  "completedAt": "2025-11-17T10:45:00Z",
  "creditsUsed": 250,
  "resultUrl": "https://api.nelliewriter.com/v1/download/uuid"
}

Response (Failed):

{
  "requestId": "uuid",
  "status": "failed",
  "progress": 34,
  "messages": [
    "Request received, starting job...",
    "Error: Insufficient credits"
  ],
  "createdAt": "2025-11-17T10:30:00Z",
  "completedAt": "2025-11-17T10:32:00Z",
  "creditsUsed": 0
}

3. Webhooks (Push Notifications)

📖 See also: Webhooks Guide for complete setup instructions and framework examples.

If you provide a webhook_url in your request, Nellie will send a POST request to that URL when the job completes or fails.

Security (Recommended): You can verify that webhooks are genuinely from Nellie by checking the X-Nellie-Signature header. You can find your unique Webhook Signing Secret in the Webhooks tab of the Developer Console.

Python Verification Example:

import hmac
import hashlib
import json

def verify_signature(request, secret):
    # 1. Get header: "t=12345,v1=abcdef..."
    header = request.headers.get("X-Nellie-Signature")
    if not header: return False
    
    # 2. Parse timestamp and signature
    parts = dict(x.split('=') for x in header.split(','))
    timestamp = parts.get('t')
    signature = parts.get('v1')
    
    # 3. Re-create the signed content string
    # Note: payload must be the exact raw JSON string received
    payload = request.get_data(as_text=True) 
    signed_content = f"{timestamp}.{payload}"
    
    # 4. Calculate expected hash (HMAC-SHA256)
    expected = hmac.new(
        key=secret.encode(),
        msg=signed_content.encode(),
        digestmod=hashlib.sha256
    ).hexdigest()
    
    # 5. Compare securely
    return hmac.compare_digest(expected, signature)

Webhook Payload (Success):

{
  "requestId": "uuid",
  "status": "completed",
  "completedAt": "2025-11-17T10:45:00Z",
  "creditsUsed": 250,
  "resultUrl": "https://api.nelliewriter.com/v1/download/uuid"
}

Webhook Payload (Failure):

{
  "requestId": "uuid",
  "status": "failed",
  "completedAt": "2025-11-17T10:45:00Z",
  "creditsUsed": 0,
  "resultUrl": null,
  "error": "Description of what went wrong"
}

4. Configuration

GET /v1/configuration: Full Reference

Returns the valid options for styles, types, and output_formats so you can dynamically populate your UI.

Headers: None required (public endpoint)

Response:

{
  "success": true,
  "data": {
    "styles": ["automatic", "action", "sci_fi", ...],
    "types": ["automatic", "novel", "comic", ...],
    "formats": ["txt", "pdf", "epub", "md", "html", "json"]
  }
}

5. Models

GET /v1/models: Full Reference

Returns the list of available AI models and their costs.

Headers: None required (public endpoint)

Response:

{
  "success": true,
  "data": [
    {
      "id": "2.0",
      "name": "Nellie 2.0 (Standard)",
      "description": "Balanced performance for fast standard books.",
      "cost_per_book": 250
    },
    {
      "id": "3.0",
      "name": "Nellie 3.0 (Premium)",
      "description": "Higher creativity and coherence for complex plots.",
      "cost_per_book": 500
    }
  ]
}

6. Usage

GET /v1/usage: Full Reference

Returns the credit usage and recent requests for the authenticated API key.

Headers:

X-API-Key: your_api_key

Security note: Because this endpoint is secured purely by the API key (no session cookies), anyone who obtains your key can view these stats. Avoid embedding keys in public websites; use a backend proxy or environment variable injection when possible.

Response:

{
  "success": true,
  "totalRequests": 150,
  "totalCreditsUsed": 12500,
  "recentRequests": [
    {
      "requestId": "uuid...",
      "status": "completed",
      "createdAt": "2025-11-17T10:30:00Z",
      "creditsUsed": 250
    }
  ]
}

Request Format

Required Fields

None! All fields are optional.

Optional Fields

FieldTypeDescriptionDefaultOptions
promptstringCore idea, synopsis, or instructions for the book. If omitted, Nellie will generate a creative prompt for you!GeneratedAny string
stylestringGenre or narrative styleautomaticautomatic, action, horror, history, sci_fi, superhero, mystery, drama, romance, fantasy, adventure, crime, western, comedy, epic, thriller, mythology, folklore, fable, fairy_tale, legend, psychological, suspense, noir, paranormal, dystopian, post_apocalyptic, cyberpunk, alternate_history, gothic, tragedy, surrealism
typestringFormat of the outputautomaticautomatic, novel, comic, non_fiction, manga, textbook, childrens, self_help, short_story
imagesbooleanWhether to generate companion imagesfalsetrue, false
authorstringDisplay author creditNellieAny string
custom_tonestringExtra tone/voice guidance to describe how the author writes or define a brand-specific voice""Any string
modelstringNellie model release to target2.02.0, 3.0
output_formatstringPrimary export format for the finished booktxttxt, pdf, epub, md, html, json
webhook_urlstringURL to call when generation finishesnullAny valid public HTTPS URL. Localhost, loopback IPs, and internal metadata services are not allowed.

Response Format

Status Values

  • queued: Job has been queued but not started
  • processing: Generation in progress
  • completed: Successfully generated
  • failed: Generation failed (see error message)

Progress Values

Progress is reported as a percentage (0-100).

Code Examples

📖 See also: Examples & Recipes for more code patterns and use cases.

Python

Option 1: Use the Official SDK (Recommended): SDK Reference

pip install nellie-api
from nellie_api import Nellie

# Initialize client
client = Nellie(api_key="nel_your_api_key_here")

# Start generation
book = client.books.create(
    prompt="A sci-fi story about Mars colonization",
    style="sci_fi",
    type="novel",
    images=True,
    author="Nellie",
    custom_tone="Tight pacing, cinematic descriptions",
    model="3.0",
    output_format="epub"
)

print(f"Job started! Request ID: {book.request_id}")

# Poll for status
status = client.books.retrieve(book.request_id)
print(f"Status: {status.status} ({status.progress}%)")
if status.status == "completed":
    print(f"Result URL: {status.result_url}")

Option 2: Raw Requests

import requests
import time

API_KEY = "nel_your_api_key_here"
BASE_URL = "https://api.nelliewriter.com/v1"

# Start generation
response = requests.post(
    f"{BASE_URL}/book",
    headers={
        "X-API-Key": API_KEY,
        "Content-Type": "application/json"
    },
    json={
        "prompt": "A sci-fi story about Mars colonization",
        "style": "sci_fi",
        "type": "novel",
        "images": True,
        "author": "Nellie",
        "custom_tone": "Tight pacing, cinematic descriptions",
        "model": "3.0",
        "output_format": "epub"
    }
)

if response.status_code == 202:
    data = response.json()
    request_id = data["requestId"]
    print(f"Job started! Request ID: {request_id}")
    
    # Poll for completion
    while True:
        status_response = requests.get(f"{BASE_URL}/status/{request_id}")
        status_data = status_response.json()
        
        print(f"Progress: {status_data['progress']}% - {status_data['status']}")
        
        if status_data['status'] in ['completed', 'failed']:
            print(f"Final status: {status_data['status']}")
            if status_data['status'] == 'completed':
                print(f"Result URL: {status_data['resultUrl']}")
            break
        
        time.sleep(120)  # Check every 2 minutes
else:
    print(f"Error: {response.text}")

JavaScript/Node.js

const axios = require('axios');

const API_KEY = 'nel_your_api_key_here';
const BASE_URL = 'https://api.nelliewriter.com/v1';

async function generateBook() {
  try {
    // Start generation
    const response = await axios.post(
      `${BASE_URL}/book`,
      {
        prompt: 'A mystery novel set in Victorian London',
        style: 'mystery',
        type: 'novel',
        images: true,
        author: 'Nellie',
        custom_tone: 'Sherlockian wit with modern pacing',
        model: '2.0',
        output_format: 'pdf'
      },
      {
        headers: {
          'X-API-Key': API_KEY,
          'Content-Type': 'application/json'
        }
      }
    );

    const { requestId } = response.data;
    console.log(`Job started! Request ID: ${requestId}`);

    // Poll for completion
    const checkStatus = async () => {
      const statusResponse = await axios.get(`${BASE_URL}/status/${requestId}`);
      const { status, progress, resultUrl } = statusResponse.data;

      console.log(`Progress: ${progress}% - ${status}`);

      if (status === 'completed') {
        console.log('Generation completed!');
        console.log('Result URL:', resultUrl);
        return true;
      } else if (status === 'failed') {
        console.error('Generation failed');
        return true;
      }

      return false;
    };

    // Check every 120 seconds
    const interval = setInterval(async () => {
      const done = await checkStatus();
      if (done) clearInterval(interval);
    }, 120000);

  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
  }
}

generateBook();

cURL

# Start generation
curl -X POST https://api.nelliewriter.com/v1/book \
  -H "X-API-Key: nel_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A fantasy adventure in a magical forest",
    "style": "fantasy",
    "type": "novel",
    "images": true,
    "author": "Nellie",
    "custom_tone": "Sophisticated and direct",
    "model": "3.0",
    "output_format": "epub"
  }'

# Check status
curl https://api.nelliewriter.com/v1/status/YOUR_REQUEST_ID

Rate Limits and Credits

📖 See also: Rate Limits & Credits for detailed explanations and code examples.

Rate Limiting

  • Minimum spacing: 1 request every 6 seconds per API key (≈10/min). Requests that arrive sooner return 429 Too Many Requests.
  • Maximum requests per day: 15 per API key. The counter resets 24 hours after the first request of the day.
  • Concurrency: Jobs run asynchronously once queued. The limits above only apply to enqueuing new work.

Rate limits are enforced transactionally in Firestore. Implement exponential backoff/retry handling for 429 responses.

Credit System

Each generation consumes credits based on:

  • Content length: More chapters = more credits
  • Image generation: Enabled = additional credits
  • Model selection: Premium models cost more

Typical credit costs:

  • Book with Nellie 2.0: 250 credits
  • Book with Nellie 3.0: 500 credits

Check your remaining credits via the Flutter app or by querying your usage endpoint.

Handling Insufficient Credits

If you don’t have enough credits, the API will return:

{
  "status": "failed",
  "errorMessage": "Insufficient credits"
}

Purchase additional credits through the Nellie app.

Error Codes

Status CodeMeaning
200Success
202Accepted (async job started)
400Bad Request (invalid parameters)
401Unauthorized (invalid or missing API key)
403Forbidden (key revoked or insufficient permissions)
404Not Found (invalid request ID)
429Too Many Requests (rate limit exceeded)
500Internal Server Error

Best Practices

📖 See also: Best Practices Guide for comprehensive recommendations.

  1. Use Webhooks: Prefer providing a webhook_url over polling. It’s faster and more efficient.
  2. Poll Responsibly: If you must poll, check status every 120 seconds.
  3. Store API Keys Securely: Never commit keys to source control, and prefer keeping them on the server side.
  4. Handle Errors Gracefully: Implement retry logic with exponential backoff.
  5. Monitor Usage: Track your credit consumption to avoid interruptions.
  6. Rotate Keys: Revoke and recreate keys if you suspect exposure or when rotating teammates.

Error Response Format

📖 See also: Error Reference for complete error handling guidance.

All error responses follow a standardized format:

{
  "success": false,
  "error": "Human-readable error message",
  "details": "Additional context (optional)",
  "errorCode": "MACHINE_READABLE_CODE (optional)"
}

Error Codes

Error CodeHTTP StatusDescription
AUTH_REQUIRED401Authentication required (missing or invalid API key)
AUTH_FAILED401API key verification failed
FORBIDDEN403Permission denied (e.g., accessing another user’s resource)
NOT_FOUND404Resource not found
METHOD_NOT_ALLOWED405HTTP method not supported for this endpoint
RATE_LIMIT_EXCEEDED429Too many requests
INVALID_PARAMETER400Invalid request parameter
INVALID_WEBHOOK_URL400Webhook URL failed validation (SSRF protection)
MISSING_PARAMETER400Required parameter missing
JOB_START_FAILED500/503Failed to start the generation job
INTERNAL_ERROR500Internal server error

Error Response Examples

400 Bad Request (Invalid Parameter):

{
  "success": false,
  "error": "Field 'style' must be one of: action, adventure, automatic, ...",
  "errorCode": "INVALID_PARAMETER"
}

401 Unauthorized:

{
  "success": false,
  "error": "Unauthorized",
  "details": "Valid API key required",
  "errorCode": "AUTH_REQUIRED"
}

429 Too Many Requests:

{
  "success": false,
  "error": "Rate limit exceeded: Please wait a few seconds between requests",
  "errorCode": "RATE_LIMIT_EXCEEDED"
}

Daily Rate Limit Exceeded:

{
  "success": false,
  "error": "Daily rate limit exceeded (15 requests/day)",
  "errorCode": "RATE_LIMIT_EXCEEDED"
}

Insufficient Credits (Job Status): Note: Credit errors typically appear as a failed job status when polling.

{
  "requestId": "uuid",
  "status": "failed",
  "errorMessage": "Insufficient credits",
  "error": "Insufficient credits"
}

API Versioning

📖 See also: Migration Guide for upgrading between versions.

The Nellie API uses URL path versioning to ensure backward compatibility.

Current Version

All endpoints are available under the /v1 prefix:

  • POST /v1/book - Start generation
  • GET /v1/status/{requestId} - Check status
  • GET /v1/configuration - Get options
  • GET /v1/models - List models
  • GET /v1/usage - View usage stats

Legacy Paths (Deprecated)

For backward compatibility, the following unversioned paths still work but are deprecated:

  • POST /book → Use /v1/book instead
  • GET /status/{requestId} → Use /v1/status/{requestId} instead

⚠️ Deprecation Notice: Unversioned endpoints will be removed in a future release. Please migrate to /v1/ paths.

Version Lifecycle

VersionStatusSunset Date
v1Current-
(unversioned)DeprecatedQ2 2026

Support

For API support:


Getting Started

Endpoint Reference

Integration Guides

Code & Examples

Troubleshooting

Reference