A lightweight, single-file URL shortener with API support, admin panel, and metadata management.
- Single PHP file - No framework dependencies
- JSON storage - No database required
- API with rate limiting - Token-based authentication
- Custom short codes - Optional user-defined slugs
- Metadata support - Add title/description to remember links
- Overwrite protection - Configurable protection for existing codes (to keep the shortcode but update long URLs as needed)
- Admin panel - Manage all URLs from a web interface
- Click tracking - Basic analytics for each link
- QR code generation - A QR code is generated to point to the generate short URL
- Upload
index.phpandrename.htaccessto your web server - Make sure PHP has write permissions to the directory and rename
rename.htaccessto.htaccess(check config in the htaccess file, as it might require you to edit the index.php accordingly) - Access
index.phpin your browser - On first run, a
config.jsonfile is created with your API token - Adjust manifest.json
start_urlandscopeto your needs
Edit config.json to customize:
{
"api_token": "your-64-char-hex-token",
"rate_limit_requests": 60,
"rate_limit_window": 3600,
"admin_path": "admin",
"admin_show_link": true,
"admin_password" : "your-secure-password",
"login_max_attempts" : 3,
"login_lockout_window" : 3600
}api_token: Auto-generated on first run. Use this in API requests. Can generate a new one from admin panel (careful, as old one will be lost)rate_limit_requests: Maximum API and frontend form requests per window (default: 60). Can edit it from admin panel.rate_limit_window: Time window in seconds (default: 3600 = 1 hour) forrate_limit_requests. Can edit it from admin panel.admin_path: Path to access the admin panel (adminby default, therefore accessible at /admin). NOT editable from admin panel.admin_show_link: Option to show the link to admin panel from homepage. NOT editable from admin panel.admin_password: Set this to enable the admin panel. NOT editable from admin panel.login_max_attempts: Maximum failed login attemps allowed. NOT editable from admin panel.login_lockout_window: Time window in seconds (default: 3600 = 1 hour) to lockout login afterlogin_max_attemptsis reached. NOT editable from admin panel.
Access the main page to shorten URLs via the web form. Advanced options include:
- Custom short code
- Title and description
- Allow overwrite: If checked, this short code can be updated later by anyone who uses the same code
Access /admin (or whatever is set in your config.json to:
- View all shortened URLs
- Edit title/description
- Delete URLs
- Copy/Regenerate your API token
- Edit the
rate_limit_requestsandrate_limit_windowvalues - See the API doc
Note: Set admin_password in config.json to enable the admin panel.
All API endpoints require authentication via the Authorization header.
Include your API token in requests:
Authorization: Bearer YOUR_API_TOKEN
Or use the X-API-Token header:
X-API-Token: YOUR_API_TOKEN
Responses include rate limit headers:
X-RateLimit-Remaining: Requests remaining in current windowX-RateLimit-Reset: Unix timestamp when the window resets
Exceeding the limit returns 429 Too Many Requests.
POST /api/cutitoff
Create a new shortened URL.
{
"url": "https://example.com/very-long-url",
"code": "my-custom-code",
"title": "My Link Title",
"description": "A description for this link",
"allow_overwrite": false
}| Field | Type | Required | Description |
|---|---|---|---|
url |
string | ✓ | The destination URL (must be valid) |
code |
string | Custom short code (alphanumeric, dash, underscore, 1-50 chars) | |
title |
string | Optional title for the link | |
description |
string | Optional description | |
allow_overwrite |
boolean | If true, this code can be updated later (default: false) |
Note: If a custom code already exists:
- If the existing URL has
allow_overwrite: true, it will be updated with the new URL - If the existing URL has
allow_overwrite: false, the request returns a409 Conflicterror
{
"success": true,
"code": "my-custom-code",
"short_url": "https://your-domain.com/my-custom-code",
"original_url": "https://example.com/very-long-url",
"title": "My Link Title",
"description": "A description for this link"
}curl -X POST https://your-domain.com/api/cutitoff \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/long-url",
"code": "my-link",
"title": "Example Link"
}'GET /api/urls
Retrieve all shortened URLs.
{
"urls": {
"abc123": {
"url": "https://example.com",
"short_url": "https://your-domain.com/abc123",
"title": "Example",
"description": "",
"created_at": "2024-01-15T10:30:00+00:00",
"clicks": 42
}
}
}curl https://your-domain.com/api/urls \
-H "Authorization: Bearer YOUR_API_TOKEN"GET /api/urls/{code}
Retrieve information about a specific shortened URL.
{
"code": "abc123",
"url": "https://example.com",
"short_url": "https://your-domain.com/abc123",
"title": "Example",
"description": "",
"created_at": "2024-01-15T10:30:00+00:00",
"updated_at": "2024-01-15T10:30:00+00:00",
"clicks": 42
}curl https://your-domain.com/api/urls/abc123 \
-H "Authorization: Bearer YOUR_API_TOKEN"PUT /api/urls/{code}
Update an existing shortened URL's metadata or destination.
{
"url": "https://new-destination.com",
"title": "Updated Title",
"description": "Updated description"
}All fields are optional. Only provided fields will be updated.
{
"success": true,
"data": {
"code": "abc123",
"url": "https://new-destination.com",
"short_url": "https://your-domain.com/abc123",
"title": "Updated Title",
"description": "Updated description"
}
}curl -X PUT https://your-domain.com/api/urls/abc123 \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "New Title"}'DELETE /api/urls/{code}
Delete a shortened URL.
{
"success": true,
"message": "URL deleted"
}curl -X DELETE https://your-domain.com/api/urls/abc123 \
-H "Authorization: Bearer YOUR_API_TOKEN"All errors return a JSON object with an error field:
{
"error": "Error message here"
}| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created (new URL) |
| 400 | Bad request (invalid input) |
| 401 | Unauthorized (invalid/missing token) |
| 404 | Not found |
| 409 | Conflict (code exists, overwrite disabled) |
| 429 | Rate limit exceeded |
-
Keep
config.jsonsecure - It contains your API token. The file is created with restricted permissions (0600). -
Use HTTPS - Always use HTTPS in production to protect your API token.
-
Set a strong admin password - Use a unique, random password for the admin panel.
-
Rate limiting - The built-in rate limiter protects against brute force. Adjust limits in config as needed.
-
Input validation - All URLs are validated, and custom codes are sanitized.
<?php
function createShortUrl($longUrl, $title = '', $customCode = null) {
$apiToken = 'YOUR_API_TOKEN';
$apiUrl = 'https://your-domain.com/api/cutitoff';
$data = ['url' => $longUrl, 'title' => $title];
if ($customCode) {
$data['code'] = $customCode;
}
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiToken,
'Content-Type: application/json'
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
return ['error' => 'cURL error: ' . $error];
}
$result = json_decode($response, true);
if ($result === null) {
return ['error' => 'Invalid JSON response', 'raw' => $response, 'http_code' => $httpCode];
}
return $result;
}
// Usage
$result = createShortUrl('https://example.com/long-url', 'My Link');
if (isset($result['error'])) {
echo 'Error: ' . $result['error'];
} else {
echo $result['short_url'];
}async function createShortUrl(longUrl, title = '', customCode = null) {
const apiToken = 'YOUR_API_TOKEN';
const apiUrl = 'https://your-domain.com/api/cutitoff';
const data = { url: longUrl, title };
if (customCode) data.code = customCode;
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return response.json();
}
// Usage
const result = await createShortUrl('https://example.com/long-url', 'My Link');
console.log(result.short_url);