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