Automating with n8n
How to drive this server from a self-hosted n8n instance: the AI nodes connect through the OpenAI or Ollama credential with the base URL pointed here, and the HTTP Request node reaches the native document API (OCR, extraction, classification, PDF operations, transcription) that no bare model runner offers. Written for automation builders; no code beyond node settings.
1Self-hosted n8n only#
Everything here assumes n8n runs on hardware that can reach this server: the same machine, the same LAN, or a network you control (self-hosting n8n is its own documented path). n8n Cloud, and cloud automation platforms generally, execute on the vendor's infrastructure and cannot reach a private server without exposing it to the internet, which defeats the reason you run models locally. Keep the runner where the data is.
2Connect the AI nodes#
n8n's AI nodes (AI Agent, the chat model nodes, the embeddings nodes, chains and summarizers) speak the OpenAI and Ollama dialects this server serves natively (API Compatibility). Two credential routes, both stable:
- OpenAI credential. Create an OpenAI credential in n8n, set its Base URL to
http://your-server:PORT/v1, and paste an API key minted in Access as the key. Every OpenAI-flavored node (chat model, embeddings, the AI Agent behind them) now runs against your models. - Ollama credential. Point the Ollama credential's base URL at the server root
(no
/v1). The Ollama-flavored chat and embeddings nodes work unchanged, including model listing.
The model field in either node names a model from your catalog; a request that names none gets the server's default for the capability. Embeddings nodes feed n8n's vector-store operations exactly as they would against a hosted API; the embedding side of the house is Embeddings and Reranking.
The same two routes serve other self-hosted automation builders. Flowise and Dify both register a custom OpenAI-compatible (or Ollama) model provider: point its base URL here, paste a key from Access, and every rule in this guide, the Docker networking trap included, applies unchanged.
3The Docker networking trap#
The most common failure is not AI configuration, it is networking: n8n runs in a Docker
container, the server runs on the host, and localhost inside the container is the
container, not the host. Two fixes, both required:
- Address the host, not localhost. On Docker Desktop (Windows, macOS), use
http://host.docker.internal:PORTas the base URL. On Linux, addhost.docker.internal:host-gatewayto the container'sextra_hosts(Compose) and use the same name, or use the host's LAN address directly. - Let the server accept the connection. A default installation listens on loopback only, and traffic from a container arrives on a bridge interface, not loopback, so it is refused regardless of the address you dial. Switch the posture to Network in Ports and mint a key first, because a network-reachable server refuses keyless calls. The checklist is Going Live.
When both run in containers on one Compose network, use the server's service name as the
host and skip host.docker.internal entirely.
4Beyond chat: the document API#
The chat and embeddings nodes are table stakes; the reason to pair n8n with this server is
the native surface under /lmkit/v1, called from the HTTP Request node. Authenticate it
with a Header Auth credential sending Authorization: Bearer lmk_..., and reuse that one
credential across every node below. The full request and response shapes are live in the
API reference.
| Endpoint | What the workflow step does |
|---|---|
POST /lmkit/v1/files/upload |
Multipart upload; returns a fileId the other endpoints accept, so a document crosses the wire once |
POST /lmkit/v1/document-to-markdown |
Any supported format becomes Markdown, OCR included (Document Processing) |
POST /lmkit/v1/document-ocr |
Text recognition on scans and images |
POST /lmkit/v1/extract-structured-data |
Your JSON Schema in, verified JSON out with review flags (Structured Extraction) |
POST /lmkit/v1/categorize |
Classifies content against categories you define (Document Classification) |
POST /lmkit/v1/audio-transcription |
Recordings, and with ffmpeg on the server, video, become transcripts (Transcription) |
POST /lmkit/v1/pdf-merge, pdf-to-pdfa, and the rest of the PDF Toolbox |
Assembly, conversion, redaction, archival |
For binary payloads, send the file from the previous node as form-data (the upload endpoint
expects the form field file); for JSON endpoints, pass the returned fileId as input
with input_format set to FileIdentifier. That upload-once pattern keeps large documents
out of base64 and out of n8n's execution data.
5A worked flow: watch a folder, extract invoices#
The classic recipe, five nodes, no code:
| Step | Node | Settings that matter |
|---|---|---|
| 1 | Local File Trigger | Watch the scanner's output folder for new PDFs |
| 2 | Read/Write Files from Disk | Load the new file as binary data |
| 3 | HTTP Request | POST the binary as form-data field file to /lmkit/v1/files/upload; keep the returned fileId |
| 4 | HTTP Request | POST JSON to /lmkit/v1/extract-structured-data: input is the fileId, input_format is FileIdentifier, jsonSchema is your invoice schema, include_elements true |
| 5 | IF | Branch on human_verification_required in the response |
The false branch writes the extracted json straight to your database, spreadsheet, or
accounting system with n8n's ordinary nodes. The true branch posts the flagged fields to a
review channel (email, chat, a ticket) with the per-field confidence and page coordinates
from elements, so a person confirms instead of re-reading. Designing schemas that extract
well, and why routing on that one flag is the whole game, is
Structured Extraction; the same pattern extends to
splitting and classification as the full IDP pipeline.
Two variants worth building next: an inbox classifier (Email Trigger (IMAP), then
/lmkit/v1/categorize against your own category list, then route by result), and an
archive pipeline (upload, document-to-markdown for the searchable text,
pdf-to-pdfa for the archival copy).
6Long work rides the jobs contract#
A 200-page scan or an hour of audio should not hold an n8n execution's HTTP connection open.
Add the header Prefer: respond-async to the HTTP Request node: the server answers
202 Accepted immediately with a job_id. Then loop a Wait node (a few seconds) into an
HTTP Request polling GET /lmkit/v1/jobs/{job_id}, and exit the loop when status is
anything other than processing; the finished result is byte-for-byte what the
synchronous call would have returned. Retry discipline in workflow terms: retry 503 and 429
after the Retry-After pause, never retry a 4xx unchanged, and remember results are held
for one hour, so poll promptly and store them on your side. The full contract is
Errors, Retries, and Jobs.
7Stated plainly#
- Self-hosted n8n only: a cloud runner cannot reach a private server, and should not.
- Two credentials cover the AI nodes (OpenAI with a base URL override, or Ollama); one Header Auth credential covers the entire native API.
- From Docker, dial
host.docker.internal, set the server's posture to Network, and mint a key first. - The document endpoints are the differentiator: upload once, chain by
fileId, branch onhuman_verification_required, and letPrefer: respond-asynchandle anything long.