The REST API

The base URL is https://api.frwrd.to. Every call except creating and recovering a workspace takes your workspace key as a bearer token:

Authorization: Bearer fw_...

The examples below read the key from $FRWRD_KEY. Bodies are JSON unless the example says otherwise.

Links

Create a short link:

curl -X POST https://api.frwrd.to/v1/links -H "Authorization: Bearer $FRWRD_KEY" -H "Content-Type: application/json" -d '{"long_url":"https://example.com/a/long/address","title":"Example"}'

The answer has the short link, its hash and the addresses of its QR code. Only http and https targets are accepted. You can also send tags, utm and, on a verified workspace, your own hash.

If your workspace has default UTM parameters, they are appended to the target before it is stored, and long_url in the answer is that stored target. A utm you send with the link wins over the defaults for the keys you give, and a utm_ parameter already in the address wins over both. Links that exist are not changed.

Set the defaults (the keys are source, medium, campaign, term and content; the new set replaces the old one, and {} clears it):

curl -X PUT https://api.frwrd.to/v1/workspace/utm -H "Authorization: Bearer $FRWRD_KEY" -H "Content-Type: application/json" -d '{"utm":{"source":"newsletter","medium":"email"}}'

List links, newest first (limit up to 200, and offset):

curl "https://api.frwrd.to/v1/links?limit=50" -H "Authorization: Bearer $FRWRD_KEY"

Numbers for one link, by default the last 30 days (since and until take a date such as 2026-10-01, at most 366 days apart):

curl "https://api.frwrd.to/v1/links/aB3xZ9/stats?since=2026-10-01" -H "Authorization: Bearer $FRWRD_KEY"

The answer gives the total, the split by channel (link for a direct click, qr for a scan, page for a click on a bio page) and the numbers per day.

For a chart, ask for a range instead: ?range=1d is the last 24 hours in 24 hourly buckets, 7d and 30d the last 7 or 30 UTC days in daily ones. The answer then adds buckets (each with start and a count per channel, zeros included, none left out) and all_time, the totals since the first click. Another value is a 400, and a range does not combine with since or until. The same works on the page stats below.

The numbers of all your links at once, so you need not open each one:

curl "https://api.frwrd.to/v1/workspace/stats?range=30d" -H "Authorization: Bearer $FRWRD_KEY"

It sums the clicks on every link of the workspace (archived links count, they were clicked; views of bio pages do not) into the same buckets, with total, by_channel and all_time, and adds top: the ten links with the most clicks in the range, each with hash, title, short_url, archived, total and by_channel. range is 1d, 7d (the default) or 30d. The answer is cached, so it may be up to 5 minutes old, and all_time is null when it could not be counted right now (everything else is still there). When the server is busy it answers 429 with a Retry-After: retry in a few seconds.

The QR code, as PNG or SVG (size from 128 to 2048):

curl "https://api.frwrd.to/v1/links/aB3xZ9/qr.png?size=512" -H "Authorization: Bearer $FRWRD_KEY" -o code.png

To change a link, send PATCH /v1/links/{hash} with any of long_url, title, tags and archived. DELETE /v1/links/{hash} archives it: it stops redirecting, and its numbers stay.

Pages

A page is a Markdown document with a short header. You save the whole document in one call. The path - is the root page; a page in another language lives under its language code, and the slash is written %2F, as in de%2Fhilfe.

curl -X PUT https://api.frwrd.to/v1/pages/hello -H "Authorization: Bearer $FRWRD_KEY" -H "Content-Type: text/markdown" --data-binary @hello.md

Every link in the document becomes a short link of your workspace. To read a page back as Markdown, send Accept: text/markdown; the answer carries an ETag. Send it back as If-Match when you save, and the call fails with 412 if someone changed the page meanwhile.

Views of a page and clicks on each of its links:

curl "https://api.frwrd.to/v1/pages/hello/stats" -H "Authorization: Bearer $FRWRD_KEY"

GET /v1/pages lists your pages, GET /v1/pages/{path}/versions shows the history and DELETE /v1/pages/{path} archives a page.

Bit.ly compatible /v4

Tools that speak Bit.ly's v4 API can point at https://api.frwrd.to/v4 and use your workspace key as the token. These calls exist, with Bit.ly's answers, timestamps and error messages:

Anything else of Bit.ly's is not served. A spent link quota answers 429 with MONTHLY_LIMIT_EXCEEDED.

Delete the workspace

DELETE /v1/workspace deletes the workspace. Deleting a workspace stops its key and takes its pages offline at once; after 7 days the pages and settings are deleted. Short links keep working. Proving the domain (key recovery) within 7 days restores everything; up to 60 days, the links. After 60 days the domain is free and the links keep working without an owner. Browsers may keep cached copies of a page, its QR code and images for up to an hour after the delete. Send the workspace's own domain as confirm; a missing or different value is a 400 and nothing is deleted. The answer is 204, and the key stops working with it.

curl -X DELETE https://api.frwrd.to/v1/workspace -H "Authorization: Bearer $FRWRD_KEY" -H "Content-Type: application/json" -d '{"confirm":"acme.com"}'

Limits and errors

The full contract is an OpenAPI document that lives with the source code, which is not public yet. Until then, the calls above, the MCP tool descriptions and frwrd.to/llms.txt describe the API.