# API reference

All paths are relative to the path in `base_url`. Every frontend, API,
metadata, deletion, and short-code route accepts trailing slashes, including
when a query string follows the slash.

## Health and frontend

- `GET /` serves the embedded frontend.
- `GET /api/health` returns HTTP 200:

```json
{"status": "ok", "service": "url-shortener"}
```

With a non-empty base path, requesting the bare path redirects to its trailing
slash form; for example, `/s` redirects to `/s/`.

## Create a short URL

`POST /api/shorten` and `GET /api/shorten` are supported without an API key.
Fields may be supplied as query parameters or in a JSON body; query parameters
take precedence.

| Field | Required | Description |
|---|---:|---|
| `url` | yes | Absolute `http://` or `https://` URL. |
| `retention_days` | no | Per-URL lifetime, or zero to keep forever. |

Success is HTTP 201 with only the short URL as UTF-8 plain text:

```text
https://example.com/s/aB3xYz
```

Codes begin at `min_short_length`. After ten collisions at one length, ushort
tries the next length through `max_short_length`.

## List URLs

`GET /api/urls?api_key=<key>` returns all rows newest first. A missing or
invalid key returns HTTP 403 with an empty body.

```json
{
  "count": 1,
  "urls": [
    {
      "short_code": "aB3xYz",
      "short_url": "https://example.com/s/aB3xYz",
      "original_url": "https://example.org",
      "created_at": 1710000000,
      "visit_count": 5,
      "retention_days": 30
    }
  ]
}
```

## Metadata and lookup

- `GET /api/urls/<code>` returns one metadata object without authentication.
- `GET /api/lookup?url=<encoded-url>` returns the newest matching metadata
  object without authentication.
- A missing record returns HTTP 404 with an empty body.

## Delete

`DELETE /api/urls/<code>?api_key=<key>` returns:

- HTTP 204 with an empty body on success;
- HTTP 404 with an empty body when the code does not exist; or
- HTTP 403 with an empty body when authentication fails.

## Redirect

`GET /<code>` atomically increments `visit_count` and responds with HTTP 302 to
the stored original URL.

Every response includes the compatibility CORS and security headers.
