Ownware
Home›Secreta›API
Secreta · API

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

EventFires when
secret.createdA secret is stored — from the share page, the REST API or /mcp
secret.viewedA recipient opens it
secret.burnedThe 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".

ToolReads or writesWhat it does
create_secretWRITESMint a one-time link from ciphertext you have already encrypted. Returns share_url without the key fragment — append # + your base64url key yourself.
statsREAD-ONLYCounts 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 addresshttps://secrets.example.com/mcp
The keyheader Authorization: Bearer apk_xxxx
The transportMCP 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.

← Back to Secreta · Manual · Quickstart · Test run

Affiliate program
Recommend tools people own — earn 35% on every sale. 90-day tracking, instant delivery, payouts by Lemon Squeezy.
Become an affiliate →