openapi/openapi.yaml
Stefan Schmidt-Egermann b13fba1d75
feat: initial openapi content (v0.1.0)
- Vollständige OpenAPI 3.1 Spezifikation
- Alle Endpoints, Schemas, Webhooks, Error-Responses
- Beispiel-Requests für minimal/async/webhook-Modi
- Initiale CHANGELOG.md
2026-04-25 12:26:00 +02:00

646 lines
20 KiB
YAML

openapi: 3.1.0
info:
title: hightrusted CAPTURE API
version: "1.0.0"
summary: Forensische Web-Captures mit qualifiziertem EU-Zeitstempel
description: |
Forensische Web-Captures mit qualifiziertem EU-Zeitstempel nach
RFC 3161 / eIDAS Art. 41.
Die API nimmt eine URL entgegen, rendert die Seite vollständig (inkl.
JavaScript), liefert sie als PDF/A-3 zurück und versieht das Ergebnis mit
einem qualifizierten Zeitstempel. Das Ergebnis ist gerichtsverwertbar und
Jahre später noch verifizierbar — auch nachdem die Original-Seite längst
offline ist.
**Drei Aufruf-Modi:**
- `sync` (Default) — Server wartet bis zu 30 s und liefert das fertige Capture
- `async` — Server liefert sofort `id`, Client pollt `GET /captures/{id}`
- `webhook` — Server liefert das fertige Capture per HTTP-POST an `webhook_url`
**Authentifizierung:** Bearer-Token mit API-Key. Erzeugung im Dashboard:
https://capture.hightrusted.net/dashboard/api-keys
**Made in Germany.** Server in Deutschland. DSGVO-nativ. eIDAS-konform.
Kein US-Cloud-Anbieter in der Verarbeitungskette.
contact:
name: hightrusted Developer Support
email: developers@hightrusted.net
url: https://capture.hightrusted.net/api
license:
name: hightrusted API Terms
url: https://hightrusted.net/legal/api-terms
termsOfService: https://hightrusted.net/legal/terms
servers:
- url: https://capture.hightrusted.net/api/v1
description: Production
security:
- bearerAuth: []
tags:
- name: Captures
description: Capture-Erstellung, -Status, -Download
- name: Verify
description: Verifikation gespeicherter Captures (kostenlos, ohne Quota)
- name: Account
description: Verbrauch und Quota-Status
paths:
# ════════════════════════════════════════════════════════════════════════
/captures:
post:
tags: [Captures]
summary: Capture erstellen
operationId: createCapture
description: |
Erstellt eine forensische Web-Capture in einem von drei Modi:
- **sync** (Default): wartet bis zu 30 s und liefert das fertige PDF
- **async**: liefert sofort `id`, Client pollt `GET /captures/{id}`
- **webhook**: Server liefert das fertige Capture per HTTP-POST
**Shortlink-Auflösung:** `maps.app.goo.gl`-Links und ähnliche Shortlinks
werden automatisch aufgelöst. Das Originalfeld `url` bleibt für die
Nachweisbarkeit erhalten.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CaptureRequest"
examples:
minimal:
summary: Minimal — synchron
value:
url: https://example.com/article/123
async_with_reference:
summary: Asynchron mit Referenz
value:
url: https://example.com/page
mode: async
reference: case-2026-001
webhook_full:
summary: Webhook mit Co-Branding
value:
url: https://example.com/page
mode: webhook
webhook_url: https://api.deinedomain.de/capture-callback
reference: case-2026-001
viewport:
width: 1920
height: 1080
co_branding:
logo_url: https://kanzlei-mueller.de/logo.png
footer_text: Kanzlei Müller — Beweissicherung
responses:
"200":
description: Synchron — fertiges Capture
content:
application/json:
schema:
$ref: "#/components/schemas/Capture"
"202":
description: Asynchron / Webhook — Capture in Bearbeitung
content:
application/json:
schema:
$ref: "#/components/schemas/CaptureQueued"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"402":
$ref: "#/components/responses/QuotaExceeded"
"422":
$ref: "#/components/responses/UnreachableUrl"
"429":
$ref: "#/components/responses/RateLimited"
"503":
$ref: "#/components/responses/TsaUnavailable"
get:
tags: [Captures]
summary: Eigene Captures auflisten
operationId: listCaptures
description: |
Listet die Captures des Accounts, paginiert per Cursor.
parameters:
- in: query
name: status
schema:
type: string
enum: [queued, rendering, ready, failed]
description: Nur Captures mit diesem Status
- in: query
name: reference
schema:
type: string
description: Filter auf Custom-Reference (z.B. Mandanten-ID)
- in: query
name: limit
schema:
type: integer
minimum: 1
maximum: 100
default: 25
- in: query
name: cursor
schema:
type: string
description: Cursor aus vorheriger Response
responses:
"200":
description: Paginierte Liste
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/Capture"
next_cursor:
type: string
nullable: true
"401":
$ref: "#/components/responses/Unauthorized"
# ════════════════════════════════════════════════════════════════════════
/captures/{id}:
get:
tags: [Captures]
summary: Capture-Status / Detail
operationId: getCapture
description: |
Liefert das Capture-Objekt mit aktuellem `status`. Solange `queued` oder
`rendering`, fehlen `pdf_url` und `timestamp`.
parameters:
- $ref: "#/components/parameters/CaptureId"
responses:
"200":
description: Capture-Detail
content:
application/json:
schema:
$ref: "#/components/schemas/Capture"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/CaptureNotFound"
# ════════════════════════════════════════════════════════════════════════
/captures/{id}/pdf:
get:
tags: [Captures]
summary: PDF herunterladen
operationId: downloadCapturePdf
description: |
Liefert PDF/A-3 mit eingebettetem RFC-3161-Zeitstempel.
Verfügbar erst, wenn `status: ready`.
parameters:
- $ref: "#/components/parameters/CaptureId"
responses:
"200":
description: PDF-Datei
headers:
Content-Disposition:
schema:
type: string
example: attachment; filename="capture_550e....pdf"
content:
application/pdf:
schema:
type: string
format: binary
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/CaptureNotFound"
"409":
description: Capture noch nicht fertig
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
# ════════════════════════════════════════════════════════════════════════
/verify:
post:
tags: [Verify]
summary: Capture / PDF verifizieren
operationId: verifyCapture
description: |
Prüft, ob ein Capture / PDF noch gültig ist (Hash, Zeitstempel,
Audit-Chain).
**Kostenlos für alle Tarife. Keine Quota-Belastung.**
Drei Eingabe-Modi:
- Per Capture-ID (`source: "550e..."`)
- Per Verify-URL (`source: "https://verify.hightrusted.net/c/..."`)
- Per PDF-Upload (multipart/form-data, Feld `pdf`)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [source]
properties:
source:
type: string
description: Capture-ID oder Verify-URL
examples:
by_id:
summary: Per Capture-ID
value:
source: 550e8400-e29b-41d4-a716-446655440000
by_url:
summary: Per Verify-URL
value:
source: https://verify.hightrusted.net/c/550e...
multipart/form-data:
schema:
type: object
properties:
pdf:
type: string
format: binary
description: PDF-Datei zur lokalen Verifikation
responses:
"200":
description: Verifikations-Ergebnis
content:
application/json:
schema:
$ref: "#/components/schemas/VerifyResult"
"404":
description: Quelle nicht auffindbar
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
# ════════════════════════════════════════════════════════════════════════
/usage:
get:
tags: [Account]
summary: Eigener Verbrauch
operationId: getUsage
description: Aktueller Verbrauch und Restkontingent für die laufende Periode.
responses:
"200":
description: Verbrauchs-Status
content:
application/json:
schema:
$ref: "#/components/schemas/Usage"
"401":
$ref: "#/components/responses/Unauthorized"
# ════════════════════════════════════════════════════════════════════════════
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: ht_live_xxx | ht_test_xxx
parameters:
CaptureId:
in: path
name: id
required: true
schema:
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
description: ID des Captures
schemas:
# ─────────────────────────────────────────────────────────
CaptureRequest:
type: object
required: [url]
properties:
url:
type: string
format: uri
description: Zu erfassende URL. Pflicht.
example: https://example.com/article/123
mode:
type: string
enum: [sync, async, webhook]
default: sync
webhook_url:
type: string
format: uri
description: Pflicht bei `mode=webhook`
reference:
type: string
maxLength: 128
description: Eigene Referenz (Mandanten-ID, Akten-Nr.)
viewport:
type: object
properties:
width:
type: integer
minimum: 360
maximum: 2560
default: 1280
height:
type: integer
minimum: 480
maximum: 4096
default: 800
wait_until:
type: string
enum: [load, domcontentloaded, networkidle]
default: networkidle
full_page:
type: boolean
default: true
description: Komplette Seite scrollen
co_branding:
type: object
description: Eigenes Logo & Footer im PDF (ab Tarif Growth)
properties:
logo_url:
type: string
format: uri
footer_text:
type: string
maxLength: 200
# ─────────────────────────────────────────────────────────
Capture:
type: object
properties:
id:
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
status:
type: string
enum: [queued, rendering, ready, failed]
url:
type: string
format: uri
description: Aufgelöste End-URL (nach Shortlink-Auflösung)
url_input:
type: string
format: uri
description: Vom Caller übergebene URL (für die Beweisführung)
reference:
type: string
nullable: true
pdf_url:
type: string
format: uri
nullable: true
description: Authentifizierter Download-Endpoint
verify_url:
type: string
format: uri
description: Öffentliche Verifikations-URL
timestamp:
$ref: "#/components/schemas/Timestamp"
document_hash:
type: string
example: sha256:a3f2...
created_at:
type: string
format: date-time
completed_at:
type: string
format: date-time
nullable: true
required: [id, status, created_at]
# ─────────────────────────────────────────────────────────
CaptureQueued:
type: object
properties:
id:
type: string
format: uuid
status:
type: string
enum: [queued]
poll_url:
type: string
format: uri
description: Endpoint für Status-Polling (nur bei `mode=async`)
created_at:
type: string
format: date-time
required: [id, status, created_at]
# ─────────────────────────────────────────────────────────
Timestamp:
type: object
properties:
tsa:
type: string
example: open-tsa.eu
issued_at:
type: string
format: date-time
algorithm:
type: string
example: SHA-256
serial:
type: string
example: "0x4F8B..."
# ─────────────────────────────────────────────────────────
VerifyResult:
type: object
properties:
valid:
type: boolean
capture_id:
type: string
format: uuid
document_hash:
type: string
timestamp:
type: object
properties:
tsa:
type: string
issued_at:
type: string
format: date-time
valid:
type: boolean
chain_valid:
type: boolean
audit_log:
type: object
properties:
present:
type: boolean
chain_valid:
type: boolean
anchored_in_open_tsa:
type: boolean
details:
type: array
items:
type: object
properties:
check:
type: string
example: tsr_signature
status:
type: string
enum: [pass, fail, warn]
message:
type: string
required: [valid]
# ─────────────────────────────────────────────────────────
Usage:
type: object
properties:
plan:
type: string
example: growth
period_start:
type: string
format: date
period_end:
type: string
format: date
included_calls:
type: integer
used_calls:
type: integer
overage_calls:
type: integer
overage_cost_eur:
type: number
format: float
rate_limit:
type: object
properties:
per_minute:
type: integer
current_minute:
type: integer
# ─────────────────────────────────────────────────────────
Error:
type: object
properties:
error:
type: object
properties:
code:
type: string
example: quota_exceeded
message:
type: string
request_id:
type: string
example: req_4xK7p2nQ8mR9
required: [code, message, request_id]
required: [error]
responses:
BadRequest:
description: Validierungsfehler
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Unauthorized:
description: API-Key fehlt, abgelaufen oder widerrufen
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
QuotaExceeded:
description: Monats-Quota voll, kein PAYG aktiv
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
CaptureNotFound:
description: Capture-ID existiert nicht oder gehört zu fremdem Key
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
UnreachableUrl:
description: Quellseite konnte nicht geladen werden
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
RateLimited:
description: |
Zu viele Anfragen pro Minute. Header `Retry-After` beachten.
headers:
Retry-After:
schema:
type: integer
description: Sekunden bis zum nächsten erlaubten Request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
TsaUnavailable:
description: |
open-tsa.eu temporär nicht erreichbar. Automatischer Retry empfohlen.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
webhooks:
capture.ready:
post:
tags: [Webhook-Events]
summary: Capture fertig (PDF und Zeitstempel verfügbar)
requestBody:
content:
application/json:
schema:
type: object
properties:
event:
type: string
enum: [capture.ready]
capture:
$ref: "#/components/schemas/Capture"
required: [event, capture]
responses:
"200":
description: Webhook empfangen
capture.failed:
post:
tags: [Webhook-Events]
summary: Capture fehlgeschlagen
requestBody:
content:
application/json:
schema:
type: object
properties:
event:
type: string
enum: [capture.failed]
capture:
$ref: "#/components/schemas/Capture"
error:
$ref: "#/components/schemas/Error"
required: [event, capture]
responses:
"200":
description: Webhook empfangen