Developer docs

API reference

Everything the Blinkhop REST API does, with exact request and response formats. JSON in, JSON out, no key needed to start.

View as Markdown

API at a glance

Base URL
https://api.blinkhop.com/v1
MCP server
https://mcp.blinkhop.com/mcp
API key
Not needed to start
Limits
20 links/min, 300/day per IP

Overview

Item Value
Base URL https://api.blinkhop.com/v1
Same API on the main domain https://blinkhop.com/api/v1 (same origin as the website, no CORS headers)
Formats JSON in and out. POST bodies may also be form-encoded.
Authentication None needed today. See Authentication.
OpenAPI 3.1 spec https://api.blinkhop.com/v1/openapi.json, also at /openapi.json
Short links https://zou.sh/<code>, see Short link behavior

New to the API? Start with the quickstart.

Endpoints at a glance

Method Path What it does
POST /links Create a short link
POST /links/bulk Create up to 100 links at once
GET /links/{code} Look up a link
GET /expand See where any short link goes
POST /reports Report a link
GET /status Live status and uptime
GET /health Health check

Authentication

You don’t need a key: every endpoint works anonymously, within the rate limits. API keys with higher limits will come with accounts, which are in early access. Keys will use a standard Authorization: Bearer header, and anonymous calls will keep working.

Requests and responses

  • Send JSON with Content-Type: application/json. POST /links also accepts application/x-www-form-urlencoded.
  • Request bodies are limited to 64 KB. Text is UTF-8.
  • Responses are JSON (UTF-8) with Cache-Control: no-store, except where noted.
  • Timestamps are ISO 8601 in UTC, like 2026-10-11T14:02:11Z.
  • Changes to v1 are additive: new fields may appear in responses, so ignore fields you don’t recognize.

Rate limits

Limits apply per IP address. Each window starts with your first request and resets one minute (or one day) later:

What Limit
Creating links (POST /links, POST /links/bulk) 20 per minute and 300 per day. A bulk call uses the whole minute; each of its URLs counts toward the day.
Expanding links (GET /expand) 30 per minute and 500 per day
Reports (POST /reports) 10 per hour
MCP server tools 120 calls per minute and 5,000 per day

Rate-limited endpoints return these headers:

Header Meaning
X-RateLimit-Limit Requests allowed per minute
X-RateLimit-Remaining Requests left in the current minute
X-RateLimit-Reset When the minute window resets, as a Unix timestamp in seconds
Retry-After Only on 429 responses: seconds to wait before retrying

When you hit a limit, the API answers 429 with the code rate_limited. Wait for Retry-After seconds, then retry. Don’t retry in a tight loop.

Errors

Every error has the same shape. Branch on code: it is stable, while message may be reworded.

{
  "error": {
    "code": "alias_taken",
    "message": "That ending is already taken. Try another one.",
    "field": "alias"
  }
}
HTTP Code Meaning
400 missing_url No url in the request.
400 missing_urls No urls array in a bulk request.
400 invalid_json The body is not valid JSON.
404 not_found The link or the endpoint doesn’t exist, or the link was disabled.
405 method_not_allowed Wrong HTTP method, for example GET /links.
409 alias_taken The custom ending is taken or reserved.
413 too_large The request body is larger than 64 KB.
422 invalid_url Malformed URL, spaces, scheme other than http or https, embedded credentials, or a private or local address.
422 already_short The URL is already a zou.sh link.
422 url_too_long The URL is longer than 4,096 characters.
422 unsafe_destination The domain is on our phishing and malware blocklist, or the destination was reported and blocked.
422 invalid_alias The ending breaks the format rules.
422 invalid_code The value is neither a zou.sh link nor a valid code.
422 too_many_urls A bulk request has more than 100 URLs.
429 rate_limited Too many requests. See Retry-After.
500 internal Something went wrong on our side. Retry later and check the status page.

POST/v1/links

Creates a short link for a long URL, or returns the existing one.

Parameter In Type Description
url body string, required The destination, http or https. A missing scheme defaults to https://.
alias body string, optional Custom ending: 3 to 40 letters, numbers, - or _, starting with a letter or a number. Unique regardless of case.
format query json or text text returns only the short URL, as plain text. Default json.
curl https://api.blinkhop.com/v1/links \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/spring", "alias": "spring-sale"}'
{
  "id": "lnk_spring-sale",
  "code": "spring-sale",
  "short_url": "https://zou.sh/spring-sale",
  "url": "https://example.com/spring",
  "qr_url": "https://zou.sh/spring-sale/qr",
  "preview_url": "https://zou.sh/spring-sale+",
  "clicks": 0,
  "created_at": "2026-10-11T14:02:11Z"
}
  • 201 Created: a new link. The Location header holds the short URL.
  • 200 OK: the link already existed. That happens when the same URL was shortened before without an ending, or when you send an ending that already points to the same URL. Retries are safe.
  • Random codes are 5 characters from an alphabet without look-alike characters (no 0/O, 1/l/I).
  • Some endings are reserved, such as api, admin, login, docs, help, pricing, mcp and qr. They return 409 alias_taken.
  • Possible errors: missing_url, invalid_url, already_short, url_too_long, unsafe_destination, invalid_alias, alias_taken, invalid_json, too_large, rate_limited.

POST/v1/links/bulk

Shortens up to 100 URLs in one request. Results come back in the same order as the input. An invalid URL gets an error object in its slot instead of failing the whole batch.

Parameter In Type Description
urls body array of strings, required 1 to 100 URLs. Custom endings aren’t supported in bulk.
curl https://api.blinkhop.com/v1/links/bulk \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com/a", "https://example.org/b", "not a url"]}'
{
  "links": [
    {
      "id": "lnk_Tq4mZ",
      "code": "Tq4mZ",
      "short_url": "https://zou.sh/Tq4mZ",
      "url": "https://example.com/a",
      "qr_url": "https://zou.sh/Tq4mZ/qr",
      "preview_url": "https://zou.sh/Tq4mZ+",
      "clicks": 0,
      "created_at": "2026-10-11T14:02:11Z",
      "created": true
    },
    {
      "id": "lnk_8wKhd",
      "code": "8wKhd",
      "short_url": "https://zou.sh/8wKhd",
      "url": "https://example.org/b",
      "qr_url": "https://zou.sh/8wKhd/qr",
      "preview_url": "https://zou.sh/8wKhd+",
      "clicks": 0,
      "created_at": "2026-10-11T14:02:11Z",
      "created": true
    },
    {
      "url": "not a url",
      "error": { "code": "invalid_url", "message": "URLs cannot contain spaces." }
    }
  ]
}
  • The response is always 200 OK when the batch is processed. created is false when a link already existed.
  • Each URL counts toward your daily limit of 300 links, and a bulk call uses up the current minute’s allowance of 20, so the next call waits for X-RateLimit-Reset. A batch larger than what you have left for the day, or sent when the minute’s allowance is partly used, is rejected as a whole with 429.
  • Possible request-level errors: missing_urls, too_many_urls, invalid_json, too_large, rate_limited.

GET/v1/links/{code}

Returns public information about a short link: destination, click count and creation date.

Parameter In Type Description
code path string, required The part after zou.sh/. Case doesn’t matter.
curl https://api.blinkhop.com/v1/links/spring-sale

The response has the same fields as Create a link. Unknown or disabled links return 404 not_found.

GET/v1/expand?url={short link}

Shows where any short link goes, without visiting the destination in a browser. It works with zou.sh links and with other shorteners. The expander follows up to 10 redirects, never downloads page content, and checks the final domain against our phishing and malware blocklist.

Parameter In Type Description
url query string, required The short link to expand. A missing scheme defaults to https://.
curl "https://api.blinkhop.com/v1/expand?url=https://zou.sh/spring-sale"
{
  "url": "https://zou.sh/spring-sale",
  "final_url": "https://example.com/spring",
  "hops": [
    { "url": "https://zou.sh/spring-sale", "status": 302 },
    { "url": "https://example.com/spring", "status": 200 }
  ],
  "redirects": 1,
  "reached": true,
  "flagged": false
}
Field Description
final_url The last URL in the chain.
hops Each step, with its HTTP status, or an error: unreachable (no answer within 5 seconds), blocked_address (private network) or unsupported_port.
redirects Number of redirects followed.
reached true when the final URL answered with a status below 400.
flagged true when the final domain is on our blocklist. false means it isn’t on the list, not that the page is guaranteed safe.

Looking up a zou.sh link this way never counts as a click. The same tool is available as a page: link expander.

POST/v1/reports

Reports a zou.sh link used for phishing, malware, spam or other abuse. We review every report.

Parameter In Type Description
short_url body string, required The zou.sh link, or just its code.
reason body string, optional What’s wrong with it, up to 1,000 characters.
curl https://api.blinkhop.com/v1/reports \
  -H "Content-Type: application/json" \
  -d '{"short_url": "https://zou.sh/Tq4mZ", "reason": "Fake bank login page"}'

Returns 202 Accepted with {"ok": true}. Unknown links return 404 not_found. Humans can use the abuse form instead.

Status

GET/v1/status

Live status of short links, the API, the MCP server and the blocklist, plus daily uptime for the last 90 days, measured by our own monitor every minute. Cached for 30 seconds. The status page shows the same data.

{
  "status": "operational",
  "checked_at": "2026-10-11T16:30:05Z",
  "monitoring_since": "2026-10-11T15:57:00Z",
  "uptime_90d": 100,
  "components": [
    { "id": "redirects", "name": "Short links (zou.sh)", "status": "operational" },
    { "id": "api", "name": "REST API", "status": "operational" },
    { "id": "mcp", "name": "MCP server", "status": "operational" },
    {
      "id": "safety",
      "name": "Phishing & malware blocklist",
      "status": "operational",
      "domains": 391542,
      "updated_at": "2026-10-11T04:17:00Z"
    }
  ],
  "days": [{ "day": "2026-10-11", "uptime": 100 }]
}

status is operational or degraded. Days before monitoring started have "uptime": null.

Health check

GET/v1/health

Returns {"ok": true, "version": "1.0.0"} when the API is up. Use it for your own monitoring.

URL Result
https://zou.sh/<code> 302 redirect to the destination, with Cache-Control: private, no-cache and X-Robots-Tag: noindex.
https://zou.sh/<code>+ Public preview page: destination, creation date, clicks, and a form to report the link.
https://zou.sh/<code>/qr SVG QR code of the short link (error correction level M).
Unknown code 404 page.
Disabled link 410 page that says the link was disabled.

Codes are case-insensitive: zou.sh/7KP2X and zou.sh/7kp2x open the same link. Redirects set no cookies, and visitor IP addresses are never stored. Clicks from bots, link previews (Slack, WhatsApp, Discord and others), scripts and headless browsers aren’t counted.

CORS

api.blinkhop.com answers every origin with Access-Control-Allow-Origin: * and exposes the X-RateLimit-*, Retry-After and Location headers, so you can call it from any web page. Browsers may cache preflight responses for up to 24 hours. blinkhop.com/api/v1 is meant for our own website and sends no CORS headers.

OpenAPI and AI tools

  • The OpenAPI 3.1 description lives at https://api.blinkhop.com/v1/openapi.json. Import it into Postman, Insomnia or an SDK generator.
  • /llms.txt lists our docs in Markdown for AI assistants, and every docs page has a Markdown version.
  • To let an assistant call Blinkhop directly, use the MCP server.