Professional, secure, and easy-to-use API for SecureNote.link
Overview
Welcome to the SecureNote.link API documentation. This API allows
you to create, retrieve, and manage secure, one-time notes with
optional password protection and expiration. All endpoints are
HTTPS only and require
Content-Type: application/json for
requests with a body. If using the API, you must encrypt your note first. See the examples below for encryption patterns.
🌐 Base URL:
https://securenote.link🛡️ Version: v1
Authentication & Rate Limiting
No authentication required for standard endpoints.
General API: 100 requests / 15 min
Password verification: 10 requests / 15 min
All requests must use HTTPS
Description
Create a new encrypted secret with optional password protection
and expiration.
# Curl example
curl https://your-domain.com/api/v1/health
Overview
These examples show AES-GCM encrypt/decrypt patterns used by the
SecureNote client and server. They are intentionally simple and
focused on interoperability: generate a 256-bit key, use a 96-bit
(12 byte) IV, and store/send the ciphertext, IV, and key using
base64 when needed. AES-GCM also produces an authentication tag
(usually appended to or transmitted with the ciphertext).
// Browser Web Crypto - AES-GCM
async function encrypt(plaintext) {
const enc = new TextEncoder();
const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt','decrypt']);
const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit
const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, enc.encode(plaintext));
const exportedKey = await crypto.subtle.exportKey('raw', key);
return {
ciphertext: btoa(String.fromCharCode(...new Uint8Array(ct))),
iv: btoa(String.fromCharCode(...iv)),
key: btoa(String.fromCharCode(...new Uint8Array(exportedKey)))
};
}
async function decrypt(ciphertextB64, ivB64, keyB64) {
const dec = new TextDecoder();
const ct = Uint8Array.from(atob(ciphertextB64), c => c.charCodeAt(0));
const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0));
const rawKey = Uint8Array.from(atob(keyB64), c => c.charCodeAt(0));
const key = await crypto.subtle.importKey('raw', rawKey, 'AES-GCM', true, ['decrypt']);
const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);
return dec.decode(plain);
}
# Python (cryptography + requests) - AESGCM + API calls
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os, base64
import requests
BASE_URL = "https://securenote.link"
def encrypt(plaintext: str):
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
iv = os.urandom(12)
ct = aesgcm.encrypt(iv, plaintext.encode('utf-8'), None)
return {
'ciphertext': base64.b64encode(ct).decode('utf-8'),
'iv': base64.b64encode(iv).decode('utf-8'),
'key': base64.b64encode(key).decode('utf-8')
}
def decrypt(ciphertext_b64: str, iv_b64: str, key_b64: str):
ct = base64.b64decode(ciphertext_b64)
iv = base64.b64decode(iv_b64)
key = base64.b64decode(key_b64)
aesgcm = AESGCM(key)
return aesgcm.decrypt(iv, ct, None).decode('utf-8')
def create_secret_api(plaintext, password=None, expires_in=24):
# Encrypt locally
enc = encrypt(plaintext)
payload = {
'encryptedData': enc['ciphertext'],
'iv': enc['iv'],
'password': password or None,
'expiresIn': expires_in
}
resp = requests.post(f"{BASE_URL}/api/v1/secrets", json=payload)
resp.raise_for_status()
data = resp.json()
# Build shareable URL: id in query, key in fragment
secret_id = data.get('id')
share_url = f"{BASE_URL}?id={secret_id}#{enc['key']}"
return { 'url': share_url, 'id': secret_id, 'key': enc['key'], 'meta': data }
def retrieve_and_decrypt(secret_id, key_b64):
# GET the secret from API (this will mark it accessed if no password)
resp = requests.get(f"{BASE_URL}/api/v1/secrets/{secret_id}")
resp.raise_for_status()
data = resp.json()
if data.get('passwordProtected'):
raise ValueError('Secret is password protected — call verify endpoint with the password')
content = data.get('content')
if not content:
raise ValueError('No content returned')
plaintext = decrypt(content['encryptedData'], content['iv'], key_b64)
return plaintext
def verify_password_and_get(secret_id, password, key_b64):
# For password-protected secrets, POST to verify endpoint to receive the encrypted content
resp = requests.post(f"{BASE_URL}/api/v1/secrets/{secret_id}/verify", json={'password': password})
resp.raise_for_status()
data = resp.json()
if not data.get('content'):
raise ValueError('Password incorrect or no content returned')
return decrypt(data['content']['encryptedData'], data['content']['iv'], key_b64)
if __name__ == '__main__':
# Example usage: create a secret and then retrieve it using the returned id/key
neat = create_secret_api('This is a very secret note', password=None, expires_in=24)
print('Share URL:', neat['url'])
# Simulate someone opening the URL: parse id and key from the URL
# (In a browser the key is in window.location.hash)
secret_id = neat['id']
key_b64 = neat['key']
# Retrieve and decrypt
try:
plaintext = retrieve_and_decrypt(secret_id, key_b64)
print('Decrypted:', plaintext)
except ValueError as e:
print('Error retrieving secret:', e)
// .NET (C#) - AesGcm (netcore / net5+)
using System;
using System.Security.Cryptography;
using System.Text;
static (string ciphertextB64, string ivB64, string keyB64) Encrypt(string plaintext) {
byte[] key = new byte[32];
RandomNumberGenerator.Fill(key);
byte[] iv = new byte[12];
RandomNumberGenerator.Fill(iv);
byte[] plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
byte[] ciphertext = new byte[plaintextBytes.Length];
byte[] tag = new byte[16];
using (var aesgcm = new AesGcm(key)) {
aesgcm.Encrypt(iv, plaintextBytes, ciphertext, tag, null);
}
// store ciphertext + tag or send separately
var combined = new byte[ciphertext.Length + tag.Length];
Buffer.BlockCopy(ciphertext, 0, combined, 0, ciphertext.Length);
Buffer.BlockCopy(tag, 0, combined, ciphertext.Length, tag.Length);
return (Convert.ToBase64String(combined), Convert.ToBase64String(iv), Convert.ToBase64String(key));
}
static string Decrypt(string ciphertextB64, string ivB64, string keyB64) {
var combined = Convert.FromBase64String(ciphertextB64);
var iv = Convert.FromBase64String(ivB64);
var key = Convert.FromBase64String(keyB64);
var tag = new byte[16];
var ciphertext = new byte[combined.Length - tag.Length];
Buffer.BlockCopy(combined, combined.Length - tag.Length, tag, 0, tag.Length);
Buffer.BlockCopy(combined, 0, ciphertext, 0, ciphertext.Length);
var plaintext = new byte[ciphertext.Length];
using (var aesgcm = new AesGcm(key)) {
aesgcm.Decrypt(iv, ciphertext, tag, plaintext, null);
}
return Encoding.UTF8.GetString(plaintext);
}
// Java - AES/GCM/NoPadding
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.SecureRandom;
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256);
SecretKey key = kg.generateKey();
byte[] iv = new byte[12]; new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] ct = cipher.doFinal("secret message".getBytes(java.nio.charset.StandardCharsets.UTF_8));
// store ct and iv; decode with the same key and GCMParameterSpec
// C++ (libsodium) - Secretbox is not AES-GCM, but example for authenticated encryption
#include <sodium.h>
// libsodium's crypto_aead_xchacha20poly1305_ietf_* can be used for AEAD in place of AES-GCM
Note: examples show how to encrypt/decrypt locally and how to represent
binary fields (ciphertext, iv, key) as base64 strings for
transmission/storage. In production, never transmit the raw key; the
key should remain in the client URL fragment (like SecureNote) or a
secure KMS. Use HTTPS and enforce proper key handling policies.