Nellie
Sign in Open Nellie
Building with Nellie

Build a secure asynchronous book-generation workflow with the Nellie API

Learn how to submit a Nellie book job, store credentials safely, poll without overloading the API, handle failures, and save the finished file.

Nellie Editorial · · 7 min read · Updated
A laptop open on a wooden desk displaying code in an editor beside a coffee mug.
Photo by Daniil Komov on Pexels
Listen to the article AI narration · 9 min
Open audio ↗

The Nellie API generates books asynchronously. Your application submits a job, saves the returned requestId, checks its status periodically, and downloads the result only after the job reaches completed.

That separation matters. Book generation can take much longer than a normal HTTP request, so an application should not leave an incoming web request open while it waits. Treat generation as a background job with durable state, retry rules, and a separate download step.

Nellie is published by Buzzle LLC, which also publishes this guide. The workflow below follows the current Nellie documentation rather than an independent product test. Exact fields, models, costs, limits, and response behavior can change, so verify them against the API documentation before deploying.

The workflow in five steps#

A reliable integration has five distinct stages:

  1. Collect and validate the book request in your application.
  2. Send POST https://api.nelliewriter.com/v1/book from a trusted server.
  3. Persist the returned requestId, statusUrl, and local job state.
  4. Poll the status endpoint until the job completes or fails.
  5. Download the result into storage you control, then send the user a link to your copy.

The submission endpoint returns 202 Accepted for a successfully queued request. According to the current POST /v1/book reference, every body parameter is optional. An empty JSON object asks Nellie to choose the book settings. Most business integrations should be more explicit so their output is reproducible and easier to audit.

A request can include prompt, style, type, images, author, custom_tone, model, output_format, and an optional webhook_url. Valid values should come from the live configuration and model endpoints rather than a hard-coded list that may become stale.

Keep the API key on the server#

Create an API key through Nellie’s API Management screen, then copy it when it is shown. The quick-start guide says keys use the nel_... form and are sent in the X-API-Key header.

Store the key in a secrets manager or an encrypted deployment secret. Do not:

  • Put it in browser JavaScript or a mobile application bundle.
  • Commit it to Git, including private repositories.
  • Print it in request logs, exception reports, or analytics events.
  • Accept an API key from an end user and pass it through blindly.

A browser should call your backend. Your backend authenticates the user, applies authorization and spending controls, and then calls Nellie. This also gives you one place to rotate the key and prevent individual users from creating unbounded jobs.

For local development, an environment variable is adequate if .env files are excluded from version control:

export NELLIE_API_KEY='nel_replace_with_real_key'

If a key is exposed, replace it rather than relying on log deletion. Logs and build artifacts are often copied to multiple systems.

Submit a specific, auditable request#

Here is an example payload for a nonfiction handbook:

{
  "prompt": "A practical handbook for community garden coordinators covering volunteer scheduling, shared tools, plot rules, and seasonal planning. Do not invent local regulations.",
  "style": "automatic",
  "type": "non_fiction",
  "images": false,
  "author": "Example Organization",
  "custom_tone": "Clear, concise, and suitable for first-time coordinators",
  "model": "2.0",
  "output_format": "pdf"
}

This is an invented worked example, not a tested generation or a claim about output quality. In a real product, retain the accepted request settings beside the job record. They help support staff diagnose a failure and let users see what they requested.

Do not assume factual output is ready to publish. Generated nonfiction, textbooks, captions, and other factual material should be checked against reliable sources. Every manuscript should also receive editorial, legal, privacy, and rights review appropriate to its intended distribution.

A complete Python integration#

The following invented worked example uses the REST API directly. It submits one job, polls at the documented two-minute interval, stops on a terminal state, and streams the completed file to disk. Add your own database around it before using the pattern in a multi-user service.

import os
import time
from pathlib import Path
from urllib.parse import urlparse

import requests

BASE_URL = "https://api.nelliewriter.com/v1"
API_KEY = os.environ["NELLIE_API_KEY"]
POLL_SECONDS = 120
TIMEOUT_SECONDS = 2 * 60 * 60

session = requests.Session()
session.headers.update({
    "X-API-Key": API_KEY,
    "Accept": "application/json",
})


def start_book(payload: dict) -> dict:
    response = session.post(
        f"{BASE_URL}/book",
        json=payload,
        timeout=(10, 60),
    )
    response.raise_for_status()
    data = response.json()

    if not data.get("requestId"):
        raise RuntimeError("Nellie response did not include requestId")
    return data


def wait_for_book(request_id: str) -> dict:
    deadline = time.monotonic() + TIMEOUT_SECONDS

    while time.monotonic() < deadline:
        response = session.get(
            f"{BASE_URL}/status/{request_id}",
            timeout=(10, 30),
        )
        response.raise_for_status()
        status = response.json()

        state = status.get("status")
        print(f"state={state} progress={status.get('progress')}")

        if state == "completed":
            if not status.get("resultUrl"):
                raise RuntimeError("Completed job has no resultUrl")
            return status

        if state == "failed":
            message = status.get("errorMessage") or status.get("error")
            raise RuntimeError(f"Generation failed: {message or 'unknown error'}")

        time.sleep(POLL_SECONDS)

    raise TimeoutError("Stopped waiting before the job reached a terminal state")


def download_result(result_url: str, destination: Path) -> Path:
    parsed = urlparse(result_url)
    if parsed.scheme != "https" or parsed.hostname != "api.nelliewriter.com":
        raise ValueError("Unexpected download URL")

    destination.parent.mkdir(parents=True, exist_ok=True)
    temporary = destination.with_suffix(destination.suffix + ".part")

    with session.get(result_url, stream=True, timeout=(10, 300)) as response:
        response.raise_for_status()
        with temporary.open("wb") as output:
            for chunk in response.iter_content(chunk_size=64 * 1024):
                if chunk:
                    output.write(chunk)

    temporary.replace(destination)
    return destination


payload = {
    "prompt": "A practical handbook for community garden coordinators",
    "type": "non_fiction",
    "images": False,
    "author": "Example Organization",
    "model": "2.0",
    "output_format": "pdf",
}

job = start_book(payload)
result = wait_for_book(job["requestId"])
path = download_result(result["resultUrl"], Path("output/garden-handbook.pdf"))
print(f"Saved {path}")

The two-hour local timeout in this example is an application policy, not a promise about Nellie’s completion time. Reaching it should mark the local job as “timed out while waiting,” not necessarily “failed remotely.” A worker can reconcile that job later by checking the saved requestId again.

Poll politely and retry selectively#

Nellie’s current quick start recommends polling every 120 seconds. Faster polling adds load without making generation finish sooner. Use a background worker, scheduled task, or durable queue so polling survives an application restart.

Retry temporary network failures, HTTP 429 responses, and appropriate server errors with backoff. Do not automatically retry malformed requests or authentication failures. The endpoint reference documents 400 for invalid parameters or webhook URLs, 401 for missing or invalid authentication, and 429 for rate limits. Read the response body and preserve its errorCode for diagnostics.

Be careful when retrying the initial POST. If your client loses the response after the server accepts a job, submitting the same request again could create another generation. The supplied documentation does not describe an idempotency key for this endpoint. When the outcome is ambiguous, flag it for reconciliation rather than immediately creating a duplicate.

Download into storage you control#

A completed status includes a resultUrl, which points to the download endpoint. Stream the response instead of loading an entire book into memory. Write to a temporary file, check the HTTP result, and rename it only after the transfer succeeds.

For a hosted application, object storage is usually better than a local server disk. Record the local job ID, Nellie requestId, storage key, requested format, completion time, and review status. Serve the stored object through an authenticated route or a short-lived signed link.

Do not expose the API key when giving users a download. Also avoid fetching arbitrary URLs supplied by users. The example restricts downloads to HTTPS on the documented Nellie API host, which reduces server-side request forgery risk. If Nellie later documents other download hosts or redirects, update the allowlist deliberately.

Polling or webhooks?#

Polling is the simplest starting point. It needs no public callback endpoint and is easy to reason about, but every active job creates repeated status requests.

Webhooks reduce that polling traffic. The book endpoint accepts a publicly accessible HTTPS webhook_url; localhost and internal addresses are not accepted, according to the endpoint documentation. A webhook receiver should verify the Nellie signature using the webhook secret, acknowledge valid events quickly, and move substantial work to a queue. It should also handle duplicate delivery safely.

Even with webhooks, keep a reconciliation task. Callbacks can be delayed or missed because of deployments, network failures, or application errors. The saved requestId remains the durable connection between your system and Nellie.

Production checklist#

Before launch:

  • Keep API credentials and webhook secrets in server-side secret storage.
  • Persist the job before returning success to your user.
  • Poll no more often than the documented interval.
  • Distinguish queued, processing, completed, failed, and locally timed-out states.
  • Avoid blind retries of ambiguous creation requests.
  • Stream downloads and move completed files into controlled storage.
  • Validate current models, formats, credit requirements, and limits through the live documentation or configuration endpoints.
  • Require manuscript review before publication or distribution.

Start with one end-to-end job in a non-production environment. Confirm that your database state, failure reporting, timeout handling, and stored download all behave correctly. Then add webhooks or batch submission only when the basic asynchronous path is dependable.

Give your idea a first chapter

See where your story goes.

Bring a premise, a question, or a world you’d like to explore. Nellie can help turn it into a book.

Open Nellie ↗Explore example books
Sources and further reading

Researched and written with AI assistance. Our editorial approach · Suggest a correction

Keep exploring

Another page worth opening.