API
Three endpoints. JSON in, JSON out. No auth on shorten and revoke; the revocation key is the authentication.
POST /shorten
Submit a URL, receive a short URL and a one-time revocation key.
curl -X POST https://api.fwd2.app/shorten \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com/an-honest-path"}'
Response:
{
"short_url": "https://fwd2.app/K7m-9Qw",
"code": "K7m-9Qw",
"revocation_key": "wH8h9Bk7vGqL2pYz...",
"warning": "Save the revocation key now. It is shown once."
}
DELETE /links/<code>
Delete a link given its code and revocation key. The key goes in the body or the X-Revocation-Key header.
curl -X DELETE https://api.fwd2.app/links/K7m-9Qw \
-H 'Content-Type: application/json' \
-d '{"revocation_key":"wH8h9Bk7vGqL2pYz..."}'
On success: 204 No Content. On failure (unknown code or wrong key): 403 with the same message either way.
POST /revoke
Equivalent to DELETE for callers that cannot send DELETE.
curl -X POST https://api.fwd2.app/revoke \
-H 'Content-Type: application/json' \
-d '{"code":"K7m-9Qw","revocation_key":"wH8h9Bk7vGqL2pYz..."}'
GET /<code>
302 to the target URL. 404 if the code is not stored. Available on both api.fwd2.app and fwd2.app; the public short URL uses fwd2.app.
GET /health, /health/live, /health/ready
/health and /health/live are cheap liveness checks: 200 means the process is up.
/health/ready probes the database and the rate-limit store; 200 means the service can take traffic, 503 means it cannot yet.
All three are no-cache, no-store.
Errors
Errors return {"error":"<code>","message":"<human>"} with a meaningful HTTP status.
Unknown code and wrong key both return 403 revoke_failed so a guesser cannot enumerate codes.
415 unsupported_media_type if a body is sent without Content-Type: application/json.
413 payload_too_large if the body is over the configured cap.
Rate limits
POST /shorten and the revoke endpoints are rate limited per client. When the limit trips the response is
429 rate_limited with a Retry-After header in seconds. Revoke is also throttled per-code so a
brute-force grinder cannot hammer one short link from many IPs. The defaults are tuned for humans using the site; scripted use
within the limits is fine.
Target URL safety
fwd2 refuses targets that point back at fwd2 itself, hosts that resolve to private, loopback, or link-local IPs, and any
URL that carries credentials in the userinfo. Operators can extend the rejection set through FWD2_DENYLIST_HOSTS
and FWD2_DENYLIST_SUFFIXES. These rules cut off the easy misuse paths; they do not promise that every linked
site is safe.
Worked example
Shorten a URL, then delete it. The plaintext key only ever appears in the shorten response.
# 1. shorten
$ curl -sX POST https://api.fwd2.app/shorten \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com/draft"}'
{"short_url":"https://fwd2.app/K7m-9Qw","code":"K7m-9Qw",
"revocation_key":"wH8h9Bk7vGqL2pYzPmTuVwXyA1bCdEfG","warning":"..."}
# 2. revoke with the key
$ curl -sX DELETE https://api.fwd2.app/links/K7m-9Qw \
-H 'Content-Type: application/json' \
-d '{"revocation_key":"wH8h9Bk7vGqL2pYzPmTuVwXyA1bCdEfG"}' \
-w '%{http_code}\n'
204
# 3. the redirect now 404s
$ curl -sI https://fwd2.app/K7m-9Qw | head -1
HTTP/2 404