API-Doku: echte Referenz statt Fantasie-Features
- /de/api komplett neu: reale Endpunkte, Felder, Token-Abilities, Rate-Limit und Statuscodes der API v1 (angelehnt an die Legacy-Doku von businessportal24.com/service/documentation.html); OAuth/SDKs/ Webhooks/Integrations-Behauptungen und api.example.com entfernt - docs/api/v1.yml (OpenAPI 3.1) erstellt — die docs.api.v1-Route lief bisher auf eine nie vorhandene Datei (500er aus Hub-Footer/FAQ) - Token-Vergabe korrekt beschrieben: Selfservice im Konto mit Buchungs-Gate statt 'auf Anfrage'; X-ApiKey-Migrationshinweis - ApiDocumentationTest entskippt + Seiten-Test ergaenzt Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
136ecc8f43
commit
43f563c67f
3 changed files with 534 additions and 55 deletions
310
docs/api/v1.yml
Normal file
310
docs/api/v1.yml
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
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).
|
||||
Rate-Limit: 60 Anfragen pro Minute je Token (Header X-RateLimit-Limit /
|
||||
X-RateLimit-Remaining, bei 429 zusätzlich Retry-After).
|
||||
|
||||
Migration: Der X-ApiKey-Header der Altportale wird nicht mehr
|
||||
unterstützt — legacy API keys are no longer supported.
|
||||
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-Anmeldungen (Ability: newsletter:subscribe)"
|
||||
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).
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: integer }
|
||||
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: "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]
|
||||
properties:
|
||||
image:
|
||||
type: string
|
||||
format: binary
|
||||
description: JPG, PNG oder WebP, max. 8 MB
|
||||
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]
|
||||
summary: Newsletter-Anmeldung anlegen (Double-Opt-in folgt per Mail)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [portal, email]
|
||||
properties:
|
||||
portal:
|
||||
type: string
|
||||
enum: [presseecho, businessportal24]
|
||||
email: { type: string, format: email, maxLength: 190 }
|
||||
salutation_key: { type: string, maxLength: 20 }
|
||||
first_name: { type: string, maxLength: 80 }
|
||||
last_name: { type: string, maxLength: 80 }
|
||||
responses:
|
||||
"201": { description: "Anmeldung angelegt (unbestätigt)" }
|
||||
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:
|
||||
company_id:
|
||||
type: integer
|
||||
description: ID eines eigenen Firmenprofils — bestimmt Newsroom und Portal
|
||||
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 }
|
||||
|
|
@ -1,95 +1,252 @@
|
|||
@extends('web.layouts.web-master')
|
||||
|
||||
@section('title', 'API & Integrationen')
|
||||
@section('meta_description', 'Integrieren Sie unsere Dienste per REST-API: Pressemitteilungen programmatisch veröffentlichen, verwalten und abrufen.')
|
||||
@section('title', 'API-Dokumentation')
|
||||
@section('meta_description', 'REST-API v1: Pressemitteilungen programmatisch erstellen, mit Bildern versehen und zur Prüfung einreichen — Token-basiert, JSON, versioniert.')
|
||||
|
||||
@php
|
||||
$features = [
|
||||
['title' => 'RESTful API', 'text' => 'Moderne REST-Architektur mit JSON-Responses für einfache Integration in jede Programmiersprache.'],
|
||||
['title' => 'OAuth 2.0', 'text' => 'Sichere Authentifizierung mit OAuth 2.0 und API-Tokens für maximale Sicherheit.'],
|
||||
['title' => 'Webhooks', 'text' => 'Echtzeit-Benachrichtigungen über Events wie Veröffentlichungen oder Statusänderungen.'],
|
||||
['title' => 'Dokumentation', 'text' => 'Umfassende API-Dokumentation mit Code-Beispielen in verschiedenen Sprachen.'],
|
||||
['title' => 'Rate Limiting', 'text' => 'Faire Rate Limits mit klaren Headern und automatischer Skalierung bei Bedarf.'],
|
||||
['title' => 'SDKs', 'text' => 'Offizielle SDKs für PHP, JavaScript, Python und weitere Sprachen.'],
|
||||
// Die API läuft zentral über den Publisher-Hub (pressekonto).
|
||||
$apiBase = rtrim((string) config('domains.domain_portal_url', config('app.url')), '/').'/api/v1';
|
||||
|
||||
$host = request()->getHost();
|
||||
$apiBrandKey = str_contains($host, 'pressekonto')
|
||||
? 'pressekonto'
|
||||
: (str_contains($host, 'presseecho') ? 'presseecho' : 'businessportal24');
|
||||
$apiContactEmail = config('domains.domains.'.$apiBrandKey.'.brand.contact_email') ?? 'info@businessportal24.com';
|
||||
|
||||
$endpoints = [
|
||||
[
|
||||
'group' => 'Pressemitteilungen',
|
||||
'items' => [
|
||||
['method' => 'GET', 'path' => '/press-releases', 'desc' => 'Eigene Mitteilungen auflisten (paginiert, filterbar über ?status= und ?per_page=, max. 100).', 'ability' => 'press-releases:read'],
|
||||
['method' => 'POST', 'path' => '/press-releases', 'desc' => 'Neue Mitteilung anlegen — immer als Entwurf (Status draft).', 'ability' => 'press-releases:write'],
|
||||
['method' => 'GET', 'path' => '/press-releases/{id}', 'desc' => 'Eine Mitteilung inkl. Firma, Rubrik und Bildern abrufen.', 'ability' => 'press-releases:read'],
|
||||
['method' => 'PATCH', 'path' => '/press-releases/{id}', 'desc' => 'Mitteilung ändern — nur im Status draft oder rejected.', 'ability' => 'press-releases:write'],
|
||||
['method' => 'POST', 'path' => '/press-releases/{id}/submit', 'desc' => 'Zur Inhaltsprüfung einreichen — der einzige Weg Richtung Veröffentlichung.', 'ability' => 'press-releases:write'],
|
||||
['method' => 'DELETE', 'path' => '/press-releases/{id}', 'desc' => 'Mitteilung löschen — veröffentlichte Mitteilungen sind ausgenommen.', 'ability' => 'press-releases:write'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'group' => 'Bilder',
|
||||
'items' => [
|
||||
['method' => 'GET', 'path' => '/press-releases/{id}/images', 'desc' => 'Bilder einer Mitteilung auflisten.', 'ability' => 'press-releases:read'],
|
||||
['method' => 'POST', 'path' => '/press-releases/{id}/images', 'desc' => 'Bild hochladen (multipart/form-data) — nur bei draft/rejected.', 'ability' => 'press-release-images:write'],
|
||||
['method' => 'DELETE', 'path' => '/press-release-images/{imageId}', 'desc' => 'Bild entfernen — nur bei draft/rejected.', 'ability' => 'press-release-images:write'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'group' => 'Stammdaten',
|
||||
'items' => [
|
||||
['method' => 'GET', 'path' => '/companies', 'desc' => 'Eigene Firmenprofile (Newsrooms) auflisten.', 'ability' => 'companies:read'],
|
||||
['method' => 'GET', 'path' => '/companies/{id}', 'desc' => 'Ein Firmenprofil abrufen.', 'ability' => 'companies:read'],
|
||||
['method' => 'GET', 'path' => '/categories', 'desc' => 'Aktive Rubriken inkl. Übersetzungen — liefert die category_id für neue Mitteilungen.', 'ability' => 'press-releases:read'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$integrations = ['WordPress', 'Zapier', 'Slack', 'HubSpot'];
|
||||
$pressReleaseFields = [
|
||||
['name' => 'company_id', 'type' => 'integer', 'required' => true, 'desc' => 'ID eines eigenen Firmenprofils — bestimmt Newsroom und Portal.'],
|
||||
['name' => 'category_id', 'type' => 'integer', 'required' => true, 'desc' => 'Rubrik (siehe GET /categories).'],
|
||||
['name' => 'language', 'type' => 'string', 'required' => true, 'desc' => 'Sprachcode, zwei Zeichen: de oder en.'],
|
||||
['name' => 'title', 'type' => 'string', 'required' => true, 'desc' => 'Überschrift, max. 255 Zeichen.'],
|
||||
['name' => 'text', 'type' => 'string', 'required' => true, 'desc' => 'Inhalt. Erlaubtes HTML: p, br, h2, h3, strong, em, ul, ol, li, blockquote, a — alles andere wird entfernt.'],
|
||||
['name' => 'backlink_url', 'type' => 'string', 'required' => false, 'desc' => 'Link zur Quelle/Website, max. 255 Zeichen.'],
|
||||
['name' => 'keywords', 'type' => 'string', 'required' => false, 'desc' => 'Schlagworte, kommagetrennt, max. 255 Zeichen.'],
|
||||
['name' => 'teaser_begin / teaser_end', 'type' => 'integer', 'required' => false, 'desc' => 'Zeichen-Offsets für den Teaser-Ausschnitt.'],
|
||||
['name' => 'no_export', 'type' => 'boolean', 'required' => false, 'desc' => 'Mitteilung von Feeds/Exporten ausnehmen.'],
|
||||
];
|
||||
|
||||
$imageFields = [
|
||||
['name' => 'image', 'type' => 'file', 'required' => true, 'desc' => 'JPG, PNG oder WebP, max. 8 MB.'],
|
||||
['name' => 'title', 'type' => 'string', 'required' => false, 'desc' => 'Bildtitel, max. 120 Zeichen.'],
|
||||
['name' => 'description', 'type' => 'string', 'required' => false, 'desc' => 'Beschreibung, max. 500 Zeichen.'],
|
||||
['name' => 'copyright', 'type' => 'string', 'required' => false, 'desc' => 'Urheberangabe, max. 255 Zeichen.'],
|
||||
['name' => 'is_preview', 'type' => 'boolean', 'required' => false, 'desc' => 'Als Vorschaubild setzen (ersetzt ein vorhandenes).'],
|
||||
];
|
||||
|
||||
$statusCodes = [
|
||||
['code' => '200 / 201 / 204', 'desc' => 'Erfolg — Abruf, Ressource erstellt bzw. gelöscht.'],
|
||||
['code' => '401', 'desc' => 'Kein oder ungültiger API-Token.'],
|
||||
['code' => '402', 'desc' => 'Einreichen erfordert eine aktive Buchung (Tarif oder Einzel-PM).'],
|
||||
['code' => '403', 'desc' => 'Token-Berechtigung fehlt oder die Ressource gehört nicht zu Ihrem Konto.'],
|
||||
['code' => '409', 'desc' => 'Statuskonflikt — z. B. Bearbeiten einer bereits veröffentlichten Mitteilung.'],
|
||||
['code' => '422', 'desc' => 'Validierungsfehler oder Kontingent-/Richtlinienverstoß (Details im Body).'],
|
||||
['code' => '429', 'desc' => 'Rate-Limit erreicht: 60 Anfragen pro Minute je Token (Header X-RateLimit-*).'],
|
||||
];
|
||||
|
||||
$methodColors = [
|
||||
'GET' => 'text-[#1F6B3F] bg-[#E7F2EA]',
|
||||
'POST' => 'text-[#8A4B12] bg-[#F7EBDD]',
|
||||
'PATCH' => 'text-[#6B4FA1] bg-[#EEE9F7]',
|
||||
'DELETE' => 'text-[#A13030] bg-[#F7E5E5]',
|
||||
];
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<x-web.static-page
|
||||
eyebrow="Entwickler"
|
||||
title="API & Integrationen"
|
||||
lead="Integrieren Sie unsere Dienste nahtlos in Ihre bestehenden Systeme und Workflows – per REST-API, Webhooks und SDKs."
|
||||
title="API-Dokumentation"
|
||||
lead="Pressemitteilungen programmatisch erstellen, mit Bildern versehen und zur Prüfung einreichen — über die versionierte REST-API des Publisher-Hubs."
|
||||
breadcrumb="API"
|
||||
>
|
||||
{{-- Überblick --}}
|
||||
{{-- Überblick + Authentifizierung --}}
|
||||
<div class="pm-body max-w-[760px]">
|
||||
<h2>Leistungsstarke REST-API</h2>
|
||||
<h2>Grundlagen</h2>
|
||||
<p>
|
||||
Unsere RESTful API ermöglicht es Ihnen, Pressemitteilungen programmatisch zu veröffentlichen, zu
|
||||
verwalten und abzurufen – ideal für Unternehmen mit hohem Veröffentlichungsvolumen oder bestehenden
|
||||
Content-Management-Systemen.
|
||||
Die API v1 ist ein RESTful Web Service mit JSON-Responses. Basis-URL:
|
||||
<strong>{{ $apiBase }}</strong>. Eine über die API angelegte Mitteilung ist immer ein
|
||||
Entwurf; veröffentlicht wird sie erst nach dem Einreichen über die Submit-Route und der
|
||||
anschließenden Inhaltsprüfung — sie erscheint dann wie jede andere Mitteilung auf beiden Portalen.
|
||||
</p>
|
||||
<h3>Authentifizierung</h3>
|
||||
<p>
|
||||
Die API ist vollständig dokumentiert und unterstützt moderne Authentifizierungsmethoden sowie
|
||||
Webhooks für Echtzeit-Benachrichtigungen.
|
||||
Alle Anfragen benötigen einen persönlichen API-Token als Bearer-Header:
|
||||
<code>Authorization: Bearer <token></code>. Jeder Token trägt Berechtigungen
|
||||
(z. B. <code>press-releases:write</code>), die den Zugriff auf die jeweiligen Endpunkte steuern.
|
||||
Tokens erstellen und widerrufen Sie selbst in Ihrem Publisher-Konto unter „API-Tokens" —
|
||||
Voraussetzung ist eine aktive Buchung (Tarif oder Einzel-Pressemitteilung).
|
||||
Der <code>X-ApiKey</code>-Header der Altportale wird nicht mehr unterstützt.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Features --}}
|
||||
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">API-Features</h2>
|
||||
<div class="grid gap-px bg-bg-rule border border-bg-rule grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@foreach ($features as $feature)
|
||||
<div class="bg-bg p-6">
|
||||
<h3 class="text-[14px] font-semibold text-ink m-0 mb-2">{{ $feature['title'] }}</h3>
|
||||
<p class="text-[12.5px] leading-[1.55] text-ink-2 m-0">{{ $feature['text'] }}</p>
|
||||
{{-- Endpunkte --}}
|
||||
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">Endpunkte</h2>
|
||||
<div class="max-w-[900px] space-y-8">
|
||||
@foreach ($endpoints as $group)
|
||||
<div>
|
||||
<h3 class="text-[14px] font-semibold text-ink m-0 mb-3">{{ $group['group'] }}</h3>
|
||||
<div class="border border-bg-rule divide-y divide-bg-rule">
|
||||
@foreach ($group['items'] as $endpoint)
|
||||
<div class="px-5 py-3.5 grid gap-x-4 gap-y-1 sm:grid-cols-[64px_1fr] items-baseline">
|
||||
<span class="inline-flex justify-center px-1.5 py-0.5 text-[11px] font-bold tracking-wide rounded-[2px] {{ $methodColors[$endpoint['method']] ?? 'text-ink bg-bg-elev' }}">{{ $endpoint['method'] }}</span>
|
||||
<div>
|
||||
<code class="font-mono text-[13px] text-ink">{{ $endpoint['path'] }}</code>
|
||||
<p class="text-[12.5px] leading-[1.55] text-ink-2 mt-1 mb-0">
|
||||
{{ $endpoint['desc'] }}
|
||||
<span class="text-ink-4">· Berechtigung: <code class="font-mono text-[11.5px]">{{ $endpoint['ability'] }}</code></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Code-Beispiel --}}
|
||||
{{-- Felder --}}
|
||||
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">Felder einer Pressemitteilung</h2>
|
||||
<div class="max-w-[900px] border border-bg-rule overflow-x-auto">
|
||||
<table class="w-full text-[13px] border-collapse">
|
||||
<thead>
|
||||
<tr class="text-left text-ink-3 bg-bg-elev border-b border-bg-rule">
|
||||
<th class="font-medium px-4 py-2.5">Feld</th>
|
||||
<th class="font-medium px-4 py-2.5">Typ</th>
|
||||
<th class="font-medium px-4 py-2.5">Pflicht</th>
|
||||
<th class="font-medium px-4 py-2.5">Beschreibung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-bg-rule">
|
||||
@foreach ($pressReleaseFields as $field)
|
||||
<tr>
|
||||
<td class="px-4 py-2.5 font-mono text-[12.5px] text-ink whitespace-nowrap">{{ $field['name'] }}</td>
|
||||
<td class="px-4 py-2.5 text-ink-3">{{ $field['type'] }}</td>
|
||||
<td class="px-4 py-2.5 text-ink-3">{{ $field['required'] ? 'ja' : '—' }}</td>
|
||||
<td class="px-4 py-2.5 text-ink-2">{{ $field['desc'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-12 mb-4">Felder eines Bild-Uploads</h2>
|
||||
<div class="max-w-[900px] border border-bg-rule overflow-x-auto">
|
||||
<table class="w-full text-[13px] border-collapse">
|
||||
<thead>
|
||||
<tr class="text-left text-ink-3 bg-bg-elev border-b border-bg-rule">
|
||||
<th class="font-medium px-4 py-2.5">Feld</th>
|
||||
<th class="font-medium px-4 py-2.5">Typ</th>
|
||||
<th class="font-medium px-4 py-2.5">Pflicht</th>
|
||||
<th class="font-medium px-4 py-2.5">Beschreibung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-bg-rule">
|
||||
@foreach ($imageFields as $field)
|
||||
<tr>
|
||||
<td class="px-4 py-2.5 font-mono text-[12.5px] text-ink whitespace-nowrap">{{ $field['name'] }}</td>
|
||||
<td class="px-4 py-2.5 text-ink-3">{{ $field['type'] }}</td>
|
||||
<td class="px-4 py-2.5 text-ink-3">{{ $field['required'] ? 'ja' : '—' }}</td>
|
||||
<td class="px-4 py-2.5 text-ink-2">{{ $field['desc'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- Workflow-Beispiel --}}
|
||||
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">Schneller Einstieg</h2>
|
||||
<p class="text-[13px] text-ink-2 m-0 mb-4 max-w-[640px]">Veröffentlichen Sie Ihre erste Pressemitteilung in wenigen Zeilen Code.</p>
|
||||
<div class="bg-[#16140f] border border-bg-rule-strong rounded-[3px] p-5 overflow-x-auto">
|
||||
<div class="text-[11.5px] text-white/45 mb-3 font-mono">// Beispiel: Pressemitteilung veröffentlichen</div>
|
||||
<pre class="text-[12.5px] leading-[1.6] font-mono text-[#9ad1a5] m-0"><code>curl -X POST https://api.example.com/v1/releases \
|
||||
<p class="text-[13px] text-ink-2 m-0 mb-4 max-w-[640px]">Der typische Ablauf: Entwurf anlegen, Bild hochladen, zur Prüfung einreichen.</p>
|
||||
<div class="max-w-[900px] bg-[#16140f] border border-bg-rule-strong rounded-[3px] p-5 overflow-x-auto">
|
||||
<pre class="text-[12.5px] leading-[1.6] font-mono text-[#9ad1a5] m-0"><code># 1. Entwurf anlegen
|
||||
curl -X POST {{ $apiBase }}/press-releases \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"company_id": 1,
|
||||
"category_id": 2,
|
||||
"language": "de",
|
||||
"title": "Neue Produktankündigung",
|
||||
"content": "Unser neues Produkt …",
|
||||
"category": "wirtschaft",
|
||||
"publish_at": "2026-01-15T10:00:00Z"
|
||||
}'</code></pre>
|
||||
</div>
|
||||
{{-- TODO: Link auf die vollständige API-Dokumentation, sobald sie online ist. --}}
|
||||
<p class="text-[12px] text-ink-3 m-0 mt-3">Die vollständige Dokumentation folgt. Bei Fragen zum API-Zugang hilft unser <a href="{{ route('kontakt') }}" class="text-brand hover:underline">Support</a>.</p>
|
||||
"text": "<p>Unser neues Produkt …</p>"
|
||||
}'
|
||||
|
||||
{{-- Integrationen --}}
|
||||
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">Native Integrationen</h2>
|
||||
<div class="grid gap-px bg-bg-rule border border-bg-rule grid-cols-2 md:grid-cols-4">
|
||||
@foreach ($integrations as $integration)
|
||||
<div class="bg-bg p-6 flex items-center justify-center">
|
||||
<span class="text-[13px] font-semibold text-ink-2">{{ $integration }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
# 2. Bild hochladen (multipart)
|
||||
curl -X POST {{ $apiBase }}/press-releases/123/images \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
-F "image=@pressefoto.jpg" \
|
||||
-F "copyright=Mustermann GmbH" \
|
||||
-F "is_preview=1"
|
||||
|
||||
# 3. Zur Inhaltsprüfung einreichen
|
||||
curl -X POST {{ $apiBase }}/press-releases/123/submit \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN"</code></pre>
|
||||
</div>
|
||||
<p class="text-[12px] text-ink-3 m-0 mt-3">
|
||||
Maschinenlesbare Referenz: <a href="{{ route('docs.api.v1') }}" class="text-brand hover:underline">OpenAPI-Spezifikation (YAML)</a>.
|
||||
</p>
|
||||
|
||||
{{-- Statuscodes --}}
|
||||
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">HTTP-Statuscodes</h2>
|
||||
<div class="max-w-[900px] border border-bg-rule overflow-x-auto">
|
||||
<table class="w-full text-[13px] border-collapse">
|
||||
<thead>
|
||||
<tr class="text-left text-ink-3 bg-bg-elev border-b border-bg-rule">
|
||||
<th class="font-medium px-4 py-2.5">Code</th>
|
||||
<th class="font-medium px-4 py-2.5">Bedeutung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-bg-rule">
|
||||
@foreach ($statusCodes as $status)
|
||||
<tr>
|
||||
<td class="px-4 py-2.5 font-mono text-[12.5px] text-ink whitespace-nowrap">{{ $status['code'] }}</td>
|
||||
<td class="px-4 py-2.5 text-ink-2">{{ $status['desc'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- CTA --}}
|
||||
<div class="mt-14 border border-bg-rule bg-bg-elev p-6 sm:p-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-5">
|
||||
<div class="mt-14 border border-bg-rule bg-bg-elev p-6 sm:p-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-5 max-w-[900px]">
|
||||
<div>
|
||||
<h2 class="font-serif text-[20px] font-semibold tracking-[-0.3px] text-ink m-0">Starten Sie mit der API</h2>
|
||||
<p class="text-[13px] text-ink-2 mt-1.5 mb-0">API-Zugang ist ab dem passenden Tarif verfügbar – Details finden Sie auf der Preisseite.</p>
|
||||
<h2 class="font-serif text-[20px] font-semibold tracking-[-0.3px] text-ink m-0">API-Token erstellen</h2>
|
||||
<p class="text-[13px] text-ink-2 mt-1.5 mb-0">Tokens verwalten Sie selbst im Publisher-Konto. Bei Fragen zur Anbindung hilft der Support: <a href="mailto:{{ $apiContactEmail }}" class="text-brand hover:underline">{{ $apiContactEmail }}</a></p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3 flex-shrink-0">
|
||||
<a href="{{ route('preise') }}"
|
||||
<a href="{{ rtrim((string) config('domains.domain_portal_url', config('app.url')), '/') }}/login"
|
||||
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-white bg-brand hover:bg-brand-deep rounded-[2px] transition-colors">
|
||||
Preise ansehen
|
||||
Zum Publisher-Konto
|
||||
</a>
|
||||
<a href="{{ route('kontakt') }}"
|
||||
<a href="{{ route('preise') }}"
|
||||
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-ink border border-bg-rule-strong hover:bg-bg transition-colors rounded-[2px]">
|
||||
Kontakt aufnehmen
|
||||
Preise ansehen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,5 +9,17 @@ test('api v1 documentation is publicly available', function () {
|
|||
->assertHeader('content-type', 'application/yaml; charset=UTF-8')
|
||||
->assertSee('openapi: 3.1.0', false)
|
||||
->assertSee('/press-releases:', false)
|
||||
->assertSee('Legacy API keys are no longer supported.', false);
|
||||
})->skip('OpenAPI-Spec docs/api/v1.yml ist noch nicht erstellt (eigener API-Doku-Track, siehe dev/migration 2026/07-API-MIGRATION.md). Assertions bleiben erhalten; Skip entfernen, sobald die Datei vorliegt.');
|
||||
->assertSee('legacy API keys are no longer supported', false);
|
||||
});
|
||||
|
||||
test('the public api page documents the real endpoints', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->get('https://pressekonto.test/de/api')
|
||||
->assertSuccessful()
|
||||
->assertSeeText('API-Dokumentation')
|
||||
->assertSee('/press-releases/{id}/submit', false)
|
||||
->assertSee('press-releases:write', false)
|
||||
// Keine Fantasie-Features mehr (OAuth/SDKs/Webhooks gab es nie).
|
||||
->assertDontSee('OAuth 2.0')
|
||||
->assertDontSee('api.example.com');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue