URL Shortener API

A tiny Sinatra service. Shorten a URL, get a redirect, inspect stats.

Base URL

https://url.keychain.pl

Endpoints

POST /api/shorten

Shorten a URL. Body is JSON with a single url field. Returns the new record and a fully qualified short_url you can hand to a browser.

Request
curl -X POST https://url.keychain.pl/api/shorten \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/some/long/path"}'
Response 201
{
  "id": "aZ3kQ9m",
  "target_url": "https://example.com/some/long/path",
  "created_at": "2026-07-04T18:22:31Z",
  "click_count": 0,
  "short_url": "https://url.keychain.pl/aZ3kQ9m"
}
Try it

The form posts JSON via the _demo hint; your browser will show the raw response.

Errors
StatusCause
400Body is not valid JSON.
422url missing or not an http(s) URL.
GET /:id

Resolve a short id to its target. Increments click_count atomically and returns a 302 Found to the original URL.

Request
curl -i https://url.keychain.pl/aZ3kQ9m
# HTTP/1.1 302 Found
# Location: https://example.com/some/long/path
Errors
StatusCause
404No record exists for that id.
GET /api/urls/:id

Read metadata for a single short URL. Useful for building a stats dashboard without following the redirect.

Request
curl https://url.keychain.pl/api/urls/aZ3kQ9m
Response 200
{
  "id": "aZ3kQ9m",
  "target_url": "https://example.com/some/long/path",
  "created_at": "2026-07-04T18:22:31Z",
  "click_count": 4
}
GET /api/urls

List every short URL this server knows about, newest first.

Request
curl https://url.keychain.pl/api/urls
Response 200
{
  "count": 2,
  "urls": [
    {
      "id": "aZ3kQ9m",
      "target_url": "https://example.com/some/long/path",
      "created_at": "2026-07-04T18:22:31Z",
      "click_count": 4
    },
    {
      "id": "b7H2nXp",
      "target_url": "https://sinatrarb.com/",
      "created_at": "2026-07-04T18:25:02Z",
      "click_count": 0
    }
  ]
}

How ids are generated

Each id is 7 random characters from a 62-symbol alphabet (a-z A-Z 0-9), giving ~3.5 trillion possibilities. Collisions are extremely rare but checked and retried up to 5 times at insert time.

Storage

Google Cloud Firestore in Native mode, on the Spark free tier (1 GiB storage, 50K reads / 20K writes per day, no billing account required). Configure with FIRESTORE_PROJECT_ID (required) and FIRESTORE_COLLECTION (optional, defaults to urls). Locally, GOOGLE_APPLICATION_CREDENTIALS points at a service-account JSON key; on Cloud Run the runtime service account is used automatically.