# Blinkhop developer documentation --- --- > Everything about the Blinkhop REST API and MCP server in one file. Index: https://blinkhop.com/llms.txt --- --- # Quickstart: your first short link from code > One POST request, no API key, no sign-up. Go from zero to a working short link, then learn the few details worth knowing before you ship. Source: https://blinkhop.com/docs/quickstart ## 1. Send one request The Blinkhop API lives at `https://api.blinkhop.com/v1`. To shorten a URL, send it to the `/links` endpoint: POST /v1/links ```bash curl https://api.blinkhop.com/v1/links \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/a/very/long/link"}' ``` That's the whole setup. There is no key to create, no account and nothing to install. The same request works from any language that can send HTTP: ```js const res = await fetch("https://api.blinkhop.com/v1/links", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: "https://example.com/a/very/long/link" }), }); const link = await res.json(); console.log(link.short_url); // https://zou.sh/7Kp2x ``` ```python import requests r = requests.post( "https://api.blinkhop.com/v1/links", json={"url": "https://example.com/a/very/long/link"}, timeout=10, ) print(r.json()["short_url"]) # https://zou.sh/7Kp2x ``` ## 2. Read the response A successful call returns the link as JSON: ```json { "id": "lnk_7Kp2x", "code": "7Kp2x", "short_url": "https://zou.sh/7Kp2x", "url": "https://example.com/a/very/long/link", "qr_url": "https://zou.sh/7Kp2x/qr", "preview_url": "https://zou.sh/7Kp2x+", "clicks": 0, "created_at": "2026-10-11T14:02:11Z" } ``` | Field | Description | | --- | --- | | `short_url` | The link to share. It redirects to `url` with an HTTP 302. | | `code` | The part after `zou.sh/`. Lookups ignore letter case. | | `url` | The destination, normalized (a missing `https://` is added). | | `qr_url` | A print-ready SVG QR code of the short link. | | `preview_url` | A public page that shows the destination and the click count before anyone visits. | | `clicks` | Human clicks so far. Bots and link previews are not counted. | | `created_at` | Creation time, ISO 8601 in UTC. | The status code tells you what happened. `201 Created` means a new link. `200 OK` means the same URL had already been shortened without a custom ending, so you get the existing link back. Retrying a request never creates duplicates. ## 3. Choose the ending Add an `alias` to pick the ending yourself: ```bash curl https://api.blinkhop.com/v1/links \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/spring", "alias": "spring-sale"}' ``` Endings use 3 to 40 letters, numbers, dashes or underscores, and start with a letter or a number. They are unique regardless of letter case: `zou.sh/Spring-Sale` and `zou.sh/spring-sale` are the same link. If the ending is taken or reserved, the API answers `409` with the code `alias_taken`, so you can suggest another one. ## 4. Get only the link Add `?format=text` and the API returns the short URL as plain text, which is handy in shell scripts: ```bash curl -s "https://api.blinkhop.com/v1/links?format=text" \ --data-urlencode "url=https://example.com/report?quarter=q3&year=2026" # https://zou.sh/q8Hn4 ``` The API also accepts form-encoded bodies, as above. Use `--data-urlencode` so that `&` and `?` inside your URL survive the trip. ## 5. Handle errors and limits Errors come back as JSON with a stable `code`, a readable `message` and, when it applies, the `field` at fault: ```json { "error": { "code": "invalid_url", "message": "That does not look like a valid URL.", "field": "url" } } ``` Branch on `code`, not on `message`: messages may be reworded, codes won't. The most common ones: | HTTP | Code | When | | --- | --- | --- | | 400 | `missing_url` | The request has no `url`. | | 422 | `invalid_url` | The URL is malformed, not http(s), or points to a private address. | | 422 | `unsafe_destination` | The domain is on our phishing and malware blocklist. | | 409 | `alias_taken` | The custom ending is taken or reserved. | | 429 | `rate_limited` | Too many links in a short time. Wait `Retry-After` seconds. | Without a key you can create **20 links per minute and 300 per day** per IP address. Every response from `POST /links` carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers, and a `429` adds `Retry-After`. API keys with higher limits come with accounts, which are in [early access](https://blinkhop.com/pricing#early-access). ## 6. Call it from a web page The API sends `Access-Control-Allow-Origin: *`, so you can call it straight from the browser, with no proxy. Nothing secret is involved, since no key is needed: ```js document.querySelector("#share").addEventListener("click", async () => { const res = await fetch("https://api.blinkhop.com/v1/links", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: location.href }), }); const { short_url } = await res.json(); await navigator.clipboard.writeText(short_url); }); ``` ## Next steps - Read the [API reference](https://blinkhop.com/docs/api) for every endpoint: bulk creation, link lookup, the link expander and reports. - Copy ready-made [code samples](https://blinkhop.com/docs/code-samples) for Node.js, Python, Go, PHP, PowerShell, Google Sheets and more. - Let your AI assistant do it: connect the [MCP server](https://blinkhop.com/mcp) to Claude, ChatGPT, Cursor or VS Code. - Import the [OpenAPI spec](https://blinkhop.com/openapi.json) into Postman, Insomnia or your code generator. --- # API reference > Everything the Blinkhop REST API does, with exact request and response formats. JSON in, JSON out, no key needed to start. Source: https://blinkhop.com/docs/api ## 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](#authentication). | | OpenAPI 3.1 spec | [`https://api.blinkhop.com/v1/openapi.json`](https://api.blinkhop.com/v1/openapi.json), also at [`/openapi.json`](https://blinkhop.com/openapi.json) | | Short links | `https://zou.sh/`, see [Short link behavior](#short-link-behavior) | New to the API? Start with the [quickstart](https://blinkhop.com/docs/quickstart). ### Endpoints at a glance | Method | Path | What it does | | --- | --- | --- | | `POST` | `/links` | [Create a short link](#create-a-link) | | `POST` | `/links/bulk` | [Create up to 100 links at once](#bulk) | | `GET` | `/links/{code}` | [Look up a link](#look-up-a-link) | | `GET` | `/expand` | [See where any short link goes](#expand-a-short-link) | | `POST` | `/reports` | [Report a link](#report-a-link) | | `GET` | `/status` | [Live status and uptime](#status) | | `GET` | `/health` | [Health check](#health-check) | ## Authentication You don't need a key: every endpoint works anonymously, within the [rate limits](#rate-limits). API keys with higher limits will come with accounts, which are in [early access](https://blinkhop.com/pricing#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. ```json { "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](https://blinkhop.com/status). | ## 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`. | ```bash curl https://api.blinkhop.com/v1/links \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/spring", "alias": "spring-sale"}' ``` ```json { "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`. ## 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. | ```bash 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"]}' ``` ```json { "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`. ## 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. | ```bash curl https://api.blinkhop.com/v1/links/spring-sale ``` The response has the same fields as [Create a link](#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://`. | ```bash curl "https://api.blinkhop.com/v1/expand?url=https://zou.sh/spring-sale" ``` ```json { "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](https://blinkhop.com/preview). ## 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. | ```bash 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](https://blinkhop.com/abuse) 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](https://blinkhop.com/status) shows the same data. ```json { "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/` | `302` redirect to the destination, with `Cache-Control: private, no-cache` and `X-Robots-Tag: noindex`. | | `https://zou.sh/+` | Public preview page: destination, creation date, clicks, and a form to report the link. | | `https://zou.sh//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`](https://api.blinkhop.com/v1/openapi.json). Import it into Postman, Insomnia or an SDK generator. - [`/llms.txt`](https://blinkhop.com/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](https://blinkhop.com/mcp). --- # Code samples > Working snippets to shorten links from the tools you already use. No key, no SDK to install: each one is a single HTTP request. Source: https://blinkhop.com/docs/code-samples Every sample below calls `POST https://api.blinkhop.com/v1/links` and needs no API key. They are tested as written. For the full list of fields and error codes, see the [API reference](https://blinkhop.com/docs/api). ## cURL ```bash # Create a short link curl https://api.blinkhop.com/v1/links \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/a/very/long/link"}' # Choose the ending curl https://api.blinkhop.com/v1/links \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/spring", "alias": "spring-sale"}' # Get only the short URL, as plain text curl -s "https://api.blinkhop.com/v1/links?format=text" \ --data-urlencode "url=https://example.com/report?q=3&y=2026" # See where a short link goes curl "https://api.blinkhop.com/v1/expand?url=https://zou.sh/spring-sale" ``` ## Shell function Add this to `~/.bashrc` or `~/.zshrc`, open a new terminal, then run `shorten https://example.com/long/link`: ```bash shorten() { curl -fsS "https://api.blinkhop.com/v1/links?format=text" --data-urlencode "url=$1" } ``` Pipe it to your clipboard: `shorten https://example.com | pbcopy` on macOS, `| xclip -selection clipboard` on Linux, `| clip` in Windows Git Bash. ## JavaScript Works in browsers and in Node.js 18 or later, with no dependencies. The API allows calls from any web page. ```js async function shorten(url, alias) { const res = await fetch("https://api.blinkhop.com/v1/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}`); return data.short_url; } console.log(await shorten("https://example.com/a/very/long/link")); ``` ## Node.js with retries A reusable helper that waits and retries when you hit the rate limit. Save it as `shorten.mjs` and run `node shorten.mjs`. ```js const API = "https://api.blinkhop.com/v1"; export async function shorten(url, { alias, retries = 3 } = {}) { for (let attempt = 0; ; attempt++) { const res = await fetch(`${API}/links`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(alias ? { url, alias } : { url }), }); if (res.status === 429 && attempt < retries) { const wait = Number(res.headers.get("retry-after") ?? 1); await new Promise((resolve) => setTimeout(resolve, wait * 1000)); continue; } const data = await res.json(); if (!res.ok) { throw Object.assign(new Error(data.error.message), { code: data.error.code, status: res.status }); } return data; } } const link = await shorten("https://example.com/a/very/long/link"); console.log(link.short_url, link.qr_url); ``` ## Bulk shortening Shorten up to 100 URLs in one request. Each URL gets its own result, in order. ```js const res = await fetch("https://api.blinkhop.com/v1/links/bulk", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ urls: ["https://example.com/a", "https://example.org/b"] }), }); const { links } = await res.json(); for (const l of links) { console.log(l.error ? `Failed: ${l.url} (${l.error.message})` : `${l.short_url} -> ${l.url}`); } ``` ## Python With the popular [requests](https://requests.readthedocs.io/) library (Python 3.10 or later): ```python import requests API = "https://api.blinkhop.com/v1" def shorten(url: str, alias: str | None = None) -> str: payload = {"url": url} if alias is None else {"url": url, "alias": alias} r = requests.post(f"{API}/links", json=payload, timeout=10) data = r.json() if not r.ok: raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}") return data["short_url"] print(shorten("https://example.com/a/very/long/link")) ``` ## Python without dependencies The same call with the standard library only: ```python import json import urllib.error import urllib.request def shorten(url: str) -> str: req = urllib.request.Request( "https://api.blinkhop.com/v1/links", data=json.dumps({"url": url}).encode(), headers={"Content-Type": "application/json"}, method="POST", ) try: with urllib.request.urlopen(req, timeout=10) as res: return json.load(res)["short_url"] except urllib.error.HTTPError as e: err = json.load(e)["error"] raise RuntimeError(f"{err['code']}: {err['message']}") from None print(shorten("https://example.com/a/very/long/link")) ``` ## Go Standard library only. Save as `main.go` and run `go run main.go`. ```go package main import ( "bytes" "encoding/json" "fmt" "log" "net/http" "time" ) type Link struct { ShortURL string `json:"short_url"` URL string `json:"url"` QRURL string `json:"qr_url"` } type apiError struct { Error struct { Code string `json:"code"` Message string `json:"message"` } `json:"error"` } func shorten(url string) (*Link, error) { body, _ := json.Marshal(map[string]string{"url": url}) client := &http.Client{Timeout: 10 * time.Second} res, err := client.Post("https://api.blinkhop.com/v1/links", "application/json", bytes.NewReader(body)) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode >= 300 { var e apiError _ = json.NewDecoder(res.Body).Decode(&e) return nil, fmt.Errorf("%s: %s", e.Error.Code, e.Error.Message) } var link Link if err := json.NewDecoder(res.Body).Decode(&link); err != nil { return nil, err } return &link, nil } func main() { link, err := shorten("https://example.com/a/very/long/link") if err != nil { log.Fatal(err) } fmt.Println(link.ShortURL) } ``` ## PHP With the cURL extension, enabled on most PHP installs: ```php true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['url' => $url]), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $body = curl_exec($ch); if ($body === false) { throw new RuntimeException(curl_error($ch)); } $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $data = json_decode($body, true); if ($status >= 300) { throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']); } return $data['short_url']; } echo shorten('https://example.com/a/very/long/link'), PHP_EOL; ``` ## PowerShell Works in Windows PowerShell 5.1 and PowerShell 7: ```powershell $body = @{ url = "https://example.com/a/very/long/link" } | ConvertTo-Json $link = Invoke-RestMethod -Method Post -Uri "https://api.blinkhop.com/v1/links" ` -ContentType "application/json" -Body $body $link.short_url ``` ## Google Sheets Shorten a whole column with a custom formula. In your sheet, open **Extensions → Apps Script**, paste this code, save, then type `=SHORTEN(A2)` in a cell. ```js /** * Shortens a URL with Blinkhop. * @param {string} url The long URL. * @return The short zou.sh link. * @customfunction */ function SHORTEN(url) { if (!url) return ""; const key = Utilities.base64Encode(Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, String(url))); const cache = CacheService.getScriptCache(); const hit = cache.get(key); if (hit) return hit; const res = UrlFetchApp.fetch("https://api.blinkhop.com/v1/links", { method: "post", contentType: "application/json", payload: JSON.stringify({ url: String(url) }), muteHttpExceptions: true, }); const data = JSON.parse(res.getContentText()); if (data.error) return "Error: " + data.error.message; cache.put(key, data.short_url, 21600); return data.short_url; } ``` Requests from Google Sheets come from Google's shared servers, so the per-IP limit of 20 links per minute can run out sooner than you expect. The cache above avoids calling the API again for the same URL. For large sheets, shorten in batches or use the [bulk endpoint](https://blinkhop.com/docs/api#bulk). ## Bookmarklet Shorten the page you're on with one click. Drag this button to your bookmarks bar:

Shorten with Blinkhop

It sends the current address to the API and shows the short link, ready to copy. A few sites with strict security policies block outside requests: on those, paste the link on [blinkhop.com](https://blinkhop.com/) instead. ## No-code tools Any automation tool that can send an HTTP request can shorten links: n8n (HTTP Request node), Make (HTTP module) or Zapier (Webhooks by Zapier). Use these settings: | Setting | Value | | --- | --- | | Method | `POST` | | URL | `https://api.blinkhop.com/v1/links` | | Header | `Content-Type: application/json` | | Body | `{"url": ""}` | | Result | Read `short_url` from the JSON response | These tools share their servers between many users, so add a short delay between calls if you shorten many links in a row. ## AI assistants To let Claude, ChatGPT, Cursor or VS Code shorten links for you, you don't need code at all: connect the [Blinkhop MCP server](https://blinkhop.com/mcp) with a single URL. --- # Blinkhop MCP server > A remote MCP server that lets AI assistants create and check short zou.sh links. URL: https://mcp.blinkhop.com/mcp. No account, no API key, nothing to install. Source: https://blinkhop.com/mcp ## Setup ### Claude Code 1. Run this command in your terminal. 2. Start Claude Code and type `/mcp` to check that Blinkhop is connected. ```bash claude mcp add --transport http blinkhop https://mcp.blinkhop.com/mcp ``` Add `--scope user` to make Blinkhop available in every project. ### Claude 1. In Claude (desktop or web), open Settings → Connectors. 2. Choose Add custom connector. 3. Name it Blinkhop, paste `https://mcp.blinkhop.com/mcp` as the URL, then confirm. 4. In a chat, open the tools menu and make sure Blinkhop is turned on. Custom connectors depend on your Claude plan and, in organizations, on what your admin allows. ### ChatGPT 1. In ChatGPT, open Settings → Connectors → Advanced and turn on Developer mode. 2. Create a connector named Blinkhop with the URL `https://mcp.blinkhop.com/mcp` and no authentication. 3. In a new chat, enable Developer mode and select the Blinkhop connector. Developer mode is offered on some ChatGPT plans, and menu names can change between versions. ### Cursor 1. Open or create `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project). 2. Add Blinkhop, save, and check that it shows up in Settings → MCP. File: `~/.cursor/mcp.json` ```json { "mcpServers": { "blinkhop": { "url": "https://mcp.blinkhop.com/mcp" } } } ``` ### VS Code 1. Create `.vscode/mcp.json` in your workspace. 2. Add Blinkhop, save, then start the server from the file or from the Chat view (Agent mode). File: `.vscode/mcp.json` ```json { "servers": { "blinkhop": { "type": "http", "url": "https://mcp.blinkhop.com/mcp" } } } ``` ### Windsurf 1. Open `~/.codeium/windsurf/mcp_config.json`. 2. Add Blinkhop, save, and refresh the MCP servers list in Cascade. File: `~/.codeium/windsurf/mcp_config.json` ```json { "mcpServers": { "blinkhop": { "serverUrl": "https://mcp.blinkhop.com/mcp" } } } ``` ### Other clients 1. Any MCP client that supports remote servers over Streamable HTTP can use `https://mcp.blinkhop.com/mcp` directly, with no authentication. 2. For clients that only run local (stdio) servers, bridge with the open-source `mcp-remote` package, which needs Node.js: File: `mcp config` ```json { "mcpServers": { "blinkhop": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.blinkhop.com/mcp" ] } } } ``` ## Tools ### shorten_link Creates a short zou.sh link for one URL, with an optional custom ending. The same URL always returns the same link. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | The long URL, http or https. | | `alias` | string | no | Custom ending: 3 to 40 letters, numbers, - or _. | Example request: "Make a short link to https://example.com/q3-report ending in q3-report." Example result: `https://zou.sh/q3-report → https://example.com/q3-report` ### shorten_links Shortens up to 50 URLs in one call, for example every link in a draft, a document or a spreadsheet column. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `urls` | array of strings | yes | 1 to 50 URLs. | Example request: "Shorten every link in this newsletter draft and keep the wording as is." Example result: `3 links shortened: zou.sh/T4kq9, zou.sh/m8Rzw, zou.sh/Hp2dX` ### expand_link Shows where a zou.sh link goes, when it was created and how many clicks it got. Read-only. (read-only) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `short_url` | string | yes | A zou.sh link, or just its code. | Example request: "Where does zou.sh/spring-sale go, and how many clicks does it have?" Example result: `https://zou.sh/spring-sale → https://example.com/spring (128 clicks, created 2026-10-11)` ## Example requests - Shorten every link in this email before I send it. - Turn these 12 product URLs into short links and give me a table. - Create a short link for our webinar page that ends in webinar-oct. - Check where zou.sh/launch goes before I share it. - Rewrite this post for X with short links so it fits the limit. ## Technical details - Endpoint: https://mcp.blinkhop.com/mcp - Transport: Streamable HTTP (JSON-RPC 2.0, JSON responses) - Protocol versions: 2025-06-18, 2025-03-26, 2024-11-05 - Authentication: None - Limits: 120 calls per minute and 5,000 per day, per IP address - Tools: shorten_link, shorten_links, expand_link The server is stateless, applies the same safety checks as the REST API (phishing and malware destinations are refused) and receives only tool arguments, never the conversation. Troubleshooting: https://blinkhop.com/help/mcp-troubleshooting. REST API: https://blinkhop.com/docs/api.