Overview
| Item | Value |
|---|---|
| Base URL | https://api.blinkhop.com/ |
| Same API on the main domain | https://blinkhop.com/ (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 /linksalso acceptsapplication/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. |
Create a link
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. TheLocationheader 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,mcpandqr. They return409 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.
Create links in bulk
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 OKwhen the batch is processed.createdisfalsewhen 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 with429. - Possible request-level errors:
missing_urls,too_many_urls,invalid_json,too_large,rate_limited.
Look up a link
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.
Expand a short link
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.
Report a link
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.
Short link behavior
| 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/ |
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.txtlists 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.