Secreta API, as shipped in the download
Secreta REST API, MCP & Webhooks (v3.0)
Secreta's API is deliberately one endpoint. The shape below is dictated by the product's zero-knowledge contract, not by convention — please read this section before asking where the rest is.
There is no endpoint that lists or reads secrets
A one-time secret exists for exactly one retrieval by exactly one recipient. An endpoint that enumerated outstanding secrets would hand anyone with a leaked API key a map of every live token. So there is no GET /api/secrets, no GET /api/secrets/{id}, and **no way to read a secret's content through this API at all**. Retrieval happens exactly once, in a browser, at the share link — and burns the secret.
The API takes ciphertext, never plaintext
Secreta's confidentiality comes from an AES-256-GCM key generated in the browser that travels only in the URL fragment (after #). Browsers never send a fragment to a server, so **Secreta's server cannot decrypt anything.** An API that accepted plaintext would have to encrypt server-side, putting the key on the server and silently downgrading every secret created that way.
So: you encrypt, you keep the key, you post the ciphertext.
Authentication
Authorization: Bearer apk_...
Created in Settings → API & Webhooks; shown once, stored hashed.
POST /api/secrets
curl -X POST https://secrets.example.com/api/secrets \
-H "Authorization: Bearer apk_..." -H "Content-Type: application/json" \
-d '{"ciphertext":"<base64 AES-256-GCM blob>","iv":"<base64 12-byte iv>",
"expiry_hours":24,"max_views":1}'
{ "id": 41, "token": "k3Jf...", "share_url": "https://secrets.example.com/s/k3Jf...",
"expires_at": "2026-08-07 09:00:00", "max_views": 1, "created_at": "2026-08-06 09:00:00",
"note": "Append \"#\" + your base64url key to share_url. The server never receives the key and cannot rebuild this link." }
share_url is deliberately incomplete. It has no # fragment because the server has never seen your key. Append it yourself:
FULL_LINK="${share_url}#${your_base64url_key}"
If the response ever did contain a working link, that would mean the server held the key — and the product would be broken. A test in the suite asserts the returned URL contains no #.
expiry_hours accepts 0, 1, 6, 24, 72, 168, 720; max_views is clamped to 1–100 — the same bounds the browser UI enforces. An API caller gets no wider limits than a person.
Posting something that is not base64 is rejected with 422, because the likely mistake is pasting plaintext, and storing that would hand you a "secret" that was never encrypted.
Webhooks — metadata only
| Event | Fires when |
|---|---|
secret.created | A secret is stored — from the share page, the REST API or /mcp |
secret.viewed | A recipient opens it |
secret.burned | The final view consumes it, or someone on your team burns it from Secrets (then views_left is 0) |
Payloads carry id, views_used, max_views, views_left, expires_at and passphrase_protected — and nothing else. There is no ciphertext, no iv and no key in a webhook, exactly as there is none in the audit log. A suite test asserts this.
Changed in 3.0: the token is no longer in the payload. It used to be, under a comment that said "metadata only" — but a token is the delivery credential, and for a multi-view secret secret.viewed fires while the link is still live, so the old payload posted a working share link to a third-party URL. Correlate on id instead.
X-Secreta-Event: secret.burned
X-Secreta-Signature: sha256=<hmac_sha256(raw_body, your_webhook_secret)>
MCP — the endpoint for AI agents (new in 3.0)
Secreta speaks the Model Context Protocol at POST /mcp, stateless streamable-HTTP. This is the shared best agent story: an agent that has to hand a human a credential can mint a one-time link instead of pasting the credential into a chat transcript that will be logged, summarised and retained.
Authentication is the same API key as the REST surface, and it carries the same role. GET /mcp answers 405 with Allow: POST rather than 404, so a probing client can tell the difference between "wrong method" and "no MCP support here".
| Tool | Reads or writes | What it does |
|---|---|---|
create_secret | WRITES | Mint a one-time link from ciphertext you have already encrypted. Returns share_url without the key fragment — append # + your base64url key yourself. |
stats | READ-ONLY | Counts only: created, live, burned, expired-unread, views, and the policy in force. No tokens. |
There is deliberately no read_secret tool, and that is a feature. Reading a secret burns it. An agent that fetched one would consume the recipient's only view and destroy the delivery it was asked to perform — and it would do so silently, leaving the human with a dead link and no explanation. Asking for read_secret returns Unknown tool. If an agent needs to know something, give it the value directly; Secreta exists to move a value to a person, once.
The tool takes ciphertext, never plaintext, for the same reason the REST endpoint does: a tool that accepted plaintext would have to encrypt server-side, which would put the key on the server and silently downgrade every secret created that way. Sending something that is not base64 is refused with a message that says exactly that.
Connect it to an assistant
Mint the key in the app first: Settings → API keys. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.
Every client needs the same three facts, and nothing in the handshake is vendor-specific:
| The address | https://secrets.example.com/mcp |
| The key | header Authorization: Bearer apk_xxxx |
| The transport | MCP over streamable HTTP, stateless |
Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:
claude mcp add --transport http secreta https://secrets.example.com/mcp \
--header "Authorization: Bearer apk_xxxx"
ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):
{
"type": "mcp",
"server_label": "secreta",
"server_url": "https://secrets.example.com/mcp",
"authorization": "apk_xxxx",
"require_approval": "never"
}
Own Your AI reads a list of servers in this shape:
{
"mcpServers": [
{ "id": "secreta", "name": "Secreta", "url": "https://secrets.example.com/mcp",
"token": "apk_xxxx", "enabled": true }
]
}
**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"* — not an mcpServers array. Pasted as-is, the block above will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.
A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:
curl -X POST https://secrets.example.com/mcp \
-H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Secreta implements the protocol, not an integration with a particular vendor.
What this product deliberately does NOT have
- No email notifications. Secreta sends no mail, so 2.0 adds no SMTP settings. A zero-knowledge tool should not carry a mail credential it never uses.
- No CSV import. There is nothing to import: a secret is generated, shared once, and destroyed. Bulk-loading historical secrets would mean holding plaintext to encrypt it — the exact thing this product exists to avoid.
Backup
Changed in 3.0. 2.0 exported every table with nothing redacted, reasoning that the stored secrets are already double-encrypted. That is true of the secrets — and it was false of everything around them: the same file carried users.password_hash, api_keys.token_hash, users.totp_secret and every webhook signing secret in clear text. Those are redacted now, and restoring onto a working install keeps the values that install already holds.
What deliberately stays is the secrets table — ciphertext, IV and token — because that is the data a restore exists to bring back, and it is useless without the browser key this server has never held. So be plain about what the file is: **a disaster-recovery artifact, not one to hand to a contractor**, because it contains live share tokens. What it cannot contain is any secret's plaintext. The suite proves that property survives a full backup→restore cycle by decrypting a restored secret with the original browser key.