Wirepass developer notes

Everything is plain HTTP and JSON. Replace alex with your username. K is a pass key, F a feed key, SECRET the owner secret shown once at registration.

Send a message

curl -X POST "https://wirehook.org/alex/in?key=K" -d "Disk is 91% full"
curl -X POST "https://wirehook.org/alex/in?key=K&subject=Nightly%20backup" -H "Idempotency-Key: backup-2026-09-14" -d "done in 4m12s"

The whole body is one message (up to 10,000 characters). subject is optional. Repeating an Idempotency-Key within two days stores nothing new. Responses: 200 stored, 403 unknown or deleted pass, 413 too big, 429 slow down.

Email: send to K@yourdomain.com, where an email pass's K is its name followed by 16 hex (bank-alerts-4f8a2c9e1b3d7f02) and a webhook pass's is 32 hex. Both shapes are accepted forever, so keys made before this change still work. The plain-text part is kept (5,000 characters), attachments are dropped.

Read a feed

curl "https://wirehook.org/alex/inbox/rss?key=F"              # RSS 2.0, newest first, 100 per page
curl "https://wirehook.org/alex/inbox/rss?key=F&read=1"       # HTML
curl "https://wirehook.org/alex/inbox/rss?key=F&before=2026-09-01T00:00:00.000Z"   # older page
curl "https://wirehook.org/alex/news/rss"                     # public feed (no key)

Responses carry an ETag; send If-None-Match to get 304. Private feeds may be up to 45 seconds stale, public ones 5 minutes. Add &fresh=1 to bypass the cache.

Manage

POST JSON with your username and password. No cookies.

POST /register        {"u":"alex","p":"…"}                       → config + secret (once)
                      403 once this instance has an account, unless OPEN_SIGNUP is set
POST /login           {"u":"alex","p":"…"}                       → config
POST /reset-password  {"u":"alex","secret":"SECRET","newp":"…"}
POST /passes          {"u","p","action":"create","label":"bank","kind":"webhook|email","feed":"inbox"}
                      {"u","p","action":"delete"|"reset","pass":"K"}
                      {"u","p","action":"forward","pass":"K","to":"https://… | you@example.com | """}
POST /feeds           {"u","p","action":"create"|"delete"|"reset","name":"news"}
                      {"u","p","action":"publish","name":"news","public":true}
                      {"u","p","action":"daily","name":"news","daily":true}
                      {"u","p","action":"configure","name":"news","keep":30}
POST /rules           {"u","p","pass":"K","rules":[…]}
POST /settings        {"u","p","type":"serif|sans"}          → how Read here sets message text
POST /reset-all       {"u","p","secret":"SECRET"}                → config + new secret

Rules

A rule is one condition and one action, and every rule belongs to a pass:

{"field":"from",    "contains":"bank.com", "action":"route", "value":"money"}
{"field":"subject", "contains":"urgent",   "action":"flag"}
{"field":"word",    "contains":"invoice",  "action":"tag",   "value":"bills"}
{"field":"any",                            "action":"drop"}

Fields: any, from, subject, word (subject or body). Matching is "contains", ignoring case. Actions: route, tag, flag, drop. Exactly one rule ever applies — the list is read in order and the first match wins. Sending an empty list clears the rules.

Feeds have no rules. Whether a feed arrives message by message or as one summary a day is daily on the feed, so "put these in the digest" is "send them to a feed that arrives daily". A daily feed queues arrivals in digest_items; the 08:00 UTC cron turns each feed's queue into a single message and empties it. A feed can't be both daily and public.

Links and typography

Bodies are stored exactly as received and exported that way. Rendering is where links are shortened: a bare URL becomes <a href="the whole thing">host/first-bit…</a>, in the HTML page and in the RSS <description>, which carries HTML so a reader can render it. A reader that shows descriptions as plain text will show the markup instead; add &read=1 and read it here in that case.

type on the account (serif or sans) sets the face for message text on the HTML page, as <body data-type>. Metadata is always monospace. The cache key includes the choice, so switching takes effect at once.

Forwarding

A pass has forwardUrl, forwardEmail and forwardWhen (all or important). Either destination can be set, or both. The url is sent a POST:

POST https://your-endpoint
content-type: application/json

{"user":"alex","pass":"bank alerts","feed":"money","time":"2026-09-14T08:20:40.374Z",
 "important":true,"sender":"alerts@bank.com","subject":"Statement ready","body":"…"}

forwardEmail only works on an email pass — a webhook arrival has no mail to relay — and Cloudflare will only deliver to a destination address verified in Email Routing; the original message goes on unchanged. important means a rule on that pass applied flag.

Forwarding happens after the rules, so anything drop ignored is never copied, and it never blocks storing the message: a dead endpoint or an unverified address loses the copy, not the message. Send "" for a destination to stop it. Records that used the older single forward field are still read correctly and are rewritten the next time the pass is saved.

Export

curl -H "Authorization: Bearer SECRET" "https://wirehook.org/alex/all" > export.json
curl -H "Authorization: Bearer SECRET" "https://wirehook.org/alex/all?before=…"   # next 5,000 messages, using "next" from the previous page

The export has your configuration (passes, feeds, rules), messages, and anything queued for a daily summary. Passwords and secrets are never included.

Discovery

curl "https://wirehook.org/.well-known/webfinger?resource=acct:alex@wirehook.org"

Lists public feeds only. Accounts with no public feed look the same as accounts that don't exist.

Operations