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.
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
| Feature | Route | Algorithms | API key |
|---|---|---|---|
| Secrets One‑time secrets sealed to a recipient's key | /create | ML‑KEM‑768 + passphrase (Scrypt) + ChaCha20‑Poly1305, optional ML‑DSA‑65 signature | Yes for the API (keys stay on the client) |
| File Crypto Encrypt/decrypt files (max 2 MB) | /file-crypto | ML‑KEM‑768 + ChaCha20‑Poly1305 + ML‑DSA‑65 (.pqc files) | No |
| P2P Direct WebRTC · no server storage | /p2p | ML‑KEM‑768 + ChaCha20‑Poly1305 over WebRTC | No |
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 CollectionSecrets 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: