n4nAI

Haystack setup tutorial: API keys and environment config

Configure Haystack API keys and environment for n4n.ai's OpenAI-compatible endpoint with step-by-step code examples and verification checkpoints.

n4n Team3 min read645 words

Audio narration

Coming soon — every post will get a voice note here.

Getting the haystack api keys environment setup right is the difference between a pipeline that runs and one that fails silently at 2 AM. This tutorial walks through configuring Haystack to work with n4n.ai’s OpenAI-compatible endpoint, covering environment management, component wiring, and a working RAG pipeline you can extend.

Prerequisites

Before starting, verify you have:

  • Python 3.10 or higher
  • An n4n.ai account with an API key (get one at n4n.ai if needed)
  • Basic familiarity with Haystack 2.x concepts: components, pipelines, and document stores

Install the required packages:

pip install haystack-ai python-dotenv

The python-dotenv package keeps secrets out of source control. If you prefer another secrets manager (1Password CLI, Doppler, AWS Secrets Manager), adapt the pattern — the principle stays the same.

Project structure

Create a clean workspace:

mkdir haystack-n4n-tutorial
cd haystack-n4n-tutorial

Layout:

haystack-n4n-tutorial/
├── .env                 # never commit this
├── .env.example         # commit this
├── requirements.txt
├── config.py            # centralized configuration
├── pipeline.py          # pipeline construction
└── run.py               # entry point

Environment configuration

Create .env.example first — this documents required variables for teammates and CI:

# .env.example
N4N_API_KEY=your_key_here
N4N_BASE_URL=https://api.n4n.ai/v1
EMBEDDING_MODEL=text-embedding-3-small
GENERATION_MODEL=gpt-4o-mini

Now create the actual .env with your key:

# .env
N4N_API_KEY=sk-n4n-xxxxxxxxxxxxxxxx
N4N_BASE_URL=https://api.n4n.ai/v1
EMBEDDING_MODEL=text-embedding-3-small
GENERATION_MODEL=gpt-4o-mini

Checkpoint: Verify the file loads correctly:

# test_env.py
from dotenv import load_dotenv
import os

load_dotenv()
print("API key loaded:", bool(os.getenv("N4N_API_KEY")))
print("Base URL:", os.getenv("N4N_BASE_URL"))

Run it:

python test_env.py

Expected output:

API key loaded: True
Base URL: https://api.n4n.ai/v1

Centralized configuration module

Avoid scattering os.getenv() calls throughout your codebase. Create config.py:

# config.py
from dataclasses import dataclass
from dotenv import load_dotenv
import os

load_dotenv()

@dataclass(frozen=True)
class Settings:
    n4n_api_key: str
    n4n_base_url: str
    embedding_model: str
    generation_model: str

    @classmethod
    def from_env(cls) -> "Settings":
        api_key = os.getenv("N4N_API_KEY")
        base_url = os.getenv("N4N_BASE_URL", "https://api.n4n.ai/v1")
        embedding_model = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small")
        generation_model = os.getenv("GENERATION_MODEL", "gpt-4o-mini")

        if not api_key:
            raise ValueError("N4N_API_KEY not set in environment")

        return cls(
            n4n_api_key=api_key,
            n4n_base_url=base_url.rstrip("/"),
            embedding_model=embedding_model,
            generation_model=generation_model,
        )

settings = Settings.from_env()

Checkpoint: Test the config module:

python -c "from config import settings; print(settings.generation_model)"

Expected output:

gpt-4o-mini

Haystack components with n4n.ai

Haystack 2.x uses generators and embedders that accept an api_base_url parameter. This is where the n4n.ai integration happens — point the OpenAI-compatible clients at the n4n.ai endpoint.

Create pipeline.py:

# pipeline.py
from haystack import Pipeline
from haystack.components.generators import OpenAIGenerator
from haystack.components.embedders import OpenAITextEmbedder, OpenAIDocumentEmbedder
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack.components.builders import PromptBuilder
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack import Document

from config import settings


def build_rag_pipeline() -> Pipeline:
    document_store = InMemoryDocumentStore()

    # Embedder for indexing documents
    doc_embedder = OpenAIDocumentEmbedder(
        api_key=settings.n4n_api_key,
        api_base_url=settings.n4n_base_url,
        model=settings.embedding_model,
    )

    # Embedder for query at runtime
    text_embedder = OpenAITextEmbedder(
        api_key=settings.n4n_api_key,
        api_base_url=settings.n4n_base_url,
        model=settings.embedding_model,
    )

    retriever = InMemoryEmbeddingRetriever(document_store=document_store, top_k=3)

    prompt_template = """
    Answer the question using only the provided context.
    If the answer isn't in the context, say you don't know.

    Context:
    {% for doc in documents %}
        {{ doc.content }}
    {% endfor %}

    Question: {{ question }}
    Answer:
    """

    prompt_builder = PromptBuilder(template=prompt_template)

    generator = OpenAIGenerator(
        api_key=settings.n4n_api_key,
        api_base_url=settings.n4n_base_url,
        model=settings.generation_model,
        generation_kwargs={"temperature": 0.1, "max_tokens": 512},
    )

    pipeline = Pipeline()
    pipeline.add_component("text_embedder", text_embedder)
    pipeline.add_component("retriever", retriever)
    pipeline.add_component("prompt_builder", prompt_builder)
    pipeline.add_component("generator", generator)

    pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
    pipeline.connect("retriever.documents", "prompt_builder.documents")
    pipeline.connect("prompt_builder.prompt", "generator.prompt")

    return pipeline, document_store, doc_embedder

Key points in this setup:

  1. Explicit api_base_url — Both embedders and the generator point to settings.n4n_base_url. This is the single line that routes traffic through n4n.ai instead of directly to OpenAI.

  2. Model names pass through — The model parameter uses whatever you configured in .env. n4n.ai honors the model name and routes to the appropriate provider.

  3. In-memory document store — Keeps the tutorial self-contained. Swap for WeaviateDocumentStore, QdrantDocumentStore, or PineconeDocumentStore in production.

Indexing sample documents

Create run.py to wire everything together:

# run.py
from pipeline import build_rag_pipeline

def main():
    pipeline, document_store, doc_embedder = build_rag_pipeline()

    # Sample documents — replace with your data source
    docs = [
        Document(content="Haystack 2.x uses a component-based architecture. Pipelines connect components via typed inputs and outputs."),
        Document(content="The OpenAIGenerator accepts api_base_url to support OpenAI-compatible endpoints like n4n.ai."),
        Document(content="InMemoryDocumentStore is suitable for prototypes. For production, use a vector database with persistence."),
        Document(content="Environment variables should never be committed. Use .env.example to document required keys."),
    ]

    print(f"Indexing {len(docs)} documents...")
    doc_embedder.warm_up()
    docs_with_embeddings = doc_embedder.run(docs)["documents"]
    document_store.write_documents(docs_with_embeddings)
    print("Indexing complete.")

    # Test queries
    questions = [
        "What architecture does Haystack 2.x use?",
        "How do I use n4n.ai with Haystack?",
        "What document store should I use in production?",
    ]

    for question in questions:
        print(f"\n{'='*60}")
        print(f"Q: {question}")
        print(f"{'='*60}")

        result = pipeline.run({
            "text_embedder": {"text": question},
            "prompt_builder": {"question": question},
        })

        answer = result["generator"]["replies"][0]
        print(f"A: {answer.strip()}")

if __name__ == "__main__":
    main()

Checkpoint: Run the pipeline:

python run.py

Expected output (answers will vary slightly by model):

Indexing 4 documents...
Indexing complete.

============================================================
Q: What architecture does Haystack 2.x use?
============================================================
A: Haystack 2.x uses a component-based architecture where pipelines connect components via typed inputs and outputs.

============================================================
Q: How do I use n4n.ai with Haystack?
============================================================
A: You use n4n.ai with Haystack by setting the api_base_url parameter on OpenAIGenerator and OpenAI embedders to https://api.n4n.ai/v1 and providing your n4n.ai API key.

============================================================
Q: What document store should I use in production?
============================================================
A: For production, use a vector database with persistence such as Weaviate, Qdrant, or Pinecone instead of InMemoryDocumentStore.

Verifying the request path

Confirm traffic actually goes through n4n.ai. Add a quick debug script:

# debug_request.py
import httpx
from config import settings

# Monkey-patch to log the request URL
original_post = httpx.Client.post

def logged_post(self, url, *args, **kwargs):
    print(f"POST {url}")
    return original_post(self, url, *args, **kwargs)

httpx.Client.post = logged_post

# Now run a minimal generation
from haystack.components.generators import OpenAIGenerator

gen = OpenAIGenerator(
    api_key=settings.n4n_api_key,
    api_base_url=settings.n4n_base_url,
    model=settings.generation_model,
)
gen.warm_up()
result = gen.run("Say 'ok' if you receive this")
print(result["replies"][0])

Run it:

python debug_request.py

Expected output shows the n4n.ai base URL:

POST https://api.n4n.ai/v1/chat/completions
ok

If you see api.openai.com instead, check that api_base_url is set correctly on the generator.

Common issues and fixes

Authentication failures

openai.AuthenticationError: Invalid API key

Fix: Verify N4N_API_KEY in .env matches the key in your n4n.ai dashboard. No extra prefixes, no trailing whitespace.

Model not found

openai.NotFoundError: Model 'gpt-4o-mini' not found

Fix: The model name must exist in n4n.ai’s catalog. List available models via the n4n.ai dashboard or API. Update GENERATION_MODEL in .env accordingly.

Rate limits

openai.RateLimitError: Rate limit exceeded

n4n.ai handles provider-level fallbacks automatically, but client-side retry logic is still your responsibility. Add tenacity:

from tenacity import retry, stop_after_attempt, wait_exponential_jitter

@retry(
    wait=wait_exponential_jitter(initial=1, max=10),
    stop=stop_after_attempt(3),
)
def run_with_retry(pipeline, data):
    return pipeline.run(data)

SSL certificate errors in corporate environments

If your MITM proxy intercepts TLS:

import os
os.environ["SSL_CERT_FILE"] = "/path/to/corporate-ca-bundle.crt"

Or disable verification for development only (never in production):

import httpx
httpx.Client(verify=False)  # dev only

Production hardening checklist

Before deploying:

  • Replace InMemoryDocumentStore with a persistent vector store
  • Move .env loading to your platform’s secrets manager (AWS Parameter Store, GCP Secret Manager, Vault, etc.)
  • Add structured logging (structlog) and metrics (Prometheus) around pipeline runs
  • Implement request/response logging for debugging — but redact PII and API keys
  • Set up health checks that exercise the full pipeline, not just component imports
  • Configure n4n.ai routing directives if you need specific provider preferences (e.g., {"provider": {"only": ["anthropic"]}})

Extending the pipeline

From here, typical additions:

  • Hybrid retrieval: Combine InMemoryBM25Retriever with the embedding retriever via a DocumentJoiner
  • Query rewriting: Add an OpenAIGenerator step before embedding to expand or decompose the query
  • Citation extraction: Modify the prompt template to require {{ doc.meta.source }} references
  • Streaming: Use OpenAIGenerator(streaming_callback=...) for token-by-token UX

Each addition follows the same pattern: configure the component with api_base_url=settings.n4n_base_url and api_key=settings.n4n_api_key.

Summary

You now have a working Haystack RAG pipeline routed through n4n.ai with:

  • Clean environment configuration via .env and a typed Settings class
  • OpenAI-compatible components pointing at the n4n.ai endpoint
  • A verified request path and debug technique
  • A foundation ready for production hardening

The same pattern applies to any OpenAI-compatible gateway — swap the base URL and API key, and the rest of your Haystack code stays unchanged.

Tagshaystackn4n-aiapi-keyssetup

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All haystack getting started with n4n.ai posts →