PullMD Hilfe Help

PullMD Hilfe

PullMD Help

Diese Seite erklärt, was PullMD macht, wie der Cache funktioniert und wie du den Service in Claude Code oder anderen KI-Agenten einrichtest.

This page explains what PullMD does, how the cache works, and how to wire it into Claude Code or any other AI agent.

Neu in v3New in v3v3.9.0

In v3 ist PullMD von einem reinen Web-Reader zu einem allgemeinen Alles-zu-Markdown-Dienst gewachsen. Alles über die normale Web-Extraktion hinaus ist optional und lässt sich einzeln aktivieren:

In v3, PullMD grew from a web-page reader into a general anything-to-Markdown service. Everything beyond plain web extraction is optional and enabled individually:

Seitdem dazugekommen

Added since

Was macht PullMD?What PullMD does/api

PullMD ruft eine beliebige URL ab und liefert sie als sauberes Markdown zurück. Je nach Quelle wird der passende Extraktions-Pfad gewählt:

PullMD fetches any URL and returns it as clean Markdown. It picks the right extraction path depending on the source:

Zwei Korrekturen laufen darüber: Site-Recipes passen die Extraktion pro Host an (siehe Site-Recipes), und der Coverage Guard greift ein, wenn die Extraktion nur einen Bruchteil einer großen Seite behalten hat, und konvertiert stattdessen den Container mit dem eigentlichen Body. Er kann ein Ergebnis nur vergrößern, meldet sich als source: coverage-guard und lässt sich mit PULLMD_COVERAGE_GUARD=off abschalten.

Two corrections sit on top: site recipes tune extraction per host (see Site recipes), and the coverage guard steps in when an extraction kept only a sliver of a large page, converting the container that actually holds the body instead. It can only grow a result, reports itself as source: coverage-guard, and is switched off with PULLMD_COVERAGE_GUARD=off.

Welcher Pfad genommen wurde, sieht man im Response-Header X-Source und in der History neben jedem Eintrag.

Which path was used shows up in the response header X-Source and next to each entry in the history.

Cache & TTLCache & TTLSQLite

Jeder Pull wird in einer SQLite-Datenbank gespeichert. Zwei Zeitspannen sind wichtig:

Every pull is stored in a SQLite database. Two timeouts matter:

WasWertWann zurücksetzen? WhatValueWhen does it reset?
Re-Fetch von der Quelle 1 Stunde Bei jedem erfolgreichen Pull derselben URL — egal ob über /api?url=… oder über /s/:id. Re-fetch from source 1 hour On every successful pull of the same URL — regardless of whether it came through /api?url=… or /s/:id.
Share-Link-Lebensdauer 90 Tage Bei jedem Re-Fetch (=> Cache schreibt). Auch /s/:id-Aufrufe verlängern, da sie nach 1 h einen Re-Fetch auslösen. Share link lifetime 90 days On every re-fetch (= cache write). /s/:id requests extend it too, since they trigger a re-fetch after 1h.

So funktioniert /s/:id

How /s/:id behaves

Tipp: Subreddit als Live-Feed

Tip: subreddit as a live feed

Pull einmal einen Subreddit-Listing-Link, merke dir die Share-ID — und ruf danach nur noch /s/:id auf. Nach jeder Stunde löst der nächste Aufruf einen frischen Fetch aus, die Share-ID bleibt stabil, der Inhalt aktualisiert sich. Praktisch für KI-Agenten, die einen festen Endpoint mit regelmäßig aktuellem Inhalt brauchen.

Pull a subreddit-listing URL once, remember the share ID — and from then on only call /s/:id. After each hour the next request triggers a fresh fetch, the share ID stays stable, and the content updates. Handy for AI agents that need a fixed endpoint with regularly refreshed content.

In KI-Agenten einrichtenSet up in AI agentssetup

Option 1 — Universal: Prompt einfügen

Option 1 — Universal: paste a prompt

Funktioniert in jedem Chat-Agent (ChatGPT, Claude.ai, Gemini, Perplexity, …). Kopiere den Block, füge ihn als System- oder Custom-Instruction ein:

Works in any chat-style agent (ChatGPT, Claude.ai, Gemini, Perplexity, …). Copy the block, paste it as a system or custom instruction:

prompt · drop-in
When you need to read a web page, fetch it via PullMD instead of your
built-in fetch/browse tool - not just when that one fails:

  GET https://markdown.invisibletheaters.com}/api?url=<URL>

Returns clean Markdown (text/markdown). Optional query params:

  comments=false        skip Reddit / Hacker News comments
  comment_depth=N       comment nesting depth (default 3)
  frontmatter=true      prepend YAML metadata block
  format=text           strip Markdown, return plain text
  nocache=true          bypass the 1h cache and refetch
  render=force|skip     override the auto headless-Chromium fallback
  extractor=readability|trafilatura|playwright   force one extractor
  pdf=ocr               high-quality PDF conversion (tables)
  query=<text>          got a specific question? pass it and get back only
                        the matching sections (70-95% fewer tokens)
  max_tokens=N          budget for query= (default 600, 64-20000)
  lang=de|en            language for the comments section header

Response headers worth checking:
  X-Source       reddit | hackernews | cloudflare | readability |
                 trafilatura | playwright | recipe-content |
                 coverage-guard | markitdown | youtube | pdf-ocr | ...
  X-Quality      0.0-1.0 extraction confidence
  X-Share-Id     8-hex permalink, openable as /s/<id>
  X-Transcript-Status  youtube only: ok | none | blocked | error
                 (blocked/error = transient, not cached — retry later)

Reddit URLs are auto-detected (incl. redd.it short links and /s/ shares).
Hacker News URLs are auto-detected too — items, comment permalinks, and the
front/newest/ask/show/jobs listings.
Use this whenever you would otherwise fetch raw HTML — the markdown is
much cleaner and saves significant context window space. When you only
need specific information rather than the whole document, pass your
question as query= and get back just the relevant sections.

Option 2 — Claude Code Skill

Option 2 — Claude Code skill

Für Claude Code gibt es eine fertige Skill, die WebFetch automatisch durch PullMD ersetzt (mit Fallback). Lade sie als Zip und entpacke nach ~/.claude/skills/:

For Claude Code there's a ready-made skill that automatically routes WebFetch through PullMD (with fallback). Download the zip and unpack into ~/.claude/skills/:

pullmd.zip herunterladenDownload pullmd.zip

install · shell
curl -O https://markdown.invisibletheaters.com}/pullmd.zip
mkdir -p ~/.claude/skills
unzip pullmd.zip -d ~/.claude/skills/
# Restart Claude Code; the skill activates on web-reading requests.

Upgrade von vor v3: Die Skill hieß früher web-reader. Das neue Zip ersetzt eine bestehende Installation nicht — erst die alte entfernen (rm -rf ~/.claude/skills/web-reader), sonst sind beide Skills parallel aktiv.

Upgrading from pre-v3: the skill used to be called web-reader. The new zip does not replace an existing install — remove the old one first (rm -rf ~/.claude/skills/web-reader), otherwise both skills stay active side by side.

Option 3 — MCP-Server (remote)

Option 3 — MCP server (remote)

PullMD läuft als remote MCP-Server unter https://markdown.invisibletheaters.com}/mcp (Streamable-HTTP-Transport, stateless). Drei Tools: read_url, get_share, list_recent. Server-seitige Updates erreichen automatisch alle Clients — keine lokale Installation nötig.

PullMD runs as a remote MCP server at https://markdown.invisibletheaters.com}/mcp (Streamable-HTTP transport, stateless). Three tools: read_url, get_share, list_recent. Server-side updates reach every client automatically — no local install needed.

Claude Code — Prompt einfügen, Claude installiert es selbst:

Claude Code — paste this prompt and Claude will install it for you:

prompt · claude code
Installiere den PullMD MCP-Server in Claude Code (User-Scope):
- Name: pullmd
- Transport: http
- URL: https://markdown.invisibletheaters.com}/mcp

Nutze: claude mcp add --transport http pullmd https://markdown.invisibletheaters.com}/mcp
Danach: claude mcp list zur Verifikation.
Install the PullMD MCP server in Claude Code (user scope):
- Name: pullmd
- Transport: http
- URL: https://markdown.invisibletheaters.com}/mcp

Run: claude mcp add --transport http pullmd https://markdown.invisibletheaters.com}/mcp
Then: claude mcp list to verify.

Claude Code — direkt im Terminal:

Claude Code — directly in the terminal:

claude code · cli
claude mcp add --transport http pullmd https://markdown.invisibletheaters.com}/mcp

Claude Desktop / Cursor / andere — JSON-Konfig:

Claude Desktop / Cursor / others — JSON config:

mcp config snippet
{
  "mcpServers": {
    "pullmd": {
      "type": "http",
      "url": "https://markdown.invisibletheaters.com}/mcp"
    }
  }
}

Sobald registriert, erscheinen die drei Tools nativ im Agent — keine Prompt-Anweisungen nötig, das LLM erkennt sie über ihre Schema-Beschreibungen.

Once registered, the three tools surface natively in the agent — no prompt instructions needed, the LLM picks them up via their schema descriptions.

read_url kennt dieselben Stellschrauben wie die REST-API: comments, comment_depth, comment_limit, frontmatter, lang, nocache, extractor, yt_timecodes, yt_chunk, pdf_ocr (statt pdf=ocr), query und max_tokens. MCP-Antworten haben keine Response-Header; findet die Query-Extraktion nichts, steht stattdessen ein Kommentar im Markdown.

read_url exposes the same knobs as the REST API: comments, comment_depth, comment_limit, frontmatter, lang, nocache, extractor, yt_timecodes, yt_chunk, pdf_ocr (instead of pdf=ocr), query and max_tokens. MCP responses carry no headers; when query extraction finds no match it prepends an in-band comment to the markdown instead.

Instanz mit Login? Dann brauchen /api und /mcp Zugangsdaten: einen API-Key als Authorization: Bearer pmd_… oder OAuth für Claude Desktop und claude.ai. Details unter Authentifizierung.

Instance with login? Then /api and /mcp need credentials: an API key as Authorization: Bearer pmd_…, or OAuth for Claude Desktop and claude.ai. Details under Authentication.

EndpunkteEndpointshttp

Alles, was die Instanz nach außen anbietet. GET /api ist der Hauptweg, der Rest ist Beiwerk für PWA, Betrieb und Integration.

Everything the instance exposes. GET /api is the main path; the rest supports the PWA, operations, and integration.

Endpoint Zweck Purpose
GET /api?url=… URL zu Markdown. Siehe API-Parameter. URL to Markdown. See API parameters.
GET /api/stream?url=… Dieselbe Konvertierung als Server-Sent-Events: status-Events pro Extraktionsstufe, dann ein result- oder error-Event. Die PWA zeigt damit den Fortschritt an. The same conversion as Server-Sent Events: a status event per extraction stage, then one result or error event. This is what drives the PWA's progress display.
POST /api/html Gespeicherte HTML-Datei konvertieren (max. 10 MB). Convert a saved HTML file (max 10 MB).
POST /api/file Dokument-Upload konvertieren (max. 25 MB). Convert an uploaded document (max 25 MB).
GET /s/:id Share-Link, aktualisiert sich selbst. Siehe Share-Links. Share link, refreshes itself. See Share links.
GET /api/history Letzte Konversionen als JSON (?limit=, Default 20, max. 100). Recent conversions as JSON (?limit=, default 20, max 100).
GET /api/archive Seitenweise durch das ganze Archiv (?limit= Default 50, max. 200, ?offset=). Paginated full archive (?limit= default 50, max 200, ?offset=).
GET /api/storage Cache-Größe und Aufbewahrungsdauer. Cache size and retention.
GET /api/stats Extraktions-Telemetrie: Quellen, Qualität, Laufzeit (?window=-7 days). Extraction telemetry: sources, quality, latency (?window=-7 days).
GET /api/config Was auf dieser Instanz aktiv ist (Auth-Modus, Dokumente, Vision, STT, PDF-OCR, YouTube). Die PWA blendet danach ihre Bedienelemente ein oder aus. What is enabled on this instance (auth mode, documents, vision, STT, PDF OCR, YouTube). The PWA shows or hides its controls accordingly.
GET /api/recipes/status Welche Site-Recipes geladen bzw. abgelehnt wurden. Which site recipes loaded or were rejected.
POST /mcp MCP-Server (Streamable-HTTP, stateless), drei Tools. MCP server (Streamable HTTP, stateless), three tools.
GET /pullmd.zip Claude-Code-Skill mit der URL dieser Instanz. /web-reader.zip leitet hierher weiter. Claude Code skill with this instance's URL baked in. /web-reader.zip redirects here.

Zum Schutz vor Server-Side Request Forgery (SSRF) werden Anfragen an private, loopback-, link-local-, CGNAT- und Cloud-Metadata-Adressen (z. B. 169.254.169.254, 100.100.100.200) standardmäßig abgelehnt - auch wenn eine URL erst per Redirect dorthin führt und auch über IPv6-Übergangsadressen. /api und das MCP-Tool read_url prüfen vorab und antworten mit 403, bevor irgendetwas geholt wird; /api/stream meldet die Ablehnung als error-Event. Selbst-Hoster können mit PULLMD_ALLOWED_HOSTS (kommagetrennte CIDRs und/oder Hostnamen) gezielt interne Ziele freigeben.

To guard against Server-Side Request Forgery (SSRF), requests to private, loopback, link-local, CGNAT and cloud-metadata addresses (e.g. 169.254.169.254, 100.100.100.200) are rejected by default - including when a URL only reaches them via redirect, and including IPv6 transition addressing. /api and the MCP read_url tool check up front and answer 403 before fetching anything; /api/stream surfaces the refusal as an error event. Self-hosters can allow specific internal targets via PULLMD_ALLOWED_HOSTS (comma-separated CIDRs and/or hostnames).

API-ParameterAPI parametersGET /api

Param DefaultBeschreibung DefaultDescription
url Pflicht. Beliebige öffentliche URL. Required. Any public URL.
commentstrue Kommentare einschließen (Reddit und Hacker News). false liefert nur den Post. Bei anderen URLs wirkungslos. Include comments (Reddit and Hacker News). false returns just the post. No effect on other URLs.
comment_depth3 Maximale Verschachtelungstiefe (1–10), für Reddit und Hacker News. Max nesting depth (1–10), for Reddit and Hacker News.
comment_limit Optionale Obergrenze für Top-Level-Kommentare (Reddit liefert standardmäßig ~200). Optional cap on top-level comments (Reddit returns ~200 by default).
frontmatterfalse YAML-Frontmatter mit Metadaten voranstellen. Prepend YAML frontmatter with metadata.
formatmd text = Markdown-Formatierung entfernen, Plaintext zurückgeben. json = strukturiert mit Metadaten. text = strip Markdown, return plain text. json = structured with metadata.
nocachefalse 1-h-Cache umgehen, immer frisch holen. Bypass the 1-hour cache, always refetch.
renderauto force = immer im Headless-Browser rendern, skip = nie. Umgeht den Cache. force = always render in the headless browser, skip = never. Bypasses the cache.
extractorauto Erzwingt readability, trafilatura oder playwright und überspringt die Qualitätsauswahl. Notausgang, wenn die Automatik bei einer Seite danebengreift. Umgeht den Cache; bei Reddit wirkungslos. Forces readability, trafilatura or playwright and skips the quality pick. An escape hatch for sites where the automatic choice is wrong. Bypasses the cache; ignored for Reddit.
pdf ocr schickt PDFs durch die hochwertige OCR-Stufe (saubere Tabellen). Braucht einen serverseitigen OCR-Key; ohne ihn bleibt es beim normalen Pfad. Umgeht den Cache. ocr routes PDFs through the high-quality OCR tier (clean tables). Needs a server-side OCR key; without one the normal path is used. Bypasses the cache.
yt_timecodeslinks YouTube-Transkript: links = klickbare Zeitstempel, plain = [MM:SS], none = nur Text. Umgeht den Cache. YouTube transcript: links = clickable timestamps, plain = [MM:SS], none = text only. Bypasses the cache.
yt_chunk30 Blockgröße des Transkripts in Sekunden; 0 behält die Original-Schnipsel. Umgeht den Cache. Transcript block size in seconds; 0 keeps the original snippets. Bypasses the cache.
langde Sprache des Kommentar-Headers (de oder en). Language for the comments header (de or en).
query Für eine konkrete Frage an eine lange Seite: Frage hier übergeben, zurück kommen nur die passenden Abschnitte statt der ganzen Seite, siehe Query-Extraktion. For a specific question about a long page: pass the question here and get back only the matching sections instead of the whole page, see Query extraction.
max_tokens600 Token-Budget für query (64–20000). Ohne query wirkungslos; höher setzen, wenn die Antwort über mehrere Abschnitte verteilt liegt. Wird nur geprüft, wenn query gesetzt ist; ein ungültiger Wert liefert 400. Token budget for query (64–20000). No effect without query; raise it when the answer likely spans several sections. Only validated when query is set; an invalid value returns 400.

Dieselben Parameter gelten für GET /api/stream. Alles, was das erwartete Ergebnis verändert (comment_depth, comment_limit, render, extractor, pdf=ocr, yt_*), umgeht den Cache, damit der neue Wert auch wirklich greift.

The same parameters apply to GET /api/stream. Anything that changes the expected result (comment_depth, comment_limit, render, extractor, pdf=ocr, yt_*) bypasses the cache so the new value actually takes effect.

Response-HeaderResponse headershttp

Jede Antwort von /api trägt mit, wie sie zustande kam. Für Agenten ist das der billigste Weg, die Qualität einer Extraktion zu beurteilen, ohne den Text zu analysieren.

Every /api response carries how it was produced. For agents that is the cheapest way to judge an extraction without analyzing the text.

Header Inhalt Contents
X-Source reddit · hackernews · cloudflare · readability · readability-fallback · trafilatura · playwright · recipe-content · coverage-guard · markitdown · youtube · image-caption · audio-transcript · pdf-ocr reddit · hackernews · cloudflare · readability · readability-fallback · trafilatura · playwright · recipe-content · coverage-guard · markitdown · youtube · image-caption · audio-transcript · pdf-ocr
X-Quality 0.01.0, Konfidenz der Extraktion. Niedrige Werte heißen dünner oder verrauschter Text. 0.01.0 extraction confidence. Low values mean thin or noisy output.
X-Share-Id 8-stellige Hex-ID, aufrufbar als /s/<id>. Fehlt bei /api/html und /api/file, weil lokale Dateien nicht gecacht werden. 8-hex id, openable as /s/<id>. Absent on /api/html and /api/file, since local files are never cached.
X-Suggested-Filename Vorgeschlagener Dateiname für den Download, z. B. YT-vortrag-titel-dQw4w9WgXcQ.md. YouTube bekommt Titel plus Video-ID, Bilder, Audio und Dokumente den ursprünglichen Dateinamen, alles andere den Titel. Self-Hoster können mit PULLMD_FILENAME_DATE_PREFIX ein Datum voranstellen (Vorlage mit den Platzhaltern YYYY MM DD HH mm ss, z. B. YYYY-MM-DD-). Der Download-Button in der Web-Oberfläche speichert das Ergebnis unter genau diesem Namen. Bei /api/stream gibt es keine Header, dort steckt derselbe Wert als suggestedFilename im result-Event. Suggested download filename, e.g. YT-talk-title-dQw4w9WgXcQ.md. YouTube gets title plus video id, images, audio and documents keep their original file name, everything else uses the title. Self-hosters can prepend a date with PULLMD_FILENAME_DATE_PREFIX (a template with the tokens YYYY MM DD HH mm ss, e.g. YYYY-MM-DD-). The download button in the web UI saves the result under exactly this name. /api/stream has no headers, so there the same value travels as suggestedFilename in the result event.
X-Transcript-Status Nur YouTube: ok · none · blocked · error. blocked (Rate-Limit, HTTP 429) und error sind vorübergehend und werden nicht gecacht - später erneut versuchen. none heißt, das Video hat wirklich kein Transkript. YouTube only: ok · none · blocked · error. blocked (rate limit, HTTP 429) and error are transient and not cached - retry later. none means the video genuinely has no transcript.
X-Extracted true/false. Nur vorhanden, wenn query aktiv ist. true/false. Present only when query is active.
X-Extract-Confidence high · medium · low. Fehlt, wenn die Seite ohnehin klein genug war und die Extraktion übersprungen wurde. high · medium · low. Absent when the page was small enough that extraction was skipped.
X-Extract-Sections Anzahl der zurückgelieferten Abschnitte. Number of returned sections.
X-Extract-Original-Tokens
X-Extract-Returned-Tokens
Geschätzte Tokenzahl (Zeichen / 4) der ganzen Seite und der Antwort. Die Differenz ist die Ersparnis. Estimated token count (chars / 4) of the full page and of the response. The difference is what you saved.

MCP-Antworten haben keine Header. Dort landen dieselben Angaben im Frontmatter bzw. bei format=json im extract-Objekt.

MCP responses have no headers. There the same data lands in the frontmatter, or in the extract object with format=json.

Query-ExtraktionQuery extraction?query=

Wer eine konkrete Frage an eine lange Seite hat, braucht selten die ganze Seite. ?query= nimmt die Frage in natürlicher Sprache entgegen und liefert nur die passenden Abschnitte - auf langen Seiten typischerweise 70-95 % weniger Tokens. Die ganze Seite lohnt sich dann noch, wenn man sie wirklich komplett braucht: zusammenfassen, übersetzen, archivieren.

When you have a specific question about a long page, you rarely need the whole page. ?query= takes that question in natural language and returns only the matching sections - typically 70-95% fewer tokens on long pages. Fetching the full page still makes sense when you genuinely need all of it: summarizing, translating, archiving.

example
curl -s "https://markdown.invisibletheaters.com}/api?url=https://example.com/long-article&query=how+does+caching+work&max_tokens=800"

Mit ?frontmatter=true kommen extracted, extract_confidence, sections_selected, original_tokens und returned_tokens dazu; format=json liefert dieselben Werte im extract-Objekt.

With ?frontmatter=true you also get extracted, extract_confidence, sections_selected, original_tokens and returned_tokens; format=json returns the same values in the extract object.

Dateien konvertierenConverting filesPOST /api/html · /api/file

Nicht alles steht als abrufbare URL im Netz. Zwei Upload-Wege decken den Rest ab. Beide landen aus Datenschutzgründen nicht im Cache - kein History-Eintrag, kein Share-Link, keine X-Share-Id.

Not everything lives at a fetchable URL. Two upload paths cover the rest. For privacy, neither is ever cached - no history entry, no share link, no X-Share-Id.

Gespeicherte HTML-Seiten

Saved HTML pages

Bereits gespeicherte Seiten ("Seite speichern unter", SingleFile-Exports) lassen sich direkt konvertieren: die .html-Datei auf die PullMD-Oberfläche ziehen (Desktop) oder den gestrichelten Hinweis unter dem URL-Feld antippen, um eine Datei zu wählen (Desktop + Mobile) - oder per API:

Already-saved pages ("Save Page As", SingleFile exports) can be converted directly: drag-and-drop the .html file onto the PullMD UI (desktop) or tap the dashed hint below the URL field to pick a file (desktop and mobile) - or via the API:

post · html
curl -s -X POST --data-binary @page.html \
  -H 'Content-Type: text/html' \
  -H 'X-Filename: page.html' \
  "https://markdown.invisibletheaters.com}/api/html"

Optionale Parameter: url=… (Original-URL - aktiviert Site-Recipes und den verlinkten Header), format=json|text, frontmatter=true, extractor=readability|trafilatura. Statt des Headers X-Filename (URI-encodiert) geht auch ?filename=; der Header hält den Dateinamen aus Access-Logs heraus. Maximal 10 MB. Findet die Extraktion so gut wie keinen Inhalt - typisch für eine gespeicherte JavaScript-App-Hülle - kommt 422 mit der Bitte, stattdessen die Original-URL zu schicken.

Optional parameters: url=… (original URL - enables site recipes and the linked header), format=json|text, frontmatter=true, extractor=readability|trafilatura. Instead of the X-Filename header (URI-encoded) you can use ?filename=; the header keeps the file name out of access logs. Max 10 MB. If extraction finds almost no content - typical for a saved JavaScript app shell - you get a 422 asking for the original URL instead.

Dokumente

Documents

PDF, Word, PowerPoint, Excel, EPUB, ZIP (Inhalt wird aufgelistet), CSV, JSON und XML werden zu Markdown konvertiert. Liegt das Dokument im Netz, reicht die normale API - der Dokumenttyp wird am Content-Type erkannt:

PDF, Word, PowerPoint, Excel, EPUB, ZIP (contents listed), CSV, JSON and XML are converted to Markdown. If the document is online, the regular API is enough - the type is detected from the content type:

get · document url
curl -s "https://markdown.invisibletheaters.com}/api?url=https://example.com/report.pdf"

Lokale Dateien gehen per Upload - rohe Bytes im Body, Content-Type der Datei, maximal 25 MB. In der Web-Oberfläche funktioniert dafür derselbe Drag-and-drop bzw. die Dateiauswahl:

Local files go through the upload path - raw bytes in the body, the file's content type, max 25 MB. In the web UI the same drag-and-drop and file picker handle it:

post · file
curl -s -X POST --data-binary @report.pdf \
  -H 'Content-Type: application/pdf' \
  -H 'X-Filename: report.pdf' \
  "https://markdown.invisibletheaters.com}/api/file"

Optionale Parameter: format=json|text, frontmatter=true, pdf=ocr. Die Dokument-Konvertierung braucht den markitdown-Sidecar; ist er nicht konfiguriert, antworten Dokument-URLs und /api/file mit 502. Ob diese Instanz ihn hat, verrät /api/config.

Optional parameters: format=json|text, frontmatter=true, pdf=ocr. Document conversion needs the markitdown sidecar; without it, document URLs and /api/file return 502. Whether this instance has it is reported by /api/config.

Bilder, Audio, YouTube

Images, audio, YouTube

Bilder und Audio funktionieren über denselben Weg (URL oder Upload). Standardmäßig kommen nur Metadaten zurück - EXIF beim Bild, Track-Infos beim Audio. Hat die Instanz ein Vision- bzw. Speech-to-Text-Modell hinterlegt, kommt eine Bildbeschreibung bzw. ein Transkript dazu; die verwendeten Modelle und der Tokenverbrauch stehen dann im Frontmatter. YouTube-URLs liefern Titel, Beschreibung und Transkript, wenn die Instanz das aktiviert hat.

Images and audio work the same way (URL or upload). By default you only get metadata - EXIF for images, track info for audio. If the instance has a vision or speech-to-text model configured, you also get a caption or a transcript; the models used and the token spend then show up in the frontmatter. YouTube URLs return title, description and transcript when the instance enables it.

Share-LinksShare links/s/:id

Jeder Pull bekommt eine 8-stellige Hex-ID als Share-Link, der den exakten Markdown-Stand zurückliefert. Die ID erscheint im Response-Header X-Share-Id und in der Share-Bar unter dem URL-Feld.

Every pull gets an 8-hex share ID returning the exact markdown snapshot. The ID surfaces in the response header X-Share-Id and in the share bar below the URL field.

FrontmatterFrontmatterYAML

Mit ?frontmatter=true wird vor dem Inhalt ein YAML-Block mit Metadaten eingefügt. Felder mit leerem Wert werden weggelassen:

With ?frontmatter=true a YAML metadata block is prepended to the content. Empty fields are omitted:

example
---
title: "Why I migrated my side-project from Postgres to SQLite"
url: https://news.ycombinator.com/item?id=42424242
source: readability
fetched: 2026-04-25T13:53:00Z
quality: 0.85
author: kentonv
published: 2026-04-24T18:42:00Z
description: "After two years on managed Postgres..."
language: en
share_id: a3f9c2b7
---

Basis-Felder: title, url, source, fetched, quality, author, published, modified, description, language, image, site, extractor_reason, share_id. Je nach Quelle zusätzlich: subreddit, upvotes (Reddit) · duration, views (YouTube) · image_size, audio_seconds, llm_model, llm_tokens, llm_prompt_tokens, llm_completion_tokens (Media) · pdf_pages (PDF-OCR). MCP-Antworten ergänzen share_url, cached, refreshed, age_ms. Mit PULLMD_FRONTMATTER_FIELDS lässt sich die Auswahl serverseitig einschränken.

Base fields: title, url, source, fetched, quality, author, published, modified, description, language, image, site, extractor_reason, share_id. Depending on the source, additionally: subreddit, upvotes (Reddit) · duration, views (YouTube) · image_size, audio_seconds, llm_model, llm_tokens, llm_prompt_tokens, llm_completion_tokens (media) · pdf_pages (PDF OCR). MCP responses add share_url, cached, refreshed, age_ms. PULLMD_FRONTMATTER_FIELDS can trim the selection server-side.

Bei aktiver Query-Extraktion kommen extracted, extract_confidence, sections_selected, original_tokens und returned_tokens dazu. Site-Recipes können weitere Felder beisteuern, etwa aus dem JSON-LD einer Seite.

With query extraction active, extracted, extract_confidence, sections_selected, original_tokens and returned_tokens are added. Site recipes can contribute further fields, e.g. from a page's JSON-LD.

In der Web-App schaltet der Frontmatter-Regler die Anzeige sofort um - der YAML-Block erscheint oder verschwindet, ohne dass man erneut auf Pull drücken muss.

In the web app the Frontmatter toggle switches the view instantly - the YAML block appears or disappears with no second Pull.

Site-RecipesSite recipesjson

Manche Seiten brauchen Nachhilfe: eine Paywall-Hülle, ein Cookie-Banner mitten im Text, ein Layout, in dem die Automatik den falschen Block für den Artikel hält. Site-Recipes sind kleine JSON-Einträge pro Host, die genau das geradebiegen.

Some sites need a nudge: a paywall wrapper, a cookie banner in the middle of the text, a layout where the automatic pick grabs the wrong block. Site recipes are small per-host JSON entries that straighten exactly that out.

Ein paar Recipes werden mitgeliefert. Selbst-Hoster legen eigene unter data/site-recipes.json ab oder setzen PULLMD_SITE_RECIPES auf einen Pfad; nach Änderungen muss die Instanz neu starten. Was geladen wurde und was abgelehnt, zeigt GET /api/recipes/status. Wie man ein Recipe schreibt (und beisteuert), steht in SITE-RECIPES.md.

A few recipes ship with PullMD. Self-hosters add their own in data/site-recipes.json or point PULLMD_SITE_RECIPES at a path; the instance needs a restart after changes. GET /api/recipes/status reports what loaded and what was rejected. How to write (and contribute) one is documented in SITE-RECIPES.md.

AuthentifizierungAuthenticationoptional

Standardmäßig ist eine PullMD-Instanz offen. Self-Hoster können das umstellen; welcher Modus hier läuft, steht in /api/config unter authMode.

By default a PullMD instance is open. Self-hosters can change that; the mode in use here is reported by /api/config as authMode.

Modus Bedeutung Meaning
disabled Default. Kein Login, alles offen. Default. No login, everything open.
single-admin Ein Benutzer aus den Server-Einstellungen, keine Selbstregistrierung. Für den Heimserver. One user from the server settings, no self-signup. For a homelab.
multi-user Registrierung unter /signup, Login unter /login, getrennte Daten pro Benutzer. Die Registrierung lässt sich mit PULLMD_ALLOW_SIGNUP=false schließen. Sign up at /signup, log in at /login, per-user data isolation. Self-signup can be closed with PULLMD_ALLOW_SIGNUP=false.

Ist ein Modus aktiv, brauchen /api, /api/stream, die Upload-Endpunkte, /mcp sowie History und Archiv Zugangsdaten. Öffentlich bleiben die Startseite, diese Hilfeseite, das Skill-Zip und vor allem die Share-Links unter /s/:id - geteilte Inhalte bleiben also teilbar.

With a mode active, /api, /api/stream, the upload endpoints, /mcp, history and archive all need credentials. The start page, this help page, the skill zip and above all the share links at /s/:id stay public - shared content stays shareable.

Drei Wege hinein

Three ways in

api key · curl + mcp
curl -s -H "Authorization: Bearer pmd_YOURKEY" \
  "https://markdown.invisibletheaters.com}/api?url=https://example.com"

claude mcp add --transport http pullmd https://markdown.invisibletheaters.com}/mcp \
  --header "Authorization: Bearer pmd_YOURKEY"

Fehlen die Zugangsdaten, antwortet die API mit 401; im Browser wird stattdessen zur Login-Seite weitergeleitet und danach zurück zur ursprünglichen Seite.

Without credentials the API answers 401; in the browser you are redirected to the login page and back afterwards.

Benutzerverwaltung

Managing users

Konten verwaltet der Betreiber auf der Kommandozeile der Instanz; eine Oberfläche dafür gibt es nicht. In single-admin stammt der eine Benutzer aus den Server-Einstellungen, in multi-user registrieren sich Benutzer selbst - es sei denn, PULLMD_ALLOW_SIGNUP steht auf false. Dann ist create-user der Weg, trotzdem Konten anzulegen.

Accounts are managed by the operator on the instance's command line; there is no UI for it. In single-admin the single user comes from the server settings, in multi-user users sign up themselves - unless PULLMD_ALLOW_SIGNUP is false. In that case create-user is how accounts still get created.

admin cli
docker compose exec pullmd node scripts/admin.js list-users
docker compose exec pullmd node scripts/admin.js create-user someone@example.com
docker compose exec pullmd node scripts/admin.js make-admin someone@example.com
docker compose exec pullmd node scripts/admin.js reset-password someone@example.com

Ohne Docker läuft derselbe Befehl als node scripts/admin.js … im Verzeichnis der Instanz. Wichtig: create-user und reset-password lesen das Passwort von der Standardeingabe, es braucht also docker compose exec oder docker exec -it. Ein docker exec ohne -i hängt die Eingabe nicht an; der Befehl bricht dann mit einer Meldung und Code 2 ab und nennt die funktionierenden Aufrufe. Alternativ lässt sich das Passwort hineinpipen: echo "…" | docker exec -i <container> node scripts/admin.js ….

Without Docker the same command runs as node scripts/admin.js … in the instance's directory. Important: create-user and reset-password read the password from standard input, so use docker compose exec or docker exec -it. A plain docker exec without -i does not attach input; the command then aborts with a message and exit code 2 that names the invocations which do work. You can also pipe the password in: echo "…" | docker exec -i <container> node scripts/admin.js ….

Client-ErkennungClient detectionhistory

Jeder Eintrag in der History kriegt ein Client-Badge — daran sieht man, woher der Pull kam:

Every history entry gets a client badge so you can see where the pull originated: