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 }