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

POST /api/create

This endpoint allows you to create a new short link.

Request Headers:

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:

JSON
{
    "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
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:

JSON • Status: 200 OK
{
    "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:

JSON • Status: 400 Bad Request
{
    "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.

JSON • Status: 403 Forbidden
{
    "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.

JSON • Status: 403 Forbidden
{
    "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.

JSON • Status: 403 Forbidden
{
    "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.

JSON • Status: 403 Forbidden
{
    "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.

JSON • Status: 409 Conflict
{
    "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.

JSON • Status: 503 Service Unavailable
{
    "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.

JSON • Status: 400 Bad Request
{
    "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

GET /api/stats/{short_code}

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
curl -X GET https://zip1.io/api/stats/awesome

Success Response:

JSON • Status: 200 OK
{
    "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_limit is a string, not a number (or null when the link has no limit). Cast it before comparing.
  • creation_date is 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_click is "YYYY-MM-DD HH:MM:SS" in UTC — a space, not a T, and no offset. It is null before the first click.
  • expiration_time is an ISO 8601 UTC timestamp, or null.
  • top_countries holds at most the three busiest countries, sorted by clicks.
  • recent_clicks is 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:

JSON • Status: 404 Not Found
{
    "message": "Short link not found"
}

3. Export Statistics

GET /export/{short_code}/{format}

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 - JSON Format
curl -X GET https://zip1.io/export/awesome/json
cURL - CSV Format
curl -X GET https://zip1.io/export/awesome/csv \
  -o statistics.zip

Response:

Every format is served as a file download (Content-Disposition: attachment):

  • jsonapplication/json, the full statistics document
  • xmlapplication/xml, the same data as XML
  • xlsx — an Excel workbook, one sheet per breakdown
  • csv — 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 .zip and 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:

JSON • Status: 429 Too Many Requests
{
    "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:

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

JavaScript
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 /mcp

Get server information and available tools.

Setup for Claude Code (Recommended):

Claude Code has native HTTP MCP support. Simply run this command:

Terminal
claude mcp add --transport http zip1 https://zip1.io/mcp

Setup for Claude Desktop:

⚠️ Important: Claude Desktop currently only supports stdio-based MCP servers, not HTTP servers. We recommend using Claude Code for zip1.io's MCP integration.

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"
📚 Full MCP Documentation: For detailed information about the MCP integration, see our MCP documentation.

Need Help?

If you have any questions or need assistance with our API: