Developer Documentation Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Build an MkDocs documentation site with tested code examples that stay in sync with the API via CI.
Architecture: Example code lives in examples/ as standalone runnable files with --8<-- snippet markers. Tests in tests/test_examples.py import/execute/validate those examples against the test API. MkDocs pages in docs/ pull snippets from examples/ via pymdownx.snippets. GitHub Actions deploys to GitHub Pages on push to main.
Tech Stack: MkDocs, mkdocs-material, pymdownx.snippets, pytest, FastAPI TestClient
Design doc: docs/plans/2026-05-13-developer-docs-design.md
Task 1: MkDocs Project Setup¶
Files:
- Create: mkdocs.yml
- Modify: requirements.txt:1-12
- [ ] Step 1: Add MkDocs dependencies to requirements.txt
Append to the end of requirements.txt:
- [ ] Step 2: Create mkdocs.yml
site_name: usnap.me Docs
site_url: https://granteagon.github.io/usnap.me
repo_url: https://github.com/granteagon/usnap.me
repo_name: granteagon/usnap.me
theme:
name: material
palette:
- scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
toggle:
icon: material/brightness-4
name: Switch to light mode
markdown_extensions:
- pymdownx.snippets:
base_path: "."
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- admonition
- pymdownx.details
- toc:
permalink: true
nav:
- Home: index.md
- Quickstart: quickstart.md
- Code Examples: examples.md
- API Reference: api-reference.md
- Rate Limits & Quotas: rate-limits.md
- [ ] Step 3: Create placeholder docs/index.md
This lets us verify the MkDocs build works before writing real content.
- [ ] Step 4: Install dependencies and verify MkDocs builds
Run: pip install -r requirements.txt && mkdocs build --strict
Expected: INFO - Documentation built in X.XX seconds with no errors.
- [ ] Step 5: Commit
git add mkdocs.yml requirements.txt docs/index.md
git commit -m "feat: add MkDocs project setup with material theme"
Task 2: cURL Example File¶
Files:
- Create: examples/create_link.sh
The shell script is a reference file — not meant to be executed directly. It contains cURL commands that tests/test_examples.py (Task 5) will parse and replay. Each snippet is delimited by marker comments.
- [ ] Step 1: Create examples/create_link.sh
#!/usr/bin/env bash
# Example cURL commands for the usnap.me API.
# Each snippet is delimited by markers for MkDocs inclusion.
API_KEY="your-api-key"
BASE_URL="https://usnp.me"
curl -X POST "$BASE_URL/shorten" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"redirect_url": "https://example.com/landing"
}'
curl -X POST "$BASE_URL/shorten" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"redirect_url": "https://example.com/product",
"data": {"sku": "WIDGET-42", "campaign": "summer-2026"},
"webhooks": ["https://your-server.com/webhook"]
}'
curl -O -J "$BASE_URL/abc123/qr?format=png&scale=10"
curl "$BASE_URL/usage" \
-H "X-API-Key: $API_KEY"
- [ ] Step 2: Commit
Task 3: Python Example File¶
Files:
- Create: examples/create_link.py
- [ ] Step 1: Create examples/create_link.py
"""Example: Create shortlinks using the usnap.me API with Python (requests)."""
import requests
API_KEY = "your-api-key"
BASE_URL = "https://usnp.me"
response = requests.post(
f"{BASE_URL}/shorten",
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={"redirect_url": "https://example.com/landing"},
)
print(response.status_code)
print(response.json())
response = requests.post(
f"{BASE_URL}/shorten",
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={
"redirect_url": "https://example.com/product",
"data": {"sku": "WIDGET-42", "campaign": "summer-2026"},
"webhooks": ["https://your-server.com/webhook"],
},
)
print(response.status_code)
print(response.json())
- [ ] Step 2: Commit
Task 4: JavaScript Example File¶
Files:
- Create: examples/create_link.js
- [ ] Step 1: Create examples/create_link.js
// Example: Create shortlinks using the usnap.me API with JavaScript (fetch).
const API_KEY = "your-api-key";
const BASE_URL = "https://usnp.me";
const basicResponse = await fetch(`${BASE_URL}/shorten`, {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
redirect_url: "https://example.com/landing",
}),
});
const basicResult = await basicResponse.json();
console.log(basicResult);
const dataResponse = await fetch(`${BASE_URL}/shorten`, {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
redirect_url: "https://example.com/product",
data: { sku: "WIDGET-42", campaign: "summer-2026" },
webhooks: ["https://your-server.com/webhook"],
}),
});
const dataResult = await dataResponse.json();
console.log(dataResult);
- [ ] Step 2: Commit
git add examples/create_link.js
git commit -m "feat: add JavaScript example file with snippet markers"
Task 5: Webhook Receiver Examples¶
Files:
- Create: examples/webhook_receiver.py
- Create: examples/webhook_receiver.js
- [ ] Step 1: Create examples/webhook_receiver.py
"""Example: Webhook receiver with HMAC signature verification (FastAPI)."""
import hashlib
import hmac
import json
def verify_webhook_signature(payload_bytes: bytes, signature_header: str, secret: str) -> bool:
"""Verify the X-Webhook-Signature header matches the payload."""
expected = hmac.new(secret.encode(), payload_bytes, hashlib.sha256).hexdigest()
received = signature_header.removeprefix("sha256=")
return hmac.compare_digest(expected, received)
from fastapi import FastAPI, Request, HTTPException
receiver_app = FastAPI()
WEBHOOK_SECRET = "your-webhook-secret"
@receiver_app.post("/webhook")
async def handle_webhook(request: Request):
body = await request.body()
signature = request.headers.get("X-Webhook-Signature", "")
if not verify_webhook_signature(body, signature, WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
payload = json.loads(body)
print(f"Link clicked: {payload['short_id']}")
print(f"Custom data: {payload.get('data')}")
return {"status": "ok"}
- [ ] Step 2: Create examples/webhook_receiver.js
// Example: Webhook receiver with HMAC signature verification (Express.js).
const crypto = require("crypto");
const express = require("express");
function verifyWebhookSignature(payloadBytes, signatureHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payloadBytes)
.digest("hex");
const received = signatureHeader.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(received, "hex")
);
}
const app = express();
const WEBHOOK_SECRET = "your-webhook-secret";
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-webhook-signature"] || "";
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
const payload = JSON.parse(req.body);
console.log(`Link clicked: ${payload.short_id}`);
console.log(`Custom data: ${JSON.stringify(payload.data)}`);
res.json({ status: "ok" });
});
app.listen(3000, () => console.log("Webhook receiver on :3000"));
- [ ] Step 3: Commit
git add examples/webhook_receiver.py examples/webhook_receiver.js
git commit -m "feat: add webhook receiver examples with HMAC verification"
Task 6: Tests for All Example Files¶
Files:
- Create: tests/test_examples.py
This test file validates that every code example stays in sync with the actual API. It tests:
1. Python examples: Import and execute the request-building code against the FastAPI test client.
2. cURL examples: Parse the shell commands to extract method, URL path, headers, and body. Replay as test client requests. Assert endpoints and payloads match the API.
3. JS examples: Parse the fetch calls to extract URL, method, headers, and body JSON. Assert they are syntactically correct and match the API contract (method, path, Content-Type header, body keys).
4. Webhook receivers: Call verify_webhook_signature directly with valid/invalid signatures.
- [ ] Step 1: Write the cURL parsing test
This test extracts the cURL commands from examples/create_link.sh, parses them, and replays them against the test client.
"""Tests that verify all example code stays in sync with the API."""
import hashlib
import hmac
import json
import re
from pathlib import Path
import pytest
EXAMPLES_DIR = Path(__file__).parent.parent / "examples"
def extract_snippet(filepath: Path, snippet_name: str) -> str:
"""Extract a named snippet from a file with --8<-- markers."""
text = filepath.read_text()
pattern = rf"#\s*--8<--\s*\[start:{snippet_name}\]\n(.*?)#\s*--8<--\s*\[end:{snippet_name}\]"
match = re.search(pattern, text, re.DOTALL)
assert match, f"Snippet '{snippet_name}' not found in {filepath}"
return match.group(1).strip()
def parse_curl_command(curl_str: str) -> dict:
"""
Parse a cURL command string into method, path, headers, and body.
Returns dict with keys: method, path, headers, body.
"""
# Default method
method = "GET"
if "-X POST" in curl_str:
method = "POST"
# Extract URL path (replace $BASE_URL with empty string to get path)
url_match = re.search(r'"?\$BASE_URL(/[^"'\s]*)"?', curl_str)
path = url_match.group(1) if url_match else "/"
# Extract headers
headers = {}
for h_match in re.finditer(r'-H\s+"([^"]+)"', curl_str):
key, _, value = h_match.group(1).partition(": ")
# Replace variable references with test values
value = value.replace("$API_KEY", "test-api-key")
headers[key] = value
# Extract JSON body
body = None
body_match = re.search(r"-d\s+'({.*?})'", curl_str, re.DOTALL)
if body_match:
body = json.loads(body_match.group(1))
return {"method": method, "path": path, "headers": headers, "body": body}
class TestCurlExamples:
"""Verify cURL examples match the actual API contract."""
def test_create_basic_curl(self, test_client, mock_supabase):
"""The basic cURL example hits POST /shorten with correct shape."""
snippet = extract_snippet(EXAMPLES_DIR / "create_link.sh", "create_basic")
parsed = parse_curl_command(snippet)
assert parsed["method"] == "POST"
assert parsed["path"] == "/shorten"
assert "X-API-Key" in parsed["headers"]
assert "Content-Type" in parsed["headers"]
assert "redirect_url" in parsed["body"]
# Replay against test client
mock_supabase.set_response("links", []) # collision check
mock_supabase.set_response("links", [{
"id": "test-id",
"short_id": "abc123",
"redirect_url": parsed["body"]["redirect_url"],
"created_at": "2026-01-01T00:00:00Z",
}])
response = test_client.post(
parsed["path"],
headers={"X-API-Key": "test-api-key", "Content-Type": "application/json"},
json=parsed["body"],
)
assert response.status_code == 200
def test_create_with_data_curl(self, test_client, mock_supabase):
"""The data+webhooks cURL example hits POST /shorten with correct shape."""
snippet = extract_snippet(EXAMPLES_DIR / "create_link.sh", "create_with_data")
parsed = parse_curl_command(snippet)
assert parsed["method"] == "POST"
assert parsed["path"] == "/shorten"
assert "redirect_url" in parsed["body"]
assert "data" in parsed["body"]
assert "webhooks" in parsed["body"]
mock_supabase.set_response("links", [])
mock_supabase.set_response("links", [{
"id": "test-id",
"short_id": "abc123",
"redirect_url": parsed["body"]["redirect_url"],
"created_at": "2026-01-01T00:00:00Z",
}])
response = test_client.post(
parsed["path"],
headers={"X-API-Key": "test-api-key", "Content-Type": "application/json"},
json=parsed["body"],
)
assert response.status_code == 200
def test_generate_qr_curl(self):
"""The QR code cURL example targets the correct endpoint."""
snippet = extract_snippet(EXAMPLES_DIR / "create_link.sh", "generate_qr")
# Should reference /{short_id}/qr endpoint
assert "/qr" in snippet
assert "format=png" in snippet or "format=svg" in snippet or "/qr" in snippet
def test_check_usage_curl(self):
"""The usage cURL example targets GET /usage with API key header."""
snippet = extract_snippet(EXAMPLES_DIR / "create_link.sh", "check_usage")
parsed = parse_curl_command(snippet)
assert parsed["method"] == "GET"
assert parsed["path"] == "/usage"
assert "X-API-Key" in parsed["headers"]
- [ ] Step 2: Run cURL tests to verify they fail (examples don't exist yet if running standalone)
Run: pytest tests/test_examples.py::TestCurlExamples -v
Expected: All 4 tests pass (examples were created in Tasks 2-4).
- [ ] Step 3: Write the Python example test
Append to tests/test_examples.py:
class TestPythonExamples:
"""Verify Python examples match the actual API contract."""
def test_create_basic_python(self):
"""The basic Python example uses correct endpoint, method, headers, and body keys."""
snippet = extract_snippet(EXAMPLES_DIR / "create_link.py", "create_basic")
# Verify it calls POST /shorten
assert "requests.post" in snippet
assert "/shorten" in snippet
assert "X-API-Key" in snippet
assert "redirect_url" in snippet
def test_create_with_data_python(self):
"""The data+webhooks Python example includes data and webhooks fields."""
snippet = extract_snippet(EXAMPLES_DIR / "create_link.py", "create_with_data")
assert "requests.post" in snippet
assert "/shorten" in snippet
assert '"redirect_url"' in snippet
assert '"data"' in snippet
assert '"webhooks"' in snippet
- [ ] Step 4: Run Python example tests
Run: pytest tests/test_examples.py::TestPythonExamples -v
Expected: PASS
- [ ] Step 5: Write the JavaScript example test
Append to tests/test_examples.py:
class TestJavaScriptExamples:
"""Verify JavaScript examples match the actual API contract."""
def test_create_basic_js(self):
"""The basic JS example uses correct endpoint, method, headers, and body keys."""
snippet = extract_snippet(EXAMPLES_DIR / "create_link.js", "create_basic")
assert "fetch(" in snippet
assert "/shorten" in snippet
assert '"POST"' in snippet
assert "X-API-Key" in snippet
assert "redirect_url" in snippet
def test_create_with_data_js(self):
"""The data+webhooks JS example includes data and webhooks fields."""
snippet = extract_snippet(EXAMPLES_DIR / "create_link.js", "create_with_data")
assert "fetch(" in snippet
assert "/shorten" in snippet
assert "redirect_url" in snippet
assert "data:" in snippet or '"data"' in snippet
assert "webhooks:" in snippet or '"webhooks"' in snippet
- [ ] Step 6: Run JS example tests
Run: pytest tests/test_examples.py::TestJavaScriptExamples -v
Expected: PASS
- [ ] Step 7: Write the webhook receiver tests
Append to tests/test_examples.py:
class TestWebhookReceiverExamples:
"""Verify webhook receiver examples correctly implement HMAC verification."""
def test_python_verify_valid_signature(self):
"""Python verify_webhook_signature accepts a valid HMAC signature."""
import importlib.util
spec = importlib.util.spec_from_file_location(
"webhook_receiver", EXAMPLES_DIR / "webhook_receiver.py"
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
secret = "test-secret-123"
payload = json.dumps({"short_id": "abc123", "data": {"sku": "W1"}}).encode()
signature = "sha256=" + hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
assert mod.verify_webhook_signature(payload, signature, secret) is True
def test_python_verify_invalid_signature(self):
"""Python verify_webhook_signature rejects an invalid HMAC signature."""
import importlib.util
spec = importlib.util.spec_from_file_location(
"webhook_receiver", EXAMPLES_DIR / "webhook_receiver.py"
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
secret = "test-secret-123"
payload = json.dumps({"short_id": "abc123"}).encode()
assert mod.verify_webhook_signature(payload, "sha256=badhex000", secret) is False
def test_js_verify_signature_structure(self):
"""JS webhook receiver uses crypto.createHmac with sha256 and timingSafeEqual."""
snippet = extract_snippet(EXAMPLES_DIR / "webhook_receiver.js", "verify_signature")
assert "createHmac" in snippet
assert '"sha256"' in snippet
assert "timingSafeEqual" in snippet
def test_js_receiver_checks_signature(self):
"""JS Express receiver calls verifyWebhookSignature before processing."""
snippet = extract_snippet(EXAMPLES_DIR / "webhook_receiver.js", "express_receiver")
# Verify it checks signature before processing the payload
sig_check_pos = snippet.find("verifyWebhookSignature")
parse_pos = snippet.find("JSON.parse")
assert sig_check_pos != -1, "Must call verifyWebhookSignature"
assert parse_pos != -1, "Must parse JSON payload"
assert sig_check_pos < parse_pos, "Must verify signature before parsing payload"
- [ ] Step 8: Run all example tests
Run: pytest tests/test_examples.py -v
Expected: All tests pass.
- [ ] Step 9: Run full test suite
Run: pytest --cov=. --cov-report=term-missing
Expected: All tests pass (existing + new).
- [ ] Step 10: Commit
Task 7: Documentation Pages¶
Files:
- Create: docs/index.md
- Create: docs/quickstart.md
- Create: docs/examples.md
- Create: docs/api-reference.md
- Create: docs/rate-limits.md
All code blocks in these pages MUST use --8<-- snippet inclusion from examples/ — no hand-written code blocks for API usage.
- [ ] Step 1: Write docs/index.md
# usnap.me
**Shortlinks that carry your data.**
usnap.me is a shortlink API that attaches custom JSON data to every link. When someone clicks
a link, your webhooks fire instantly with the data you embedded — SKU codes, campaign tags,
customer IDs, anything you need.
## Use Cases
- **Packaging & print QR codes** — Embed a SKU or batch number. When scanned, your system
gets a webhook with the product data.
- **Campaign attribution** — Tag links with campaign and source metadata. Every click delivers
attribution data to your analytics pipeline.
- **Real-time event triggers** — Fire webhooks on click to start workflows, send notifications,
or update dashboards.
## Get Started
Follow the [Quickstart](quickstart.md) to create your first shortlink in under a minute.
Explore the [API Reference](api-reference.md) for the full endpoint documentation, or
try the interactive [Swagger UI](https://usnp.me/docs).
- [ ] Step 2: Write docs/quickstart.md
# Quickstart
Create a data-carrying shortlink, click it, and see your webhook fire — all in five steps.
## 1. Get an API Key
You need an API key to create links. If you have the master key, skip to step 2. Otherwise,
create a scoped key:
```bash
curl -X POST https://usnp.me/api-keys \
-H "X-API-Key: YOUR_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{"label": "my first key"}'
Save the key and webhook_secret from the response.
2. Create a Shortlink¶
Create a basic shortlink that redirects to your target URL:
The response includes your short_url (e.g., https://usnp.me/abc123).
3. Create a Link with Data and Webhooks¶
Attach custom data and register a webhook to receive it on every click:
4. Click the Link¶
Open the short_url in your browser. usnap.me will:
- Redirect you to the target URL
- Record the hit
- POST your custom data to each registered webhook
Your webhook receives a JSON payload like:
{
"event": "hit",
"short_id": "abc123",
"redirect_url": "https://example.com/product",
"hit_id": "...",
"hit_at": "2026-01-15T10:30:00Z",
"data": {"sku": "WIDGET-42", "campaign": "summer-2026"}
}
5. Generate a QR Code¶
Generate a printable QR code for any shortlink:
Customize colors, format (PNG/SVG), and scale — see the API Reference.
**Important:** The `;` before `--8<--` is required by `pymdownx.snippets` when including inside fenced code blocks. The `;` is stripped during rendering.
- [ ] **Step 3: Write docs/examples.md**
```markdown
# Code Examples
Complete working examples in Python and JavaScript. All code on this page is tested in CI
to stay in sync with the API.
## Create a Shortlink
=== "Python"
```python
--8<-- "examples/create_link.py:create_basic"
```
=== "JavaScript"
```javascript
--8<-- "examples/create_link.js:create_basic"
```
=== "cURL"
```bash
--8<-- "examples/create_link.sh:create_basic"
```
## Create a Link with Data and Webhooks
=== "Python"
```python
--8<-- "examples/create_link.py:create_with_data"
```
=== "JavaScript"
```javascript
--8<-- "examples/create_link.js:create_with_data"
```
=== "cURL"
```bash
--8<-- "examples/create_link.sh:create_with_data"
```
## Webhook Receiver
Verify the `X-Webhook-Signature` header to ensure payloads come from usnap.me.
=== "Python (FastAPI)"
```python
--8<-- "examples/webhook_receiver.py:verify_signature"
```
Full receiver:
```python
--8<-- "examples/webhook_receiver.py:fastapi_receiver"
```
=== "JavaScript (Express.js)"
```javascript
--8<-- "examples/webhook_receiver.js:verify_signature"
```
Full receiver:
```javascript
--8<-- "examples/webhook_receiver.js:express_receiver"
```
- [ ] Step 4: Write docs/api-reference.md
# API Reference
Base URL: `https://usnp.me`
For interactive exploration, use the [Swagger UI](https://usnp.me/docs).
## Authentication
All endpoints except `GET /{short_id}` (redirect) and `GET /{short_id}/qr` (QR code)
require an `X-API-Key` header.
## Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/` | No | Health check |
| `POST` | `/shorten` | Yes | Create a shortlink |
| `GET` | `/{short_id}` | No | Redirect to target URL |
| `GET` | `/{short_id}/qr` | No | Generate QR code |
| `GET` | `/{short_id}/webhooks/status` | Yes | Webhook delivery status |
| `POST` | `/api-keys` | Yes | Create a new API key |
| `GET` | `/api-keys` | Yes | List API keys |
| `DELETE` | `/api-keys/{key_id}` | Yes | Revoke an API key |
| `POST` | `/api-keys/{key_id}/rotate` | Yes | Rotate an API key |
| `GET` | `/usage` | Yes | Usage summary for current month |
## POST /shorten
Create a new shortlink with an optional data payload and webhook URLs.
**Request body:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `redirect_url` | string (URL) | Yes | Target URL for the redirect |
| `data` | object | No | Custom JSON data (max 10 KB) delivered to webhooks |
| `webhooks` | array of URLs | No | Webhook endpoints to notify on each click |
**Response (200):**
```json
{
"short_id": "abc123",
"short_url": "https://usnp.me/abc123",
"redirect_url": "https://example.com/landing",
"created_at": "2026-01-15T10:30:00Z"
}
Response headers (for scoped API keys):
| Header | Description |
|---|---|
X-Usage-Links-Created |
Links created this month |
X-Usage-Links-Limit |
Monthly link creation limit (or unlimited) |
GET /{short_id}¶
Resolves a shortlink and returns a 302 redirect. Records the hit and fires webhooks in the background.
GET /{short_id}/qr¶
Generate a QR code image for a shortlink.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
format |
png or svg |
png |
Output image format |
scale |
int (1-20) | 5 |
Size multiplier |
dark |
string | #000000 |
QR module color (hex or CMYK) |
light |
string | #FFFFFF |
Background color (hex, CMYK, or transparent) |
Webhook Payload¶
When a shortlink is clicked, each registered webhook receives a POST with:
{
"event": "hit",
"short_id": "abc123",
"redirect_url": "https://example.com/product",
"hit_id": "550e8400-...",
"hit_at": "2026-01-15T10:30:00Z",
"data": {"sku": "WIDGET-42"}
}
Headers:
| Header | Description |
|---|---|
X-Webhook-Signature |
sha256=<HMAC-SHA256 hex digest> — sign the raw JSON body with your webhook_secret to verify |
Content-Type |
application/json |
Webhooks retry up to 3 times with exponential backoff (1s, 5s delays).
GET /usage¶
Returns usage counters for the current month.
Response:
{
"api_key_id": "550e8400-...",
"month": "2026-05",
"usage": {
"links_created": {"current": 42, "limit": 100},
"hits_served": {"current": 1583, "limit": null},
"webhook_deliveries": {"current": 312, "limit": 1000}
}
}
limit: null means unlimited for that metric.
- [ ] **Step 5: Write docs/rate-limits.md**
```markdown
# Rate Limits & Quotas
## Rate Limits
Per-minute rate limits based on your API key tier:
| Endpoint | Free | Pro |
|----------|------|-----|
| `POST /shorten` | 10/min | 60/min |
| `GET /{short_id}` | 100/min | 100/min |
Rate-limited responses return `429 Too Many Requests` with a `Retry-After` header.
## Monthly Quotas
| Metric | Free | Pro |
|--------|------|-----|
| Links created | 100 | Unlimited |
| Hits served | Unlimited (tracked) | Unlimited (tracked) |
| Webhook deliveries | 1,000 | 50,000 |
When a quota is exceeded:
- **Link creation:** Returns `403` with `{"detail": "Monthly link creation limit reached", "limit": 100, "current": 100}`.
- **Webhook deliveries:** Delivery is skipped and logged as failed with error `"quota exceeded"`.
- **Hits:** Never limited — existing links always work.
## Check Your Usage
```bash
--8<-- "examples/create_link.sh:check_usage"
See the GET /usage endpoint for response details.
- [ ] **Step 6: Verify MkDocs builds with all pages**
Run: `mkdocs build --strict`
Expected: Build succeeds with no warnings about missing snippets or broken links.
- [ ] **Step 7: Commit**
```bash
git add docs/
git commit -m "feat: add documentation pages with tested snippet inclusion"
Task 8: Replace README.md¶
Files:
- Modify: README.md
- [ ] Step 1: Replace README.md
# usnap.me
**Shortlinks that carry your data.**
[](https://granteagon.github.io/usnap.me)
usnap.me is a shortlink API that attaches custom JSON data to every link.
When someone clicks a link, your webhooks fire instantly with the embedded data.
**[Get Started](https://granteagon.github.io/usnap.me/quickstart/)** |
[API Reference](https://granteagon.github.io/usnap.me/api-reference/) |
[Swagger UI](https://usnp.me/docs)
## Development
```bash
# Install dependencies
pip install -r requirements.txt
# Run dev server
uvicorn main:app --reload
# Run tests
pytest --cov=. --cov-report=term-missing
# Build docs locally
mkdocs serve
- [ ] **Step 2: Commit**
```bash
git add README.md
git commit -m "feat: replace README with landing page linking to docs site"
Task 9: GitHub Actions — Deploy Docs¶
Files:
- Modify: .github/workflows/test.yml:1-104
Add a docs job that runs mkdocs gh-deploy on push to main, after tests pass.
- [ ] Step 1: Add docs deploy job to .github/workflows/test.yml
Add this job after the existing deploy job:
docs:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: test
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install mkdocs mkdocs-material
- name: Deploy docs to GitHub Pages
run: mkdocs gh-deploy --force
- [ ] Step 2: Verify the workflow YAML is valid
Run: python -c "import yaml; yaml.safe_load(open('.github/workflows/test.yml'))"
Expected: No error (exits cleanly).
Note: If pyyaml is not installed, run pip install pyyaml first or just visually verify indentation. The CI will validate it on push.
- [ ] Step 3: Commit
git add .github/workflows/test.yml
git commit -m "ci: add docs deployment to GitHub Pages on push to main"
Task 10: Final Verification¶
Files: None (verification only)
- [ ] Step 1: Run full test suite
Run: pytest --cov=. --cov-report=term-missing -v
Expected: All tests pass, including the new tests/test_examples.py tests.
- [ ] Step 2: Build docs and verify
Run: mkdocs build --strict
Expected: Builds with no errors or warnings.
- [ ] Step 3: Serve docs locally and spot-check
Run: mkdocs serve (then open http://127.0.0.1:8000 in a browser)
Verify:
- Home page shows tagline and use cases
- Quickstart shows cURL snippets rendered from examples/
- Code Examples page has tabs for Python/JS/cURL
- API Reference has endpoint table
- Rate Limits page has tier tables
- [ ] Step 4: Verify snippet rendering
Check that no pages show raw --8<-- markers — all snippets should be replaced with actual code content.
- [ ] Step 5: Report results
Report test count, coverage percentage, and MkDocs build status. List any issues found.