presseportale/public/api/v1.yml
Kevin Adametz ccef83b952 api: Newsletter-Anmeldung über die API eingestellt (410)
Der Endpunkt legte Abos ohne Bestätigungsweg an, die Spezifikation versprach
ein Double-Opt-in; seit dem Go-Live kein Aufruf. Antwortet jetzt mit 410,
die Ability ist für neue Tokens nicht mehr wählbar (Roadmap 4.3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:35:54 +00:00

347 lines
13 KiB
YAML

openapi: 3.1.0
info:
title: pressekonto Publisher API
version: "1.0"
description: |
REST-API des Publisher-Hubs (pressekonto) für presseecho.de und
businessportal24.com. Pressemitteilungen werden als Entwurf angelegt,
optional mit Bildern versehen und über die Submit-Route zur
Inhaltsprüfung eingereicht — eine direkte Veröffentlichung über die
API ist bewusst nicht möglich.
Authentifizierung über einen persönlichen API-Token (Bearer). Jeder
Token trägt Berechtigungen (Abilities), die den Zugriff auf die
Endpunkte steuern. Tokens werden im Publisher-Konto unter „API-Tokens"
selbst erstellt und widerrufen (Voraussetzung: aktive Buchung).
Die Buchung wird bei jeder Anfrage geprüft: Endet sie, antwortet die
API mit 403 („API access requires an active subscription or booking."),
bis wieder gebucht ist — die Tokens selbst bleiben erhalten. Optional
lässt sich beim Erstellen ein Ablaufdatum setzen; danach antwortet die
API für diesen Token mit 401.
Rate-Limit: 60 Anfragen pro Minute je Token (Header X-RateLimit-Limit /
X-RateLimit-Remaining, bei 429 zusätzlich Retry-After).
Migration: Bestehende API-Schlüssel der Altportale wurden übernommen
und bleiben gültig — auch in der alten Übergabeform (X-ApiKey-Header)
und auf den dokumentierten /service/…-Pfaden. Unbekannte Alt-Schlüssel
erhalten 410 mit Migrationshinweis. Neue Integrationen nutzen bitte
Bearer-Tokens auf /api/v1.
servers:
- url: https://pressekonto.de/api/v1
security:
- bearerToken: []
tags:
- name: press-releases
description: "Eigene Pressemitteilungen (Ability: press-releases:read bzw. press-releases:write)"
- name: images
description: "Bilder einer Pressemitteilung (Ability: press-release-images:write zum Schreiben)"
- name: companies
description: "Eigene Firmenprofile/Newsrooms (Ability: companies:read)"
- name: categories
description: "Aktive Rubriken (Ability: press-releases:read)"
- name: newsletter
description: "Newsletter-Anmeldung über die API — eingestellt am 09.10.2026"
paths:
/press-releases:
get:
tags: [press-releases]
summary: Eigene Pressemitteilungen auflisten
parameters:
- name: status
in: query
description: Nach Status filtern (draft, review, published, rejected, archived)
schema: { type: string }
- name: per_page
in: query
description: Seitengröße, Standard 25, max. 100
schema: { type: integer, maximum: 100 }
- name: page
in: query
schema: { type: integer }
responses:
"200":
description: Paginierte Liste
content:
application/json:
schema:
type: object
properties:
data:
type: array
items: { $ref: "#/components/schemas/PressRelease" }
post:
tags: [press-releases]
summary: Pressemitteilung anlegen (immer als Entwurf)
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/PressReleaseInput" }
responses:
"201":
description: Entwurf angelegt
content:
application/json:
schema:
type: object
properties:
data: { $ref: "#/components/schemas/PressRelease" }
"403": { description: "Ability fehlt oder Firma gehört nicht zum Konto" }
"422": { description: "Validierungsfehler" }
/press-releases/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: integer }
get:
tags: [press-releases]
summary: Pressemitteilung abrufen
responses:
"200":
description: Mitteilung inkl. Firma, Rubrik und Bildern
content:
application/json:
schema:
type: object
properties:
data: { $ref: "#/components/schemas/PressRelease" }
"403": { description: "Nicht gefunden oder fremdes Konto" }
patch:
tags: [press-releases]
summary: Pressemitteilung ändern (nur draft/rejected)
requestBody:
content:
application/json:
schema: { $ref: "#/components/schemas/PressReleaseInput" }
responses:
"200": { description: "Aktualisierte Mitteilung" }
"409": { description: "Nur Entwürfe und abgelehnte Mitteilungen sind änderbar" }
delete:
tags: [press-releases]
summary: Pressemitteilung löschen (nicht bei published)
responses:
"204": { description: "Gelöscht" }
"409": { description: "Veröffentlichte Mitteilungen können nicht per API gelöscht werden" }
/press-releases/{id}/submit:
post:
tags: [press-releases]
summary: Zur Inhaltsprüfung einreichen
description: |
Der einzige API-Weg Richtung Veröffentlichung. Durchläuft denselben
Funnel wie das Web-Formular (Richtlinien-Filter, Kontingent, Status-Log).
Der Aufruf erklärt verbindlich den deklarierten KI-Grad des Textes
(`ai_text_declaration`, Transparenzpflicht nach Art. 50 KI-VO) und
schließt die Kenntnisnahme der dauerhaften Archivierung ein
(AGB § 13: Depublikation nur als kostenpflichtiger Service,
gesetzliche Ansprüche unberührt).
parameters:
- name: id
in: path
required: true
schema: { type: integer }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [ai_text_declaration]
properties:
ai_text_declaration:
type: string
enum: [none, assisted, mostly_ai, fully_ai]
description: >
Deklarierter KI-Grad des Textes: ohne generative KI (none),
KI als Formulierungshilfe mit redaktioneller Überarbeitung
(assisted), überwiegend KI-erstellt und geprüft (mostly_ai),
vollständig KI-generiert (fully_ai). Ab mostly_ai trägt die
Veröffentlichung ein öffentliches Transparenz-Label.
responses:
"200": { description: "Eingereicht (Status review)" }
"402": { description: "Aktive Buchung erforderlich (Tarif oder Einzel-PM)" }
"409": { description: "Nur draft/rejected kann eingereicht werden" }
"422": { description: "KI-Deklaration fehlt/ungültig, Kontingent erschöpft oder Richtlinienverstoß" }
/press-releases/{id}/images:
parameters:
- name: id
in: path
required: true
schema: { type: integer }
get:
tags: [images]
summary: Bilder einer Mitteilung auflisten
responses:
"200":
description: Bilderliste
content:
application/json:
schema:
type: object
properties:
data:
type: array
items: { $ref: "#/components/schemas/PressReleaseImage" }
post:
tags: [images]
summary: Bild hochladen (nur bei draft/rejected)
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [image, ai_declaration]
properties:
image:
type: string
format: binary
description: JPG, PNG oder WebP, max. 8 MB
ai_declaration:
type: string
enum: [original, ai_generated, ai_edited]
description: >
KI-Kennzeichnung des Bildes (Art. 50 KI-VO): Originalfoto,
KI-generiert oder KI-bearbeitet.
title: { type: string, maxLength: 120 }
description: { type: string, maxLength: 500 }
copyright: { type: string, maxLength: 255 }
is_preview:
type: boolean
description: Als Vorschaubild setzen (ersetzt ein vorhandenes)
responses:
"201": { description: "Bild gespeichert" }
"409": { description: "Mitteilung ist nicht mehr änderbar" }
"422": { description: "Ungültige Datei" }
/press-release-images/{imageId}:
delete:
tags: [images]
summary: Bild entfernen (nur bei draft/rejected)
parameters:
- name: imageId
in: path
required: true
schema: { type: integer }
responses:
"204": { description: "Gelöscht" }
"409": { description: "Mitteilung ist nicht mehr änderbar" }
/companies:
get:
tags: [companies]
summary: Eigene Firmenprofile auflisten
parameters:
- name: per_page
in: query
schema: { type: integer, maximum: 100 }
responses:
"200": { description: "Paginierte Liste eigener Firmen" }
/companies/{id}:
get:
tags: [companies]
summary: Firmenprofil abrufen
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200": { description: "Firmenprofil" }
"403": { description: "Nicht gefunden oder fremdes Konto" }
/categories:
get:
tags: [categories]
summary: Aktive Rubriken auflisten
responses:
"200": { description: "Rubriken inkl. Übersetzungen" }
/newsletter/subscribe:
post:
tags: [newsletter]
deprecated: true
summary: Eingestellt — Newsletter-Anmeldung über die API
description: >
Seit 09.10.2026 abgeschaltet. Die Route antwortet mit 410 und legt
keine Anmeldung an.
responses:
"410": { description: "Endpunkt eingestellt" }
components:
securitySchemes:
bearerToken:
type: http
scheme: bearer
description: "Persönlicher API-Token aus dem Publisher-Konto (Selfservice unter „API-Tokens")."
schemas:
PressReleaseInput:
type: object
required: [company_id, category_id, language, title, text]
properties:
portal:
type: string
enum: [presseecho, businessportal24]
description: >
Ziel-Portal der Veröffentlichung. Optional: Ohne Angabe wird das
Portal aus der aufgerufenen Domain bestimmt (presseecho.de bzw.
businessportal24.com); ein explizit gesetzter Wert hat Vorrang
vor der Domain. Fehlen beide (z. B. Aufruf über pressekonto.de),
gilt das Portal des Firmenprofils. Beim PATCH wechselt das Portal
ausschließlich über diesen Parameter — die aufgerufene Domain
verschiebt eine bestehende Mitteilung nicht.
company_id:
type: integer
description: ID eines eigenen Firmenprofils — bestimmt den Newsroom
category_id: { type: integer }
language:
type: string
minLength: 2
maxLength: 2
description: de oder en
title: { type: string, maxLength: 255 }
text:
type: string
description: "Erlaubtes HTML: p, br, h2, h3, strong, em, ul, ol, li, blockquote, a"
backlink_url: { type: string, format: uri, maxLength: 255 }
keywords: { type: string, maxLength: 255 }
teaser_begin: { type: integer, minimum: 0 }
teaser_end: { type: integer, minimum: 0 }
no_export: { type: boolean }
PressRelease:
type: object
properties:
id: { type: integer }
uuid: { type: string, format: uuid }
portal: { type: string }
language: { type: string }
title: { type: string }
slug: { type: string }
text: { type: string }
backlink_url: { type: [string, "null"] }
keywords: { type: [string, "null"] }
status:
type: string
enum: [draft, review, published, rejected, archived]
hits: { type: integer }
teaser:
type: object
properties:
begin: { type: [integer, "null"] }
end: { type: [integer, "null"] }
no_export: { type: boolean }
published_at: { type: [string, "null"], format: date-time }
company: { type: object }
category: { type: object }
images:
type: array
items: { $ref: "#/components/schemas/PressReleaseImage" }
created_at: { type: string, format: date-time }
updated_at: { type: string, format: date-time }
PressReleaseImage:
type: object
properties:
id: { type: integer }
title: { type: [string, "null"] }
description: { type: [string, "null"] }
copyright: { type: [string, "null"] }
is_preview: { type: boolean }
width: { type: integer }
height: { type: integer }
mime: { type: string }