REST API Reference

Complete REST API documentation for secrets and post‑quantum cryptography

Security model — your responsibility as a developer

The server is zero‑knowledge by design: it never sees the plaintext, the passphrase or any private key. Each secret is sealed to one recipient's ML‑KEM‑768 public key AND to a passphrase, so a secure integration depends on how YOU handle the four pieces below. Treat it as a pattern, not a limitation.

Recipient's private key (ML‑KEM‑768)

Where: In the recipient's browser vault or on their machine (keygenpqc.sh). Only the public key is ever shared.

Who holds it: The recipient alone. Never the sender, never the server.

Passphrase

Where: Only on your machine or in a vault you control.

Who holds it: You and the recipient. Never the server.

Encrypted blob

Where: Server storage with TTL (Redis).

Who holds it: The server holds it, but it is opaque without both the private key and the passphrase.

Token

Where: Returned by POST /secrets, travels in the path.

Who holds it: The server (as index) and whoever needs to retrieve the secret.

Pattern rules

  • The passphrase and the private keys must never appear in logs, dashboards, shared databases, or the URL path.
  • Deliver the link and the passphrase through separate channels (Signal, Bitwarden Send, Vault, KMS, …): the delivery happens outside the NoTrust server.
  • Check a recipient's public key fingerprint out of band before you encrypt to it: a swapped key means a secret sealed to someone else. Sign with ML‑DSA‑65 and have the recipient pin your key, so they can tell who sent it.
  • Use a strong passphrase. It is a second factor: the private key alone opens nothing and neither does the passphrase, but if the private key leaks, the passphrase is all that is left.
  • Recovery does not exist. Lose the private key or the passphrase and the secret is gone: that is the guarantee that stops the server from helping an attacker. Keep the API key in your secret store; it only authorizes creating secrets.

Typical integration patterns

Dev → dev (human)
Ask the recipient for their public key (Vault → Export public key, or the .kem.pub from keygenpqc.sh), check its fingerprint with them, create the secret and send the token (or the /secret/<token> link the API returns) and the passphrase through two different channels. The web app works the same way with the token: neither the token nor the link ever carries the passphrase.
CI/CD → runner
The runner owns an ML‑KEM‑768 key pair: its public key ships with the pipeline, its private key lives in the runner's secret store. The pipeline creates secrets with the API key and a passphrase from its secret store, and never prints either in logs.
Bot / automated agent
Each agent has its own key pairs and keeps the (token, passphrase) pair in ephemeral memory after consuming them. It signs what it sends with ML‑DSA‑65 and pins its peers' keys, so a forged sender is refused before anything is decrypted. It never writes a private key or a passphrase to disk in clear.

Bottom line: NoTrust gives you the primitive (encrypt to a key, store opaquely, deliver once, self‑destruct); identity and key distribution belong to your integration. And to be precise: AES‑256 is not broken by quantum computers. What ML‑KEM‑768 adds is binding each secret to the recipient's private key through a key encapsulation designed to resist them.

Authentication

Creating a secret through the API requires an API key. The keys you create in your account carry the secrets scopes (secrets:create, secrets:read, secrets:delete); the Crypto (PQC) helper endpoints need the crypto scopes they list, which those keys do not have (403 INSUFFICIENT_SCOPE). The web app needs no key. Reading a secret needs none either: what protects it is the recipient's private key and the passphrase. The API key only authorizes creating secrets and decrypts nothing, and every key you generate stays on your client.

Header: X-API-Key: sk_live_your_api_key

Or send it as Authorization: Bearer sk_live_your_api_key. Without a key the API answers 401 API_KEY_REQUIRED_FOR_PQC; with an unknown or deleted key, 401 INVALID_API_KEY; with a revoked or deactivated one, 401 REVOKED_API_KEY; with an expired one, 401 EXPIRED_API_KEY.

Three shell scripts run the whole flow on your machine and write the same format the web app reads: a secret created here opens in the browser, and the other way round. You needpip install pqcrypto cryptography and curl. Creating a secret takes an API key: register, then create one in your account. Every key stays on your machine; the server only stores the encrypted blob.

Step 1 — Check PQC support (optional, no auth)

curl -sS https://notrust.now/api/v1/crypto/capabilities
# {"postQuantumSupported":true,"algorithms":{"kem":{"ML-KEM-768":{...}},"sig":{"ML-DSA-65":{...}}},...}

Step 2 — Install the dependencies and download the scripts

pip install pqcrypto cryptography
for f in keygenpqc.sh createpqc.sh decryptpqc.sh; do
  curl -sSO "https://notrust.now/scripts/$f"
done
chmod +x keygenpqc.sh createpqc.sh decryptpqc.sh

Step 3 — Generate your key pairs

One ML‑KEM‑768 pair, which others encrypt to, and one ML‑DSA‑65 pair, which you sign with. Share the .pub files and check fingerprints with your contacts; the .key files never leave your machine. A web user adds you in the vault under Contacts → Import Contact Public Keys.

./keygenpqc.sh ./me
# ML-KEM-768  ./me.kem.pub / .kem.key   fingerprint EA32DFFC845BE8CD
# ML-DSA-65   ./me.dsa.pub / .dsa.key   fingerprint D2D52250A960FE9E
# Share the .pub files. The .key files never leave this machine.

Step 4 — Create a secret for a recipient

-r is the recipient's ML‑KEM‑768 public key: a web user downloads it from the vault with Export public key. Use your own .kem.pub to keep a secret for yourself. The passphrase is mandatory (at least 6 characters) and is prompted unless NOTRUST_PASSPHRASE is set. Send it through a different channel than the link.

export NOTRUST_API_KEY="sk_live_your_api_key"   # from https://notrust.now/en/account

./createpqc.sh -r alice.kem.pub -s me.dsa.key -S me.dsa.pub -t 60 "the secret"
# Passphrase (min. 6 characters):
# Repeat the passphrase:
# https://notrust.now/secret/1acf9280a2cb4a6a9032edcd60a0a71c

Step 5 — Open a secret sent to you

With -p, the secret is refused unless it carries a valid signature from that sender's ML‑DSA‑65 key. The signature is checked before anything is decrypted.

./decryptpqc.sh -k me.kem.key -p alice.dsa.pub \
  https://notrust.now/secret/1acf9280a2cb4a6a9032edcd60a0a71c
# Passphrase:
# Signature valid: signed by A9A120728164FF6B (the sender you pinned).
# the secret

This call CONSUMES a view — the secret is deleted afterwards. If you mistype the passphrase, the script keeps the blob so you can retry with -i.

Payload format (v2), to write your own client

What createpqc.sh sends and the web app reads. The server rejects anything that is not an ML‑KEM‑768 payload (400 PQC_REQUIRED).

# key  = scrypt(passphrase, salt; N=65536, r=8, p=1, dkLen=32)
#        XOR ML-KEM-768 shared secret (encapsulated to the recipient's public key)
# box  = ChaCha20-Poly1305(key, iv), no associated data
# sign = ML-DSA-65 (optional) over these lines joined with "\n":
#        pqc-sig-v1, id=, ttl=, maxViews=, version= (from metadata),
#        ciphertext=, iv=, salt=, kemCiphertext=, recipientPublicKey=

POST /api/v1/secrets        X-API-Key: sk_live_your_api_key
{ "secret": "<payload as a JSON string>", "ttl": 15, "maxViews": 1 }   # ttl in minutes

payload = {
  "ciphertext": "<Base64: ChaCha20-Poly1305 output, tag included>",
  "iv": "<Base64, 12 bytes>",
  "salt": "<Base64, 32 bytes>",
  "algorithm": "ChaCha20-Poly1305",
  "keyDerivation": "Scrypt+KEM",
  "scryptParams": { "N": 65536, "r": 8, "p": 1, "dkLen": 32 },
  "ttl": 900, "maxViews": 1, "hasPassphrase": true,              # ttl in seconds here
  "metadata": { "id": "<UUID v4>", "ttl": 900, "maxViews": 1, "version": 2,
                "requireSignature": true, "clientSidePQC": true,
                "timestamp": 1790000000000 },
  "kyberData": { "publicKey": "<recipient's ML-KEM-768 key, 1184 bytes>",
                 "ciphertext": "<ML-KEM-768 ciphertext, 1088 bytes>" },
  "signatureData": { "algorithm": "ML-DSA-65",
                     "publicKey": "<sender's ML-DSA-65 key, 1952 bytes>",
                     "signature": "<3309 bytes>" }                  # or null
}

Algorithms by feature

FeatureRouteAlgorithmsAPI key
Secrets

One‑time secrets sealed to a recipient's key

/createML‑KEM‑768 + passphrase (Scrypt) + ChaCha20‑Poly1305, optional ML‑DSA‑65 signatureYes for the API (keys stay on the client)
File Crypto

Encrypt/decrypt files (max 2 MB)

/file-cryptoML‑KEM‑768 + ChaCha20‑Poly1305 + ML‑DSA‑65 (.pqc files)No
P2P Direct

WebRTC · no server storage

/p2pML‑KEM‑768 + ChaCha20‑Poly1305 over WebRTCNo

The web runs in your browser without an account: your keys live in its encrypted vault. Creating secrets through the API takes an API key, and every key stays on the client: the server only stores the encrypted blob — create account · get API key

Postman Collection

Secrets API

Post‑quantum one‑time secrets, end‑to‑end encrypted. The server never sees the plaintext, the passphrase or any private key.

Zero‑Knowledge Architecture

Everything happens on the client: a key is encapsulated with ML‑KEM‑768 to the recipient's public key, combined (XOR) with a key that Scrypt derives from the passphrase, and the secret is encrypted with ChaCha20‑Poly1305. The server accepts nothing but this format and only stores the encrypted blob.

Download Scripts

Three shell scripts that create and open secrets in the same format as the web app. They need python3 with pqcrypto and cryptography, and curl: