An API for Krabber, and a crab that reads the tide
Part of my Krabber series, a Twitter clone in Go. The full source is on GitHub.
Intro
Krabber works, but for a long time the Sea (the public timeline) was quiet. A social network with almost nobody posting is a sad thing to look at, even one you built mostly to prove a point. I wanted something on it every day, I wanted an excuse to finally give Krabber an API, and I liked the idea that a network full of crabs ought to know when the tide comes in. So I built two things at once: a small write API, and a bot called @scuttle that reads the Manhattan tide and sky and molts about it. This post is how both came together.
I. The API lives inside the app
The tempting thing is to stand up a separate service for the API. I didn’t. Krabber is one box and one table on purpose, and a second service would have meant a second deploy, a second thing to watch, and a second copy of the rules about what a molt is allowed to be. Instead the API is just new routes in the same Go app:
POST /api/v1/molts
Authorization: Bearer kb_...
Content-Type: application/json
{"text": "high tide at 6:42a %nyc"}
That handler calls the exact same store function the compose box on the website calls, and then runs the same publish path: the molt lands in the Sea, fans out to followers, and notifies anyone it mentions. There is no second code path to keep honest. The one thing the API skips is the browser machinery, the session cookie and the CSRF checks, because an API client carries a bearer key instead of a cookie. It still sits behind the same CloudFront origin check, the same security headers, and the same hourly write limit as everything else.
II. Keys you only see once
The auth is personal API keys, not a whole third-party app dance. A key is kb_ followed by 32 random bytes, and it acts as the krab who made it. I only ever store its SHA-256 hash, the same way I store session tokens, so reading the table (or a backup) never hands anyone a working key. You see the key once, when you mint it, and never again.
Minting is a terminal job, not a web page, for the same reason becoming an admin is: the operations that hand out power shouldn’t be reachable from a browser at all.
krabctl bot scuttle scuttle@krabber.net
krabctl apikey scuttle "scuttle bot"
Writing the auth is where I caught my own bug. When you delete a Krabber account, the tombstone frees your email so someone else could sign up with it later. My first draft loaded the key’s account by that email, which means a deleted bot’s key could have woken up one day acting as whoever claimed the address next. The website’s session code already guards against exactly this by checking the account’s id, and the API has to do the same. So a key is now rejected unless the account it points at is the same one that made it, is still allowed to sign in, and hasn’t changed its password since. A per-network rate limit in front of the whole thing keeps anyone from spraying random keys to run up my DynamoDB bill. None of that is exciting, but an API is a new front door, and I wanted the locks on before I handed out keys.
III. @scuttle, a crab with a tide chart
The bot is a tiny Go function on AWS Lambda. Twice a day a schedule wakes it up, it reads two free, no-key government feeds, and it posts one molt:
- the tide, from NOAA’s station at The Battery, and
- the forecast, from the National Weather Service for Central Park.
Daily Scuttle: low 1:46a, high 7:52a, low 2:09p, high 8:07p. Sunny, 71F, wind 3 to 10 mph. %nyc
Because it runs on a schedule and posts a fresh digest each time, it needs no memory of what it said before: no database, no dedupe, nothing to clean up. Fetch, format, post, done. The whole bot is small enough to read in one sitting.
Testing it taught me something about my own site. Krabber’s hashtags aren’t hashtags, they’re crabtags, and they start with a percent sign, not a pound sign (a small tribute to Crabber, the project Krabber is my Go take on). My first molt used #nyc and the tag quietly did nothing. One character later, %nyc, and the molt showed up on its tag page like it should. You write the thing, and the thing teaches you how it actually works.
Conclusion
Giving Krabber an API turned out to be mostly an exercise in not building too much: new routes instead of a new service, keys that reuse the session design, a bot with no state to lose. The payoff is that the API has a real first client proving it works, the Sea has something on it every day, and I get a tide chart delivered by a crab. If you want to watch it, @scuttle is posting. Thanks for reading, and may your tides always be in your favor.