Introduction
Authenticated API for creating, managing, and inspecting short URLs on ShortURL.FM.
This documentation covers the authenticated v1 API for ShortURL.FM.
All endpoints require a Sanctum personal access token sent as a Bearer token.
Common errors
401 Unauthenticated: missing or invalid bearer token404 Not Found: resource does not exist or is not owned by the current user422 Validation Error: request payload or query string is invalid429 Too Many Attempts: rate limit exceeded
Rate limits
- Multiple endpoint limits can apply to one request. If any applicable limit is exceeded, the request is rejected with
429. - Rate-limit identity is per authenticated user.
What month means
monthis a calendar-month window in UTC.- The anchor datetime is set when the first API token is created.
- The window end is the same day/time in the next month.
- If that day does not exist (
29/30/31), the end uses the last day of the month at the same time. - Token rotation/revoke does not reset the anchor or monthly usage.
Endpoint limits
GET /api/v1/domains,GET /api/v1/short-urls,GET /api/v1/short-urls/{id}:200/hour,4,800/day,30,000/monthPOST /api/v1/short-urls:200/hour,4,800/day,30,000/month- Additional on
POST /api/v1/short-urlswhenlog_stats=true:100/hour,2,400/day,10,000/month - Additional on
POST /api/v1/short-urlswhenqr_enabled=true:100/hour,2,400/day,10,000/month PATCH /api/v1/short-urls/{id}:100/hour,2,400/day,10,000/monthGET /api/v1/short-urls/{id}/stats:100/hour,2,400/day,10,000/monthPOST /api/v1/unshorten:100/hour,2,400/day,10,000/monthPOST /api/v1/tokens/rotate,DELETE /api/v1/tokens/{id}:10/hour,50/day,500/month
Rate limit reset policy
- Hourly quota resets 1 hour after the first request in the current hourly window.
- Daily quota resets 24 hours after the first request in the current daily window.
- Monthly quota resets at the next anchored month boundary (UTC).
- On
429, the API returns headersRetry-After,X-RateLimit-Reset,X-RateLimit-Limit, andX-RateLimit-Remaining; the response body includeserror.details.retry_after_secondsanderror.details.rate_limit_reset_at_unix.
Request tracing
X-Request-Idis optional. If provided, it is mirrored inmeta.trace_id; otherwise the server generates a UUID.
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer YOUR_API_TOKEN".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Create a Sanctum token from the API tokens section in your profile.
Domains
Look up short-link domains that can be used when creating URLs.
List active domains.
requires authentication
Returns the domains currently available for short URL creation, with the default domain first.
Rate limit: 200/hour, 4,800/day, 30,000/month.
Example request:
curl --request GET \
--get "https://shorturl.fm/api/v1/domains" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://shorturl.fm/api/v1/domains"
);
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/domains';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/domains'
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"success": true,
"data": [
{
"id": 1,
"host": "shorturl.fm",
"is_default": true
},
{
"id": 2,
"host": "shorturl.sh",
"is_default": false
}
],
"error": null,
"meta": {
"trace_id": "bcde4e74-26d5-4261-8c86-c1cb270d3af0",
"timestamp": "2026-03-06T12:03:00+00:00"
}
}
Example response (401):
{
"success": false,
"data": null,
"error": {
"message": "Unauthenticated.",
"details": {}
},
"meta": {
"trace_id": "9f106f90-d1fc-4f85-a5dc-a2245d756590",
"timestamp": "2026-03-08T12:30:00+00:00"
}
}
Example response (403):
{
"success": false,
"data": null,
"error": {
"message": "Please verify your email address before continuing.",
"details": {
"status": "email-verification-required",
"verify_email_url": "/app/verify-email"
}
},
"meta": {
"trace_id": "f0e14d9f-d652-4b3b-857c-0f5f0a9ce0f3",
"timestamp": "2026-03-24T12:00:00+00:00"
}
}
Example response (429):
{
"success": false,
"data": null,
"error": {
"message": "Too Many Attempts.",
"details": {
"retry_after_seconds": 3600,
"rate_limit_reset_at_unix": 1772985600
}
},
"meta": {
"trace_id": "2bb87fd5-4128-48dc-88ca-f3a62e2be9a7",
"timestamp": "2026-03-08T12:31:00+00:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Short URLs
Create, inspect, update, and analyze the authenticated user's short URLs.
Create a short URL.
requires authentication
Creates a new short URL for the authenticated user. If alias is omitted, the server generates a random code.
Base rate limit: 200/hour, 4,800/day, 30,000/month.
Additional limits apply when log_stats=true and/or qr_enabled=true: 100/hour, 2,400/day, 10,000/month.
Example request:
curl --request POST \
"https://shorturl.fm/api/v1/short-urls" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"url\": \"https:\\/\\/example.com\\/very\\/long\\/path\"
}"
const url = new URL(
"https://shorturl.fm/api/v1/short-urls"
);
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "https:\/\/example.com\/very\/long\/path"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/short-urls';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'url' => 'https://example.com/very/long/path',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/short-urls'
payload = {
"url": "https:\/\/example.com\/very\/long\/path"
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Example response (201):
{
"success": true,
"data": {
"id": 42,
"short_url": "https://shorturl.fm/Docs-42",
"long_url": "https://example.com/very/long/path",
"domain": "shorturl.fm",
"short_code": "Docs-42",
"total_clicks": 131,
"log_stats": true,
"expires_at": "2026-12-31 23:59:59",
"has_password": true,
"qr_enabled": true,
"qr_svg_download_url": "https://shorturl.fm/short-urls/42/qr/svg",
"qr_png_download_url": "https://shorturl.fm/short-urls/42/qr/png",
"qr_webp_download_url": "https://shorturl.fm/short-urls/42/qr/webp",
"created_at": "2026-03-01 09:30:00",
"updated_at": "2026-03-06 11:45:00",
"stats_url": "https://shorturl.fm/Docs-42+"
},
"error": null,
"meta": {
"trace_id": "2bdb40b1-49fd-4abf-8b7b-26db74bc2825",
"timestamp": "2026-03-06T12:04:00+00:00"
}
}
Example response (401):
{
"success": false,
"data": null,
"error": {
"message": "Unauthenticated.",
"details": {}
},
"meta": {
"trace_id": "9f106f90-d1fc-4f85-a5dc-a2245d756590",
"timestamp": "2026-03-08T12:30:00+00:00"
}
}
Example response (403, URL is blacklisted):
{
"success": false,
"data": null,
"error": {
"message": "This URL is blacklisted.",
"details": {}
},
"meta": {
"trace_id": "7287efd5-3071-4829-9fda-b5ef4ed6cb90",
"timestamp": "2026-03-06T12:06:00+00:00"
}
}
Example response (403, Email verification required):
{
"success": false,
"data": null,
"error": {
"message": "Please verify your email address before continuing.",
"details": {
"status": "email-verification-required",
"verify_email_url": "/app/verify-email"
}
},
"meta": {
"trace_id": "f0e14d9f-d652-4b3b-857c-0f5f0a9ce0f3",
"timestamp": "2026-03-24T12:00:00+00:00"
}
}
Example response (422):
{
"success": false,
"data": null,
"error": {
"message": "The given data was invalid.",
"details": {
"validation": {
"url": [
"The url field is required."
]
}
}
},
"meta": {
"trace_id": "f4066217-792f-4f28-a243-e3a36f1b1e3e",
"timestamp": "2026-03-06T12:07:00+00:00"
}
}
Example response (429):
{
"success": false,
"data": null,
"error": {
"message": "Too Many Attempts.",
"details": {
"retry_after_seconds": 3600,
"rate_limit_reset_at_unix": 1772985600
}
},
"meta": {
"trace_id": "2bb87fd5-4128-48dc-88ca-f3a62e2be9a7",
"timestamp": "2026-03-08T12:31:00+00:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List short URLs.
requires authentication
Returns the authenticated user's short URLs in reverse chronological order.
Rate limit: 200/hour, 4,800/day, 30,000/month.
Example request:
curl --request GET \
--get "https://shorturl.fm/api/v1/short-urls?page=1&per_page=20&search=promo" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://shorturl.fm/api/v1/short-urls"
);
const params = {
"page": "1",
"per_page": "20",
"search": "promo",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/short-urls';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'page' => '1',
'per_page' => '20',
'search' => 'promo',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/short-urls'
params = {
'page': '1',
'per_page': '20',
'search': 'promo',
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"success": true,
"data": [
{
"id": 42,
"short_url": "https://shorturl.fm/Docs-42",
"long_url": "https://example.com/very/long/path",
"domain": "shorturl.fm",
"short_code": "Docs-42",
"total_clicks": 131,
"log_stats": true,
"expires_at": "2026-12-31 23:59:59",
"has_password": true,
"qr_enabled": true,
"qr_svg_download_url": "https://shorturl.fm/short-urls/42/qr/svg",
"qr_png_download_url": "https://shorturl.fm/short-urls/42/qr/png",
"qr_webp_download_url": "https://shorturl.fm/short-urls/42/qr/webp",
"created_at": "2026-03-01 09:30:00",
"updated_at": "2026-03-06 11:45:00",
"stats_url": "https://shorturl.fm/Docs-42+"
},
{
"id": 41,
"short_url": "https://go.shorturl.fm/Sale-25",
"long_url": "https://example.com/spring-sale",
"domain": "go.shorturl.fm",
"short_code": "Sale-25",
"total_clicks": 54,
"log_stats": true,
"expires_at": null,
"has_password": false,
"qr_enabled": false,
"qr_svg_download_url": null,
"qr_png_download_url": null,
"qr_webp_download_url": null,
"created_at": "2026-02-25 14:10:00",
"updated_at": "2026-03-02 08:00:00",
"stats_url": "https://go.shorturl.fm/Sale-25+"
}
],
"error": null,
"meta": {
"trace_id": "f23696de-c811-41fd-b465-cf53539f7f4b",
"timestamp": "2026-03-06T12:05:00+00:00",
"pagination": {
"current_page": 1,
"last_page": 3,
"per_page": 20,
"total": 42
}
}
}
Example response (401):
{
"success": false,
"data": null,
"error": {
"message": "Unauthenticated.",
"details": {}
},
"meta": {
"trace_id": "9f106f90-d1fc-4f85-a5dc-a2245d756590",
"timestamp": "2026-03-08T12:30:00+00:00"
}
}
Example response (403):
{
"success": false,
"data": null,
"error": {
"message": "Please verify your email address before continuing.",
"details": {
"status": "email-verification-required",
"verify_email_url": "/app/verify-email"
}
},
"meta": {
"trace_id": "f0e14d9f-d652-4b3b-857c-0f5f0a9ce0f3",
"timestamp": "2026-03-24T12:00:00+00:00"
}
}
Example response (429):
{
"success": false,
"data": null,
"error": {
"message": "Too Many Attempts.",
"details": {
"retry_after_seconds": 3600,
"rate_limit_reset_at_unix": 1772985600
}
},
"meta": {
"trace_id": "2bb87fd5-4128-48dc-88ca-f3a62e2be9a7",
"timestamp": "2026-03-08T12:31:00+00:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
meta
object
pagination
object
Pagination metadata for this endpoint.
current_page
integer
Current page number.
last_page
integer
Last available page number.
per_page
integer
Number of items returned per page.
total
integer
Total number of matching items.
Get a short URL.
requires authentication
Returns one short URL owned by the authenticated user.
Rate limit: 200/hour, 4,800/day, 30,000/month.
Example request:
curl --request GET \
--get "https://shorturl.fm/api/v1/short-urls/42" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://shorturl.fm/api/v1/short-urls/42"
);
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/short-urls/42';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/short-urls/42'
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"success": true,
"data": {
"id": 42,
"short_url": "https://shorturl.fm/Docs-42",
"long_url": "https://example.com/very/long/path",
"domain": "shorturl.fm",
"short_code": "Docs-42",
"total_clicks": 131,
"log_stats": true,
"expires_at": "2026-12-31 23:59:59",
"has_password": true,
"qr_enabled": true,
"qr_svg_download_url": "https://shorturl.fm/short-urls/42/qr/svg",
"qr_png_download_url": "https://shorturl.fm/short-urls/42/qr/png",
"qr_webp_download_url": "https://shorturl.fm/short-urls/42/qr/webp",
"created_at": "2026-03-01 09:30:00",
"updated_at": "2026-03-06 11:45:00",
"stats_url": "https://shorturl.fm/Docs-42+"
},
"error": null,
"meta": {
"trace_id": "2bdb40b1-49fd-4abf-8b7b-26db74bc2825",
"timestamp": "2026-03-06T12:04:00+00:00"
}
}
Example response (401):
{
"success": false,
"data": null,
"error": {
"message": "Unauthenticated.",
"details": {}
},
"meta": {
"trace_id": "9f106f90-d1fc-4f85-a5dc-a2245d756590",
"timestamp": "2026-03-08T12:30:00+00:00"
}
}
Example response (403):
{
"success": false,
"data": null,
"error": {
"message": "Please verify your email address before continuing.",
"details": {
"status": "email-verification-required",
"verify_email_url": "/app/verify-email"
}
},
"meta": {
"trace_id": "f0e14d9f-d652-4b3b-857c-0f5f0a9ce0f3",
"timestamp": "2026-03-24T12:00:00+00:00"
}
}
Example response (404):
{
"success": false,
"data": null,
"error": {
"message": "Short URL not found.",
"details": {}
},
"meta": {
"trace_id": "7b1d2f2b-d8fb-42f5-8a2d-1cdac1c4f7dd",
"timestamp": "2026-03-06T12:01:00+00:00"
}
}
Example response (429):
{
"success": false,
"data": null,
"error": {
"message": "Too Many Attempts.",
"details": {
"retry_after_seconds": 3600,
"rate_limit_reset_at_unix": 1772985600
}
},
"meta": {
"trace_id": "2bb87fd5-4128-48dc-88ca-f3a62e2be9a7",
"timestamp": "2026-03-08T12:31:00+00:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a short URL.
requires authentication
Updates mutable properties of a short URL owned by the authenticated user.
Rate limit: 100/hour, 2,400/day, 10,000/month.
Example request:
curl --request PATCH \
"https://shorturl.fm/api/v1/short-urls/42" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"url\": \"https:\\/\\/example.com\\/updated-destination\",
\"log_stats\": true,
\"expires_at\": \"2026-12-31 23:59:59\",
\"qr_enabled\": true,
\"link_password\": \"secret-passphrase\"
}"
const url = new URL(
"https://shorturl.fm/api/v1/short-urls/42"
);
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "https:\/\/example.com\/updated-destination",
"log_stats": true,
"expires_at": "2026-12-31 23:59:59",
"qr_enabled": true,
"link_password": "secret-passphrase"
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/short-urls/42';
$response = $client->patch(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'url' => 'https://example.com/updated-destination',
'log_stats' => true,
'expires_at' => '2026-12-31 23:59:59',
'qr_enabled' => true,
'link_password' => 'secret-passphrase',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/short-urls/42'
payload = {
"url": "https:\/\/example.com\/updated-destination",
"log_stats": true,
"expires_at": "2026-12-31 23:59:59",
"qr_enabled": true,
"link_password": "secret-passphrase"
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('PATCH', url, headers=headers, json=payload)
response.json()Example response (200):
{
"success": true,
"data": {
"id": 42,
"short_url": "https://shorturl.fm/Docs-42",
"long_url": "https://example.com/very/long/path",
"domain": "shorturl.fm",
"short_code": "Docs-42",
"total_clicks": 131,
"log_stats": true,
"expires_at": "2026-12-31 23:59:59",
"has_password": true,
"qr_enabled": true,
"qr_svg_download_url": "https://shorturl.fm/short-urls/42/qr/svg",
"qr_png_download_url": "https://shorturl.fm/short-urls/42/qr/png",
"qr_webp_download_url": "https://shorturl.fm/short-urls/42/qr/webp",
"created_at": "2026-03-01 09:30:00",
"updated_at": "2026-03-06 11:45:00",
"stats_url": "https://shorturl.fm/Docs-42+"
},
"error": null,
"meta": {
"trace_id": "2bdb40b1-49fd-4abf-8b7b-26db74bc2825",
"timestamp": "2026-03-06T12:04:00+00:00"
}
}
Example response (401):
{
"success": false,
"data": null,
"error": {
"message": "Unauthenticated.",
"details": {}
},
"meta": {
"trace_id": "9f106f90-d1fc-4f85-a5dc-a2245d756590",
"timestamp": "2026-03-08T12:30:00+00:00"
}
}
Example response (403):
{
"success": false,
"data": null,
"error": {
"message": "Please verify your email address before continuing.",
"details": {
"status": "email-verification-required",
"verify_email_url": "/app/verify-email"
}
},
"meta": {
"trace_id": "f0e14d9f-d652-4b3b-857c-0f5f0a9ce0f3",
"timestamp": "2026-03-24T12:00:00+00:00"
}
}
Example response (404):
{
"success": false,
"data": null,
"error": {
"message": "Short URL not found.",
"details": {}
},
"meta": {
"trace_id": "7b1d2f2b-d8fb-42f5-8a2d-1cdac1c4f7dd",
"timestamp": "2026-03-06T12:01:00+00:00"
}
}
Example response (422):
{
"success": false,
"data": null,
"error": {
"message": "The given data was invalid.",
"details": {
"validation": {
"expires_at": [
"The expires at field must be a date after now."
]
}
}
},
"meta": {
"trace_id": "5d431ca1-5a30-4338-a799-78a141a3d2f8",
"timestamp": "2026-03-06T12:08:00+00:00"
}
}
Example response (429):
{
"success": false,
"data": null,
"error": {
"message": "Too Many Attempts.",
"details": {
"retry_after_seconds": 3600,
"rate_limit_reset_at_unix": 1772985600
}
},
"meta": {
"trace_id": "2bb87fd5-4128-48dc-88ca-f3a62e2be9a7",
"timestamp": "2026-03-08T12:31:00+00:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get short URL analytics.
requires authentication
Returns analytics for an owned short URL, including total clicks, recent click detail, country breakdown, and daily series.
Rate limit: 100/hour, 2,400/day, 10,000/month.
Example request:
curl --request GET \
--get "https://shorturl.fm/api/v1/short-urls/42/stats?period=last_30_days" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://shorturl.fm/api/v1/short-urls/42/stats"
);
const params = {
"period": "last_30_days",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/short-urls/42/stats';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'period' => 'last_30_days',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/short-urls/42/stats'
params = {
'period': 'last_30_days',
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"success": true,
"data": {
"short_url": {
"id": 42,
"short_url": "https://shorturl.fm/Docs-42",
"long_url": "https://example.com/very/long/path",
"domain": "shorturl.fm",
"short_code": "Docs-42",
"log_stats": true,
"expires_at": "2026-12-31 23:59:59",
"has_password": true,
"qr_enabled": true,
"qr_svg_download_url": "https://shorturl.fm/short-urls/42/qr/svg",
"qr_png_download_url": "https://shorturl.fm/short-urls/42/qr/png",
"qr_webp_download_url": "https://shorturl.fm/short-urls/42/qr/webp",
"created_at": "2026-03-01 09:30:00",
"updated_at": "2026-03-06 11:45:00",
"stats_url": "https://shorturl.fm/Docs-42+"
},
"period": {
"key": "last_30_days",
"from": "2026-02-05T12:00:00+00:00",
"to": "2026-03-06T12:00:00+00:00"
},
"total_clicks": 131,
"country_breakdown": [
{
"country_name": "United States",
"clicks": 84
},
{
"country_name": "Germany",
"clicks": 19
}
],
"daily_series": [
{
"date": "2026-03-04",
"clicks": 9
},
{
"date": "2026-03-05",
"clicks": 11
},
{
"date": "2026-03-06",
"clicks": 7
}
],
"click_detail": [
{
"id": 901,
"short_url_id": 42,
"request_uri": "https://shorturl.fm/Docs-42",
"country_code": "US",
"country_name": "United States",
"user_agent": "Mozilla/5.0",
"browser": "Chrome",
"crawler_name": null,
"os": "macOS",
"device_type": "desktop",
"referrer": "https://example.com/newsletter",
"is_bot": false,
"clicked_at": "2026-03-06T11:50:00.000000Z"
},
{
"id": 900,
"short_url_id": 42,
"request_uri": "https://shorturl.fm/Docs-42",
"country_code": "DE",
"country_name": "Germany",
"user_agent": "Mozilla/5.0",
"browser": "Firefox",
"crawler_name": null,
"os": "Linux",
"device_type": "desktop",
"referrer": null,
"is_bot": false,
"clicked_at": "2026-03-06T09:12:00.000000Z"
}
]
},
"error": null,
"meta": {
"trace_id": "4cb5ce2c-664f-4ec9-8f58-df1834b9d1f5",
"timestamp": "2026-03-06T12:10:00+00:00"
}
}
Example response (401):
{
"success": false,
"data": null,
"error": {
"message": "Unauthenticated.",
"details": {}
},
"meta": {
"trace_id": "9f106f90-d1fc-4f85-a5dc-a2245d756590",
"timestamp": "2026-03-08T12:30:00+00:00"
}
}
Example response (403):
{
"success": false,
"data": null,
"error": {
"message": "Please verify your email address before continuing.",
"details": {
"status": "email-verification-required",
"verify_email_url": "/app/verify-email"
}
},
"meta": {
"trace_id": "f0e14d9f-d652-4b3b-857c-0f5f0a9ce0f3",
"timestamp": "2026-03-24T12:00:00+00:00"
}
}
Example response (404):
{
"success": false,
"data": null,
"error": {
"message": "Short URL not found.",
"details": {}
},
"meta": {
"trace_id": "7b1d2f2b-d8fb-42f5-8a2d-1cdac1c4f7dd",
"timestamp": "2026-03-06T12:01:00+00:00"
}
}
Example response (422):
{
"success": false,
"data": null,
"error": {
"message": "Statistics collection was not enabled for this short URL.",
"details": {}
},
"meta": {
"trace_id": "346dcebd-1036-4624-a501-930249bcaedc",
"timestamp": "2026-03-06T12:11:00+00:00"
}
}
Example response (429):
{
"success": false,
"data": null,
"error": {
"message": "Too Many Attempts.",
"details": {
"retry_after_seconds": 3600,
"rate_limit_reset_at_unix": 1772985600
}
},
"meta": {
"trace_id": "2bb87fd5-4128-48dc-88ca-f3a62e2be9a7",
"timestamp": "2026-03-08T12:31:00+00:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Tokens
Manage Sanctum personal access tokens for API clients.
Rotate current token.
requires authentication
Creates a new personal access token and revokes the currently authenticated token in the same operation.
The plain-text token is only returned once.
Rate limit: 10/hour, 50/day, 500/month.
Example request:
curl --request POST \
"https://shorturl.fm/api/v1/tokens/rotate" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"deployment-script-rotated\"
}"
const url = new URL(
"https://shorturl.fm/api/v1/tokens/rotate"
);
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "deployment-script-rotated"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/tokens/rotate';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'name' => 'deployment-script-rotated',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/tokens/rotate'
payload = {
"name": "deployment-script-rotated"
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Example response (201):
{
"success": true,
"data": {
"token": "18|vJKDo4jNnRkRpwFdcN5a2qM5b6e0VxYp3XGf9Q2c",
"name": "deployment-script-rotated",
"token_id": 18,
"revoked_token_id": 17
},
"error": null,
"meta": {
"trace_id": "3508206b-e84a-4323-8dd4-42decb202804",
"timestamp": "2026-03-09T12:12:00+00:00"
}
}
Example response (401):
{
"success": false,
"data": null,
"error": {
"message": "Unauthenticated.",
"details": {}
},
"meta": {
"trace_id": "9f106f90-d1fc-4f85-a5dc-a2245d756590",
"timestamp": "2026-03-08T12:30:00+00:00"
}
}
Example response (403):
{
"success": false,
"data": null,
"error": {
"message": "Please verify your email address before continuing.",
"details": {
"status": "email-verification-required",
"verify_email_url": "/app/verify-email"
}
},
"meta": {
"trace_id": "f0e14d9f-d652-4b3b-857c-0f5f0a9ce0f3",
"timestamp": "2026-03-24T12:00:00+00:00"
}
}
Example response (422):
{
"success": false,
"data": null,
"error": {
"message": "Token rotation requires bearer token authentication.",
"details": {
"validation": {
"token": [
"Token rotation requires bearer token authentication."
]
}
}
},
"meta": {
"trace_id": "df62a7a0-a252-4c46-8e6d-3f7efb395dcb",
"timestamp": "2026-03-09T12:13:00+00:00"
}
}
Example response (429):
{
"success": false,
"data": null,
"error": {
"message": "Too Many Attempts.",
"details": {
"retry_after_seconds": 3600,
"rate_limit_reset_at_unix": 1772985600
}
},
"meta": {
"trace_id": "2bb87fd5-4128-48dc-88ca-f3a62e2be9a7",
"timestamp": "2026-03-08T12:31:00+00:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a token.
requires authentication
Revokes a token owned by the authenticated user.
Rate limit: 10/hour, 50/day, 500/month.
Example request:
curl --request DELETE \
"https://shorturl.fm/api/v1/tokens/17" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://shorturl.fm/api/v1/tokens/17"
);
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/tokens/17';
$response = $client->delete(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/tokens/17'
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('DELETE', url, headers=headers)
response.json()Example response (200):
{
"success": true,
"data": {
"success": 1
},
"error": null,
"meta": {
"trace_id": "104f3d16-0c61-4307-a671-1838828be126",
"timestamp": "2026-03-06T12:00:00+00:00"
}
}
Example response (401):
{
"success": false,
"data": null,
"error": {
"message": "Unauthenticated.",
"details": {}
},
"meta": {
"trace_id": "9f106f90-d1fc-4f85-a5dc-a2245d756590",
"timestamp": "2026-03-08T12:30:00+00:00"
}
}
Example response (403):
{
"success": false,
"data": null,
"error": {
"message": "Please verify your email address before continuing.",
"details": {
"status": "email-verification-required",
"verify_email_url": "/app/verify-email"
}
},
"meta": {
"trace_id": "f0e14d9f-d652-4b3b-857c-0f5f0a9ce0f3",
"timestamp": "2026-03-24T12:00:00+00:00"
}
}
Example response (404):
{
"success": false,
"data": null,
"error": {
"message": "Token not found.",
"details": {}
},
"meta": {
"trace_id": "8e24b740-2546-4db8-9d1d-614f03058d9d",
"timestamp": "2026-03-06T12:02:00+00:00"
}
}
Example response (429):
{
"success": false,
"data": null,
"error": {
"message": "Too Many Attempts.",
"details": {
"retry_after_seconds": 3600,
"rate_limit_reset_at_unix": 1772985600
}
},
"meta": {
"trace_id": "2bb87fd5-4128-48dc-88ca-f3a62e2be9a7",
"timestamp": "2026-03-08T12:31:00+00:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Utilities
Resolve existing short URLs.
Unshorten a URL.
requires authentication
Resolves a known short URL back to its original destination URL.
Rate limit: 100/hour, 2,400/day, 10,000/month.
Example request:
curl --request POST \
"https://shorturl.fm/api/v1/unshorten" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"url\": \"https:\\/\\/shorturl.fm\\/AbCdE-1\"
}"
const url = new URL(
"https://shorturl.fm/api/v1/unshorten"
);
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "https:\/\/shorturl.fm\/AbCdE-1"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/unshorten';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'url' => 'https://shorturl.fm/AbCdE-1',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/unshorten'
payload = {
"url": "https:\/\/shorturl.fm\/AbCdE-1"
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Example response (200):
{
"success": true,
"data": {
"url": "https://example.com/original-destination"
},
"error": null,
"meta": {
"trace_id": "8df84d5d-5d9d-4f83-976d-8b4f4b64f2ce",
"timestamp": "2026-03-06T12:14:00+00:00"
}
}
Example response (401):
{
"success": false,
"data": null,
"error": {
"message": "Unauthenticated.",
"details": {}
},
"meta": {
"trace_id": "9f106f90-d1fc-4f85-a5dc-a2245d756590",
"timestamp": "2026-03-08T12:30:00+00:00"
}
}
Example response (403):
{
"success": false,
"data": null,
"error": {
"message": "Please verify your email address before continuing.",
"details": {
"status": "email-verification-required",
"verify_email_url": "/app/verify-email"
}
},
"meta": {
"trace_id": "f0e14d9f-d652-4b3b-857c-0f5f0a9ce0f3",
"timestamp": "2026-03-24T12:00:00+00:00"
}
}
Example response (404):
{
"success": false,
"data": null,
"error": {
"message": "Short URL was not found or blocked.",
"details": {}
},
"meta": {
"trace_id": "5fe6a969-b3b3-4f75-be63-a4fe403ab932",
"timestamp": "2026-03-06T12:16:00+00:00"
}
}
Example response (422):
{
"success": false,
"data": null,
"error": {
"message": "The given data was invalid.",
"details": {
"validation": {
"url": [
"The url field format is invalid."
]
}
}
},
"meta": {
"trace_id": "8505ac29-db5d-4534-a729-7fd71b34ccbf",
"timestamp": "2026-03-06T12:15:00+00:00"
}
}
Example response (429):
{
"success": false,
"data": null,
"error": {
"message": "Too Many Attempts.",
"details": {
"retry_after_seconds": 3600,
"rate_limit_reset_at_unix": 1772985600
}
},
"meta": {
"trace_id": "2bb87fd5-4128-48dc-88ca-f3a62e2be9a7",
"timestamp": "2026-03-08T12:31:00+00:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Endpoints
POST api/v1/internal/block-short-urls
requires authentication
Example request:
curl --request POST \
"https://shorturl.fm/api/v1/internal/block-short-urls" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://shorturl.fm/api/v1/internal/block-short-urls"
);
const headers = {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://shorturl.fm/api/v1/internal/block-short-urls';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://shorturl.fm/api/v1/internal/block-short-urls'
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.