Developer guide
Build with the Qopanza API
Post-quantum encryption, signatures, secrets and cryptography discovery over plain JSON and HTTPS. Call it from any language, or use one of nine SDKs that wrap it.
- Base URL
https://api.qopanza.com- Authentication
X-API-Keyheader- Format
- JSON in and out; binary as base64
- Algorithms
- ML-KEM (FIPS 203), ML-DSA (FIPS 204), SLH-DSA (FIPS 205)
Two ways to use Qopanza
What you can do with the API
- Encrypt and decrypt data with ML-KEM and AES-256-GCM, without managing keys — Encrypt and decrypt.
- Sign and verify releases, documents and tokens with ML-DSA or SLH-DSA — Sign and verify.
- Store secrets and issue certificates under quantum-safe keys, with versions, rollback and an audit trail — Endpoint reference.
- Find cryptography that will break in code, repositories, TLS endpoints and cloud accounts, and find secrets your app leaks today — Scanning.
- Gate builds on scan results from any CI system — Command line and CI.
Using the dashboard
Everything the API does is also in the dashboard at qopanza.com, and none of it needs code. The Getting started page in the sidebar walks through these steps in order.
Scan your app
Open App Security Scan and paste the address of anything you have deployed, or paste a file. It reads the JavaScript your site serves to every visitor and reports leaked API keys, database credentials, published source maps and missing security headers — with the fix for each. You can try it without an account on the free scan.
See what a quantum computer breaks
Open PQC Scanner and choose what to scan: a file of code, a live TLS endpoint, a Git repository, or a cloud account. It finds RSA, elliptic-curve and other quantum-vulnerable algorithms. Inventory lists every algorithm found across all scans, and Posture turns it into a risk score with what to fix first. An app with no cryptography of its own usually has an empty result here, and that is fine.
Keep scanning and prove it
Connect GitHub from the PQC Scanner so repositories are rescanned automatically, and turn on a public verification page — a link for your site's footer that shows what was scanned and when. Notifications sends scan results, keys about to expire and certificate changes to a Slack channel or to your own webhook, signed with HMAC-SHA256.
Plan the migration and report on it
Migration builds a plan from your inventory, ordered by exposure, and tracks it to done. Compliance scores the account against the controls Qopanza can evidence, and the inventory exports as a CycloneDX CBOM for an auditor.
Hand it to your developers
API Keys issues keys for your services, and SDKs shows the install line and a working snippet for each language with your new key filled in. Team invites colleagues, and Usage shows your plan's limits.
Quickstart
Three steps from nothing to a working round trip. No key management needed.
Get an API key
Create an account. Your first key is shown on the next screen, and you can make more under API Keys in the dashboard. A key is shown once and stored hashed, so copy it somewhere safe then.
export QOPANZA_API_KEY="qsk_…" # the key you were shownEncrypt something
Binary travels as base64. With no
key_id, Qopanza creates a managed ML-KEM key for your account on first use and reuses it after.curl -s https://api.qopanza.com/v1/encrypt \ -H "X-API-Key: $QOPANZA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"plaintext": "Y2FyZCBlbmRpbmcgNDI0Mg=="}'Y2FyZCBlbmRpbmcgNDI0Mg==iscard ending 4242in base64. The response:{ "key_id": "8f14e45f-…", "ciphertext_kem": "…", "ciphertext_payload": "…", "nonce": "…", "backend": "liboqs", "used_managed_key": true }Decrypt it
Send back
key_id,ciphertext_kem,ciphertext_payloadandnonce. Store all four together; losing any one of them makes the data unrecoverable.curl -s https://api.qopanza.com/v1/decrypt \ -H "X-API-Key: $QOPANZA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"key_id": "…", "ciphertext_kem": "…", "ciphertext_payload": "…", "nonce": "…"}' # → {"plaintext": "Y2FyZCBlbmRpbmcgNDI0Mg=="}
Authentication
Send your API key in the X-API-Key header on every request. Keys start with qsk_ and belong to one account; everything a key creates is scoped to that account.
curl https://api.qopanza.com/v1/security/posture -H "X-API-Key: $QOPANZA_API_KEY"- Keep keys on the server. A key in browser JavaScript or a mobile app can be read by anyone who opens it — it is exactly what Qopanza's own app scan reports. Read it from an environment variable or your secret manager.
- One key per service. Create, rotate and revoke keys under API Keys in the dashboard. Rotating issues a new key and retires the old one, so a leaked key can be replaced without touching the others.
- Restrict by IP. A key can carry an IP allowlist; requests from anywhere else get
403. - Dashboard-only operations — managing users, API keys, webhooks, billing and usage — take a signed-in session rather than an API key, so a key that leaks cannot mint more keys.
Keys and algorithms
You rarely need to pick an algorithm. Create a key by purpose and a security level, and the policy engine chooses the NIST algorithm and parameter set.
| purpose | Use it for | Algorithm |
|---|---|---|
kem | Encrypting data: /encrypt, /decrypt, secrets | ML-KEM (FIPS 203) |
hybrid_kem | Key exchange that stays safe if either half is broken | ML-KEM combined with X25519 |
signature | Signing releases, documents, tokens | ML-DSA-65 by default, or SLH-DSA-SHA2-128S |
curl -s https://api.qopanza.com/v1/keys \
-H "X-API-Key: $QOPANZA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"purpose": "signature", "label": "releases", "security_level": "high"}'security_levelisstandard(the default),highormaximum. The response'spolicy_rationalesays why that algorithm was chosen.expires_in_daysandrotation_period_daysset a lifetime and automatic rotation. Rotation adds a version; data encrypted under older versions still decrypts.- Zero-knowledge keys: generate the key pair on your own machine and register only the public half with
POST /v1/keys/register. Qopanza inventories and polices it but can never decrypt with it — so/encrypt,/decryptand/signreturn409for it by design. There is no escrow: lose the private key and the data is gone.
Encrypt and decrypt
POST /v1/encrypt takes plaintext (base64) and an optional key_id. Each call runs an ML-KEM encapsulation to get a fresh 256-bit shared secret, and encrypts your data under it with AES-256-GCM — so the plaintext can be any size, and no two messages share a data key.
| Field returned | What it is |
|---|---|
key_id | The key used. Required to decrypt. |
ciphertext_kem | The ML-KEM ciphertext the shared secret is recovered from. |
ciphertext_payload | Your data, AES-256-GCM encrypted. |
nonce | The AES-GCM nonce. |
used_managed_key | True when no key_id was sent. |
Store the four required fields together, for example as one JSON column. POST /v1/decrypt takes them back and returns plaintext in base64.
Sign and verify
Create a key with purpose: "signature", then sign a base64 message. Verification returns {"valid": true} or false; a bad signature is not an error.
curl -s https://api.qopanza.com/v1/sign \
-H "X-API-Key: $QOPANZA_API_KEY" -H "Content-Type: application/json" \
-d '{"key_id": "…", "message": "cmVsZWFzZS0xLjQuMg=="}'
# → {"key_id": "…", "signature": "…", "backend": "liboqs"}
curl -s https://api.qopanza.com/v1/verify \
-H "X-API-Key: $QOPANZA_API_KEY" -H "Content-Type: application/json" \
-d '{"key_id": "…", "message": "cmVsZWFzZS0xLjQuMg==", "signature": "…"}'
# → {"valid": true}Scanning
There are two scanners, answering different questions.
Cryptography discovery
Which of my cryptography does a quantum computer break? Scans code, repositories, live TLS endpoints and cloud accounts, and builds an inventory you can export as a CycloneDX CBOM.
curl -s https://api.qopanza.com/v1/scan/tls \
-H "X-API-Key: $QOPANZA_API_KEY" -H "Content-Type: application/json" \
-d '{"host": "api.example.com", "port": 443}'App security
What can a stranger read out of my app today? Reads the JavaScript, source maps and headers a deployed site sends to every visitor and reports leaked keys and exposed data.
curl -s https://api.qopanza.com/v1/app-scan/url \
-H "X-API-Key: $QOPANZA_API_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://app.example.com"}'What comes back
A discovery scan (from /v1/scan/tls):
{
"id": "3c9f…",
"source_type": "tls",
"target": "api.example.com:443",
"status": "completed",
"assets_found": 4,
"quantum_vulnerable": 3,
"quantum_safe": 1,
"risk_score": 72,
"created_at": "2026-09-25T09:14:02Z",
"completed_at": "2026-09-25T09:14:05Z"
}An app scan (from /v1/app-scan/url), shortened:
{
"target": "https://app.example.com",
"reachable": true,
"score": 41,
"critical_count": 1,
"high_count": 2,
"summary": "A service-role key is readable in your JavaScript…",
"findings": [ … ],
"fix_available": true,
"detected_host": "vercel"
}A discovery scan returns a run with counts — assets_found, quantum_vulnerable, quantum_safe, risk_score. Fetch the individual findings with GET /v1/scans/{scan_id}/assets, and the account-wide picture with GET /v1/security/posture. An app scan returns its findings directly, with every secret already redacted.
Endpoint reference
The operations you call with an API key, grouped by job. The walkthroughs above show the request and response for the common ones. For a field that is not covered here, email support@qopanza.com.
Encryption
POST/v1/encrypt | Encrypt base64 data. Omit key_id to use your account's managed key. |
POST/v1/decrypt | Decrypt. Send back all four fields /encrypt returned. |
Signatures
POST/v1/sign | Sign a base64 message with a signature key. |
POST/v1/verify | Check a signature. Returns {"valid": true | false}. |
Keys
POST/v1/keys | Create a key: purpose kem, signature or hybrid_kem. |
POST/v1/keys/register | Register the public half of a key you generated yourself (zero-knowledge). |
GET/v1/keys/{key_id} | Read a key's algorithm, public key and expiry. |
POST/v1/keys/{key_id}/rotate | Create the next version. Older versions still decrypt. |
GET/v1/keys/{key_id}/versions | List every version of a key. |
PATCH/v1/keys/{key_id}/rotation-policy | Set or clear automatic rotation. |
POST/v1/keys/{key_id}/revoke | Revoke a key version. |
POST/v1/kem/encapsulate | Raw ML-KEM: derive a shared secret and its ciphertext. |
POST/v1/kem/decapsulate | Recover the shared secret from a KEM ciphertext. |
Secrets
PUT/v1/secrets/{name} | Store a new version of a secret, encrypted with a quantum-safe key. |
POST/v1/secrets/{name}/reveal | Read a secret's value. Every read is audited. |
GET/v1/secrets | List secrets (names and metadata, never values). |
GET/v1/secrets/{name}/versions | List a secret's versions. |
POST/v1/secrets/{name}/rollback | Make an earlier version current again. |
DELETE/v1/secrets/{name} | Destroy a secret and every version of it. |
Certificates
POST/v1/certificates/ca | Create an ML-DSA certificate authority for internal mTLS. Not publicly trusted — browsers reject ML-DSA chains. |
POST/v1/certificates | Issue a certificate from one of your CAs. |
GET/v1/certificates | List issued certificates. |
GET/v1/certificates/{certificate_id}/verify | Verify a certificate's chain and status. |
POST/v1/certificates/{certificate_id}/revoke | Revoke a certificate. |
Cryptography discovery
Find the cryptography a quantum computer breaks — in code, on live endpoints and in cloud accounts.
POST/v1/scan/code | Scan source or manifest text you send. |
POST/v1/scan/tls | Probe a live TLS endpoint and record what it negotiates. |
POST/v1/scan/repository | Clone a repository and scan it. |
POST/v1/scan/cloud | Inventory keys and certificates in a cloud account. |
GET/v1/scans/{scan_id}/assets | The algorithms one scan found, with file and line. |
GET/v1/inventory | Everything found across all scans. |
GET/v1/inventory/cbom | The inventory as a CycloneDX 1.6 CBOM. |
GET/v1/security/posture | Quantum risk score and what to fix first. |
App security
Find secrets and misconfigurations a stranger can read out of a deployed app.
POST/v1/app-scan/url | Scan a live site from the outside: bundles, source maps, headers. |
POST/v1/app-scan/code | Scan code you send for leaked keys and credentials. |
POST/v1/app-scan/fix | Generate fixes for the findings of a scan. |
Migration, compliance and audit
POST/v1/migration/plans | Build a migration plan from your inventory. |
GET/v1/migration/progress | How far the migration has got. |
GET/v1/compliance/report | Scores your account against the compliance controls Qopanza can evidence. |
GET/v1/audit-log | The tamper-evident log of every key operation. |
GET/v1/audit-log/verify | Verify the log's hash chain has not been altered. |
No API key needed
POST/v1/app-scan/public | The free scan on the home page. Rate-limited per visitor. |
GET/v1/sdks | Every SDK, with the install command that works today. |
GET/v1/health | Liveness check. |
Errors and rate limits
Errors are JSON with a detail field that says what went wrong in plain words. Validation errors (422) carry a list naming each bad field.
{"detail": "Rate limit exceeded: 60 requests/minute for the 'free' plan. Try again in 23s."}| Status | Meaning |
|---|---|
400 | The request is valid but cannot be carried out — for example, decryption failed because the ciphertext was altered or belongs to another key. |
401 | No X-API-Key header, or the key is wrong, revoked or expired. |
402 | The feature is not included in your plan. The detail names the plan that has it. |
403 | The key is valid but not allowed here — for example, the request came from an IP outside the key's allowlist. |
404 | No such key, scan, secret or certificate on your account. |
409 | The key cannot be used this way: it has been revoked, or it is a zero-knowledge key whose private half the server never has. |
422 | A field is missing or the wrong type. Binary fields that are not valid base64 land here. |
429 | Rate limit reached. Wait for the number of seconds in Retry-After. |
503 | A dependency is briefly unavailable. Safe to retry with backoff. |
Rate limits are per account, per minute, and rise with your plan. A limited request gets 429 with two headers: Retry-After (seconds until the window resets) and X-RateLimit-Limit (your per-minute allowance). Wait for Retry-After rather than retrying at once. The Usage page in the dashboard shows your limit and recent traffic.
SDKs
Each SDK is a thin client over the same API. Pick a language — your choice is remembered on this device.
Install
pip install qopanzaCreate a client
Create it once and reuse it. The key is read from QOPANZA_API_KEY, never written into source.
import os
from qopanza import QopanzaClient
client = QopanzaClient(
base_url="https://api.qopanza.com",
api_key=os.environ["QOPANZA_API_KEY"],
)Encrypt and decrypt
# No key management: a managed key is created for your account on
# first use and reused after that.
sealed = client.encrypt(plaintext=b"card ending 4242")
# Store every field of `sealed` — decrypt needs all four.
plaintext = client.decrypt(**sealed)
# Or encrypt to a key you created and named yourself.
key = client.create_key(purpose="kem", label="orders")
sealed = client.encrypt(key_id=key["id"], plaintext=b"card ending 4242")Sign and verify
sig_key = client.create_key(purpose="signature", label="releases")
signed = client.sign(key_id=sig_key["id"], message=b"release-1.4.2.tar.gz sha256:…")
ok = client.verify(
key_id=sig_key["id"],
message=b"release-1.4.2.tar.gz sha256:…",
signature=signed["signature"],
)Scan
run = client.scan_tls("api.example.com") # a live endpoint
run = client.scan_code("payments.py", open("payments.py").read())
run = client.scan_repository("https://github.com/org/repo")
for asset in client.list_scan_assets(run["id"]):
print(asset)
posture = client.security_posture() # quantum risk score for the accountHandle errors
from qopanza import QopanzaAPIError
try:
client.get_key("does-not-exist")
except QopanzaAPIError as err:
print(err.status_code, err.detail)- Synchronous. Under asyncio, call it through asyncio.to_thread.
- Installing it also installs the `qopanza` command line — see Command line and CI below.
- `pip install 'qopanza[zk]'` adds zero-knowledge keys: the private key is generated on your machine and never uploaded.
Full reference: sdks/python/README.md in the qopanza-sdks repository ↗.
What each SDK covers
| Python | TypeScript | Go | Rust | Java | C# | PHP | Ruby | C++ | |
|---|---|---|---|---|---|---|---|---|---|
| Encrypt / decrypt | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Managed key (no key_id) | ✓ | – | ✓ | ✓ | – | – | ✓ | ✓ | – |
| Sign / verify | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| AI analysis | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Scanning and posture | ✓ | – | ✓ | ✓ | – | – | ✓ | ✓ | – |
| Secrets, certificates, migration | ✓ | – | – | – | – | – | – | – | – |
| Zero-knowledge keys | ✓ | – | – | – | – | – | – | – | – |
Anything an SDK does not wrap is still one HTTPS request away — every endpoint works with any HTTP client and the X-API-Key header.
Command line and CI
Installing the Python SDK also installs the qopanza command. It reads QOPANZA_API_KEY and QOPANZA_BASE_URL, so CI can inject both without writing anything to disk.
qopanza login # store credentials locally
qopanza scan . # scan a directory
qopanza scan --tls api.example.com # probe a live TLS endpoint
qopanza posture # quantum risk score
qopanza inventory # cryptographic inventory
qopanza key create --purpose hybrid_kemUse it as a build gate. Add --json to any command for machine-readable output.
qopanza scan . --fail-on highExit codes: 0 clean, 1 the scan could not run, 2 findings at or above the threshold. The two failures are different on purpose — a scan that did not run must never pass a build.
AI coding agents (MCP)
If you build with an AI coding agent — Lovable, Replit, Claude Code, Codex, Grok Build, Cursor, Windsurf or Claude Desktop — you can give it Qopanza as a set of tools. Then you ask in plain words, "scan my app for exposed secrets and fix what you find", and the agent runs the scan, reads the findings and edits your code. No dashboard and no report to read.
This works through the Model Context Protocol (MCP), the standard way agents load extra tools. There are two ways in, with the same four tools behind both, and each needs an API key from API Keys in the dashboard:
- Agents in the cloud (Lovable, Replit) connect to Qopanza's hosted server by URL:
https://api.qopanza.com/mcp. - Agents on your computer (Claude Code, Codex, Grok Build, Cursor, Windsurf, Claude Desktop) run the qopanza-mcp ↗ package with
npx, so there is nothing to clone or build. It needs Node 18 or newer.
Lovable
- Open Connectors, select +, and choose MCP server.
- Server name:
Qopanza. Server URL:https://api.qopanza.com/mcp. - For authentication choose Bearer token or API key and paste your
qsk_…key.
Replit
- In your Repl, open Integrations, go to MCP Servers, and click + Add MCP server.
- Display name:
Qopanza. MCP Server URL:https://api.qopanza.com/mcp. - Open Advanced settings, add a header named
X-API-Keywith yourqsk_…key as its value, then click Test & save.
Any other agent that connects to MCP servers by URL works the same way: point it at https://api.qopanza.com/mcp and send the key as X-API-Key or as Authorization: Bearer qsk_….
Claude Code
claude mcp add qopanza -e QOPANZA_API_KEY=qsk_… -- npx -y qopanza-mcpCodex (OpenAI)
codex mcp add qopanza --env QOPANZA_API_KEY=qsk_… -- npx -y qopanza-mcpOr add it by hand to ~/.codex/config.toml:
[mcp_servers.qopanza]
command = "npx"
args = ["-y", "qopanza-mcp"]
[mcp_servers.qopanza.env]
QOPANZA_API_KEY = "qsk_…"Grok Build (xAI)
Add this to ~/.grok/config.toml. Grok fills in ${QOPANZA_API_KEY} from your shell when it starts, so the key itself never has to be written into the file. Then run grok mcp doctor qopanza to check the connection.
[mcp_servers.qopanza]
command = "npx"
args = ["-y", "qopanza-mcp"]
env = { QOPANZA_API_KEY = "${QOPANZA_API_KEY}" }Cursor, Windsurf and Claude Desktop
Add this to the agent's MCP settings file — ~/.cursor/mcp.json for Cursor, ~/.codeium/windsurf/mcp_config.json for Windsurf, or claude_desktop_config.json for Claude Desktop — then restart the agent.
{
"mcpServers": {
"qopanza": {
"command": "npx",
"args": ["-y", "qopanza-mcp"],
"env": { "QOPANZA_API_KEY": "qsk_…" }
}
}
}The key stays in your agent's own settings on your machine, not in your project, so it is never committed. Create one under API Keys in the dashboard.
The tools your agent gets
| Tool | Plan | What the agent can do with it |
|---|---|---|
scan_app_url | Free | Scan a deployed site for leaked keys, credentials and readable databases. |
scan_app_code | Free | Scan a file it is editing, before the change ships. |
scan_crypto | Free | Find cryptography a quantum computer breaks, for code that has its own. |
get_fixes | App Security and up | Turn findings into exact code edits, config files and SQL policies to apply. |
One thing an agent cannot do for you: rotate a leaked key. That happens in the provider's console — Stripe, Supabase, OpenAI — and every response from get_fixes lists those manual steps separately, so the agent does not report "fixed" while the old key still works.
More resources
- SDK and MCP server source ↗ — every client library and the MCP server, in one public repository. See AI coding agents to set up the MCP server.
- FAQ and Security — how keys are stored, and what Qopanza can and cannot see.
- Stuck? support@qopanza.com.