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.
python3 ~/.claude/skills/shortlink/scripts/shortlink.py listpython3 ~/.claude/skills/shortlink/scripts/shortlink.py create <url> \ --slug yt-ig --utm utm_source=instagrampython3 ~/.claude/skills/shortlink/scripts/shortlink.py stats --days 30python3 ~/.claude/skills/shortlink/scripts/shortlink.py dimensions --days 7python3 ~/.claude/skills/shortlink/scripts/shortlink.py debug yt-igIt 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.
Writing your own
Section titled “Writing your own”Three rules cover most of it:
- Set a
User-Agent. Cloudflare returns403/error code: 1010for some default client agents, before the worker runs. This is the mistake everyone makes once. See Authentication. - Send
content-type: application/jsononPOSTandPATCH; a body that doesn’t parse comes back asinvalid_json. - Treat
409 slug_takenas normal. Slugs stay reserved after archiving, so a collision means the address was used before — not that something is broken.
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"}'No OpenAPI spec
Section titled “No OpenAPI spec”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.