# Store API

{% hint style="info" %}
Diese Seite richtet sich an Entwickler:innen. Sie beschreibt die Store-API-Endpunkte des Plugins **PremsBlog** für Headless-Frontends und externe Integrationen.
{% endhint %}

### Allgemeines

* **Basis-URL:** `https://<shop>/store-api`
* **Authentifizierung:** via `sw-access-key`-Header des Verkaufskanals
* **Datenformat:** JSON mit `Content-Type: application/json`
* **Routen-Scope:** `store-api`
* **Sprachbehandlung:** Antworten respektieren den per `sw-language-id` gesetzten Sprachkontext. Übersetzbare Felder stehen zusätzlich im Block `translated`.

### Endpunkte im Überblick

| Methode      | Pfad                                               | Routen-Name                            |
| ------------ | -------------------------------------------------- | -------------------------------------- |
| `GET` `POST` | `/store-api/prems-blog/articles`                   | `store-api.prems-blog.article.list`    |
| `GET` `POST` | `/store-api/prems-blog/article/{articleId}`        | `store-api.prems-blog.article.detail`  |
| `GET` `POST` | `/store-api/prems-blog/authors`                    | `store-api.prems-blog.author.list`     |
| `GET` `POST` | `/store-api/prems-blog/author/{authorId}`          | `store-api.prems-blog.author.detail`   |
| `GET` `POST` | `/store-api/prems-blog/category/{categoryId}`      | `store-api.prems-blog.category.detail` |
| `GET` `POST` | `/store-api/prems-blog/tag/{tagId}`                | `store-api.prems-blog.tag.detail`      |
| `POST`       | `/store-api/prems-blog/article/{articleId}/review` | `store-api.prems-blog.review.save`     |

### Beiträge auflisten

`GET /store-api/prems-blog/articles`

Liefert eine paginierte Liste aller aktiven Beiträge im aktuellen Sichtbarkeitsfenster.

#### Parameter

| Name         | Typ      | In              | Beschreibung                                                                                                 |
| ------------ | -------- | --------------- | ------------------------------------------------------------------------------------------------------------ |
| `page`       | `int`    | Query oder Body | Seitenzahl. Standard ist `1`.                                                                                |
| `limit`      | `int`    | Query oder Body | Treffer pro Seite. Standard ist `12`. Das IAP-Feature `numberOfBlogPosts` kann den Wert nach oben begrenzen. |
| `categoryId` | `string` | Query oder Body | Filtert nach Blog-Kategorie.                                                                                 |
| `tagId`      | `string` | Query oder Body | Filtert nach Tag.                                                                                            |
| `authorId`   | `string` | Query oder Body | Filtert nach Autor:in.                                                                                       |
| `sort`       | `string` | Query oder Body | `displayDate` absteigend oder `title` aufsteigend.                                                           |

#### Filter

Die Route liefert nur Beiträge mit `status = 'active'`.

Zusätzlich muss das Sichtbarkeitsfenster erfüllt sein:

* `activeFrom IS NULL` oder `activeFrom <= NOW()`
* `activeTo IS NULL` oder `activeTo >= NOW()`

#### Assoziationen

`author`, `categories`, `tags`, `previewMedia`

#### Response

```json
{
  "apiAlias": "blog_article_list_route_response",
  "elements": [
    {
      "id": "…",
      "title": "…",
      "translated": {
        "title": "…",
        "shortDescription": "…"
      },
      "displayDate": "2026-05-12T08:00:00+00:00",
      "author": { "…": "…" },
      "categories": [{ "…": "…" }],
      "tags": [{ "…": "…" }],
      "previewMedia": { "url": "…" }
    }
  ],
  "total": 42,
  "aggregations": []
}
```

#### Beispiel

```bash
curl -X GET \
  "https://<shop>/store-api/prems-blog/articles?page=1&limit=10&sort=displayDate" \
  -H "sw-access-key: <SALES_CHANNEL_ACCESS_KEY>"
```

### Beitragsdetail

`GET /store-api/prems-blog/article/{articleId}`

Liefert einen einzelnen Beitrag mit allen relevanten Assoziationen.

#### Parameter

| Name        | Typ      | In   | Beschreibung                     |
| ----------- | -------- | ---- | -------------------------------- |
| `articleId` | `string` | Pfad | UUID des Blog-Beitrags. Pflicht. |

#### Assoziationen

`author`, `author.media`, `categories`, `tags`, `media.media`, `reviews`, `products`, `products.cover`, `products.cover.media`, `products.options.group`, `products.seoUrls`, `previewMedia`

#### Sichtbarkeit

Die Route liefert den Beitrag nur aus, wenn `status = 'active'` gesetzt ist und das Zeitfenster über `activeFrom` und `activeTo` erfüllt ist.

Andernfalls antwortet die Route mit `500` und `RuntimeException: Article not found or not active`.

#### Response

```json
{
  "apiAlias": "blog_article_detail_route_response",
  "id": "…",
  "title": "…",
  "translated": { "…": "…" },
  "author": { "…": "…" },
  "categories": [/* … */],
  "tags": [/* … */],
  "media": [/* gallery items (pivot) */],
  "reviews": [/* alle Reviews inkl. inaktiver */],
  "products": [/* … */],
  "previewMedia": { "…": "…" }
}
```

{% hint style="warning" %}
Das Array `reviews` enthält auch noch nicht freigeschaltete Bewertungen mit `active = false`. In eigenen Storefronts solltest du clientseitig auf `active = true` filtern.
{% endhint %}

### Autor:innen auflisten

`GET /store-api/prems-blog/authors`

Liefert eine paginierte Liste aller Blog-Autor:innen.

#### Parameter

| Name     | Typ      | In              | Beschreibung                                                                                               |
| -------- | -------- | --------------- | ---------------------------------------------------------------------------------------------------------- |
| `page`   | `int`    | Query oder Body | Seitenzahl. Standard ist `1`.                                                                              |
| `limit`  | `int`    | Query oder Body | Treffer pro Seite. Standard ist `12`. Das IAP-Feature `numberOfAuthors` kann den Wert nach oben begrenzen. |
| `letter` | `string` | Query oder Body | A–Z-Filter auf `lastName`.                                                                                 |

#### Sortierung

`lastName` aufsteigend, danach `firstName` aufsteigend

#### Assoziationen

`media`, `articles`

#### Response

```json
{
  "apiAlias": "blog_author_list_route_response",
  "elements": [
    {
      "id": "…",
      "firstName": "…",
      "lastName": "…",
      "media": { "…": "…" },
      "articles": [/* … */]
    }
  ],
  "total": 12
}
```

### Autorendetail

`GET /store-api/prems-blog/author/{authorId}`

Liefert eine Autorin oder einen Autor sowie deren oder dessen aktive Beiträge.

#### Parameter

| Name       | Typ      | In              | Beschreibung                              |
| ---------- | -------- | --------------- | ----------------------------------------- |
| `authorId` | `string` | Pfad            | UUID des Autors. Pflicht.                 |
| `page`     | `int`    | Query oder Body | Seite der Artikelliste. Standard ist `1`. |
| `limit`    | `int`    | Query oder Body | Treffer pro Seite. Standard ist `12`.     |

#### Verhalten

* Der Datensatz wird mit `media` geladen.
* Beiträge werden auf `status = 'active'`, Sichtbarkeitsfenster und `authorId` gefiltert.
* Sortiert wird nach `displayDate` absteigend.

#### Response

```json
{
  "apiAlias": "blog_author_detail",
  "author": {
    "id": "…",
    "firstName": "…",
    "lastName": "…",
    "media": { "…": "…" }
  },
  "articles": {
    "elements": [/* Artikelkarten mit author, categories, tags, previewMedia */],
    "total": 8
  }
}
```

Existiert `authorId` nicht, antwortet die Route mit `500` und `RuntimeException: Author not found`.

### Kategorie mit Beiträgen

`GET /store-api/prems-blog/category/{categoryId}`

Liefert eine Blog-Kategorie und alle aktiven Beiträge dieser Kategorie.

#### Parameter

| Name         | Typ      | In              | Beschreibung                              |
| ------------ | -------- | --------------- | ----------------------------------------- |
| `categoryId` | `string` | Pfad            | UUID der Blog-Kategorie. Pflicht.         |
| `page`       | `int`    | Query oder Body | Seite der Artikelliste. Standard ist `1`. |
| `limit`      | `int`    | Query oder Body | Treffer pro Seite. Standard ist `12`.     |

#### Response

```json
{
  "apiAlias": "blog_category_article",
  "category": {
    "id": "…",
    "translated": { "name": "…" }
  },
  "articles": {
    "elements": [/* … */],
    "total": 17
  }
}
```

Existiert `categoryId` nicht, antwortet die Route mit `500` und `RuntimeException: Category not found`.

### Tag mit Beiträgen

`GET /store-api/prems-blog/tag/{tagId}`

Liefert ein Blog-Tag und alle aktiven Beiträge mit diesem Tag.

#### Parameter

| Name    | Typ      | In              | Beschreibung                              |
| ------- | -------- | --------------- | ----------------------------------------- |
| `tagId` | `string` | Pfad            | UUID des Blog-Tags. Pflicht.              |
| `page`  | `int`    | Query oder Body | Seite der Artikelliste. Standard ist `1`. |
| `limit` | `int`    | Query oder Body | Treffer pro Seite. Standard ist `12`.     |

#### Response

```json
{
  "apiAlias": "blog_tag_article",
  "tag": {
    "id": "…",
    "translated": { "name": "…" }
  },
  "articles": {
    "elements": [/* … */],
    "total": 5
  }
}
```

Existiert `tagId` nicht, antwortet die Route mit `500` und `RuntimeException: Tag not found`.

### Bewertung speichern

`POST /store-api/prems-blog/article/{articleId}/review`

Speichert eine Beitragsbewertung. Neue Reviews werden immer mit `active = false` angelegt und erscheinen erst nach manueller Freigabe in der Storefront.

#### Voraussetzungen

| Voraussetzung                                                           | Verhalten bei Verstoß                                                     |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Aktiver IAP **`premsBlogPremium`** mit Feature `RATING_FEATURE_ENABLED` | `404 Not Found` mit „Blog reviews are not available on the current plan." |
| Eingeloggter Kunde ohne Gastmodus                                       | `403 Forbidden` mit Fehlercode `CHECKOUT__CUSTOMER_NOT_LOGGED_IN`         |
| `articleId` existiert                                                   | `404 Not Found` mit „Blog article with id … not found."                   |

#### Parameter

| Name        | Typ      | In   | Pflicht | Beschreibung                                                                                |
| ----------- | -------- | ---- | ------- | ------------------------------------------------------------------------------------------- |
| `articleId` | `string` | Pfad | ja      | UUID des Beitrags.                                                                          |
| `title`     | `string` | Body | ja      | Titel der Bewertung mit 5 bis 255 Zeichen.                                                  |
| `content`   | `string` | Body | ja      | Inhalt der Bewertung mit mindestens 40 Zeichen.                                             |
| `stars`     | `int`    | Body | ja      | Sternebewertung zwischen `1` und `5`.                                                       |
| `name`      | `string` | Body | nein    | Anzeigename. Bei leerem Wert wird `firstName + lastName` des eingeloggten Kunden verwendet. |
| `email`     | `string` | Body | nein    | E-Mail-Adresse. Bei leerem Wert wird die E-Mail des eingeloggten Kunden verwendet.          |

#### Response

`204 No Content`

#### Fehlercodes

| Status | Ursache                                                     |
| ------ | ----------------------------------------------------------- |
| `204`  | Bewertung wurde gespeichert und wartet auf Freigabe.        |
| `400`  | Validierung fehlgeschlagen.                                 |
| `403`  | Kunde ist nicht eingeloggt oder befindet sich im Gastmodus. |
| `404`  | Premium-IAP fehlt oder `articleId` ist unbekannt.           |

#### Beispiel

```bash
curl -X POST \
  "https://<shop>/store-api/prems-blog/article/<ARTICLE_ID>/review" \
  -H "sw-access-key: <SALES_CHANNEL_ACCESS_KEY>" \
  -H "sw-context-token: <LOGGED_IN_CONTEXT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Sehr informativ",
    "content": "Der Beitrag erklärt das Thema verständlich und mit guten Beispielen. Klare Empfehlung.",
    "stars": 5
  }'
```

### Plan-Limits über IAPs

Einige Endpunkte respektieren die im aktiven Plan gesetzten Feature-Limits.

| Feature                  | Wirkung                                       |
| ------------------------ | --------------------------------------------- |
| `numberOfBlogPosts`      | Begrenzt `limit` bei `GET /articles`.         |
| `numberOfAuthors`        | Begrenzt `limit` bei `GET /authors`.          |
| `RATING_FEATURE_ENABLED` | Aktiviert `POST /article/{articleId}/review`. |

Ist ein Feature unbegrenzt, hat es keinen Einfluss auf den übergebenen `limit`-Wert.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.premsoft.de/plugins/blog/store-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
