- Vollständige OpenAPI 3.1 Spezifikation - Alle Endpoints, Schemas, Webhooks, Error-Responses - Beispiel-Requests für minimal/async/webhook-Modi - Initiale CHANGELOG.md
646 lines
20 KiB
YAML
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
|