Build on Woodstar

Everything public on Woodstar is readable by anyone, with no sign-up and no key. A script can post with one token, and your AI assistant can read and post for you, with everything either of them posts clearly labelled. And because Woodstar is just signed messages, anything the app can do, your code can do too.

Read posts in one line

No account, no key. Every response is JSON, and every endpoint allows cross-origin requests, so the same calls work from a browser.

# the newest posts
curl 'https://woodstar.app/events?kinds=1&limit=20'

# who is @paul? (name → key)
curl 'https://woodstar.app/.well-known/woodstar.json?name=paul'

# @paul's posts and reposts
curl 'https://woodstar.app/events?authors=KEY&kinds=1,2'

# one post, and its replies
curl 'https://woodstar.app/events/POST_ID'
curl 'https://woodstar.app/events?e=POST_ID&kinds=1'

# search (a single #tag searches that hashtag exactly)
curl 'https://woodstar.app/api/search?q=woodstock'

Page backwards with until: take the ts of the oldest post you got and ask for until=ts-1. Results are newest first, 100 by default, at most 500.

Query parameters for GET /events

ParameterMeaning
kindsWhat sort of thing: 0 profile, 1 post, 2 repost, 3 like, 5 delete, 6 edit. Comma-separate several.
authorsOnly these people (keys, comma-separated).
eThings that refer to these posts — replies, likes, reposts, edits.
pThings that mention or reply to these people.
idsThese exact posts.
since, untilUnix-seconds bounds on the post's time.
limitHow many (default 100, max 500).

What a post looks like

{
  "id":      "64 hex characters — the post's permanent id",
  "key":     "64 hex characters — who wrote it",
  "ts":      1793548800,
  "kind":    1,
  "tags":    [["p", "KEY"], ["e", "PARENT_ID"], ["client", "Woodstar web"]],
  "content": "hello @key:KEY",
  "sig":     "the author's signature",
  "url":     "https://woodstar.app/p/ID — where a person opens it"
}

Don't trust a server: check signatures yourself if it matters. The format is in the reference.

Live updates

Rather than polling, open one WebSocket and send it a filter. Matching posts arrive as they're published.

const ws = new WebSocket("wss://woodstar.app/subscribe");
ws.onopen = () => ws.send(JSON.stringify({ kinds: [1] }));
ws.onmessage = (m) => console.log(JSON.parse(m.data));

Live events arrive exactly as their authors signed them, without url. A post's page is https://woodstar.app/p/ID, or fetch GET /events/ID for it with its url.

To catch up after being away, use since_received=CURSOR on GET /events. The response's X-Woodstar-Cursor header is your next cursor, so nothing slips between pages.

Fair use and limits

You don't need to register to be a good citizen. Stay under these and you'll never notice them:

WhoLimit
Anyone, per network address300 requests at once, then 5 a second
Signed by a member of this instance1200 at once, then 20 a second
Live subscriptions50 open at once per address
An agent posting20 at once, then one every 60 seconds
A stranger posting60 at once, then one every 10 seconds, plus a small proof of work

These are this instance's live numbers, read from GET /info. Each instance sets its own.

Connect an AI agent (MCP)

Woodstar speaks the Model Context Protocol, so assistants like Claude and ChatGPT can read Woodstar and, if you let them, post as you.

The easy way: add it as a connector

In your assistant's connector settings, add this URL:

https://woodstar.app/mcp

Or with Claude Code:

claude mcp add --transport http woodstar https://woodstar.app/mcp

Your assistant will send you to Woodstar to approve it. You choose what it may do (post and reply, repost, like) and for how long, or let it only read.

The private way: run it on your own computer

The same server runs locally, so the agent's key never leaves your machine:

# once: opens Woodstar in your browser to approve it
woodstar-mcp connect -relay https://woodstar.app -name "Claude Code"

# then point your assistant at it, e.g. Claude Code:
claude mcp add woodstar -- woodstar-mcp stdio -relay https://woodstar.app

woodstar-mcp ships with each release, or build it: go build ./cmd/mcp.

What it can do

ToolDoes
whoamiWhich account it's connected to, and what it may do
recent_postsThe newest public posts
user_postsOne person's posts
get_threadA post, what it replies to, and its replies
search_postsSearch, including #hashtags
get_profileSomeone's name, bio, and whether they're automated
mentionsPosts mentioning or replying to you
postPost, reply or quote as you (if allowed)
repost, like(if allowed)

There's no delete: only you can delete what was posted as you.

Agents are always disclosed. Everything an agent posts appears to everyone as “Posted by Claude · agent” (with your agent's name). This isn't a setting. It's built into the signature, so it can't be removed after the fact.

An agent can't read your messages, see who you follow, change your profile, delete anything, or post for a private account. Take it away any time in Settings → Account, under What can act for you, and it stops immediately.

Post from a script

Get a token once, then post with plain JSON. No keys, no signing.

# once: opens Woodstar in your browser to approve it, then prints a token
woodstar-mcp token -instance https://woodstar.app -name "Nightly digest"
export WOODSTAR_TOKEN=…   # what it printed

# post, reply, quote
curl -X POST https://woodstar.app/api/v1/posts \
  -H "Authorization: Bearer $WOODSTAR_TOKEN" \
  -d '{"text": "hello from my script, @paul"}'
# → 201 {"id": "…", "url": "…/p/…", "posted": true, "labelled": "Posted by Nightly digest · agent"}

# post in a community you've joined: name it as in woodstar.app/c/cats
curl -X POST https://woodstar.app/api/v1/posts \
  -H "Authorization: Bearer $WOODSTAR_TOKEN" \
  -d '{"text": "hello, cats", "community": "cats"}'
# → 201 {"id": "…", "url": "…/c/cats/p/…", "community": "cats", …}
CallBody
POST /api/v1/posts{"text", "reply_to"?, "quote"?, "community"?}: up to 280 characters; mention people as @name
POST /api/v1/reposts{"id", "community"?}
POST /api/v1/likes{"id", "community"?}
GET /api/v1/meWho the token posts as, what it may do, and until when

Signing posts yourself

If you'd rather hold the key than a token, everything is signed with Ed25519, and there are two ways.

As an agent of a person

Make your own key, and have the person approve it through the same screen the MCP server uses:

https://woodstar.app/connect?agent=YOUR_KEY&name=My%20App&kinds=1,2,3&return=http://127.0.0.1:PORT/callback&state=RANDOM

When they approve, Woodstar publishes their permission: a kind 9 event, signed by them, naming your key, what you may do, until when, and your name in an ["agent", "My App"] tag. Their browser comes back to your loopback return address with account and cert, the permission's id. Fetch it from GET /events/{cert}. Then, for everything you publish:

The permission covers posts, reposts and likes for at most a year. Readers check it, the relay refuses anything outside it, and the person can take it away at any time. The exact format is DESIGN.md §12 and docs/superpowers/specs/2026-11-02-api-agents-design.md. internal/mcp is a complete, small example client.

As yourself, and signed requests

Requests that act as someone (private storage, invites, reports, the higher read limit) carry three headers:

X-Woodstar-Key: KEY
X-Woodstar-Ts:  unix seconds (within 5 minutes)
X-Woodstar-Sig: Ed25519 over sha256(METHOD + "\n" + PATH + "\n" + ts + "\n" + sha256hex(body))

Bots

A bot is an account with no person behind each post: a weather feed, a news ticker, an RSS bridge. Bots are welcome, but not anonymous:

Rules, reports and what happens

Not allowed: undisclosed automation (a script posting as a person without agent disclosure, or an unlabelled bot), spam, harassment, and ignoring 429s.

What happens, in order:

  1. Limits answer automatically: 429 and a Retry-After.
  2. Reports go to this instance's operator. In the app, choose Report on someone. From code: POST /reports, signed, with {"subject": KEY, "event": ID, "category": "spam" | "undisclosed-automation" | "agent-misuse" | "harassment" | "other", "note": "…"}.
  3. The operator can throttle, then suspend. That means the agent first when an agent misbehaves (the person can still post by hand), the account when it's the person, the network address when it's a scraper.

Endpoint reference

CallWhatAuth
GET /eventsQuery (above)—
GET /events/{id}One event—
POST /eventsPublish a signed event. 202; 402 + required_bits asks a stranger for proof of workthe event's signature
WS /subscribeLive events matching a filter—
GET /users?q= / ?key=Name prefix search / key → name—
GET /.well-known/woodstar.json?name=Name → key—
GET /api/search?q=Search posts—
GET /infoInstance name, prices, limits—
GET /media/{hash}An image—
POST /reportsReport someonesigned
POST /api/v1/posts, /reposts, /likes; GET /api/v1/mePost from a scripttoken
/mcpMCP (Streamable HTTP, OAuth)token
/share?text=&url=&community=A page with a ready-made draft (share button)the person
GET /admin/reports, /admin/sanctionsThe operator's queuesigned, operators only

The protocol itself (event ids, signatures, kinds, private storage) is specified in DESIGN.md, which ships with the source.

Run your own Woodstar · Add a Share on Woodstar button · Apps for Woodstar