| Sector | URL | Compliance |
|---|---|---|
| General | https://relay.paramant.app | — |
| Healthcare | https://health.paramant.app | NEN 7510, DICOM |
| Legal | https://legal.paramant.app | eIDAS, KNB |
| Finance | https://finance.paramant.app | NIS2, DORA |
| IoT | https://iot.paramant.app | IEC 62443 |
Three credential types are in use across different API surfaces:
| Credential | Header / Mechanism | Used for |
|---|---|---|
API key (pgp_ prefix) |
X-Api-Key: pgp_your_key |
Data plane — uploads, downloads, CT log (developer clients) |
Operator key (plk_ prefix) |
X-Api-Key: plk_your_key |
Data plane — unlimited throughput (operator license) |
| Session cookie | Cookie: paramant_user_session=<token> |
/api/user/* endpoints — set automatically after TOTP login |
| Admin token | X-Admin-Token: <token> |
/admin/api/admin/* endpoints — admin panel only |
CT log and STH endpoints are public — no credential required.
The /v2/auth/capabilities endpoint is public and returns which authentication modes are enabled on this relay instance.
curl -X POST https://relay.paramant.app/v2/inbound \
-H "X-Api-Key: pgp_your_key" \
-H "Content-Type: application/json" \
-d '{"hash":"sha256hex","payload":"base64_5mb_blob","ttl_ms":3600000}'Response:
{
"ok": true,
"hash": "a3f2…",
"ttl_ms": 3600000,
"size": 5242880,
"sig_verified": true,
"download_token": "6b3c…",
"merkle_proof": {
"leaf_hash": "d4e1…",
"leaf_index": 42,
"tree_size": 43,
"audit_path": [
{"hash": "8a0b…", "position": "left"},
{"hash": "f391…", "position": "right"}
],
"root": "c7a9…",
"sth": { "relay_id": "relay.paramant.app", "sha3_root": "c7a9…", "tree_size": 43, "timestamp": 1744123456789, "signature": "…" },
"sth_signature": "ML-DSA-65 base64…"
}
}merkle_proof proves the blob was appended to the CT log. Re-walk audit_path from leaf_hash to reproduce root, then verify sth.signature with /v2/pubkey.
curl https://relay.paramant.app/v2/outbound/a3f2… \
-H "X-Api-Key: pgp_your_key" \
--output received.binResponse headers:
| Header | Value |
|---|---|
X-Paramant-Burned |
true if blob was destroyed |
X-Paramant-Hash |
SHA-256 hex of the blob |
X-Paramant-Receipt |
Base64url-encoded signed delivery receipt |
The X-Paramant-Receipt value is a base64url-encoded JSON object:
{
"blob_hash": "a3f2…",
"sector": "health",
"retrieved_at": "2026-04-15T09:00:00.000Z",
"relay_id": "health.paramant.app",
"tree_size_at_retrieval": 43,
"inclusion_proof": { "leaf_hash": "d4e1…", "audit_path": […], "root": "c7a9…" },
"burn_confirmed": true,
"signature": "ML-DSA-65 base64…"
}Pass this to POST /v2/verify-receipt to cryptographically confirm delivery.
Public. No API key required.
curl -X POST https://relay.paramant.app/v2/verify-receipt \
-H "Content-Type: application/json" \
-d '{"receipt":"<base64url from X-Paramant-Receipt>"}'Success:
{
"valid": true,
"blob_hash": "a3f2…",
"burn_confirmed": true,
"tree_size_at_retrieval": 43,
"retrieved_at": "2026-04-15T09:00:00.000Z"
}Failure (signature invalid, proof mismatch, missing fields):
{ "valid": false, "reason": "signature_invalid" }
{ "valid": false, "reason": "inclusion_proof_invalid", "detail": "recomputed root 8a0b… ≠ claimed root c7a9…" }Verification performs two independent checks: ML-DSA-65 signature over the canonical receipt JSON, then re-walks the Merkle audit path to recompute the root.
curl https://relay.paramant.app/v2/stream-next \
-H "X-Api-Key: pgp_your_key" \
-H "X-Device-Id: receiver-001"
# 200: {"blob_hash":"a3f2…","queued_at":"2026-04-15T…"}
# 204: no pending blobscurl https://relay.paramant.app/v2/status/a3f2… \
-H "X-Api-Key: pgp_your_key"
# {"available":true,"bytes":5242880,"ttl_remaining_ms":3598012,"sig_valid":true}All CT endpoints are public — no API key required.
curl https://relay.paramant.app/v2/sth{
"ok": true,
"sth": {
"relay_id": "relay.paramant.app",
"sha3_root": "c7a9ef34…",
"tree_size": 43,
"timestamp": 1744123456789,
"version": 1,
"signature": "ML-DSA-65 base64…",
"pk_hash": "sha3-256 of relay public key"
}
}The relay signs {relay_id, sha3_root, timestamp, tree_size, version} (keys sorted, JSON-serialised) using ML-DSA-65. Verify the signature against the key returned by GET /v2/pubkey.
curl "https://relay.paramant.app/v2/sth/history?limit=10"
# {"ok":true,"count":10,"total":48,"sths":[…]}limit max 100.
curl https://relay.paramant.app/v2/sth/1744100000000
# {"ok":true,"sth":{…}} — first STH at or after that Unix millisecond timestamp
# 404 if none existscurl https://relay.paramant.app/v2/pubkey{
"ok": true,
"alg": "ML-DSA-65",
"public_key": "base64…",
"pk_hash": "sha3-256 hex of the key"
}Use this key to independently verify any STH signature or delivery receipt signature. The key is generated once at first boot and persisted; pk_hash is its SHA3-256 fingerprint.
curl "https://relay.paramant.app/v2/ct/log?limit=20"
# {"ok":true,"entries":[{…}],"tree_size":43,"root":"c7a9…"}curl "https://relay.paramant.app/v2/ct/proof?index=7"
# {"ok":true,"leaf_hash":"d4e1…","audit_path":[…],"root":"c7a9…","tree_size":43}Prove that tree at size from is a prefix of tree at size to:
curl "https://relay.paramant.app/v2/sth/consistency?from=20&to=43"
# {"ok":true,"from":20,"to":43,"proof":["hash1","hash2",…]}to defaults to current tree size if omitted.
These endpoints power the peer-to-peer STH exchange. They allow any relay (or auditor) to independently archive and verify each other's tree heads.
curl -X POST https://relay.paramant.app/v2/sth/ingest \
-H "Content-Type: application/json" \
-d '{
"relay_id": "health.paramant.app",
"sha3_root": "c7a9…",
"timestamp": 1744123456789,
"tree_size": 43,
"version": 1,
"signature": "base64…",
"public_key":"base64…"
}'
# {"ok":true,"relay_pk_hash":"sha3-256 hex"}The relay verifies the ML-DSA-65 signature before storing. Replay attacks are blocked by a 5-minute timestamp window.
curl https://relay.paramant.app/v2/sth/peers{
"ok": true,
"count": 3,
"peers": [
{
"relay_pk_hash": "a1b2…",
"relay_id": "health.paramant.app",
"sth_count": 12,
"latest_root": "c7a9…",
"latest_tree_size": 43,
"latest_ts": "2026-04-15T09:00:00.000Z"
}
]
}curl "https://relay.paramant.app/v2/sth/peers/a1b2…?limit=50&offset=0"
# {"ok":true,"relay_pk_hash":"a1b2…","sths":[…],"total":12,"limit":50,"offset":0}| Path | Description |
|---|---|
GET /ct/ |
Public web UI — live tree view, verify button, no auth |
GET /ct/feed |
JSON feed for the UI (auto-refresh every 10s) |
GET /ct/feed.xml |
RSS feed — last 20 STHs. Subscribe to independently archive roots. |
The RSS feed is designed for external archiving: any subscriber retains an independent copy of each signed tree head, making log tampering detectable even if the relay is compromised later.
curl https://relay.paramant.app/health
# {"ok":true,"version":"2.4.5","sector":"relay","edition":"community"}curl https://relay.paramant.app/v2/relays
# {"total":5,"relays":[{"url":"…","version":"2.4.5","sector":"relay",…}]}curl -X POST https://relay.paramant.app/v2/request-trial \
-H "Content-Type: application/json" \
-d '{"name":"Jane Smith","email":"jane@example.com","use_case":"DICOM transfer for radiology dept"}'
# {"ok":true,"message":"Trial key sent to jane@example.com"}Rate limits: 3 requests per IP per 24 hours, 1 request per email address per 7 days. Key is delivered via Resend. Also available via the web form at https://paramant.app/request-key.
curl -X POST https://relay.paramant.app/v2/pubkey \
-H "X-Api-Key: pgp_your_key" \
-H "Content-Type: application/json" \
-d '{"device_id":"phone-001","ecdh_pub":"base64…","kyber_pub":"base64…"}'
# {"ok":true}curl https://relay.paramant.app/v2/pubkey/phone-001 \
-H "X-Api-Key: pgp_your_key"
# {"device_id":"phone-001","ecdh_pub":"…","kyber_pub":"…","registered_at":"…"}Ghost Pipe supports W3C-compatible decentralised identifiers (did:paramant:) for field devices. Registering a device identity enrolls it in the CT log and allows transfers to be attributed to a specific device without exposing the API key.
curl -X POST https://iot.paramant.app/v2/did/register \
-H "X-Api-Key: plk_your_key" \
-H "Content-Type: application/json" \
-d '{
"device_id": "plc-factory-01",
"ecdh_pub": "<base64 ECDH P-256 uncompressed public key, 65 bytes>",
"dsa_pub": "<base64 ML-DSA-65 public key — optional>"
}'Response:
{
"ok": true,
"did": "did:paramant:a3f2b7c1…",
"document": {
"id": "did:paramant:a3f2b7c1…",
"verificationMethod": [
{
"id": "did:paramant:a3f2b7c1…#keys-1",
"type": "JsonWebKey2020",
"controller": "did:paramant:a3f2b7c1…",
"publicKeyHex": "<ecdh_pub>"
}
]
},
"ct_index": 42
}ct_index is the CT log position of this registration — auditors can verify the enrollment timestamp via /v2/ct/proof?index=42.
Limits: max 500 DIDs per API key. Receiver sessions (device_id starting with inv_) do not require an API key.
Public endpoint — no API key required.
curl https://iot.paramant.app/v2/did/did:paramant:a3f2b7c1…Returns the W3C DID document including the device's public key and CT registration index.
curl https://iot.paramant.app/v2/did \
-H "X-Api-Key: plk_your_key"{
"ok": true,
"count": 3,
"dids": [
{ "did": "did:paramant:a3f2…", "device": "plc-factory-01", "ts": "2026-04-01T…" },
{ "did": "did:paramant:b8e1…", "device": "plc-factory-02", "ts": "2026-04-01T…" }
]
}Verify that a device holds the private key corresponding to its registered public key:
curl -X POST https://iot.paramant.app/v2/attest \
-H "X-Api-Key: plk_your_key" \
-H "Content-Type: application/json" \
-d '{
"device_id": "plc-factory-01",
"attestation": {
"method": "ecdh-challenge",
"challenge": "<base64 challenge bytes>",
"response": "<base64 signed response>"
}
}'{ "ok": true, "valid": true, "device": "plc-factory-01" }| Tier | Uploads/day | Retention |
|---|---|---|
| Free (pgp_) | 10 | 1 hour |
| Community (plk_) | unlimited | 1 hour |
| Professional | unlimited | 24 hours |
| Enterprise | unlimited | configurable |
| Code | Meaning |
|---|---|
| 400 | Bad request — missing or invalid fields |
| 401 | Invalid API key or signature |
| 403 | Forbidden — wrong API key for this blob |
| 404 | No blob / no STH at that timestamp |
| 429 | Rate limit exceeded |
| 503 | ML-DSA-65 not available on this relay |
| 500 | Relay error |
pip install paramant-sdkfrom paramant_sdk import GhostPipe
gp = GhostPipe(api_key="pgp_xxx", device="device-001", sector="health")
# Send — returns (hash, inclusion_proof)
hash_, proof = gp.send(open("scan.dcm", "rb").read(), ttl=3600)
print(proof["root"]) # Merkle root at time of upload
print(proof["leaf_index"]) # Position in the tree
# Receive — returns (data, receipt)
data, receipt = gp.receive(hash_)
print(receipt["burn_confirmed"]) # True if blob was destroyed
print(receipt["tree_size_at_retrieval"])
# Verify receipt (calls POST /v2/verify-receipt)
result = gp.verify_receipt(receipt)
print(result["valid"]) # True if ML-DSA-65 sig + Merkle proof both check out
# Anonymous drop (BIP39 mnemonic)
mnemonic = gp.drop(b"sensitive data", ttl=3600)
data, _ = gp.receive(mnemonic) # pickup by mnemonicInstall via:
curl -fsSL https://paramant.app/install-client.sh | bashOr included in paramantOS. Full list and source: scripts/.
# Fetch the latest STH and verify the ML-DSA-65 signature
paramant-verify-sth --relay https://relay.paramant.app
# Verify against a specific relay and print the tree state
paramant-verify-sth --relay https://health.paramant.app --verbose
# Cross-check STH consistency across all peer relays
paramant-verify-peers
paramant-verify-peers --relay https://relay.paramant.appparamant-verify-sth fetches /v2/sth and /v2/pubkey, verifies the ML-DSA-65 signature, and exits non-zero if invalid.
paramant-verify-peers fetches /v2/sth/peers and verifies that each mirrored STH is internally consistent and that tree sizes only grow.
# View the receipt returned after a receive operation
paramant-receipt --hash a3f2…
# Save receipt to file
paramant-receipt --hash a3f2… --save receipt.json
# Verify a saved receipt
paramant-receipt --verify receipt.json
paramant-receipt --verify <base64url>paramant-receipt --verify calls POST /v2/verify-receipt and prints the result. Exit code 0 = valid, 1 = invalid.
The CT log follows the same trust model as Certificate Transparency (RFC 6962): you need at least one honest participant in the ecosystem to detect misbehaviour.
- Monitors call
GET /v2/sthon a schedule and archive each root. A root that changes without a corresponding tree extension is a fork — proof of log manipulation. - Auditors call
GET /v2/ct/proof?index=Nto check inclusion of any known blob hash. - Gossip (
/v2/sth/ingest,/v2/sth/peers) lets relays cross-check each other's trees. A relay cannot silently show different trees to different parties if peers are exchanging STHs. - RSS archiving (
/ct/feed.xml) lets anyone subscribe to the STH feed. Once published, a root cannot be un-published without leaving evidence.
You do not need to trust the relay operator to detect log tampering — you only need to trust that at least one monitor, auditor, or RSS subscriber is honest and retains their copy.
All /api/user/ endpoints are served by the admin panel (https://paramant.app), not the sector relays. They require either an active session cookie or are part of the unauthenticated signup and login flows.
curl -X POST https://paramant.app/api/user/signup \
-H "Content-Type: application/json" \
-d '{"email":"jane@example.com"}'
# {"ok":true}Sends a TOTP setup link to the given email address. Rate-limited per IP and per email.
curl -X POST https://paramant.app/api/user/auth/setup/abc123... \
-H "Content-Type: application/json"
# {"secret":"BASE32SECRET","backup_codes":["code1","code2",…]}Returns the TOTP secret (as a Base32 string) and one-time backup codes. Idempotent: if the enrollment is provisional (QR scanned but not yet confirmed), the same secret is returned on repeat calls. Returns 409 only if TOTP is already fully activated.
curl -X POST https://paramant.app/api/user/auth/setup/abc123.../confirm \
-H "Content-Type: application/json" \
-d '{"totp_code":"123456"}'
# {"ok":true}Verifies the first TOTP code and activates the account. After this call the setup token is consumed and cannot be reused.
curl -X POST https://paramant.app/api/user/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"jane@example.com","totp_code":"123456"}'
# Sets: Set-Cookie: paramant_user_session=<token>; HttpOnly; Secure; SameSite=Strict
# {"ok":true}curl -X POST https://paramant.app/api/user/auth/login-with-backup \
-H "Content-Type: application/json" \
-d '{"email":"jane@example.com","backup_code":"abc-def-ghi"}'
# {"ok":true}Backup codes are single-use. The account is re-locked after use; a new TOTP enrollment is required.
curl -X POST https://paramant.app/api/user/auth/logout \
-H "Cookie: paramant_user_session=<token>"
# {"ok":true}curl https://paramant.app/api/user/session/verify \
-H "Cookie: paramant_user_session=<token>"
# {"ok":true,"user_id":"…","email":"jane@example.com"}Returns the account profile, active sessions, and billing status.
curl https://paramant.app/api/user/account \
-H "Cookie: paramant_user_session=<token>"
# {"ok":true,"email":"jane@example.com","plan":"free","sessions":[…]}Permanently deletes the account and all associated Redis state.
curl -X DELETE https://paramant.app/api/user/account \
-H "Cookie: paramant_user_session=<token>"
# {"ok":true}Sends a new TOTP setup link, invalidating the current TOTP secret. Requires an active session.
Revokes all sessions except the current one.
Generates a new set of backup codes and invalidates all previous ones.
Initiates a Stripe checkout session (stub — Stripe integration pending).
Cancels the active subscription.
Returns current plan and subscription state.
Returns billing history.
These endpoints are served by the sector relays and are intended for internal calls from the admin panel. They require the X-Internal-Auth header set to the value of INTERNAL_AUTH_TOKEN in the relay environment.
They are not accessible from the public internet.
| Method | Path | Description |
|---|---|---|
GET |
/v2/auth/capabilities |
Public — returns {user_totp_available: bool} |
POST |
/v2/user/setup-totp |
Provision a TOTP secret; idempotent for provisional state |
POST |
/v2/user/verify-totp |
Verify a TOTP code against the stored secret |
POST |
/v2/user/activate-totp |
Mark TOTP as fully activated |
POST |
/v2/user/consume-backup |
Consume and invalidate a single backup code |
POST |
/v2/user/regenerate-backup |
Generate a new backup code set |
POST |
/v2/user/delete-totp |
Remove all TOTP state for a user |
POST |
/v2/user/get-totp-provisional |
Return the existing secret if enrollment is provisional |
POST |
/v2/user/get-backup-codes-plaintext |
Return plaintext backup codes (used during setup display) |
Idempotency note for /v2/user/setup-totp: If a TOTP secret already exists but totp_active is false (provisional), the endpoint returns the existing secret and backup codes rather than 409. A 409 is returned only when totp_active is true.
| HTTP | Code | Meaning |
|---|---|---|
| 400 | bad_request |
Malformed request body or missing required field |
| 401 | unauthorized |
Missing or invalid API key, session cookie, or admin token |
| 401 | invalid_or_expired_token |
Setup token not found or past TTL |
| 401 | invalid_totp_code |
TOTP code incorrect or outside allowed window |
| 401 | invalid_backup_code |
Backup code not found or already consumed |
| 403 | totp_not_activated |
Account exists but TOTP setup was not completed |
| 404 | not_found |
Resource does not exist |
| 409 | totp_already_configured |
TOTP is fully activated; cannot re-enroll without a reset |
| 409 | email_already_registered |
Signup attempted for an email that already has an account |
| 429 | rate_limited |
Too many requests from this IP or email address |
| 500 | internal |
Unexpected server error; check relay logs |
Require or remove TOTP for a specific user.
Body:
{ "key": "pgp_...", "required": true, "reason": "compliance policy" }Effect when required: true:
paramant:user:totp_required:{key}set in Redis- All active user sessions revoked
- Setup email sent automatically
- Login blocked until TOTP active
Rate limit: 20/admin/24h
Audit event: admin_totp_required_toggled