API-Referenz

Sellat API v2

Alles, was die Web-App kann, aus deinem eigenen System: den SHA-256-Fingerabdruck einer Datei registrieren, in Polygon und Bitcoin verankern, den qualifizierten (eIDAS, RFC 3161) Zeitstempel anfordern, Zertifikat und Beweispaket laden und das Original verwahren. REST und JSON, mit einem API-Key.

Basis-URL https://sellat.app/api/v2

Einführung

Die API arbeitet mit dem Fingerabdruck der Datei, nicht mit der Datei: Du berechnest den SHA-256 bei dir und sendest nur diese 64 Zeichen. Das Original verlässt deinen Rechner nicht, außer du gibst es in Verwahrung.

Jeder Nachweis kommt in einen Merkle-Batch, der in Polygon verankert wird; derselbe Batch wird in Bitcoin bestätigt. Mit dem kostenlosen Konto schließen die Batches alle 4 Stunden zu festen Zeiten (00:00, 04:00, 08:00, 12:00, 16:00 und 20:00 UTC): Jeder Batch sammelt die Nachweise dieser Stunden und wird in einer einzigen Transaktion verankert. Auf Wunsch erhält der Fingerabdruck zusätzlich den qualifizierten Zeitstempel eines qualifizierten Vertrauensdiensteanbieters der EU-Vertrauensliste, im Moment der Anfrage.

Alle Routen hängen an der Basis-URL und sprechen JSON. Nachweise aus der API und aus dem Web gehören zum selben Konto und erscheinen im selben Dashboard.

Sie ist für Aufrufe vom Server gedacht: Sie sendet keine CORS-Header, ein Browser kann sie also nicht von einer anderen Website aus aufrufen, und dein Key darf nie in Code stehen, der einen Browser erreicht.

Authentifizierung

Jede Anfrage trägt deinen Key im Header Authorization: Bearer sellat_.... Keys erstellst und widerrufst du in deinem Dashboard; ein Konto kann bis zu 5 aktive Keys haben, einen je Integration.

Ohne Key oder mit einem widerrufenen antwortet die API 401 unauthorized mit dem Header WWW-Authenticate: Bearer. Die einzige öffentliche Route ist der portable Nachweis (/proof/{id}.json).

Anfrage
curl https://sellat.app/api/v2/account \
  -H "Authorization: Bearer sellat_3f9c..."

Schnellstart

Berechne den Fingerabdruck, erstelle den Nachweis mit qualifiziertem Zeitstempel und bewahre die zurückgegebene id auf. Sobald sein Batch geschlossen und verankert ist, ist er anchored und alle URLs funktionieren; das Feld anchoring der Antwort sagt, wann der Batch schließt (mit dem kostenlosen Konto zur nächsten festen UTC-Zeit, höchstens 4 Stunden später). Der qualifizierte Zeitstempel wartet nicht auf den Batch: Er trägt den Zeitpunkt seiner Ausstellung.

Ohne "qualified": true ist der Nachweis genauso überprüfbar (Polygon und Bitcoin) und verbraucht keinen Zeitstempel. Den Zeitstempel kannst du später mit POST /proofs/{id}/qualified anfordern.

Terminal
# Your key, from the dashboard
export SELLAT_API_TOKEN=sellat_...

# The fingerprint: the file stays on your machine
HASH=$(sha256sum contract.pdf | cut -d' ' -f1)

# The proof, with the qualified seal

curl https://sellat.app/api/v2/proofs \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"hash\":\"$HASH\",\"name\":\"contract.pdf\",\"qualified\":true}"

Status eines Nachweises

Das Feld state sagt, was Sellat im jeweiligen Moment weiß: Eine Transaktion wird nie als bestätigt gemeldet, bevor sie geprüft ist.

Die Bitcoin-Bestätigung (bitcoin) läuft getrennt: pending, bis OpenTimestamps sie in einen Block aufnimmt, dann confirmed mit Blockhöhe. Der qualifizierte Zeitstempel (qualified) hängt nicht vom Status ab: Er wird sofort ausgestellt.

StatusBedeutung
queuedEmpfangen. Wartet, bis sein Batch schließt: mit dem kostenlosen Konto zur nächsten festen UTC-Zeit, alle 4 Stunden (anchoring.batch_closes_after).
batchedIn einem Merkle-Batch, der Pfad steht fest. Wartet auf die Verankerung in Polygon.
anchoredVerankert: Die Transaktion steht in einem Polygon-Block. proof.json ist verfügbar.
confirmedDer Block hat 12 Bestätigungen.

Fehler

Ein Fehler ist immer JSON mit error.code (stabil, für deinen Code) und error.message (für Menschen). Manche enthalten weitere Felder, etwa seals_remaining und buy_url bei einem 402. Der portable Nachweis ist öffentlich und antwortet {"error": "..."}.

Bricht das Gateway eine Anfrage ab, bevor sie die API erreicht (zu viele Anfragen von einer IP oder ein Body über 25 MB), hat dieses 429 oder 413 nicht dieses Format.

CodeHTTPWas passiert ist
invalid_request400Ein Feld fehlt oder hat nicht das erwartete Format. Die Meldung sagt, welches.
unauthorized401Kein Key, ein fehlerhafter oder ein widerrufener Key.
payment_required402Keine qualifizierten Zeitstempel mehr. Enthält seals_remaining und buy_url.
key_without_account403Der Key gehört zu keinem Konto (alte Keys).
not_found404Der Nachweis existiert nicht oder gehört einem anderen Konto.
conflict409Ein anderes Konto hat diese Bytes zuerst registriert, oder das Original ist noch nicht gespeichert.
hash_mismatch409Die hochgeladene Datei hat nicht den Fingerabdruck des Nachweises.
file_too_large413Die Datei überschreitet das Limit pro Datei deines Plans (max_file_bytes).
rate_limited429Zu viele Anfragen pro Minute. Beachte Retry-After.
quota_exceeded429Tageslimit für Nachweise erreicht. Retry-After bis 00:00 UTC.
download_quota_exceeded429Tageslimit für Downloads des Originals erreicht.
integrity_failed500Das gespeicherte Original passt nicht mehr zu seinem Fingerabdruck und wird nicht ausgeliefert.
unavailable502 · 503Ein interner Dienst antwortet nicht. Später erneut versuchen.
seal_unavailable503Die Zeitstempelstelle ist gerade nicht erreichbar.
custody_unavailable503Die Verwahrung ist gerade nicht verfügbar.
custody_quota_exceeded507Kein Verwahrungsplatz mehr in deinem Konto.
Antwort
{
  "error": {
    "code": "payment_required",
    "message": "No qualified seals left on this account.",
    "seals_remaining": 0,
    "buy_url": "https://sellat.app/precios"
  }
}

Limits

Jedes Konto kann pro Tag eine bestimmte Zahl von API-Nachweisen erstellen, über alle Keys zusammen. Nachweise aus dem Web zählen nicht, und das erneute Senden von Bytes, die dir schon gehören, auch nicht. Der Tag beginnt um 00:00 UTC neu.

Am Limit antwortet POST /proofs mit 429 quota_exceeded, einem quota-Objekt und Retry-After. Außerdem hat jede Route eine Bremse pro Minute (etwa 60 Erstellungen und 10 Zeitstempel pro Minute), die mit 429 rate_limited antwortet, und das Gateway erlaubt bis zu 120 Anfragen pro Minute und IP. Ein Body darf höchstens 25 MB groß sein.

Qualifizierte Zeitstempel haben kein Tageslimit: Jeder verbraucht einen Zeitstempel aus deinem Guthaben. Was dir bleibt, siehst du jederzeit mit GET /account; brauchst du mehr Volumen, schreib an hola@sellat.app.

Kostenloses KontoMit Verwahrung
API-Nachweise pro Tag1.0001.000
Verankerung in Polygon und BitcoinAlle 4 Stunden, zu festen UTC-ZeitenAlle 4 Stunden, zu festen UTC-Zeiten
Aktive Keys55
Original in Verwahrung, pro Datei10 MB50 MB
Verwahrung insgesamt1 GB20 GB
Downloads des Originals pro Tag1050
Antwort
HTTP/1.1 429 Too Many Requests
Retry-After: 41231

{
  "error": {
    "code": "quota_exceeded",
    "message": "Daily limit reached: 1000 proofs per account per UTC day across all its keys. Write to hola@sellat.app for more.",
    "quota": {
      "limit": 1000,
      "used": 1000,
      "resets_in_seconds": 41231
    }
  }
}

Bezahlen pro Aufruf (x402, KI-Agenten)

Ein KI-Agent mit Wallet kann die API ohne Konto und ohne Schlüssel nutzen: Er bezahlt jeden Aufruf in USDC im Base-Netzwerk mit dem Protokoll x402 (v2, Schema exact). Bezahlen ist die Anmeldung: Die zahlende Wallet ist das Konto.

Rufe ohne Schlüssel und ohne Zahlung auf: Die Antwort ist 402 mit dem Header PAYMENT-REQUIRED (Base64-JSON) mit genauem Betrag, Token, Netzwerk und Empfängeradresse. Signiere eine EIP-3009-transferWithAuthorization über diesen Betrag und wiederhole die Anfrage mit dem Header PAYMENT-SIGNATURE. Die Zahlung wird vor der Arbeit geprüft und danach abgewickelt: Schlägt der Aufruf fehl, wird nichts berechnet. Die Antwort enthält PAYMENT-RESPONSE mit der Transaktion und ein Objekt payment im Body.

Der erste Nachweis liefert einmalig api_key, einen Schlüssel nur zum Lesen: Er liest die Nachweise der Wallet und lädt Zertifikat und Beweispaket herunter. Nachweise erstellt er nicht, denn in diesem Kanal ist nichts kostenlos. Dieselben Bytes noch einmal von derselben Wallet werden nicht berechnet. Die Preisliste steht unter GET /x402.

Mit der Zahlung werden die Nutzungsbedingungen angenommen: Jedes 402-Angebot verlinkt sie und nennt ihre Version (im Satz error von PAYMENT-REQUIRED und im Objekt terms des Bodys), und Sellat speichert diese Version zur Zahlung.

AufrufPreisWas du bekommst
POST /proofs0,01 USDCEin Nachweis im schnellen Batch (Minuten), mit Zertifikat, proof.json und Beweis-ZIP
POST /proofs/{id}/qualified0,90 USDCDer qualifizierte eIDAS-Zeitstempel auf einem Nachweis deiner Wallet, in Sekunden
POST /wallet/key0,01 USDCEin neuer Schlüssel nur zum Lesen für das Konto der Wallet

Setze das Maximum pro Zahlung auf 1 USDC. Viele x402-Clients begrenzen den Betrag jeder Zahlung: @x402/fetch und @x402/axios (v2) standardmäßig auf 1 USDC, andere auf den Wert, den du setzt. Der qualifizierte Zeitstempel kostet 0,90 USDC: Mit einem niedrigeren Maximum lehnt dein Client die Zahlung ab, bevor er sie signiert. Die Pakete der Version 1 (x402-fetch, x402-axios, standardmäßig 0,10 USDC) funktionieren nicht: Sellat spricht x402 v2.

Fehler unterscheiden. Ein 402 mit payment_required sendet Sellat, wenn die Anfrage ohne PAYMENT-SIGNATURE ankommt: Das ist das Angebot, kein Fehler. payment_invalid heißt, dass eine Signatur ankam, aber nicht gültig ist (Betrag, Netzwerk, Guthaben oder eine bereits benutzte Autorisierung; die Meldung sagt, welches), und payment_failed, dass die Arbeit erledigt wurde, die Zahlung aber nicht abgewickelt wurde. Lehnt dein Client den Betrag wegen seines Limits ab, erreicht die bezahlte Anfrage Sellat nie: Es gibt keine Antwort von uns, nur eine Ausnahme in deinem Client („rejected by spendControls.maxAmountPerPayment“ in @x402/fetch v2, „Payment amount exceeds maximum allowed“ in thirdweb).

Vollständiges Beispiel (TypeScript)
// npm install @x402/fetch @x402/evm viem
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

const client = new x402Client();
// Per-payment cap. The qualified timestamp costs 0.90 USD: a cap below
// that makes the client refuse to sign, and Sellat never sees the request.
// "$1" is the v2 default; set it explicitly so nobody lowers it by accident.
client.setSpendControls({ maxAmountPerPayment: "$1" });
client.register("eip155:8453", new ExactEvmScheme(privateKeyToAccount(process.env.AGENT_KEY as `0x${string}`)));
const pay = wrapFetchWithPayment(fetch, client);

// 1. Hash locally: only the SHA-256 leaves your machine.
const hash = createHash("sha256").update(readFileSync("contract.pdf")).digest("hex");

// 2. The proof (0.01 USD).
const proofRes = await pay("https://sellat.app/api/v2/proofs", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ hash, name: "contract.pdf" }),
});
if (!proofRes.ok) throw new Error(await proofRes.text());
const proof = await proofRes.json();
// proof.id, proof.state ("queued", then "anchored"), proof.urls.proof_json,
// proof.payment.transaction; proof.api_key comes once: keep it (read-only).

// 3. The qualified eIDAS timestamp on that proof (0.90 USD).
const sealRes = await pay(`https://sellat.app/api/v2/proofs/${proof.id}/qualified`, { method: "POST" });
if (!sealRes.ok) throw new Error(await sealRes.text());
const seal = await sealRes.json();
// seal.qualified.state === "issued", seal.qualified.time, seal.qualified.tsr_url

// thirdweb sets no cap unless asked: wrapFetchWithPayment(fetch, client, wallet, { maxValue: 1_000_000n }) from "thirdweb/x402" (1 USDC in base units).

Idempotenz

Sende bei POST /proofs einen Header Idempotency-Key (bis 200 Zeichen), dann kannst du gefahrlos wiederholen: Hast du ihn mit demselben API-Key schon benutzt, bekommst du den ursprünglichen Nachweis mit 200 und "created": false zurück, egal was der neue Body sagt, und es wird kein Zeitstempel ausgestellt.

Ohne den Header liefert das erneute Senden von Bytes, die deinem Konto schon gehören, ebenfalls den bestehenden Nachweis (200), ohne Name oder Metadaten zu ändern. Hat aber ein anderes Konto diese Bytes zuerst registriert, erzeugt jede Anfrage einen neuen Nachweis mit limitations: Wenn du wiederholen könntest, nutze immer Idempotency-Key.

Auch der Zeitstempel wird nie doppelt ausgestellt: Ein Nachweis hat genau einen, und eine erneute Anfrage liefert denselben, ohne einen weiteren zu verbrauchen.

Ohne Sellat prüfen

Ein Sellat-Nachweis braucht Sellat nicht, um geprüft zu werden. Mit der Datei und ihrer proof.json berechnet der Open-Source-Verifier den Fingerabdruck und den Merkle-Pfad neu und liest die Verankerung in der öffentlichen Blockchain.

Den qualifizierten Zeitstempel prüfst du mit OpenSSL, mit der Datei, der .tsr und dem Zertifikat der Zeitstempelstelle, das im Beweispaket liegt. -partial_chain ist nötig, weil die Vertrauensliste das Zertifikat der Stelle selbst veröffentlicht, nicht das einer CA.

Terminal
# 1. The proof: file + proof.json + the public blockchain
curl https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json -o proof.json
npx sellat-verify contract.pdf proof.json

# 2. The qualified seal: file + .tsr + the authority's certificate
#    (autoridad-sellado.pem comes inside evidence.zip)
openssl ts -verify -in seal.tsr -data contract.pdf \
  -CAfile autoridad-sellado.pem -partial_chain

Nachweise

Nachweis erstellen

POST /proofs

Registriert den SHA-256-Fingerabdruck einer Datei und gibt den Nachweis zurück. Mit "qualified": true stellt sie im selben Aufruf auch den qualifizierten (eIDAS) Zeitstempel aus und verbraucht dafür einen Zeitstempel des Kontos.

Parameter

hash string · Body Pflicht
Der SHA-256-Fingerabdruck der Datei: 64 Hexadezimalzeichen.
name string · Body optional
Wie Dashboard und Zertifikat sie nennen. Gespeichert wird nur der Dateiname, ohne Pfad. Standard: die ersten 12 Zeichen des Hashs.
metadata object · Body optional
Ein eigenes JSON-Objekt, bis 4 KB. Wird mit dem Nachweis gespeichert, aber nie zurückgegeben oder veröffentlicht.
size integer · Body optional
Dateigröße in Bytes. Informativ.
mime_type string · Body optional
Typ der Datei, genutzt beim Ausliefern des Originals, wenn du es verwahrst. Standard: application/octet-stream.
qualified boolean · Body optional
true stellt zusätzlich den qualifizierten (eIDAS) Zeitstempel aus.
Idempotency-Key string · Header optional
Wiederholen ohne Duplikate (siehe Idempotenz).

Antworten

201
Nachweis erstellt.
200
Er existierte schon: Diese Bytes gehören bereits deinem Konto, oder du hast einen Idempotency-Key wiederholt. Enthält "created": false.
400
invalid_request: Ein Feld fehlt oder ist ungültig.
401
unauthorized.
402
payment_required: Zeitstempel angefordert, aber keiner mehr übrig. Es wird nichts geschrieben.
409
conflict: Zeitstempel für Bytes angefordert, die ein anderes Konto zuerst registriert hat.
429
quota_exceeded (Tageslimit) oder rate_limited (pro Minute).
503
seal_unavailable oder unavailable. Wurde der Nachweis schon erstellt, enthält der Fehler seine proof_id.

Antwortet die Zeitstempelstelle langsam, wird der Nachweis trotzdem erstellt und qualified kommt als {"state": "pending"}: Fordere ihn mit POST /proofs/{id}/qualified erneut an, kostenlos.

Hat ein anderes Konto diese Bytes zuerst registriert, wird der Nachweis erstellt und verankert, aber ohne eigenes Zertifikat: Die Zertifikats-URLs sind null und die Antwort enthält "limitations": ["hash_registered_by_another_account"].

Anfrage
curl https://sellat.app/api/v2/proofs \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: contract-2026-0142" \
  -d '{
    "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
    "name": "contract.pdf",
    "qualified": true,
    "metadata": { "ref": "case-2026-0142" }
  }'
Antwort
{
  "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "created": true,
  "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
  "algorithm": "SHA-256",
  "name": "contract.pdf",
  "state": "queued",
  "received_at": "2026-09-30T09:12:03.418Z",
  "anchoring": {
    "policy": "scheduled",
    "batch_closes_after": "2026-09-30T12:00:00.000Z"
  },
  "anchors": [],
  "bitcoin": {
    "state": "pending",
    "block_height": null
  },
  "qualified": {
    "state": "issued",
    "authority": "FNMT-RCM",
    "time": "2026-09-30T09:12:05.000Z",
    "serial_number": "175755C77B4239226AA0428FE1C107C1",
    "policy_oid": "0.4.0.2023.1.1",
    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
  },
  "original": null,
  "urls": {
    "self": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
    "proof_json": "https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json",
    "certificate": "https://sellat.app/certificate/3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b",
    "certificate_pdf": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf",
    "evidence_zip": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip"
  }
}

Nachweis abrufen

GET /proofs/{id}

Gibt den Nachweis mit Status, Verankerungen, Bitcoin-Bestätigung, dem Zeitstempel falls vorhanden, dem Original falls verwahrt, und allen URLs zurück.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.

Antworten

200
Der Nachweis.
401
unauthorized.
404
not_found: existiert nicht oder gehört einem anderen Konto.
Anfrage
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Antwort
{
  "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
  "algorithm": "SHA-256",
  "name": "contract.pdf",
  "state": "anchored",
  "received_at": "2026-09-30T09:12:03.418Z",
  "anchoring": {
    "policy": "scheduled",
    "batch_closes_after": "2026-09-30T12:00:00.000Z"
  },
  "anchors": [
    {
      "network": "Polygon Mainnet",
      "chain_id": 137,
      "state": "anchored",
      "tx_hash": "0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a",
      "block_number": 94653624,
      "block_timestamp": "2026-09-30T12:01:44.000Z",
      "explorer_url": "https://polygonscan.com/tx/0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a"
    }
  ],
  "bitcoin": {
    "state": "pending",
    "block_height": null
  },
  "qualified": {
    "state": "issued",
    "authority": "FNMT-RCM",
    "time": "2026-09-30T09:12:05.000Z",
    "serial_number": "175755C77B4239226AA0428FE1C107C1",
    "policy_oid": "0.4.0.2023.1.1",
    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
  },
  "original": null,
  "urls": {
    "self": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
    "proof_json": "https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json",
    "certificate": "https://sellat.app/certificate/3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b",
    "certificate_pdf": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf",
    "evidence_zip": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip"
  }
}

Nachweise auflisten

GET /proofs

Die Nachweise deines Kontos, neueste zuerst: aus der API und aus dem Web. Jedes Element in data ist ein vollständiger Nachweis wie bei GET /proofs/{id}.

Für die nächste Seite übergib den next_cursor der vorigen. Ist er null, gibt es keine weiteren.

Parameter

limit integer · Query optional
Nachweise pro Seite, 1 bis 100. Standard: 25.
cursor string · Query optional
Der next_cursor der vorigen Seite.

Antworten

200
data und next_cursor.
400
invalid_request: ungültiges limit oder cursor.
401
unauthorized.
Anfrage
curl "https://sellat.app/api/v2/proofs?limit=25" \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Antwort
{
  "data": [
    {
      "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
      "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
      "name": "contract.pdf",
      "state": "confirmed",
      "received_at": "2026-09-30T09:12:03.418Z"
    }
  ],
  "next_cursor": "1843"
}

Qualifizierter Zeitstempel (eIDAS)

Qualifizierten Zeitstempel anfordern

POST /proofs/{id}/qualified

Stellt einen qualifizierten RFC-3161-Zeitstempel über den Fingerabdruck der Datei aus, signiert von einem qualifizierten Vertrauensdiensteanbieter auf der EU-Vertrauensliste. Nach Art. 41 Abs. 2 der eIDAS-Verordnung genießt ein qualifizierter elektronischer Zeitstempel die Vermutung der Richtigkeit des Datums und der Zeit, die er angibt, und der Unversehrtheit der Daten.

Verbraucht einen Zeitstempel des Kontos: den Willkommens-Zeitstempel oder einen aus deinen Paketen. Die API belastet nie eine Karte und öffnet keine Zahlung; Pakete kaufst du auf der Preisseite.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.

Antworten

200
Zeitstempel ausgestellt, oder er war schon da ("created": false, nichts verbraucht). Enthält seals_remaining.
202
Der Zeitstempel ist bezahlt, aber die Stelle hat nicht geantwortet. Erneut aufrufen (Retry-After: 30): Er wird kostenlos abgeschlossen.
401
unauthorized.
402
payment_required: keine Zeitstempel mehr. Enthält seals_remaining und buy_url.
404
not_found.
409
conflict: Ein anderes Konto hat diese Bytes zuerst registriert.
503
seal_unavailable.

Ein Nachweis hat genau einen Zeitstempel, für immer. Scheitert die Ausstellung endgültig, kommt der Zeitstempel zurück in dein Guthaben.

Anfrage
curl -X POST https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Antwort
{
  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "created": true,
  "qualified": {
    "state": "issued",
    "authority": "FNMT-RCM",
    "time": "2026-09-30T09:12:05.000Z",
    "serial_number": "175755C77B4239226AA0428FE1C107C1",
    "policy_oid": "0.4.0.2023.1.1",
    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
  },
  "seals_remaining": 4
}

Zeitstempel abrufen

GET /proofs/{id}/qualified

Gibt nur den Zeitstempel des Nachweises zurück, oder null, wenn er noch keinen hat. Stellt nichts aus und verbraucht nichts.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.

Antworten

200
proof_id und qualified.
401
unauthorized.
404
not_found.
Anfrage
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Antwort
{
  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "qualified": {
    "state": "issued",
    "authority": "FNMT-RCM",
    "time": "2026-09-30T09:12:05.000Z",
    "serial_number": "175755C77B4239226AA0428FE1C107C1",
    "policy_oid": "0.4.0.2023.1.1",
    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
  }
}

Token laden (.tsr)

GET /proofs/{id}/qualified.tsr

Der Zeitstempel genau so, wie die Stelle ihn geliefert hat: eine RFC-3161-TimeStampResp in DER. Diese Datei kann ein Gutachter oder ein Gericht ohne Sellat validieren.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.

Antworten

200
Die .tsr (application/timestamp-reply).
401
unauthorized.
404
not_found: Der Nachweis existiert nicht oder hat noch keinen Zeitstempel.
Anfrage
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o seal.tsr
Antwort (Datei)
HTTP/1.1 200 OK
Content-Type: application/timestamp-reply
Content-Disposition: attachment; filename="sellat-6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e-qualified-timestamp.tsr"

<RFC 3161 TimeStampResp, DER>

Zertifikat und Beweise

PDF-Zertifikat

GET /proofs/{id}/certificate.pdf

Das Zertifikat des Nachweises, für Menschen: Fingerabdruck, Datum, Verankerungen und der qualifizierte Zeitstempel falls vorhanden, mit Anleitung zur Prüfung.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.
lang string · Query optional
Sprache des Zertifikats: es, en, de oder fr. Jeder andere Wert ergibt en.

Antworten

200
Das PDF.
401
unauthorized.
404
not_found.
409
conflict: Ein anderes Konto hat diese Bytes zuerst registriert, der Nachweis hat kein eigenes Zertifikat.
Anfrage
curl "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf?lang=es" \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o certificate.pdf
Antwort (Datei)
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="certificado-3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b.pdf"

<PDF>

Beweispaket

GET /proofs/{id}/evidence.zip

Ein ZIP mit allem, was man braucht, um den Nachweis ohne Sellat zu verteidigen: Zertifikat, proof.json, Zeitstempel und Zertifikat der Stelle falls vorhanden, die Anleitung und, wenn verwahrt, das Original.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.
lang string · Query optional
Sprache der Texte im Paket: es, en, de oder fr.

Antworten

200
Das ZIP.
401
unauthorized.
404
not_found.
409
conflict: Ein anderes Konto hat diese Bytes zuerst registriert.

Das Original beizulegen verbraucht einen der Downloads des Tages. Sind keine mehr übrig, kommt das Paket ohne Original, und das README sagt es.

Anfrage
curl "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip?lang=es" \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o evidence.zip
Antwort (Datei)
HTTP/1.1 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename="sellat-evidencia-3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b.zip"

<ZIP>

Verwahrung des Originals

Original hinterlegen

PUT /proofs/{id}/original

Speichert die Datei neben ihrem Nachweis, auf Servern in der Europäischen Union. Sellat berechnet den Fingerabdruck des Empfangenen neu und lehnt ab, wenn er nicht zum Nachweis passt.

Sende die rohen Bytes mit dem Content-Type der Datei oder multipart/form-data im Feld file. POST funktioniert wie PUT.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.
file binary · Body Pflicht
Die Datei. Bis 10 MB mit dem kostenlosen Konto und 50 MB mit Verwahrung, und höchstens 25 MB pro Anfrage.

Antworten

201
Gespeichert. Gibt den Nachweis mit original zurück.
200
War bereits gespeichert.
400
invalid_request: Body leer oder nicht lesbar.
401
unauthorized.
404
not_found.
409
hash_mismatch (nicht die Datei des Nachweises) oder conflict (Bytes eines anderen Kontos).
413
file_too_large: über dem Limit pro Datei deines Plans. Enthält max_file_bytes.
502
unavailable: Der Speicher hat nicht geantwortet.
503
custody_unavailable.
507
custody_quota_exceeded: kein Platz mehr in deinem Konto. Enthält used_bytes und max_account_bytes.

Die Verwahrung ist optional: Der Nachweis gilt auch ohne Original. Sie ergänzt die Aufbewahrung, sie bedingt den Nachweis nicht.

Anfrage
curl -X PUT https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -H "Content-Type: application/pdf" \
  --data-binary @contract.pdf
Antwort
{
  "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "state": "anchored",
  "original": {
    "stored": true,
    "file_name": "contract.pdf",
    "size_bytes": 184320,
    "mime_type": "application/pdf",
    "stored_at": "2026-09-30T09:15:10.000Z",
    "url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original"
  }
}

Original laden

GET /proofs/{id}/original

Gibt die verwahrte Datei zurück. Sellat berechnet ihren Fingerabdruck beim Ausliefern neu und verweigert die Auslieferung, wenn er nicht mehr passt. Jeder Download zählt zum Tageslimit deines Plans; der Header X-Sellat-Custody-Downloads-Remaining sagt, wie viele übrig sind.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.

Antworten

200
Die Datei, mit Typ und Namen.
401
unauthorized.
404
Kein Original in Verwahrung.
409
conflict: Das Original ist noch nicht gespeichert.
429
download_quota_exceeded: Tageslimit für Downloads. Neustart um 00:00 UTC.
500
integrity_failed: Das Gespeicherte passt nicht mehr zum Fingerabdruck.
502
unavailable.
Anfrage
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o contract.pdf
Antwort (Datei)
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="contract.pdf"
X-Sellat-Custody-Downloads-Remaining: 9

<the original bytes>

Verwahrung beenden

DELETE /proofs/{id}/original

Löscht das verwahrte Original. Der Nachweis ändert sich nicht: Er bleibt verankert, gestempelt und überprüfbar.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.

Antworten

200
"original": null und "proof_unaffected": true. "purged": false heißt, dass die physische Löschung in der Warteschlange steht.
401
unauthorized.
404
Kein Original in Verwahrung.
Anfrage
curl -X DELETE https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Antwort
{
  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "original": null,
  "purged": true,
  "proof_unaffected": true
}

Konto

Dein Konto

GET /account

Was der aufrufende Key kann: der Plan, deine übrigen Zeitstempel (Willkommen und Pakete), Verwahrungsplatz und Downloads, die heutigen Nachweise und ihr Limit, und die aktiven Keys. Frag es vor einem großen Batch ab.

Der Block connector enthält die Limits für verbundene Shops (WooCommerce).

Antworten

200
Das Konto.
401
unauthorized.
Anfrage
curl https://sellat.app/api/v2/account \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Antwort
{
  "plan": "free",
  "plan_name": "Free",
  "seals": {
    "available": true,
    "remaining": 1,
    "welcome_remaining": 1,
    "pack_remaining": 0,
    "price_eur": 6,
    "buy_url": "https://sellat.app/precios"
  },
  "custody": {
    "available": true,
    "used_bytes": 0,
    "max_account_bytes": 52428800,
    "max_file_bytes": 10485760,
    "downloads_used_today": 0,
    "max_downloads_per_day": 10
  },
  "proofs": {
    "daily_limit": 1000,
    "used_today": 3,
    "resets_in_seconds": 41231
  },
  "connector": {
    "mode_available": "daily",
    "eidas_available": false,
    "custody_available": false,
    "operations": {
      "daily_limit": 1000,
      "resets_in_seconds": 41231
    }
  },
  "keys": {
    "active": 1
  },
  "urls": {
    "dashboard": "https://sellat.app/dashboard",
    "plans": "https://sellat.app/precios#pricing-custody",
    "seals": "https://sellat.app/precios#pricing-seal-title",
    "docs": "https://sellat.app/developers/docs",
    "connect": "https://sellat.app/connect/woocommerce"
  }
}

Portabler Nachweis

Portabler Nachweis (proof.json)

GET /proof/{id}.json Ohne Key

Das Dokument, mit dem jeder den Nachweis ohne Sellat prüfen kann, im öffentlichen Format sellat-proof/2: Fingerabdruck, Blatt, Merkle-Pfad, Wurzel, Polygon-Verankerung und die Bestätigungen (Bitcoin und der qualifizierte Zeitstempel). Er ist absichtlich öffentlich: Wer ihn hat, kann den Nachweis ohne Konto prüfen.

Parameter

id string · Pfad Pflicht
Die id des Nachweises.

Antworten

200
Die proof.json.
202
Noch nicht verankert: {"ready": false, "state": "..."} mit Retry-After: 60.
400
Die id ist keine UUID.
404
Existiert nicht.
Anfrage
curl https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json -o proof.json
Antwort
{
  "schema": "sellat-proof/2",
  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "content": {
    "algorithm": "SHA-256",
    "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f"
  },
  "leaf": {
    "formula": "SHA-256('sellat-leaf:v2:' + proof_id + ':' + content.hash)",
    "value": "31668b4d846680cd920a1cb5be200ab0562909e8778223087c1738746a1cbb26"
  },
  "merkle": {
    "index": 1,
    "path": [
      {
        "position": "left",
        "hash": "9e246a6d6dda91ee3916e149d834cd9a140e3470fbf5b6ec67824705dbbc82d4"
      }
    ],
    "root": "88fa1c4c3587d86d6f713e438c7ae745a91a6e03a5dd1b8282f17f35f30bfa14"
  },
  "anchors": [
    {
      "chain_id": 137,
      "network": "Polygon Mainnet",
      "tx_hash": "0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a",
      "block_number": 94653624,
      "payload": "sellat:v2:88fa1c4c3587d86d6f713e438c7ae745a91a6e03a5dd1b8282f17f35f30bfa14"
    }
  ],
  "attestations": [
    {
      "type": "opentimestamps",
      "state": "submitted"
    },
    {
      "type": "rfc3161-qualified-timestamp",
      "state": "issued"
    }
  ]
}

Fehlt dir etwas, oder brauchst du mehr Volumen? Schreib an hola@sellat.app: Du sprichst mit den Leuten, die den Code geschrieben haben.