Skip to content

Clients

There is no SDK. The intended way to drive 301.so is the shortlink skill, which lives in ~/.claude/skills/shortlink/ — a Claude Code skill, so the usual interface is a sentence rather than a command.

Terminal window
python3 ~/.claude/skills/shortlink/scripts/shortlink.py list
python3 ~/.claude/skills/shortlink/scripts/shortlink.py create <url> \
--slug yt-ig --utm utm_source=instagram
python3 ~/.claude/skills/shortlink/scripts/shortlink.py stats --days 30
python3 ~/.claude/skills/shortlink/scripts/shortlink.py dimensions --days 7
python3 ~/.claude/skills/shortlink/scripts/shortlink.py debug yt-ig

It is worth reading as a reference implementation for two reasons. It is the only client that talks to both stat sources — stats hits /api/stats (D1), dimensions hits the Cloudflare SQL API (Analytics Engine) — and it handles the user-agent trap.

Three rules cover most of it:

  1. Set a User-Agent. Cloudflare returns 403 / error code: 1010 for some default client agents, before the worker runs. This is the mistake everyone makes once. See Authentication.
  2. Send content-type: application/json on POST and PATCH; a body that doesn’t parse comes back as invalid_json.
  3. Treat 409 slug_taken as normal. Slugs stay reserved after archiving, so a collision means the address was used before — not that something is broken.
Terminal window
curl -s -X POST https://301.so/api/links \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-A "my-client/1.0" \
-d '{"url":"https://example.com","slug":"demo"}'

Six endpoints do not justify a generated spec — writing one would cost more than writing this page. If the API surface grows, or if something other than Vincent’s own tooling starts consuming it, that decision gets reopened.