HTTP API

Five endpoints, no keys to register, no accounts. This is the same API the pages on this site use.

The server stores bytes it cannot read. There is no encryption endpoint, because encryption is not the server's job. You seal the secret on your side, send the ciphertext, and keep the key. Anything you post in the clear is stored in the clear.

Base URL

https://sendkey.xyz

Every path on this page hangs off it. Self-hosting? The binary serves this exact API at whatever address you run it on; nothing here is special to sendkey.xyz.

Endpoints

POST/api/secret

Store ciphertext. Returns the public id used to fetch it back.

Body {ct, iv, ttl, views} → {id}

  • ct, iv: base64url, unpadded. Lengths and encoding are validated; the contents are never interpreted.
  • ttl: seconds until it expires on its own, up to 7 days.
  • views: how many reads it survives, 1 to 10.

Rate limited per IP.

GET/api/secret/{id}/meta

Expiry and remaining views, so a client can render a gate before committing to a read.

→ {expiresAt, views}

Does not burn.

GET/api/secret/{id}

Return the ciphertext and spend one view, in one atomic step. When the last view goes, the ciphertext is dropped on the spot.

→ {ct, iv}

Burns. There is no second call.

GET/api/secret/{id}/receipt

When the secret was opened, as a list of timestamps. Outlives the ciphertext: it keeps answering for the original lifetime, so a sender can still learn that a burned link was read.

→ {opens: [RFC3339, ...]}

Does not burn.

GET/api/stats

The aggregate counters behind /numbers: totals only, nothing per-secret and nothing per-person. Public so anyone can watch the same numbers we do.

→ {created, opened, burned, answered, sealed, ...}

Does not burn.

GET/healthz

Liveness, plus the deployment's own size ceiling so a client can refuse an oversized body before uploading it.

→ {ok, maxBytes}

A whole secret, start to finish

Storing and reading back, with the encryption left where it belongs. The id is public; the key is yours to deliver.

$ curl -s https://sendkey.xyz/api/secret -H 'Content-Type: application/json' -d '{"ct":"BASE64URL","iv":"BASE64URL","ttl":86400,"views":1}'

{"id":"Xk3vTzqLmARt7NpQw2ZbYg"}

$ curl -s https://sendkey.xyz/api/secret/Xk3vTzqLmARt7NpQw2ZbYg

{"ct":"BASE64URL","iv":"BASE64URL"}

$ curl -s -o /dev/null -w '%{http_code}\n' https://sendkey.xyz/api/secret/Xk3vTzqLmARt7NpQw2ZbYg

404

Calling it from a browser

Every endpoint answers with Access-Control-Allow-Origin: *, so your own page can use SendKey as a backend and still encrypt on the client. There is no cookie and no session to protect, which is exactly why opening it costs nothing.

If you would rather not write the crypto yourself, the envelope format is small and documented, and the browser implementation in /assets/crypto.js is the whole of it. The Go side is kept byte compatible with it by tests.