MENU navbar-image
API Docs ShortURL.FM Authenticated developer reference for shortening, analytics, tokens, and utility endpoints.

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

Rate limits

What month means

Endpoint limits

Rate limit reset policy

Request tracing

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"
    }
}
 

Request      

GET api/v1/domains

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json

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"
    }
}
 

Request      

POST api/v1/short-urls

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

url   string     

The destination URL to shorten. Example: https://example.com/very/long/path

domain_id   integer  optional    

The active short domain to use. If omitted, the default domain is used.

alias   string  optional    

Custom short code. Must be 5-30 characters using letters, digits, and hyphens.

log_stats   boolean  optional    

Enable click tracking for this short URL.

qr_enabled   boolean  optional    

Generate QR code downloads for the short URL.

expires_at   string  optional    

Expiration timestamp in the future.

link_password   string  optional    

Password that must be entered before redirecting.

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"
    }
}
 

Request      

GET api/v1/short-urls

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

page   integer  optional    

Page number for pagination. Example: 1

per_page   integer  optional    

Number of items per page. Must be between 1 and 100. Example: 20

search   string  optional    

Filter results by matching part of the original URL or short code. Example: promo

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"
    }
}
 

Request      

GET api/v1/short-urls/{id}

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The short URL ID. Example: 42

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"
    }
}
 

Request      

PATCH api/v1/short-urls/{id}

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The short URL ID. Example: 42

Body Parameters

url   string  optional    

New destination URL. Example: https://example.com/updated-destination

log_stats   boolean  optional    

Enable or disable click tracking. Example: true

expires_at   string  optional    

New expiration timestamp, or null to remove it. Example: 2026-12-31 23:59:59

qr_enabled   boolean  optional    

Enable or disable QR downloads. Example: true

link_password   string  optional    

Password required before redirecting. Send null to remove existing password. Example: secret-passphrase

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"
    }
}
 

Request      

GET api/v1/short-urls/{id}/stats

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The short URL ID. Example: 42

Query Parameters

period   string  optional    

Analytics preset. Supported values: last_hour, last_4_hours, last_12_hours, last_24_hours, last_48_hours, last_3_days, last_7_days, last_30_days, last_60_days, last_90_days, last_180_days, last_year, custom. Example: last_30_days

start_time   string  optional    

Required when period=custom. Example value: 2026-02-01 00:00:00.

end_time   string  optional    

Required when period=custom. Example value: 2026-02-28 23:59:59.

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"
    }
}
 

Request      

POST api/v1/tokens/rotate

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

A label to help you identify the new token later. Example: deployment-script-rotated

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"
    }
}
 

Request      

DELETE api/v1/tokens/{id}

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The token ID returned when the token was created. Example: 17

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"
    }
}
 

Request      

POST api/v1/unshorten

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

url   string     

The short URL to expand. Example: https://shorturl.fm/AbCdE-1

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()

Request      

POST api/v1/internal/block-short-urls

Headers

Authorization        

Example: Bearer YOUR_API_TOKEN

Content-Type        

Example: application/json

Accept        

Example: application/json