API-Dokumentation

Version 1 • Basis-URL: https://ls-tipps.de/api/v1

Format

Alle Anfragen und Antworten verwenden JSON. Sende immer den Header Accept: application/json.

Authentifizierung

Alle Endpunkte erfordern einen Bearer-Token im Authorization-Header.


Authentifizierung

API-Keys werden im Admin-Bereich generiert. Füge den Token bei jeder Anfrage als Bearer-Token im HTTP-Header ein:

Authorization: Bearer DEIN_API_KEY
Accept: application/json
Hinweis: API-Keys werden nur einmal beim Erstellen angezeigt. Bewahre ihn sicher auf und gib ihn nicht weiter.

Fehlerbehandlung

Die API gibt standardisierte HTTP-Statuscodes zurück:

StatusBedeutung
200 OKAnfrage erfolgreich
201 CreatedRessource wurde erstellt
401 UnauthorizedKein oder ungültiger API-Key
422 UnprocessableValidierungsfehler – prüfe den errors-Schlüssel im Body
500 Server ErrorInterner Fehler auf dem Server

Beispiel einer Fehlerantwort (422):

{
  "message": "The title field is required.",
  "errors": {
    "title": ["The title field is required."],
    "category_id": ["The selected category id is invalid."]
  }
}

GET

/api/v1/categories

Gibt alle verfügbaren Kategorien zurück. Wird benötigt, um beim Erstellen von Beiträgen die korrekte category_id zu ermitteln.

Anfrage-Header
Authorization: Bearer DEIN_API_KEY
Accept: application/json
Beispielantwort
[
  {
    "id": 1,
    "name": "Farming Basics",
    "slug": "farming-basics",
    "description": "Grundlagen für Einsteiger",
    "created_at": "2026-04-09T10:00:00.000000Z",
    "updated_at": "2026-04-09T10:00:00.000000Z"
  }
]
Codebeispiele
curl -X GET https://ls-tipps.de/api/v1/categories \
  -H "Authorization: Bearer DEIN_API_KEY" \
  -H "Accept: application/json"
import requests

headers = {
    "Authorization": "Bearer DEIN_API_KEY",
    "Accept": "application/json",
}

response = requests.get("https://ls-tipps.de/api/v1/categories", headers=headers)
categories = response.json()

for cat in categories:
    print(f"{cat['id']}: {cat['name']}")
<?php

$ch = curl_init("https://ls-tipps.de/api/v1/categories");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer DEIN_API_KEY",
        "Accept: application/json",
    ],
]);

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

foreach ($response as $category) {
    echo $category["id"] . ": " . $category["name"] . "\n";
}
const response = await fetch("https://ls-tipps.de/api/v1/categories", {
  headers: {
    "Authorization": "Bearer DEIN_API_KEY",
    "Accept": "application/json",
  },
});

const categories = await response.json();
console.log(categories);

GET

/api/v1/posts

Gibt alle freigegebenen Beiträge paginiert zurück (20 pro Seite), sortiert nach Veröffentlichungsdatum (neueste zuerst).

Query-Parameter
ParameterTypStandardBeschreibung
pageinteger1Seitennummer für die Paginierung
Beispielantwort
{
  "current_page": 1,
  "data": [
    {
      "id": 42,
      "title": "Effizientes Pflügen im LS25",
      "slug": "effizientes-pfluegen-im-ls25",
      "excerpt": "Mit dem richtigen Pflug und der passenden Einstellung...",
      "category_id": 1,
      "tags": ["pflügen", "boden", "ls25"],
      "status": "approved",
      "ai_model": "claude-sonnet-4-5",
      "published_at": "2026-04-10T08:30:00.000000Z",
      "category": {
        "id": 1,
        "name": "Farming Basics",
        "slug": "farming-basics"
      }
    }
  ],
  "per_page": 20,
  "total": 93,
  "last_page": 5,
  "next_page_url": "https://ls-tipps.de/api/v1/posts?page=2"
}
Codebeispiele
curl -X GET "https://ls-tipps.de/api/v1/posts?page=1" \
  -H "Authorization: Bearer DEIN_API_KEY" \
  -H "Accept: application/json"
import requests

headers = {
    "Authorization": "Bearer DEIN_API_KEY",
    "Accept": "application/json",
}

page = 1
while True:
    r = requests.get(
        "https://ls-tipps.de/api/v1/posts",
        headers=headers,
        params={"page": page},
    )
    data = r.json()
    for post in data["data"]:
        print(post["title"])

    if not data["next_page_url"]:
        break
    page += 1
<?php

$page = 1;
do {
    $ch = curl_init("https://ls-tipps.de/api/v1/posts?page={$page}");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            "Authorization: Bearer DEIN_API_KEY",
            "Accept: application/json",
        ],
    ]);
    $data = json_decode(curl_exec($ch), true);
    curl_close($ch);

    foreach ($data["data"] as $post) {
        echo $post["title"] . "\n";
    }
    $page++;
} while ($data["next_page_url"]);
async function getAllPosts() {
  let page = 1;
  let allPosts = [];

  while (true) {
    const res = await fetch(`https://ls-tipps.de/api/v1/posts?page=${page}`, {
      headers: {
        "Authorization": "Bearer DEIN_API_KEY",
        "Accept": "application/json",
      },
    });
    const data = await res.json();
    allPosts = allPosts.concat(data.data);

    if (!data.next_page_url) break;
    page++;
  }
  return allPosts;
}

POST

/api/v1/posts

Erstellt einen neuen Beitrag mit Status pending. Der Beitrag landet im Review-Queue und wird von einem Admin freigegeben. Eine Telegram-Benachrichtigung wird automatisch versendet.

Request-Body (JSON)
FeldTypPflichtBeschreibung
titlestringJaTitel des Beitrags (max. 255 Zeichen)
excerptstringJaKurzzusammenfassung für Vorschaukarten
bodystringJaVollständiger Inhalt als HTML
category_idintegerJaID einer existierenden Kategorie
tagsarrayNeinListe von Tags als String-Array
ai_modelstringNeinName des verwendeten KI-Modells (max. 100 Zeichen)
Beispiel-Request
{
  "title": "Effizientes Pflügen im LS25",
  "excerpt": "Mit dem richtigen Pflug und der passenden Einstellung sparst du Zeit und Kraftstoff.",
  "body": "<h2>Einleitung</h2><p>Der Pflug ist eines der wichtigsten Werkzeuge...</p>",
  "category_id": 1,
  "tags": ["pflügen", "boden", "ls25", "tipps"],
  "ai_model": "claude-sonnet-4-5"
}
Beispielantwort 201 Created
{
  "id": 42,
  "title": "Effizientes Pflügen im LS25",
  "slug": null,
  "excerpt": "Mit dem richtigen Pflug...",
  "status": "pending",
  "source": "api",
  "ai_model": "claude-sonnet-4-5",
  "approved_by": null,
  "approved_at": null,
  "published_at": null,
  "created_at": "2026-04-12T14:22:00.000000Z"
}
Codebeispiele
curl -X POST https://ls-tipps.de/api/v1/posts \
  -H "Authorization: Bearer DEIN_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Effizientes Pflügen im LS25",
    "excerpt": "Mit dem richtigen Pflug sparst du Zeit.",
    "body": "<h2>Einleitung</h2><p>...</p>",
    "category_id": 1,
    "tags": ["pflügen", "boden"],
    "ai_model": "claude-sonnet-4-5"
  }'
import requests

headers = {
    "Authorization": "Bearer DEIN_API_KEY",
    "Accept": "application/json",
}

payload = {
    "title": "Effizientes Pflügen im LS25",
    "excerpt": "Mit dem richtigen Pflug sparst du Zeit und Kraftstoff.",
    "body": "<h2>Einleitung</h2><p>Der Pflug ist eines der wichtigsten Werkzeuge...</p>",
    "category_id": 1,
    "tags": ["pflügen", "boden", "ls25"],
    "ai_model": "claude-sonnet-4-5",
}

response = requests.post(
    "https://ls-tipps.de/api/v1/posts",
    headers=headers,
    json=payload,
)

if response.status_code == 201:
    post = response.json()
    print(f"Erstellt: ID {post['id']} – {post['title']}")
else:
    print("Fehler:", response.json())
<?php

$payload = json_encode([
    "title"       => "Effizientes Pflügen im LS25",
    "excerpt"     => "Mit dem richtigen Pflug sparst du Zeit und Kraftstoff.",
    "body"        => "<h2>Einleitung</h2><p>Der Pflug ist eines der wichtigsten Werkzeuge...</p>",
    "category_id" => 1,
    "tags"        => ["pflügen", "boden", "ls25"],
    "ai_model"    => "claude-sonnet-4-5",
]);

$ch = curl_init("https://ls-tipps.de/api/v1/posts");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $payload,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer DEIN_API_KEY",
        "Accept: application/json",
        "Content-Type: application/json",
    ],
]);

$response = json_decode(curl_exec($ch), true);
$status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status === 201) {
    echo "Erstellt: ID " . $response["id"] . " – " . $response["title"] . "\n";
} else {
    print_r($response["errors"]);
}
const response = await fetch("https://ls-tipps.de/api/v1/posts", {
  method: "POST",
  headers: {
    "Authorization": "Bearer DEIN_API_KEY",
    "Accept": "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Effizientes Pflügen im LS25",
    excerpt: "Mit dem richtigen Pflug sparst du Zeit und Kraftstoff.",
    body: "<h2>Einleitung</h2><p>Der Pflug ist eines der wichtigsten Werkzeuge...</p>",
    category_id: 1,
    tags: ["pflügen", "boden", "ls25"],
    ai_model: "claude-sonnet-4-5",
  }),
});

if (response.status === 201) {
  const post = await response.json();
  console.log(`Erstellt: ID ${post.id} – ${post.title}`);
} else {
  const error = await response.json();
  console.error("Validierungsfehler:", error.errors);
}

POST

/api/v1/posts/html-import

Importiert eine HTML-Datei als neuen Beitrag. Titel wird automatisch aus dem <title>-Tag extrahiert, der Excerpt aus den ersten 300 Zeichen. Der Beitrag landet ebenfalls im Review-Queue.

Dieser Endpunkt erwartet multipart/form-data, nicht JSON. Die Datei darf maximal 10 MB groß sein.
Request-Parameter (multipart/form-data)
FeldTypPflichtBeschreibung
filefileJaHTML-Datei (.html oder .htm, max. 10 MB)
category_idintegerJaID einer existierenden Kategorie
ai_modelstringNeinName des verwendeten KI-Modells
Beispielantwort 201 Created
{
  "id": 43,
  "title": "Mein importierter Beitrag",
  "excerpt": "Dies ist der automatisch extrahierte Anfangstext...",
  "status": "pending",
  "source": "html_import",
  "ai_model": "claude-opus-4-6",
  "created_at": "2026-04-12T15:00:00.000000Z"
}
Codebeispiele
curl -X POST https://ls-tipps.de/api/v1/posts/html-import \
  -H "Authorization: Bearer DEIN_API_KEY" \
  -H "Accept: application/json" \
  -F "file=@/pfad/zu/beitrag.html" \
  -F "category_id=1" \
  -F "ai_model=claude-opus-4-6"
import requests

headers = {
    "Authorization": "Bearer DEIN_API_KEY",
    "Accept": "application/json",
}

with open("/pfad/zu/beitrag.html", "rb") as f:
    response = requests.post(
        "https://ls-tipps.de/api/v1/posts/html-import",
        headers=headers,
        data={
            "category_id": 1,
            "ai_model": "claude-opus-4-6",
        },
        files={"file": ("beitrag.html", f, "text/html")},
    )

if response.status_code == 201:
    print("Importiert:", response.json()["title"])
else:
    print("Fehler:", response.json())
<?php

$ch = curl_init("https://ls-tipps.de/api/v1/posts/html-import");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => [
        "file"        => new CURLFile("/pfad/zu/beitrag.html", "text/html", "beitrag.html"),
        "category_id" => 1,
        "ai_model"    => "claude-opus-4-6",
    ],
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer DEIN_API_KEY",
        "Accept: application/json",
    ],
]);

$response = json_decode(curl_exec($ch), true);
$status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status === 201
    ? "Importiert: " . $response["title"] . "\n"
    : "Fehler: " . print_r($response["errors"], true);
// Node.js (mit FormData aus dem "form-data" Paket)
import FormData from "form-data";
import fs from "fs";
import fetch from "node-fetch";

const form = new FormData();
form.append("file", fs.createReadStream("/pfad/zu/beitrag.html"), "beitrag.html");
form.append("category_id", "1");
form.append("ai_model", "claude-opus-4-6");

const response = await fetch("https://ls-tipps.de/api/v1/posts/html-import", {
  method: "POST",
  headers: {
    "Authorization": "Bearer DEIN_API_KEY",
    "Accept": "application/json",
    ...form.getHeaders(),
  },
  body: form,
});

const result = await response.json();
console.log(response.status === 201 ? "Importiert: " + result.title : result);

POST

/api/v1/images

Lädt ein Bild hoch und speichert es auf dem Hetzner Object Storage (S3-kompatibel). Die zurückgegebene URL kann direkt als featured_image beim Erstellen von Beiträgen verwendet werden.

Dieser Endpunkt erwartet multipart/form-data. Unterstützte Formate: jpg, jpeg, png, gif, webp. Maximale Dateigröße: 10 MB.
Request-Parameter (multipart/form-data)
FeldTypPflichtBeschreibung
image file Ja Bilddatei (jpg, png, gif, webp, max. 10 MB)
Beispielantwort 201 Created
{
  "url":  "https://your-bucket.fsn1.your-objectstorage.com/images/2026/04/550e8400-e29b-41d4-a716-446655440000.jpg",
  "path": "images/2026/04/550e8400-e29b-41d4-a716-446655440000.jpg",
  "size": 204800,
  "mime": "image/jpeg"
}
Tipp: Bild direkt als featured_image verwenden
{
  "title": "Mein Beitrag",
  "featured_image": "https://your-bucket.fsn1.your-objectstorage.com/images/2026/04/550e8400.jpg",
  "..."  : "..."
}
Codebeispiele
curl -X POST https://ls-tipps.de/api/v1/images \
  -H "Authorization: Bearer DEIN_API_KEY" \
  -H "Accept: application/json" \
  -F "image=@/pfad/zu/bild.jpg"
import requests

headers = {
    "Authorization": "Bearer DEIN_API_KEY",
    "Accept": "application/json",
}

with open("/pfad/zu/bild.jpg", "rb") as f:
    response = requests.post(
        "https://ls-tipps.de/api/v1/images",
        headers=headers,
        files={"image": ("bild.jpg", f, "image/jpeg")},
    )

if response.status_code == 201:
    data = response.json()
    print("URL:", data["url"])
else:
    print("Fehler:", response.json())
<?php

$ch = curl_init("https://ls-tipps.de/api/v1/images");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => [
        "image" => new CURLFile("/pfad/zu/bild.jpg", "image/jpeg", "bild.jpg"),
    ],
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer DEIN_API_KEY",
        "Accept: application/json",
    ],
]);

$response = json_decode(curl_exec($ch), true);
$status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status === 201) {
    echo "Hochgeladen: " . $response["url"] . "\n";
} else {
    print_r($response["errors"]);
}
// Browser
const input = document.querySelector('input[type="file"]');
const formData = new FormData();
formData.append("image", input.files[0]);

const response = await fetch("https://ls-tipps.de/api/v1/images", {
  method: "POST",
  headers: {
    "Authorization": "Bearer DEIN_API_KEY",
    "Accept": "application/json",
  },
  body: formData,
});

if (response.status === 201) {
  const data = await response.json();
  console.log("URL:", data.url);
} else {
  const error = await response.json();
  console.error("Fehler:", error.errors);
}