Workflows, Mu-Plugin, Feldnachweis und Doku

This commit is contained in:
2026-09-20 12:09:25 +02:00
parent 13688a621d
commit 095e72e95a

230
docs/feldnachweis.md Normal file
View File

@@ -0,0 +1,230 @@
# Feldnachweis: live ermittelte Feldnamen
Alle Angaben stammen aus echten API-Antworten vom 20.09.2026, nicht aus Dokumentation.
Rohbelege liegen als `docs/beleg-*.json` daneben.
---
## 1. NeuronWriter
Basis-URL `https://app.neuronwriter.com/neuron-api/0.5/writer`, Header `X-API-KEY`.
### 1.1 POST /list-projects
Aufruf mit leerem Body `{}`.
```json
[
{"id":"3da4d745e0e223cb","project":"3da4d745e0e223cb","name":"skrift.de","language":"German","engine":"google.de"}
]
```
Verwendet: `project` = `3da4d745e0e223cb`.
Anmerkung: `id` und `project` sind wertgleich, die Doku nennt nur `project`.
### 1.2 POST /new-query
Request: `{"project":"...","keyword":"...","engine":"google.de","language":"German"}`
Antwort (Beleg: `beleg-01-new-query.json`):
```json
{
"query": "565abcca58f65e52",
"query_url": "https://app.neuronwriter.com/analysis/view/565abcca58f65e52",
"share_url": "https://app.neuronwriter.com/analysis/share/565abcca58f65e52/d8418...",
"readonly_url": "https://app.neuronwriter.com/analysis/content-preview/565abcca58f65e52/060df..."
}
```
**Abweichung vom Auftrag:** Das Feld heisst `query`, nicht `query_id`.
Es ist gleichzeitig Request-Parameter aller Folgeaufrufe.
### 1.3 POST /get-query
Request: `{"query":"565abcca58f65e52"}`
Statuswerte real beobachtet: `in progress` -> `ready`.
Die Analyse war nach 7 Polls a 20 Sekunden fertig, also nach rund 2 Minuten.
Das konfigurierte Limit von 30 Versuchen entspricht 10 Minuten und ist reichlich.
Top-Level-Schluessel der fertigen Antwort (42 KB):
```
ideas, competitors, metrics, terms, terms_txt, status, keyword,
language, engine, project, query, serp_summary, query_url, share_url, readonly_url
```
#### Mapping auf die im Auftrag geforderten Felder
| Auftrag | Tatsaechlicher Pfad | Struktur |
|---|---|---|
| `query_id` | `query` | String |
| `keyword` | `keyword` | String |
| `begriffe_pflicht` | `terms.content_basic` | `[{t, usage_pc, sugg_usage:[min,max]}]` |
| `begriffe_erweitert` | `terms.content_extended` | `[{t, usage_pc, sugg_usage:[min,max]}]` |
| `fragen` | `ideas.topic_matrix` (Objekt!) + `ideas.people_also_ask` + `ideas.content_questions` + `ideas.suggest_questions` | siehe unten |
| `wortzahl_ziel` | `metrics.word_count.target` | Integer (hier 1178) |
#### Details, die man nicht raten kann
`terms` enthaelt genau diese Schluessel:
`title`, `desc`, `h1`, `h2`, `content_basic`, `content_extended`, `entities`.
Es gibt **kein** `terms.content`. Die Doku suggeriert das.
Beispiel-Eintraege:
```json
terms.content_basic[0] = {"t":"handschriftlich","usage_pc":70,"sugg_usage":[1,17]}
terms.content_extended[0] = {"t":"handgeschriebene briefe","usage_pc":40,"sugg_usage":[1,5]}
terms.h2[0] = {"t":"handschriftlich","usage_pc":50} // kein sugg_usage
terms.entities[0] = {"t":"Brief","importance":33.89,"relevance":0.53,
"confidence":1.70,"links":[["wikipedia","http://de.wikipedia.org/wiki/Brief"]]}
```
`sugg_usage` ist ein Array `[min, max]`, keine Zahl.
`usage_pc` ist der Anteil der Wettbewerber, die den Begriff nutzen, keine Empfehlung.
`terms_txt` spiegelt dasselbe als Fliesstext. Schluesselnamen sind hier **nicht identisch**:
`title`, `desc_title` (nicht `desc`), `h1`, `h2`, `content_basic`,
`content_basic_w_ranges`, `content_extended`, `content_extended_w_ranges`, `entities`.
Die `_w_ranges`-Varianten enthalten die Nutzungsspanne direkt im Text
(`handschriftlich: 1-17x`) und sind als Schreibvorlage am brauchbarsten.
`ideas` hat vier Schluessel, die sich strukturell unterscheiden:
```json
ideas.suggest_questions = [] // war im Test LEER
ideas.people_also_ask = [{"q":"..."}] // 4 Eintraege
ideas.content_questions = [{"q":"..."}] // 28 Eintraege
ideas.topic_matrix = {"Frage als Schluessel": {"importance": 10}} // OBJEKT, 10 Eintraege
```
`topic_matrix` ist kein Array. Die Frage steht im Schluessel, die Gewichtung im Wert.
In der Doku taucht `topic_matrix` gar nicht auf, obwohl es die wertvollste Quelle ist:
die Fragen sind bereits nach Wichtigkeit 1 bis 10 gewichtet.
Der Workflow sortiert danach und dedupliziert gegen die anderen drei Quellen.
`metrics` hat nur `word_count` und `readability`, beide mit `median` und `target`.
Im Test waren `median` und `target` identisch (1178 bzw. 29).
`competitors` liefert 31 Eintraege mit
`rank, url, title, desc, headers, content_score, readability, word_count, content_len`.
`headers` ist ein Array von Zweier-Arrays: `["h2","Text der Ueberschrift"]`.
Der Workflow uebernimmt die Gliederungen der Top 5 in den Brief.
`serp_summary` ist in der Doku nicht erwaehnt und enthaelt
`top_intent` (hier `informational`), `intent_stats`, `top_content_type` (hier `video`)
und `content_type_stats` in Prozent.
### 1.4 POST /evaluate-content und /import-content
Request laut Doku: `query` plus **`html`** (nicht `content_html`), optional `title`, `description`.
Antwort: `{"status":"ok","content_score":<number>}`.
Wird in Workflow 2 live gegengeprueft.
---
## 2. n8n
- Version `1.120.4`, ermittelt ueber `GET /rest/settings` -> `data.versionCli`
- Public API unter `/api/v1` mit Header `X-N8N-API-KEY` funktioniert
- `/rest/node-types` und `/types/nodes.json` sind nicht ohne Session-Login erreichbar
Verwendete Node-Versionen, verifiziert durch einen importierten und
erfolgreich ausgefuehrten Testworkflow:
| Node | typeVersion |
|---|---|
| `n8n-nodes-base.manualTrigger` | 1 |
| `n8n-nodes-base.scheduleTrigger` | 1.2 |
| `n8n-nodes-base.webhook` | 2.1 |
| `n8n-nodes-base.set` | 3.4 |
| `n8n-nodes-base.code` | 2 |
| `n8n-nodes-base.if` | 2.2 |
| `n8n-nodes-base.httpRequest` | 4.3 |
| `n8n-nodes-base.splitInBatches` | 3 |
| `n8n-nodes-base.wait` | 1.1 |
| `n8n-nodes-base.noOp` | 1 |
| `n8n-nodes-base.emailSend` | 2.1 |
Bei `splitInBatches` v3 ist **Ausgang 0 = done** und **Ausgang 1 = loop**.
Vertauschen fuehrt zu einem Workflow, der genau einmal laeuft und dann still endet.
**Gelernt beim Testlauf:** Ein Webhook mit `responseMode: "lastNode"` haelt die
HTTP-Verbindung bis zum Workflow-Ende offen. Bei Poll-Schleifen laeuft der
Reverse Proxy (openresty) nach 60 Sekunden in ein 504, obwohl der Workflow
weiterlaeuft. Beide Workflows nutzen deshalb `responseMode: "onReceived"`.
---
## 3. WordPress (skrift.de)
Benutzer `ai-agent`, Rolle `editor`, Authentifizierung per Application Password.
Geprueft ueber `GET /wp-json/wp/v2/users/me?context=edit`.
### 3.1 Registrierte Meta-Felder
Ermittelt ueber `OPTIONS /wp-json/wp/v2/posts`, Pfad
`endpoints[].args.meta.properties`. 43 Schluessel registriert.
**Vorhanden und direkt ueber `meta` beschreibbar:**
```
_seopress_titles_title (string)
_seopress_titles_desc (string)
_seopress_analysis_target_kw (string)
_seopress_robots_index (string)
_seopress_social_fb_title (string)
... sowie 38 weitere _seopress_* und _elementor_* Schluessel
```
**Nicht vorhanden:** `kurzantwort`, `faq`, `cta_ziel`, `stand_datum`.
### 3.2 JetEngine ist auf skrift.de nicht installiert
`GET /wp-json/` liefert diese Namespaces:
```
oembed/1.0, skrift/v1, elementor-one/v1, maspik/v1, elementor/v1,
elementor-pro/v1, seopress/v1, llar/v1, elementor-hello-elementor/v1,
jet-form-builder/v1, elementor/v1/documents, elementor-ai/v1,
elementor/v1/feedback, wp/v2, wp-site-health/v1, wp-block-editor/v1, wp-abilities/v1
```
`jet-form-builder/v1` ist JetFormBuilder, ein anderes Plugin. Es gibt keinen
JetEngine-Namespace und `GET /wp-json/wp/v2/types` zeigt keine JetEngine-CPTs.
Die im Auftrag genannten JetEngine-Felder existieren also nicht.
### 3.3 Cache-Endpunkt
Das Skrift-Plugin registriert nur `GET /skrift/v1/preis`, also keinen Cache-Purge.
WP Rocket ist nicht als Namespace sichtbar.
Nutzbar ist stattdessen ein echter, vorhandener Endpunkt:
```
POST /wp-json/seopress/v1/commands/clear-cache
```
### 3.4 Ausgangslage Inhalte
`GET /wp-json/wp/v2/posts?status=any` liefert 0 Beitraege.
`GET /wp-json/wp/v2/categories` liefert nur `uncategorized` (ID 1).
Workflow 2 muss Kategorien also anlegen koennen, nicht nur zuordnen.
---
## 4. Gitea
Version `1.25.4`, Repo `lucas.orth/Skrift-Auto-Article`, Default-Branch `main`, leer.
Der hinterlegte Token hat die Scopes `read:issue, read:repository`.
Schreibende Aufrufe scheitern mit:
```json
{"message":"token does not have at least one of required scope(s),
required=[write:repository], token scope=read:issue,read:repository"}
```
Benoetigt wird `write:repository`.