Developers

URL shortener API: shorten links from code, no key required

Use the Blinkhop URL shortener API without a key: create zou.sh links with curl, Python or JavaScript, shorten 100 URLs at once, and handle errors and rate limits.

About this guide

Topic
Developers
Reading time
7 minutes
Updated
October 11, 2026
Written by
The Blinkhop team
On this page

A URL shortener API lets your code turn a long URL into a short link with a single HTTP request. Blinkhop’s URL shortener API is a JSON REST API at https://api.blinkhop.com/v1 that you can call without a key: send a URL, get back a zou.sh link with its QR code and preview page. This guide walks through working examples in curl, Python and JavaScript, then covers bulk shortening, errors, rate limits and safe retries.

The URL shortener API at a glance

  • Base URL: https://api.blinkhop.com/v1, also available at https://blinkhop.com/api/v1
  • Format: JSON in, JSON out. POST /links also accepts a form-encoded url=... field.
  • Authentication: none needed to try. API keys come with accounts, which are in early access.
  • CORS: open (Access-Control-Allow-Origin: *), so any web page can call the API.
  • Specification: OpenAPI 3.1 at https://api.blinkhop.com/v1/openapi.json
Method and path What it does
POST /links Create a short link, with an optional custom ending
POST /links/bulk Shorten up to 100 URLs in one request
GET /links/{code} Get public information about a link: destination, clicks and creation date
GET /expand?url=... Follow any short link’s redirects and return the chain, the final destination and a safety verdict
POST /reports Report an abusive link
GET /status Live status of redirects, the API and the MCP server
GET /health A simple health check that returns {"ok": true}

The API reference documents every field. If you just want a first link in a minute, the quickstart is shorter.

POST/links

curl -s https://api.blinkhop.com/v1/links \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/docs/getting-started?ref=readme"}'

The response describes the link:

{
  "id": "lnk_7Kp2x",
  "code": "7Kp2x",
  "short_url": "https://zou.sh/7Kp2x",
  "url": "https://example.com/docs/getting-started?ref=readme",
  "qr_url": "https://zou.sh/7Kp2x/qr",
  "preview_url": "https://zou.sh/7Kp2x+",
  "clicks": 0,
  "created_at": "2026-10-11T14:02:11Z"
}
Field Meaning
short_url The link to share
url The destination
qr_url A print-ready SVG QR code for the short link
preview_url The public preview page: the short link followed by +
clicks Total clicks so far
created_at Creation time in UTC

The status code tells you what happened: 201 means a new link was created, and 200 means the same URL already had a link, which you get back.

Choose a custom ending

Add an alias to pick the ending yourself:

curl -s https://api.blinkhop.com/v1/links \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/docs/getting-started", "alias": "getting-started"}'

The short link becomes https://zou.sh/getting-started. Endings use 3 to 40 letters, numbers, dashes or underscores, start with a letter or a number, and are unique regardless of letter case. Some words, such as api, admin, docs and qr, are reserved. If the ending already belongs to another link, the API answers with HTTP 409 and the error code alias_taken.

Get plain text with ?format=text

In shell scripts, you often want the short URL and nothing else. Add ?format=text:

SHORT=$(curl -s "https://api.blinkhop.com/v1/links?format=text" \
  --data-urlencode "url=https://example.com/docs/getting-started")
echo "Read the docs: $SHORT"

The --data-urlencode option sends the URL as a form field and encodes characters such as & and ?, so URLs with query strings arrive intact.

This example uses the requests library. It retries after a rate limit and turns API errors into exceptions:

import time
import requests

API = "https://api.blinkhop.com/v1"


def shorten(url, alias=None, attempts=3):
    payload = {"url": url}
    if alias:
        payload["alias"] = alias
    for _ in range(attempts):
        resp = requests.post(f"{API}/links", json=payload, timeout=10)
        if resp.status_code == 429:
            time.sleep(int(resp.headers.get("Retry-After", "60")))
            continue
        data = resp.json()
        if resp.status_code in (200, 201):
            return data["short_url"]
        error = data["error"]
        raise ValueError(f"{error['code']}: {error['message']}")
    raise RuntimeError("Still rate limited after several attempts")


print(shorten("https://example.com/docs/getting-started"))

Retrying is safe here: because the same URL returns the same link, a request repeated after a timeout does not create a duplicate.

The same call with fetch runs in browsers and in Node.js 18 or later. Because CORS is open, a page on any domain can call the API directly:

const API = "https://api.blinkhop.com/v1";

async function shorten(url, alias) {
  const res = await fetch(`${API}/links`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(alias ? { url, alias } : { url }),
  });
  const data = await res.json();
  if (!res.ok) {
    throw new Error(`${data.error.code}: ${data.error.message}`);
  }
  console.log("Remaining in this window:", res.headers.get("X-RateLimit-Remaining"));
  return data.short_url;
}

console.log(await shorten("https://example.com/docs/getting-started"));

Top-level await works in ES modules. In a classic script, call shorten(...).then(console.log) instead. More examples are on the code samples page.

Shorten up to 100 URLs with the bulk endpoint

POST/links/bulk

The bulk endpoint takes a urls array with up to 100 URLs:

curl -s https://api.blinkhop.com/v1/links/bulk \
  -H "Content-Type: application/json" \
  -d '{"urls": [
    "https://example.com/pricing",
    "https://example.com/changelog",
    "not a url"
  ]}'

The response contains one result per URL: a link for each URL that could be shortened, and an error for each one that could not, like the third entry above. One bad URL does not sink the whole batch.

Each URL in a bulk request counts toward your rate limit. Without a key, that means at most 20 new links per minute and 300 per day, so split large jobs into batches of 20 and check X-RateLimit-Remaining between requests. The exact response schema is in the API reference and the OpenAPI spec.

Errors: one predictable format

Every error uses the same JSON shape, with a machine-readable code, a human-readable message and, when relevant, the field that caused it:

{
  "error": {
    "code": "invalid_url",
    "message": "That does not look like a valid URL.",
    "field": "url"
  }
}

Branch on code, not on message: messages are written for people and may change.

Code Meaning
missing_url The request has no URL
invalid_url The URL is not a valid public web address
already_short The URL is already a zou.sh link
url_too_long The URL is longer than the API accepts
unsafe_destination The destination is on the phishing and malware blocklist
invalid_alias The custom ending breaks the format rules
alias_taken (409) The custom ending already belongs to another link
rate_limited (429) Too many requests; wait before retrying
not_found (404) No link exists with that code
invalid_json The request body is not valid JSON
too_large The request body is too large

Rate limits and response headers

Without a key, each IP address can create 20 new links per minute and 300 per day. Every response carries rate-limit headers, so your code never has to guess:

Header Meaning
X-RateLimit-Limit The number of requests allowed in the current window
X-RateLimit-Remaining How many are left in the current window
X-RateLimit-Reset When the current window resets
Retry-After On 429 responses only: how many seconds to wait

Higher limits come with API keys. A free account (early access) includes a key with 1,000 links per day, and paid plans go up to 50,000 or 250,000 API links per month. The pricing page has the details.

HTTP does not treat POST requests as idempotent, but this endpoint behaves like one for plain links. Shortening a URL that already has a link, without a custom ending, returns that existing link with status 200 instead of creating another one. In practice:

  • A request retried after a timeout or a dropped connection cannot create a duplicate.
  • A job that runs twice over the same list of URLs returns the same short links.
  • The status code tells you whether a link is new (201) or existing (200).

Matching is done on the URL itself, so changing any part of it, such as a UTM value, produces a different link. For requests with a custom ending, handle alias_taken explicitly.

Best practices for production

  • Set a timeout on every request, as in the Python example, and retry on network errors. Retries are safe because the same URL returns the same link.
  • Respect the headers. Slow down when X-RateLimit-Remaining gets low, and after a 429, wait for Retry-After instead of retrying in a tight loop.
  • Store the result. Save short_url next to the destination in your own database, so you do not call the API on every page view.
  • Never shorten secrets. Anyone with a zou.sh link can see its destination on the preview page, so keep access tokens, password reset links and private documents out of short links.
  • Show people where links go. If your app shortens links for others, display the preview_url so readers can check a destination before they click.

Other endpoints worth knowing

GET/links/{code}

Look up a link’s destination, clicks and creation date:

curl -s https://api.blinkhop.com/v1/links/7Kp2x

GET/expand?url=...

Check where any short link leads, including bit.ly, tinyurl.com and t.co links. The API follows up to 10 redirects and returns the chain, the final destination and a safety verdict:

curl -s -G https://api.blinkhop.com/v1/expand --data-urlencode "url=https://bit.ly/XXXXXXX"

Report an abusive zou.sh link with POST /reports and a JSON body such as {"short_url": "https://zou.sh/7Kp2x", "reason": "Phishing page"}. To monitor the service, poll GET /status or GET /health. If your users work in AI assistants rather than scripts, see the guide to shortening links with an MCP server.

Key takeaways

  • One POST to https://api.blinkhop.com/v1/links returns a zou.sh link, a QR code URL and a preview URL, with no key needed.
  • Add ?format=text for a plain short URL, or an alias for a custom ending.
  • POST /links/bulk shortens up to 100 URLs per request, with one result per URL.
  • Errors share one JSON format: branch on error.code.
  • Watch X-RateLimit-Remaining, and wait for Retry-After seconds after a 429.
  • The same URL returns the same link, so retries are safe.

FAQ

Frequently asked questions

Do I need an API key to use the Blinkhop API?

No. You can call the API without a key, within 20 new links per minute and 300 per day per IP address. API keys with higher limits come with accounts, which are in early access.

What happens if I shorten the same URL twice?

The API returns the existing link with HTTP 200 instead of creating a new one, which returns HTTP 201. This applies to links without a custom ending.

Can I call the API from a web page?

Yes. CORS is open, with Access-Control-Allow-Origin set to *, so browser code on any site can call the API directly.

Is there an OpenAPI specification?

Yes. An OpenAPI 3.1 spec is available at https://api.blinkhop.com/v1/openapi.json, ready to import into API clients and code generators.

Related reading

  • Developers

    API reference

    Every endpoint, parameter, response and error code.

  • Developers

    API quickstart

    Your first short link from code in 60 seconds, no key needed.

  • Developers

    Code samples

    Copy-paste examples in JavaScript, Python, Go, PHP and the shell.

  • Guide

    Shorten links from your AI assistant with an MCP server

    Connect Blinkhop's MCP server to Claude, ChatGPT, Cursor, VS Code or Windsurf with one URL, then ask your assistant to shorten and check links. No account or key.

Your next link is one paste away.

Free, no sign-up. Links that never expire.