# URLZ API documentation for agents

This file is the machine-oriented copy of the URLZ API docs at https://urlz.ca/docs. Use it to integrate URL shortening, QR generation, link management, and click analytics into a project.

- Base URL: `https://urlz.ca`
- API prefix: `https://urlz.ca/api/v1`
- HTML docs: https://urlz.ca/docs
- Create a key: https://urlz.ca/developers
- Placeholder key in examples: `YOUR_API_KEY` (never a real secret)

When this file disagrees with the live HTML docs, treat the HTML docs as source of truth and update this file.

## Authentication

One API key per account. Create or rotate it on Developers. The full secret is shown once at creation.

Preferred header:

```http
Authorization: Bearer YOUR_API_KEY
```

Query `?key=` is for scripts and browser GET tests only. If both Bearer and `?key=` are sent, Bearer wins.

Optional IP and Origin allowlists: when set on the key, every request must match. If you do not set IP or origin restrictions, API actions from any IP and any origin are accepted.

Never put the key in frontend HTML, public JavaScript, screenshots, or `img src`. Use a backend proxy for browser apps.

## Quick start

1. Create a URLZ account and open Developers.
2. Generate the one API key and store it in an environment variable (`URLZ_API_KEY`).
3. Call `GET /api/v1/shorten?url=https://example.com` with the Bearer header.
4. Open the returned `short_url`. Manage it later from My Links or `GET /api/v1/links/{identifier}`.

## Limits (free tier)

An Action is one shorten, one QR generation, or one link-management call (list, get, update, delete). Rotating a key does not reset the daily quota.

| Who | Limit |
| --- | --- |
| Anonymous website use | 300 Actions / day |
| Signed-in accounts and API keys | 600 Actions / day |
| API rate limits | 2 / second, 30 / minute, 100 / hour, 600 Actions / day per account |
| API keys | 1 key per account |
| QR API | API key required on every request |

429 responses include `Retry-After`.

## Language examples

### Browser (tests only)

Do not ship a production website that puts the key in HTML, JavaScript, or image URLs.

```
https://urlz.ca/api/v1/shorten?url=https%3A%2F%2Fexample.com&alias=my-clean-link&key=YOUR_API_KEY
```

### PHP

```php
$ch = curl_init('https://urlz.ca/api/v1/shorten?url=' . rawurlencode('https://example.com'));
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('URLZ_API_KEY')],
]);
$data = json_decode(curl_exec($ch), true);
```

### Python

```python
import os, requests
print(requests.get(
  'https://urlz.ca/api/v1/shorten',
  params={'url': 'https://example.com'},
  headers={'Authorization': 'Bearer ' + os.environ['URLZ_API_KEY']},
).json())
```

### JavaScript (server / trusted backend)

```javascript
const response = await fetch('https://urlz.ca/api/v1/shorten?url=' + encodeURIComponent('https://example.com'), {
  headers: { Authorization: 'Bearer ' + process.env.URLZ_API_KEY }
});
const data = await response.json();
```

### Command-line (Bash)

```bash
curl -H "Authorization: Bearer $URLZ_API_KEY" \
  "https://urlz.ca/api/v1/shorten?url=https%3A%2F%2Fexample.com&alias=optional"
```

## Shorten URLs

`GET` or `POST` `/api/v1/shorten`. Authentication required. Counts as 1 Action.

Parameters:

- `url` (required) — destination http(s) URL. Bare domains such as `abc.com` are accepted and normalized to HTTPS.
- `alias` (optional) — 7–64 characters, letters, numbers, `_` or `-`. Custom aliases are unique case-insensitively (`BellCanada` and `bellcanada` are the same reservation). The casing you submit is stored and shown on the short URL. Auto-generated identifiers stay case-sensitive.
- `expires` — `never`, `1h`, `1d`, `7d`, `30d`, `90d`, `1y`, `custom`.
- `custom_expires_at` — used when `expires=custom`.
- `max_clicks` — 1–1000000, or empty for unlimited.

Success: **201**.

```json
{
  "success": true,
  "short_url": "https://urlz.ca/my-clean-link",
  "identifier": "my-clean-link",
  "expires_at": null,
  "risk_status": "SAFE",
  "usage": { "used": 1, "limit": 600, "label": "1/600 Actions" }
}
```

Example:

```bash
curl -H "Authorization: Bearer YOUR_API_KEY" "https://urlz.ca/api/v1/shorten?url=https%3A%2F%2Fexample.com&alias=my-clean-link"
```

## QR codes

`GET` `/api/v1/qr` or `GET /api/v1/qr/{id}`. Returns a PNG or SVG image. API key required on every request. Counts as 1 Action. URLZ never fetches the destination page.

Parameters:

- `url` (on `/qr`) — website to encode.
- `format=png|svg|jpg|jpeg|webp`
- `download=1` — attachment filename.

Path style `/api/v1/qr/{id}` encodes a website URL, or an existing short identifier as `https://urlz.ca/{identifier}` (the public short URL, not the private destination).

```
https://urlz.ca/api/v1/qr?url=quebecstore.ca&format=png&key=YOUR_API_KEY
```

```bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://urlz.ca/api/v1/qr?url=quebecstore.ca&format=png" -o qr.png
```

Website QR generator (no API key in the browser): `https://urlz.ca/qr-code-generator`. POST encode always mints a tracked short (Smart QR), then opens the website studio for that short. Pasting an existing `urlz.ca/{id}` URL creates a wrapper short whose destination is the pasted URLZ link. My Links QR buttons open the studio for the existing identifier and do not mint another wrapper. Website customization is web-only. Website `GET /qr/{id}` is owner-only and requires a time-limited `token` query parameter. API `GET /api/v1/qr?url=` encodes the URL you pass without creating a short, always in the standard URLZ-branded style. Path `GET /api/v1/qr/{id}` only encodes shorts owned by the API key.

## Manage links

List, inspect, update, or delete links you own. Each call counts as 1 Action. Unknown identifiers and other users’ identifiers both return **404** (no existence leak).

- `GET /api/v1/links` — up to 100 owned, non-deleted links.
- `GET /api/v1/links/{identifier}` — one owned link plus `stats`.
- `POST` or `PATCH /api/v1/links/{identifier}` — optional `url`, `expires`, `max_clicks`, `status`.
- `DELETE /api/v1/links/{identifier}` — soft-delete. Success 200.

Status: readable values are `active`, `disabled`, `expired`, `deleted`. You may set `active` or `disabled` (`inactive` is accepted as `disabled`). `expired` and `deleted` are system-controlled (time cap, max clicks, or delete). Invalid values return 422 `INVALID_STATUS`.

A short link cannot be pointed at itself (`https://urlz.ca/{same-identifier}`).

```bash
curl -H "Authorization: Bearer YOUR_API_KEY" "https://urlz.ca/api/v1/links"

curl -X POST -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/new","expires":"7d","max_clicks":100,"status":"disabled"}' \
  "https://urlz.ca/api/v1/links/my-clean-link"
```

```json
{
  "success": true,
  "data": {
    "identifier": "my-clean-link",
    "short_url": "https://urlz.ca/my-clean-link",
    "destination_url": "https://example.com/new",
    "status": "disabled",
    "risk_status": "SAFE",
    "expires_at": null,
    "max_clicks": 100,
    "click_count": 3
  }
}
```

## Analytics

There is no standalone analytics route. `GET /api/v1/links/{identifier}` includes `stats` for that owned link only:

`total_clicks`, `unique_visitors`, `daily`, `devices`, `browsers`, `os`, `referrers`, `countries`.

```bash
curl -H "Authorization: Bearer YOUR_API_KEY" "https://urlz.ca/api/v1/links/my-clean-link"
```

## Errors

```json
{
  "success": false,
  "error": {
    "code": "INVALID_URL",
    "message": "The supplied URL is invalid."
  }
}
```

Common codes:

- `INVALID_API_KEY` (401)
- `NOT_FOUND` (404)
- `RATE_LIMITED` / `DAILY_QUOTA_EXCEEDED` (429 + `Retry-After`)
- `INVALID_URL`, `INVALID_STATUS`, `INVALID_MAX_CLICKS`, `ALIAS_TAKEN`, `ALIAS_TOO_SHORT`, `ALIAS_TOO_LONG`, `ALIAS_INVALID_CHARS`, `ALIAS_RESERVED` (422)
- `API_KEY_LIMIT` (422) — only one active API key per account

Unknown internals are not leaked; unexpected exceptions become `REQUEST_FAILED`.

## Security rules for integrators

- API keys are private credentials. Never commit them to Git.
- Never expose them in frontend production code, public screenshots, or client-side bundles.
- If a key is exposed: revoke it, generate a replacement. The old key stops working immediately.
- One API key per account, tied to that account only.
- Requests can only access resources owned by that account.
- Rate limits and daily quotas exist to reduce abuse.
- Browser and client-side apps should call a backend proxy rather than embedding the private key.

## Integration checklist

1. Store `URLZ_API_KEY` in the server environment.
2. Shorten with `/api/v1/shorten` and persist `identifier` + `short_url` in your database.
3. Optionally generate a QR of the short URL so printed codes stay reprint-safe when the destination changes.
4. Pause, expire, or cap visits via `/api/v1/links/{identifier}`.
5. Read `stats` from `GET /api/v1/links/{identifier}` for dashboards.
6. Handle 401 / 404 / 422 / 429, including `Retry-After`.
