API Documentation
Welcome to the zip1.io API documentation. Our API allows you to create and manage short links programmatically. Below you'll find detailed information on available endpoints, request parameters, and response formats.
1. Create Short Link
This endpoint allows you to create a new short link.
Request Headers:
Content-Type: application/json
Request Body:
Only url is required — every other field below is optional.
This body is valid JSON and can be pasted as-is:
{
"url": "https://example.com/very-long-url-that-needs-shortening",
"alias": "custom-alias",
"max-clicks": 100,
"password": "team2024@",
"expiration-time": "2027-12-31T23:59:00Z",
"description": "My important link"
}
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | The long URL to be shortened |
alias |
string | No | Custom alias for the short link: letters, numbers, hyphens, or underscores; alternatively, up to 15 emoji. Anything past the first 12 characters is silently truncated, so read short_url from the response rather than assuming you got the alias you asked for. Any other character is rejected with 400. |
max-clicks |
integer | No | Maximum number of allowed clicks. Must be a whole number — negative values and non-numeric strings are rejected with 400, and 0 is ignored (the link is created without a limit). |
password |
string | No | Password to protect the link |
expiration-time |
string | No | ISO 8601 datetime with timezone (e.g. 2026-12-31T23:59:00Z) at which the link stops redirecting. Must be at least 5 minutes in the future. |
description |
string | No | Description for the link (max 100 characters) |
Example Request:
curl -X POST https://zip1.io/api/create \
-H "Content-Type: application/json" \
-d '{
"url": "https://github.com/username/awesome-project",
"alias": "awesome",
"max-clicks": 1000,
"description": "My awesome GitHub project"
}'
Success Response:
{
"short_url": "https://zip1.io/awesome",
"created_at": "2026-01-15T10:30:45.482913+00:00",
"description": "My awesome GitHub project"
}
Note: description is only included when provided in the request.
created_at is an ISO 8601 UTC timestamp with microseconds and a
+00:00 offset — parse it, don't string-match it. An emoji alias comes
back percent-encoded in short_url.
Error Responses:
{
"message": "Start the address with https:// — try https://example.com",
"reason": "no_scheme"
}
message explains the problem in plain English.
reason is a stable machine-readable code
(empty, no_scheme, scheme_typo,
unsupported_scheme, email,
own_domain, ip_address, not_public,
no_tld, whitespace, too_long,
malformed, blocked, destination-velocity,
unsafe-destination, redirect-destination,
duplicate, not-saved) — match on this rather than the
wording.
A rejected alias, password, max-clicks,
expiration-time or description also returns 400, but with a
message only and no reason.
{
"message": "That is already a short link. Paste the final destination instead.",
"reason": "blocked"
}
A destination on the abuse blocklist returns 403. So do
destination-velocity, unsafe-destination and
redirect-destination below; every other reason comes
with a 400.
{
"message": "Too many links to this destination are being created right now, so we have paused it. This is about the destination, not you — try again in an hour.",
"reason": "destination-velocity"
}
This one is temporary and not about you. We measure how fast links to each destination are being created per hour, because that is what separates a phishing campaign from a busy customer. Cross the line and the destination pauses until the hour rolls over. A bulk integration is counted per exact destination URL, so creating thousands of distinct links under one domain never trips it. Retry after an hour rather than immediately, and if you hit it routinely tell us at /contact.
{
"message": "Google Safe Browsing lists this destination for distributing unwanted software, so we cannot shorten it. If you think that is wrong, tell us at /report and we will look at it.",
"reason": "unsafe-destination",
"threat": "UNWANTED_SOFTWARE"
}
A destination Google Safe Browsing lists as unsafe also returns
403, with reason unsafe-destination and a
threat field (MALWARE, SOCIAL_ENGINEERING,
UNWANTED_SOFTWARE or POTENTIALLY_HARMFUL_APPLICATION).
The check fails open, so it never rejects a link when Safe Browsing is
unreachable.
{
"message": "This link redirects to a destination we block (https://verify.example-phish.live/aQ6E), so we cannot shorten it. Shortening another shortener hides where the link really goes. If you think that is wrong, tell us at /report and we will look at it.",
"reason": "redirect-destination",
"target": "https://verify.example-phish.live/aQ6E"
}
We follow up to three redirects from the URL you send and check where it
actually lands. If that is a destination we block, the submission is refused with
403, reason redirect-destination and a
target field naming the hop we objected to. This is usually not
about you — most people who hit it were sent a short link by someone else
and are passing it on. Send the final destination instead of a link that points at
it. The check fails open, so an unreachable or slow destination is never rejected on
that account, and it never runs on a URL already refused by one of the checks
above.
{
"message": "Alias already exists",
"reason": "duplicate"
}
duplicate comes back only when you chose the
alias yourself. If you sent none and a generated code collided, the
collision is ours to retry, not yours.
{
"message": "We could not save your link just now. Please try again in a moment.",
"reason": "not-saved"
}
not-saved means the link was not created —
a write to our database failed. No short_url is returned, because a URL
we handed back without storing would 404 the first time anyone opened it. Safe to
retry.
{
"message": "Invalid JSON data"
}
Returned for an empty JSON object {}. Two malformed-request
cases do not produce JSON at all: a body that is not parseable JSON returns a
plain 400 HTML page, and a request sent without the
Content-Type: application/json header returns a 415 HTML
page. Check the status code before parsing a response as JSON.
2. Get Link Statistics
Retrieve detailed statistics for a specific short link.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
short_code |
string | The short code or alias of the link |
Example Request:
curl -X GET https://zip1.io/api/stats/awesome
Success Response:
{
"message": "Statistics retrieved successfully",
"data": {
"short_code": "awesome",
"total_clicks": 523,
"unique_clicks": 421,
"creation_date": "2026-01-15T10:30:45.482913+00:00",
"last_click": "2026-01-20 15:45:12",
"click_limit": "1000",
"expiration_time": null,
"is_password_protected": false,
"top_countries": [
{"country": "United States", "clicks": 234},
{"country": "United Kingdom", "clicks": 89},
{"country": "Germany", "clicks": 67}
],
"recent_clicks": []
}
}
Field Notes:
click_limitis a string, not a number (ornullwhen the link has no limit). Cast it before comparing.creation_dateis a full ISO 8601 UTC timestamp, whichever way the link was made. It read back as a bare clock time such as"14:22:07"for website-created links until 2026-09-01; that is fixed. Links too old to carry a usable timestamp still report"Unknown".last_clickis"YYYY-MM-DD HH:MM:SS"in UTC — a space, not aT, and no offset. It isnullbefore the first click.expiration_timeis an ISO 8601 UTC timestamp, ornull.top_countriesholds at most the three busiest countries, sorted by clicks.recent_clicksis present for forward compatibility and is always an empty array today — per-click detail is not exposed here. Use/export/{short_code}/{format}for the full breakdown.
Error Response:
{
"message": "Short link not found"
}
3. Export Statistics
Export detailed statistics for a short link in various formats.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
short_code |
string | The short code or alias of the link |
format |
string | Export format: json, csv, xlsx, or xml |
Example Requests:
curl -X GET https://zip1.io/export/awesome/json
curl -X GET https://zip1.io/export/awesome/csv \
-o statistics.zip
Response:
Every format is served as a file download (Content-Disposition: attachment):
json—application/json, the full statistics documentxml—application/xml, the same data as XMLxlsx— an Excel workbook, one sheet per breakdowncsv— a ZIP archive (application/zip), not a single CSV file: one CSV per breakdown (general info, countries, browsers, referrers, and so on). Save it as.zipand unpack it.
A password-protected link needs its password: send it as
?password=… or as a form field. Without it the endpoint returns 400.
Errors here do not follow the message convention used by
/api/create and /api/stats. A GET returns an HTML
error page; the same request as a POST (this endpoint accepts both) returns
JSON keyed by error type:
- 404 unknown short code —
{"UrlError": "The requested Url never existed"} - 400 unsupported format —
{"FormatError": "Invalid format; format must be json, csv, xlsx or xml"} - 400 missing or wrong password —
{"PasswordError": "Invalid Password"}
Rate Limiting
Our API implements rate limiting to ensure fair usage:
- Create Short Link: 10 requests per minute per IP
- Get Statistics: 30 requests per minute per IP
- Export Statistics: No rate limit
When rate limited, you'll receive a 429 status code with the following response:
{
"error": "ratelimit exceeded 10 per 1 minute"
}
Best Practices
- Always validate URLs on your end before sending them to our API
- Handle rate limiting gracefully by implementing exponential backoff
- Store the short URLs returned by our API for future reference
- Use descriptive aliases when possible for better link management
- Implement proper error handling for all possible response codes
- Consider caching statistics data to reduce API calls
Code Examples
Python:
import requests
import json
# Create a short link
def create_short_link(url, alias=None, max_clicks=None):
endpoint = "https://zip1.io/api/create"
payload = {"url": url}
if alias:
payload["alias"] = alias
if max_clicks:
payload["max-clicks"] = max_clicks
response = requests.post(
endpoint,
headers={"Content-Type": "application/json"},
data=json.dumps(payload)
)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Error: {response.json()['message']}")
# Get statistics
def get_stats(short_code):
endpoint = f"https://zip1.io/api/stats/{short_code}"
response = requests.get(endpoint)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Error: {response.json()['message']}")
# Example usage
try:
# Create a short link
result = create_short_link(
url="https://github.com/username/project",
alias="myproject",
max_clicks=1000
)
print(f"Short URL: {result['short_url']}")
# Get statistics
stats = get_stats("myproject")
print(f"Total clicks: {stats['data']['total_clicks']}")
except Exception as e:
print(f"Error: {e}")
JavaScript (Node.js):
const axios = require('axios');
// Create a short link
async function createShortLink(url, options = {}) {
try {
const response = await axios.post('https://zip1.io/api/create', {
url: url,
...options
}, {
headers: {
'Content-Type': 'application/json'
}
});
return response.data;
} catch (error) {
throw new Error(error.response.data.message);
}
}
// Get statistics
async function getStats(shortCode) {
try {
const response = await axios.get(`https://zip1.io/api/stats/${shortCode}`);
return response.data;
} catch (error) {
throw new Error(error.response.data.message);
}
}
// Example usage
(async () => {
try {
// Create a short link
const result = await createShortLink('https://github.com/username/project', {
alias: 'myproject',
'max-clicks': 1000,
description: 'My awesome project'
});
console.log(`Short URL: ${result.short_url}`);
// Get statistics
const stats = await getStats('myproject');
console.log(`Total clicks: ${stats.data.total_clicks}`);
} catch (error) {
console.error(`Error: ${error.message}`);
}
})();
🤖 AI Integration (Model Context Protocol)
Zip1.io supports the Model Context Protocol (MCP), enabling AI assistants like Claude to interact with the URL shortener directly.
MCP Endpoint:
Get server information and available tools.
Setup for Claude Code (Recommended):
Claude Code has native HTTP MCP support. Simply run this command:
claude mcp add --transport http zip1 https://zip1.io/mcp
Setup for Claude Desktop:
Available MCP Tools:
- create_short_url - Create shortened URLs with custom aliases, passwords, and max clicks
- get_url_stats - Retrieve detailed analytics for shortened URLs
- validate_url - Check if URLs can be shortened
- generate_short_code - Generate random short code suggestions
Example Usage:
After configuring Claude Code, you can use natural language prompts like:
- "Shorten https://github.com/my-repo with alias 'repo'"
- "Get statistics for short code 'docs'"
- "Create a password-protected link for https://example.com/secret"
Need Help?
If you have any questions or need assistance with our API:
- Contact our support team through the contact form
- Report abuse or issues via the report form