Skip to content
VoidNote

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.

1. You encrypt content with AES-256-GCM. Key = SHA-256(hex_decode(secret)).
2. Send only the ciphertext + IV to the server. The secret stays with you.
3. Share the full URL: https://voidnote.net/n/<tokenId>#<secret>. The #fragment is never sent to the server per HTTP spec.
4. Recipient fetches the encrypted blob, decrypts locally with the secret from the URL.

Authentication

Creating notes requires a vn_ API key from your dashboard. Reading and peeking are unauthenticated.

request header
Authorization: Bearer vn_...
POST /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.

request body
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
response 201
{
  "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.

GET /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.

response 200
{
  "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)
GET /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.

response 200
{
  "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.

GET /api/v1/vault/:tokenId/meta

Returns {"exists","fileName","sizeBytes","createdAt","expiresAt"} — no auth, no download.

GET /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)).

X-Vault-IV: <hex nonce>  ·  X-VoidNote-Algorithm: AES-256-GCM  ·  Content-Type: application/octet-stream

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.

POST /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.

POST /api/stream/:token/write

Send encrypted content to all connected listeners. Body: {"content": "..."}

GET /api/stream/:token/events

SSE endpoint. Connect to receive real-time encrypted messages as text/event-stream.

POST /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

pip install voidnote
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

npm install @voidnote/sdk
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

go get github.com/quantum-encoding/voidnote-go
result, err := voidnote.Create("my secret", voidnote.CreateOptions{
    APIKey:    "vn_...",
    MaxViews:  1,
    ExpiresIn: 1,
})
fmt.Println(result.URL)

Java

Maven: net.voidnote:voidnote-java:0.2.0
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

Swift Package Manager
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