API Reference
Base URL: https://voidnote.net
All /api/v1/ endpoints return JSON, support CORS, and accept
Authorization: Bearer vn_... for authenticated routes.
Machine-readable spec: /api/v1/openapi.json
Security model
VoidNote is zero-knowledge. The server stores only ciphertext — plaintext never leaves your machine.
SHA-256(hex_decode(secret)). https://voidnote.net/n/<tokenId>#<secret>. The #fragment is never sent to the server per HTTP spec. Authentication
Creating notes requires a vn_ API key from your
dashboard.
Reading and peeking are unauthenticated.
/api/v1/note auth required · 1 credit
Create a note. You encrypt the content; the server generates a tokenId and
returns a base URL. Append #secret to form the full shareable link.
| Field | Type | Description |
|---|---|---|
| encrypted * | string | AES-256-GCM ciphertext (hex-encoded) |
| iv * | string | 12-byte GCM nonce (hex-encoded) |
| viewLimit | int | Views before destruction. 1–100, default 1 |
| expiresIn | int | Lifetime in seconds. 60–2592000, default 3600 |
{
"token": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"url": "https://voidnote.net/n/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"expiresAt": "2025-01-01 12:00:00",
"viewLimit": 1
}
Append #<secret> to url — that's the shareable link.
The secret never touches the server.
/api/v1/note/:tokenId/consume no auth · free Fetch the encrypted blob and decrement the view count. Returns the ciphertext — decrypt locally. On the final view the note is permanently destroyed.
{
"token": "a1b2c3...",
"encrypted": "...", // hex — AES-256-GCM ciphertext
"iv": "...", // hex — 12-byte nonce
"algorithm": "AES-256-GCM",
"keyDerivation": "SHA-256", // key = SHA-256(hex_decode(secret))
"viewsRemaining": 0,
"destroyed": true
} | Status | Meaning |
|---|---|
| 200 | Encrypted blob returned, view counted |
| 404 | Note not found or already destroyed |
| 429 | Rate limited (60 req/min unauthenticated) |
/api/v1/note/:tokenId/meta no auth · free · no view consumed Peek at metadata without consuming a view. Use this to check if a note still exists before revealing it.
{
"token": "a1b2c3...",
"exists": true,
"type": "secure",
"createdAt": "2025-01-01 10:00:00",
"expiresAt": "2025-01-01 11:00:00",
"viewLimit": 1,
"viewsRemaining": 1
} Vault
Encrypted file storage. Upload once, download once — the file is destroyed after the first download.
/api/v1/vault/:tokenId/meta Returns {"exists","fileName","sizeBytes","createdAt","expiresAt"} — no auth, no download.
/api/v1/vault/:tokenId/consume
Download encrypted binary. File is destroyed after this call.
IV is returned in X-Vault-IV header.
Decrypt: key = SHA-256(hex_decode(secret)).
Streams
Live encrypted channels over SSE. The server relays ciphertext only — keys never leave the sender. Streams auto-expire after their TTL and can be closed early at any time.
/api/stream auth required · 1 credit
Create a stream. Pass tokenId (first 32 chars of a client-generated 64-char hex token),
ttl (seconds: 3600, 21600, or 86400),
mode ("broadcast" or "chat"),
and optional title and password.
/api/stream/:token/write Send encrypted content to all connected listeners. Body: {"content": "..."}
/api/stream/:token/events SSE endpoint. Connect to receive real-time encrypted messages as text/event-stream.
/api/stream/:token/close Close and destroy the stream immediately. Owner only.
SDK examples
All SDKs handle encryption/decryption automatically. No crypto boilerplate needed.
Python
import voidnote
result = voidnote.create(
"my secret API key",
api_key="vn_...",
max_views=1,
expires_in=1, # hours
)
print(result.url) # https://voidnote.net/n/<token>#<secret> import voidnote
note = voidnote.read("https://voidnote.net/n/<token>#<secret>")
print(note.content)
print(f"{note.views_remaining} views remaining") import voidnote
meta = voidnote.peek("https://voidnote.net/n/<token>#<secret>")
print(meta.exists, meta.views_remaining, meta.expires_at) TypeScript / JavaScript
import { create } from "@voidnote/sdk";
const result = await create("my secret", {
apiKey: "vn_...",
maxViews: 1,
expiresIn: 1,
});
console.log(result.url); // https://voidnote.net/n/<token>#<secret> import { read } from "@voidnote/sdk";
const note = await read("https://voidnote.net/n/<token>#<secret>");
console.log(note.content); Go
result, err := voidnote.Create("my secret", voidnote.CreateOptions{
APIKey: "vn_...",
MaxViews: 1,
ExpiresIn: 1,
})
fmt.Println(result.URL) Java
import net.voidnote.*;
CreateResult result = VoidNote.create(
"my secret API key",
CreateOptions.of("vn_...")
.withMaxViews(1)
.withExpiresIn(1));
System.out.println(result.url()); ReadResult note = VoidNote.read("https://voidnote.net/note/TOKEN");
System.out.println(note.content());
System.out.println(note.destroyed() ? "destroyed" : note.viewCount() + " views"); StreamHandle stream = VoidNote.createStream(
StreamOptions.of("vn_...").withTitle("Deploy log"));
System.out.println("share: " + stream.url);
stream.write("Build started...");
stream.write("All tests passed");
stream.close(); Swift
import VoidNote
let result = try await VoidNote.create(
"my secret API key",
apiKey: "vn_...",
maxViews: 1,
expiresIn: 1)
print(result.url) let note = try await VoidNote.read("https://voidnote.net/note/TOKEN")
print(note.content)
print("views remaining: \(note.viewsRemaining)") let stream = try await VoidNote.createStream(
apiKey: "vn_...", title: "Deploy log")
print("share: \(stream.url)")
try await stream.write("Build started...")
try await stream.write("All tests passed")
try await stream.close() Rate limits
| Auth | Limit | Window |
|---|---|---|
| Authenticated (Bearer vn_...) | 300 | per minute |
| Unauthenticated (IP-based) | 60 | per minute |