API

Every endpoint speaks JSON. Errors return { "error": "...", "code": "..." } with the right HTTP status. Authenticate with Authorization: Bearer <owner_token | api_key>.

Endpoints

Method & pathAuthConsumes view?Purpose
POST /api/jotoptional API keyn/aCreate a jot. Optional password protects it. Returns id, url, raw_url, owner_token (shown once), expires_at.
GET /api/jot/{id}/metanoneNoMetadata only (no body), incl. is_password_protected. The "look before you consume" call.
POST /api/jot/{id}/unlocknoneNoVerify a password (body { "password": "..." }) → returns an unlock_token. Throttled.
GET /api/jot/{id}owner optionalYes (burn/limit)JSON including content. Owner (Bearer) reads never consume. A password-protected jot returns 401 until unlocked (no view consumed).
GET /raw/{id}owner optionalYes (burn/limit)text/plain body, hardened headers.
PATCH /api/jot/{id}owner token or API keyNoUpdate content/settings.
DELETE /api/jot/{id}owner tokenNoDelete the jot now. Returns 204.
POST /api/keynone (PoW)n/aMint an API key with no registration.
GET /api/jotsAPI keyNoList the jots created with this key.
GET /healthznonen/aReturns { status, version }.

Create a jot

curl -X POST https://jotary.com/api/jot \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: my-unique-key-123' \
  -d '{"content":"hello world","syntax":"plain","expiration":"1d","view_limit":1,"exposure":"unlisted"}'

Python

import requests
r = requests.post(
    "https://jotary.com/api/jot",
    headers={"Idempotency-Key": "my-unique-key-123"},
    json={"content": "hello world", "syntax": "plain", "expiration": "1d"},
)
data = r.json()
print(data["url"], data["owner_token"])

Node

const res = await fetch("https://jotary.com/api/jot", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "my-unique-key-123",
  },
  body: JSON.stringify({ content: "hello world", syntax: "plain", expiration: "1d" }),
});
const data = await res.json();
console.log(data.url, data.owner_token);

Read a jot

Use /meta to inspect without consuming a view, then read content when you mean it. For burn/view-limited jots, a content read (GET /api/jot/{id} or /raw) consumes a view; an owner (Bearer) read never consumes.

# Inspect without consuming a view:
curl https://jotary.com/api/jot/ABC12345/meta

# Read content (consumes a view for burn/view-limited jots):
curl https://jotary.com/api/jot/ABC12345

# Raw text:
curl https://jotary.com/raw/ABC12345

Password protection

Pass password at create time to protect a jot. A protected jot's content surfaces (GET /api/jot/{id}, /raw, and the HTML reader) return 401 until you unlock — no view is consumed while locked, so a burn/view-limited jot survives a failed or absent unlock. Unlock with POST /api/jot/{id}/unlock to receive a short-lived unlock_token; re-present it on reads via the X-Unlock-Token header (agents) — browsers receive an unlock_{id} cookie automatically. The owner can change or clear the password with PATCH (clearing = an empty password); any change invalidates outstanding unlock tokens.

# Create a password-protected jot:
curl -X POST https://jotary.com/api/jot \
  -H 'Content-Type: application/json' \
  -d '{"content":"top secret","password":"hunter2"}'

# A protected read is 401 until you unlock (no view is consumed):
curl -i https://jotary.com/api/jot/ABC12345        # -> 401

# Unlock with the password to get a short-lived unlock_token:
curl -X POST https://jotary.com/api/jot/ABC12345/unlock \
  -H 'Content-Type: application/json' \
  -d '{"password":"hunter2"}'
# -> {"unlock_token":"ABC12345.<exp>.<ver>.<sig>","expires_at":<ms>}

# Re-present the token on reads via the X-Unlock-Token header:
curl https://jotary.com/api/jot/ABC12345 \
  -H 'X-Unlock-Token: <unlock_token>'

# Owner can change/clear the password via PATCH (clearing = empty string):
curl -X PATCH https://jotary.com/api/jot/ABC12345 \
  -H 'Authorization: Bearer <owner_token>' \
  -H 'Content-Type: application/json' \
  -d '{"password":""}'

Edit & delete (owner token)

The owner_token is returned once on create. Keep it — it is required for PATCH and DELETE.

# Edit (owner token required):
curl -X PATCH https://jotary.com/api/jot/ABC12345 \
  -H 'Authorization: Bearer <owner_token>' \
  -H 'Content-Type: application/json' \
  -d '{"content":"updated body"}'

# Delete:
curl -X DELETE https://jotary.com/api/jot/ABC12345 \
  -H 'Authorization: Bearer <owner_token>'

API keys

An API key is a keyless identity handle — request one with a small proof-of-work, no signup. It lets you list and manage the jots you created; it never raises the per-IP create ceiling.

# Request an API key (proof-of-work gated, no registration), then list
# the jots created with it:
curl -X POST https://jotary.com/api/key

curl https://jotary.com/api/jots \
  -H 'Authorization: Bearer <api_key>'

Idempotency

Send an Idempotency-Key header on POST /api/jot and retries with the same key + body replay the original 201 response (including the same one-time owner_token).