> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/reference/api-endpoints.md).

# API Endpoints

{% hint style="warning" %}
**This is not a public API.** These routes exist to serve MyIP's own frontend. They are gated by a `Referer` check, their shapes change without notice, and there is no versioning, deprecation policy, or stability contract. Do not build integrations against another instance's `/api/*`. This page documents them so that **you can operate and debug your own deployment**.
{% endhint %}

Every route is defined in `backend-server.js` and mounted on the backend server (`BACKEND_PORT`, default `11966`). In production, `frontend-server.js` proxies `/api` from `FRONTEND_PORT` (default `18966`) to the backend, so from outside the deployment everything lives under `/api` on one origin. See [Backend](/developer/architecture/backend.md).

## Global middleware

These apply to **every** `/api/*` route, in mount order.

| Order | Middleware                         | Behaviour                                                                                                                                                 |
| ----- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1     | `pino-http`                        | Only when `LOG_HTTP=true`. Logs method, URL and status. Mounted before the limiters so 429s are logged.                                                   |
| 2     | `express-rate-limit`               | Only when `SECURITY_RATE_LIMIT` is non-zero. Max N requests per IP per 20-minute window → `429 {"message":"Too Many Requests"}`. Skips `/api/monitoring`. |
| 3     | `express-slow-down`                | Only when `SECURITY_DELAY_AFTER` is non-zero. After N requests per IP per 60-minute window, adds `hits × 400 ms` of delay. Skips `/api/monitoring`.       |
| 4     | `express.json({ limit: '500kb' })` | JSON body parsing. Bodies above 500 KB get a raw `413`.                                                                                                   |
| 5     | `Cache-Control: no-store`          | **Default for every route.** Routes that want edge caching override it explicitly.                                                                        |
| 6     | `requireReferer`                   | Rejects any request whose `Referer` hostname is not `localhost` or in `ALLOWED_DOMAINS`.                                                                  |

### Referer gate

`requireReferer` is the first thing every request meets. Matching is exact hostname matching against `['localhost', ...ALLOWED_DOMAINS.split(',')]`.

| Condition                                  | Response                              |
| ------------------------------------------ | ------------------------------------- |
| No `Referer` header at all                 | `403 {"error":"What are you doing?"}` |
| `Referer` present but hostname not allowed | `403 {"error":"Access denied"}`       |
| `Referer` unparseable as a URL             | `403 {"error":"Access denied"}`       |

This is why `curl http://your-host:18966/api/configs` always 403s — curl sends no `Referer`. See [Security Options](/developer/configuration/security-options.md).

### Caching

The `cacheable(seconds)` middleware factory wraps `res.json` and sets `Cache-Control: public, max-age=<seconds>` **only on 2xx responses**, so errors are never cached at the edge. It also stashes the value on `res.locals.cacheControl` for handlers that stream binary and bypass `res.json` (only `/api/map` does).

Anything not marked cacheable inherits the global `no-store` default.

### Guards

Guards live in `common/guards.js` and are applied per route. All of them reject with `400` and a JSON `error` string.

| Guard                      | Reads             | Validates                                                                                 | Errors                                            |
| -------------------------- | ----------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------- |
| `requireValidIP()`         | `?ip`             | Valid IPv4 or IPv6                                                                        | `No IP address provided` / `Invalid IP address`   |
| `requireValidDomain()`     | `?domain`         | Syntactic domain; lowercases the value in place so the edge cache sees one canonical form | `No domain provided` / `Invalid domain`           |
| `requireValidPrefix()`     | `?prefix`         | Well-formed CIDR (any length — the frontend decides quantization)                         | `No prefix provided` / `Invalid prefix`           |
| `requireValidASN()`        | `?asn`            | Numeric, optional `AS` prefix; strips the prefix in place                                 | `No ASN provided` / `Invalid ASN`                 |
| `requireValidProviderId()` | `?id`             | Membership in the service-status provider slug allowlist                                  | `No provider id provided` / `Invalid provider id` |
| `requireValidReportId()`   | route param `:id` | Exactly 22 base64url chars (16 random bytes)                                              | `Invalid report id`                               |

## IP geolocation

All of these return the same normalized shape (`ip`, `city`, `region`, `country`, `country_name`, `country_code`, `latitude`, `longitude`, `asn`, `org`), produced by the `makeGeoHandler` factory in `common/geo-handler.js`. See [IP Data Sources](/developer/architecture/ip-data-sources.md).

| Route                  | Params                      | Guard · Cache               | Purpose                                                                                                                                                 |
| ---------------------- | --------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/ipinfo`      | `ip`                        | `requireValidIP` · 1 day    | Geolocation via ipinfo.io. `IPINFO_API_KEY` optional.                                                                                                   |
| `GET /api/ipapicom`    | `ip`, `lang` (default `en`) | `requireValidIP` · 1 day    | Geolocation via ip-api.com. No key.                                                                                                                     |
| `GET /api/ipsb`        | `ip`                        | `requireValidIP` · 1 day    | Geolocation via api.ip.sb. No key.                                                                                                                      |
| `GET /api/ipapiis`     | `ip`                        | `requireValidIP` · 1 day    | Geolocation via api.ipapi.is; also returns `isHosting` / `isProxy`. Needs `IPAPIIS_API_KEY`.                                                            |
| `GET /api/ip2location` | `ip`                        | `requireValidIP` · 1 day    | Geolocation via ip2location.io. Needs `IP2LOCATION_API_KEY`.                                                                                            |
| `GET /api/maxmind`     | `ip`, `lang` (default `en`) | `requireValidIP` · 1 day    | Local GeoLite2 City + ASN lookup. Needs MaxMind credentials or pre-seeded `.mmdb`; **503** when the databases are not loaded.                           |
| `GET /api/ipchecking`  | `ip`, `lang` (default `en`) | `requireValidIP` · no-store | Geolocation via the private IPCheck.ing API. Needs `IPCHECKING_API_KEY` + `IPCHECKING_API_ENDPOINT`; `500 {"error":"API key is missing"}` without them. |

`lang` on `/api/maxmind` is validated against `['zh-CN', 'en', 'fr', 'ru']`; anything else falls back to `en`. `lang` on `/api/ipapicom` and `/api/ipchecking` is forwarded to the upstream without validation.

Upstream failures on the geo handlers return `500 {"error": "<message>"}`.

## Network tools

| Route                                 | Params                                                                                    | Guard · Cache                | Purpose                                                                                                                                                                                                    |
| ------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/whois`                      | `q` (IP or domain)                                                                        | inline · 1 day               | WHOIS / RDAP lookup. IPs go to RDAP first with `whoiser` as fallback.                                                                                                                                      |
| `GET /api/dnsresolver`                | `hostname`, `type`                                                                        | inline · no-store            | Resolves a hostname across several plain-DNS and DoH resolvers in parallel, for contamination comparison.                                                                                                  |
| `GET /api/dnsleaktest/session/:token` | route `:token` (32 hex chars), `lang` (default `zh-CN`)                                   | inline · no-store            | Fetches an enhanced DNS-leak session result. Forwards request headers (including `Authorization`) and passes the upstream status through verbatim. Needs `IPCHECKING_API_KEY` + `IPCHECKING_API_ENDPOINT`. |
| `GET /api/ooni-blocking`              | `domain`                                                                                  | `requireValidDomain` · 1 day | OONI censorship aggregate over a 30-day UTC window; queries both the apex and the `www.` variant and merges.                                                                                               |
| `GET /api/globalping-probes`          | —                                                                                         | — · 7 days                   | Compact country inventory of online Globalping probes, for the MTR / latency / censorship country pickers.                                                                                                 |
| `GET /api/macchecker`                 | `mac` (exactly 12 hex chars after stripping `:` and `-`)                                  | inline · 30 days             | IEEE OUI vendor lookup via maclookup.app. `MAC_LOOKUP_API_KEY` optional.                                                                                                                                   |
| `GET /api/map`                        | `latitude`, `longitude`, `language` (2 letters), `CanvasMode` (`Dark` for the dark style) | inline · 1 year              | Proxies a Google Static Maps JPEG. **Returns binary**, not JSON. Needs `GOOGLE_MAP_API_KEY`.                                                                                                               |
| `GET /api/invisibility`               | `id` (28 alphanumeric chars)                                                              | inline · no-store            | Polls the proxy-detection result. An upstream 404 is translated to `200 {"status":"pending"}`. Needs `IPCHECKING_API_KEY` + `IPCHECKING_API_ENDPOINT`.                                                     |

## ASN & BGP

| Route                       | Params          | Guard · Cache                  | Purpose                                                                                                                                                                                                    |
| --------------------------- | --------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/cfradar`          | `asn`           | inline · 30 days               | Cloudflare Radar traffic / adoption segments for an ASN. Partial segment failures are logged and served; a total failure returns 500. Needs `CLOUDFLARE_API_KEY`.                                          |
| `GET /api/asn-history`      | `prefix` (CIDR) | `requireValidPrefix` · 30 days | Historical BGP announcements for a prefix, from RIPEstat routing-history, with relative visibility percentages. Upstream non-2xx returns `502 {"error":"Upstream error"}`. `RIPESTAT_SOURCE_APP` optional. |
| `GET /api/asn-connectivity` | `asn`           | `requireValidASN` · 30 days    | Upstream topology graph from an ASN toward the Tier-1 backbones, built from the local CAIDA as-rel snapshot.                                                                                               |

## Service status

Both handlers read an in-memory snapshot maintained by a background poller on a fixed 5-minute schedule. Neither touches an upstream at request time, so request volume never affects upstream load.

| Route                            | Params               | Guard · Cache                    | Purpose                                                          |
| -------------------------------- | -------------------- | -------------------------------- | ---------------------------------------------------------------- |
| `GET /api/service-status`        | —                    | — · 5 min                        | Overview: one status light per provider, no heavy detail arrays. |
| `GET /api/service-status/detail` | `id` (provider slug) | `requireValidProviderId` · 5 min | One provider's sub-components plus recent incidents.             |

## Platform

| Route                            | Params                                              | Guard · Cache                          | Purpose                                                                                                          |
| -------------------------------- | --------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `GET /api/configs`               | —                                                   | — · 1 hour                             | Feature flags for the frontend. Returns **booleans only**, never key values.                                     |
| `GET /api/github-stars`          | —                                                   | — · 1 day                              | Stargazer count for `jason5ng32/MyIP`, fetched unauthenticated.                                                  |
| `GET /api/getuserinfo`           | — (forwards request headers, incl. `Authorization`) | — · no-store                           | Signed-in user profile from the companion API. Needs `IPCHECKING_API_KEY` + `IPCHECKING_API_ENDPOINT`.           |
| `PUT /api/updateuserachievement` | JSON body                                           | — · no-store                           | Records an achievement unlock. Needs `IPCHECKING_API_KEY` + `IPCHECKING_API_ENDPOINT`.                           |
| `POST /api/report`               | JSON body `{ report, ttlDays }`                     | schema whitelist + size cap · no-store | Stores a shareable diagnostic report in Workers KV and returns its id. Needs all three `CLOUDFLARE_*` variables. |
| `GET /api/report/:id`            | route `:id` (22 chars)                              | `requireValidReportId` · no-store      | Reads a stored report for the read-only `/r/:id` page. Needs the same three `CLOUDFLARE_*` variables.            |
| `POST /api/monitoring`           | raw envelope body (max 10 MB)                       | own limiter · no-store                 | First-party Sentry tunnel. **Mounted only when `VITE_SENTRY_DSN_FRONTEND` is set.**                              |

### `/api/configs` response

Every field is a boolean. `originalSite` is `true` only when the request's `Referer` hostname is one of the canonical IPCheck.ing hostnames.

{% code title="GET /api/configs" %}

```json
{
  "map": false,
  "ipInfo": false,
  "ipChecking": false,
  "ip2location": false,
  "originalSite": false,
  "cloudFlare": false,
  "ipapiis": false,
  "reportSharing": false
}
```

{% endcode %}

### Report sharing status codes

| Code  | Meaning                                                                                  |
| ----- | ---------------------------------------------------------------------------------------- |
| `503` | The three `CLOUDFLARE_*` variables are not all set.                                      |
| `400` | The report body failed schema validation (`{"error":"Invalid report","details":[...]}`). |
| `413` | Serialized report exceeds 256 KB.                                                        |
| `404` | On `GET`: report not found or its KV TTL expired.                                        |

`ttlDays` must be `1`, `3` or `7`; any other value is silently forced down to `1`.

### `/api/monitoring`

Mounted only when `VITE_SENTRY_DSN_FRONTEND` is set on the **backend process**. It has a dedicated limiter of 600 requests per IP per 20-minute window and is explicitly skipped by both global limiters — telemetry sharing the app quota is how error reporting silently dies. The body is parsed with `express.raw` using a catch-all type matcher, because Sentry Replay envelopes are sent with no `Content-Type` at all.

## Cache TTL summary

| TTL        | Routes                                                                                                                                                                                                                 |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 5 minutes  | `/api/service-status`, `/api/service-status/detail`                                                                                                                                                                    |
| 1 hour     | `/api/configs`                                                                                                                                                                                                         |
| 1 day      | `/api/ipinfo`, `/api/ipapicom`, `/api/ipsb`, `/api/ipapiis`, `/api/ip2location`, `/api/maxmind`, `/api/whois`, `/api/github-stars`, `/api/ooni-blocking`                                                               |
| 7 days     | `/api/globalping-probes`                                                                                                                                                                                               |
| 30 days    | `/api/cfradar`, `/api/asn-history`, `/api/asn-connectivity`, `/api/macchecker`                                                                                                                                         |
| 1 year     | `/api/map`                                                                                                                                                                                                             |
| `no-store` | everything else — `/api/ipchecking`, `/api/dnsresolver`, `/api/dnsleaktest/session/:token`, `/api/invisibility`, `/api/getuserinfo`, `/api/updateuserachievement`, `/api/report`, `/api/report/:id`, `/api/monitoring` |

TTLs are picked against each upstream's natural refresh cadence. Shared reports stay `no-store` deliberately: an edge cache could serve a report past its KV expiry, and private diagnostic data does not belong in a public cache.

## Non-`/api` routes

`frontend-server.js` serves everything else: the static `dist/` output with per-asset-class `Cache-Control`, and an SPA history fallback that returns `index.html` (with `no-store`) for GET navigations whose final path segment has no file extension.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/reference/api-endpoints.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
