17NAS DEVELOPER API

IP Lookup API

One request gives you the caller's real IP, whether it came over IPv4 or IPv6, the country, ASN, network operator and time zone.

Our own IPv6 test tool runs on this very edge network — 171,719 tests have been completed here so far, and every one of them went through the same path this API uses.

Endpoint
ipapi.17nas.com
Authentication
Bearer API Key
Output format
JSON / Text
curl
curl https://ipapi.17nas.com/v1/ip \
  -H "Authorization: Bearer 17nas_live_xxx"
{
  "ip": "203.0.113.10",
  "protocol": "IPv4",
  "country": { "code": "CN" },
  "asn": { "number": 4134 },
  "edge": { "colo": "HKG" }
}
Tests run
171,719
Unique IPs
53,029
Last 30 days
26,582
Serving from
Global edge network
LIVE DEMO

No sign-up — see it work first

One click calls the public endpoint from your current network and shows the raw, unedited response. Note this demo hits the public endpoint; its field layout differs from the keyed /v1/ip, which is described in the reference below.

GET https://ip.17nas.com?format=json
Click the button above and the real response will appear here.
That was the public endpoint — fine for one-off checks and manual troubleshooting.

The moment you wire it into your own site, app, mini program or server-side script, you need an API key. The public endpoint has no dedicated quota, no usage stats, no error log and no IP allowlist — it can be throttled at any time because of abuse, and when something breaks there is nothing for you to look at.

Claim 1,000 free calls a month
USE CASES

What people build with it

Every snippet below runs as-is — just swap 17nas_live_xxx for your own key.

Update DDNS the moment your home IP changes

Home broadband IPs change without warning, and your NAS hostname ends up pointing at an address that no longer exists. Check every five minutes from cron and only call your DNS provider when the address actually changed — less noise, and you won't burn through the provider's API quota.

ddns-update.sh
#!/bin/bash
# crontab: */5 * * * * /opt/ddns-update.sh
KEY="17nas_live_xxx"
CACHE="/tmp/.last_wan_ip"

IP=$(curl -fsS --max-time 10 "https://ipapi.17nas.com/v1/ip?format=text" \
      -H "Authorization: Bearer $KEY") || exit 0

# Nothing returned, or same as last time: bail out and leave the DNS provider alone
[ -z "$IP" ] && exit 0
[ "$IP" = "$(cat "$CACHE" 2>/dev/null)" ] && exit 0

echo "$IP" > "$CACHE"
logger -t ddns "WAN IP changed to $IP"

# Replace with your own DNS provider's API
curl -fsS -X POST "https://dns.example.com/api/record" \
  -d "name=nas.example.com&type=A&value=$IP"

Get pinged when your public IP changes

You don't want anything rewriting DNS automatically — you just want to know when your IP moved. Push a message the moment it changes, with the operator and location attached. Handy for spotting when your ISP shuffles address pools.

wan_watch.py
import json, pathlib, requests

KEY = "17nas_live_xxx"
PUSH = "https://www.pushplus.plus/send"   # Swap in your own push channel
TOKEN = "your_pushplus_token"
CACHE = pathlib.Path("/var/tmp/wan_ip.json")

r = requests.get(
    "https://ipapi.17nas.com/v1/ip",
    headers={"Authorization": f"Bearer {KEY}"},
    timeout=10,
)
r.raise_for_status()
now = r.json()

# Remaining quota comes back in the headers — no extra call needed
print("Calls left this month:", r.headers.get("X-Quota-Remaining"))

last = json.loads(CACHE.read_text()) if CACHE.exists() else {}
if last.get("ip") == now["ip"]:
    raise SystemExit(0)

CACHE.write_text(json.dumps(now))
requests.post(PUSH, json={
    "token": TOKEN,
    "title": f"Public IP changed: {now['ip']}",
    "content": (
        f"Protocol {now['protocol']}\n"
        f"Operator {now['asn']['organization']} (AS{now['asn']['number']})\n"
        f"Location {now.get('city') or '-'} / {now['country']['code']}\n"
        f"Previous IP {last.get('ip', 'none on record')}"
    ),
}, timeout=10)

Flag logins from unfamiliar places

Record the location and ASN of the IP at login time. If the next sign-in comes from a different region, or jumps from a home line to a datacenter address, ask for a second factor. Far more reliable than storing the raw IP — home IPs change daily, but the region and operator don't.

login_risk.php
<?php
/** Call after a successful login; returns whether a second factor is needed */
function login_needs_verify(PDO $pdo, int $userId): bool
{
    $ch = curl_init('https://ipapi.17nas.com/v1/ip');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 5,
        CURLOPT_HTTPHEADER => ['Authorization: Bearer 17nas_live_xxx'],
    ]);
    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    // If the lookup is down, don't lock users out — fail open
    if ($status !== 200 || !$body) {
        error_log('ip-api unavailable, skip risk check');
        return false;
    }

    $now = json_decode($body, true);
    $fingerprint = $now['country']['code'] . '|' . $now['region'] . '|' . $now['asn']['number'];

    $stmt = $pdo->prepare('SELECT ip_fingerprint FROM user_login_env WHERE user_id = ?');
    $stmt->execute([$userId]);
    $known = $stmt->fetchColumn();

    $pdo->prepare('REPLACE INTO user_login_env (user_id, ip_fingerprint, updated_at) VALUES (?, ?, NOW())')
        ->execute([$userId, $fingerprint]);

    return $known !== false && $known !== $fingerprint;
}

Pick the origin based on IPv4 vs IPv6

In a dual-stack setup, a visitor arriving over IPv6 while your backend is IPv4-only pays tens of milliseconds for the translation hop. Check the protocol first, then choose the origin path — or the asset URLs — that avoids it.

origin-select.js
const KEY = process.env.IP_API_KEY;

async function pickOrigin() {
  const res = await fetch('https://ipapi.17nas.com/v1/ip', {
    headers: { Authorization: `Bearer ${KEY}` },
    signal: AbortSignal.timeout(5000),
  });

  if (res.status === 429) {
    // Quota exhausted or rate limited — X-Quota-Reset says when it comes back
    console.warn('quota/rate limited, reset at', res.headers.get('X-Quota-Reset'));
    return ORIGINS.v4;   // Fall back to the default origin instead of throwing
  }
  if (!res.ok) return ORIGINS.v4;

  const { protocol, edge } = await res.json();
  console.log('edge colo:', edge.colo);
  return protocol === 'IPv6' ? ORIGINS.v6 : ORIGINS.v4;
}

const ORIGINS = {
  v4: 'https://origin-v4.example.com',
  v6: 'https://origin-v6.example.com',
};

Audit logs for apps and mini programs

App stores and mini-program platforms require you to retain user action logs, and the IP plus its location are mandatory fields. The critical part: call this server-side. Shipping the key inside a client bundle hands your quota to everyone who downloads it.

audit_log.php
<?php
/**
 * Write one compliance log entry, server-side.
 * Note: the key belongs on the server only. The app or mini program calls your
 * own server, and that server calls 17NAS. The client never sees the key.
 */
function write_audit_log(PDO $pdo, int $userId, string $action): void
{
    $ctx = ['ip' => null, 'country' => null, 'isp' => null];

    $ch = curl_init('https://ipapi.17nas.com/v1/ip');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 5,
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('IP_API_KEY')],
    ]);
    if ($body = curl_exec($ch)) {
        $data = json_decode($body, true);
        $ctx = [
            'ip' => $data['ip'] ?? null,
            'country' => $data['country']['code'] ?? null,
            'isp' => $data['asn']['organization'] ?? null,
        ];
    }
    curl_close($ch);

    $pdo->prepare(
        'INSERT INTO audit_logs (user_id, action, ip, country, isp, created_at)
         VALUES (?, ?, ?, ?, ?, NOW())'
    )->execute([$userId, $action, $ctx['ip'], $ctx['country'], $ctx['isp']]);
}

Audit the egress IP of every uplink

You have a pile of VPSes, proxies or bonded lines and want to confirm the real egress IP and landing site of each one. Walk through them in a loop and anything routed wrong shows up immediately.

egress-check.sh
#!/bin/bash
KEY="17nas_live_xxx"
IFACES=("eth0" "eth1" "wg0")

printf "%-8s %-40s %-10s %s\n" "IFACE" "EGRESS IP" "PROTO" "OPERATOR"
for IF in "${IFACES[@]}"; do
  JSON=$(curl -fsS --max-time 8 --interface "$IF" \
          "https://ipapi.17nas.com/v1/ip" \
          -H "Authorization: Bearer $KEY" 2>/dev/null)

  if [ -z "$JSON" ]; then
    printf "%-8s %-40s %-10s %s\n" "$IF" "(no route)" "-" "-"
    continue
  fi

  printf "%-8s %-40s %-10s %s\n" \
    "$IF" \
    "$(echo "$JSON" | jq -r .ip)" \
    "$(echo "$JSON" | jq -r .protocol)" \
    "$(echo "$JSON" | jq -r .asn.organization)"
done
COMPARISON

Should you just build it yourself?

Returning a real IP is the easy part; keeping it running for years is not. Here is what each of the three routes actually costs you.

Dimension Self-hosted Overseas service 17NAS
Latency from mainland ChinaDepends on your serverCross-border, unstableAnswered at the nearest edge
IPv6 supportYou configure dual-stackMostly supportedNative dual-stack
GeoIP / ASN database updatesYou download and refresh itHandled by the providerHandled by the provider
Day-to-day upkeepCertificates, hosts, monitoring — all yoursNoneNone
Usage stats and error logsYou build itIncludedBuilt into the console
Payment methods—Usually credit card onlyAlipay
Who you call when it breaksYourselfEnglish tickets, different time zoneChinese support, same time zone as CN
Cost to startA server plus your timePay per request1,000 free calls a month

To be fair: if this is a hobby project that fires a handful of requests a day, returning $remote_addr from your own Nginx is plenty — don't spend the money. Paying makes sense when someone comes looking for you if it breaks: production traffic, retained logs, automation that has to keep running for years.

PLANS

API plans

When the quota runs out, calls simply stop. Nothing goes into arrears and nothing is charged automatically. Annual plans release their quota month by month.

Free with an account — no payment

A registered developer account gets 1 API key(s) and 1,000 calls a month, shared across IPv4 and IPv6 — enough to finish your integration before deciding whether to pay. Personal development and testing only; pick a plan below for commercial use.

Create a free API key

API Developer

¥19.9/mo
  • 100,000 calls a month
  • 5 QPS
  • 1 API key(s)
  • No IP allowlist
  • 7 days of error logs
  • Commercial use allowed

API Business

¥299.0/mo
  • 5,000,000 calls a month
  • 50 QPS
  • 5 API key(s)
  • 100 IP/CIDR allowlist entries
  • 90 days of error logs
  • Commercial use allowed

Enterprise

¥999/mo and up
  • Custom quota and concurrency
  • Contract and dedicated SLA
  • Dedicated technical support
  • On-premises deployment available
Contact us
Running low? You don't have to upgrade

If you already have a paid plan you can top up at any time with a one-off booster pack: ¥14.90 for 100,000 calls, ¥59.90 for 1,000,000 calls. Boosters stack on top of the current period's quota, expire with it, and never auto-renew. Buy one in the console.

Payment methods currently accepted: Stripe. Enterprise contracts with an SLA are handled separately from the standard commercial plans.

DATA

While we're here: how far has IPv6 actually got?

Based on 171,719 real tests run by 17NAS visitors since Feb 5, 2026, across 53,029 unique IPs. Those tests ran on the exact edge network this page is selling.

Dual stack (IPv4 + IPv6)The ideal case — both protocols work
61.2%102,604
IPv4 onlyIPv6 not enabled, or blocked by the router or firewall
35.8%60,103
IPv6 onlyUncommon — some mobile networks and IPv6-only setups
1.4%2,292
No resultFailed on a weak link, a timeout, or behind a proxy
1.6%2,763

In total 62.5% of visitors are IPv6-capable. One caveat worth stating plainly: anyone who goes out of their way to test IPv6 on 17NAS already cares about IPv6 more than the average user, so this sample carries a self-selection bias. Do not read it as a nationwide adoption rate — it describes the people who care about IPv6, nothing more. Refreshed hourly. Want to check your own network? the IPv6 test tool is here.

REFERENCE

API reference

Start with the three steps below — five minutes to your first successful request. The details follow.

  1. 1
    Create an API key

    Sign in and click "Create API key" in the console. The secret is shown in full exactly once — the server only stores a hash, so if you lose it you have to generate a new one.

    Open the console
  2. 2
    Send a request with a Bearer header

    Put the key in the Authorization header and call /v1/ip.

    See the example
  3. 3
    Read the quota from the headers

    Every successful response carries X-Quota-Remaining, so there is no separate usage endpoint to call.

    See the header reference

Authentication

Send the Bearer key in the Authorization header of a server-side request.

Authorization: Bearer 17nas_live_xxxxxxxxx

Never put the key in browser scripts, app bundles or mini-program packages — all of those can be decompiled. When the front end needs the data, have it call your own server and let the server make the request. You can tighten this further with an IP/CIDR allowlist in the console.

GET/v1/ip

ipapi.17nas.com

Returns the IP and protocol actually used by this HTTP connection. If the request came over IPv4 you get IPv4; over IPv6 you get IPv6. Nothing to specify.

ParameterInDescription
AuthorizationHeaderRequired. Bearer <your API key>
formatQueryOptional. json (default) or text. text returns the bare IP, handy for shell scripts
# JSON (default)
curl https://ipapi.17nas.com/v1/ip \
  -H "Authorization: Bearer 17nas_live_xxx"

# Bare IP as plain text — drop it straight into a shell variable
curl "https://ipapi.17nas.com/v1/ip?format=text" \
  -H "Authorization: Bearer 17nas_live_xxx"

# Force IPv4 or IPv6 with curl's own flags
curl -4 https://ipapi.17nas.com/v1/ip -H "Authorization: Bearer 17nas_live_xxx"
curl -6 https://ipapi.17nas.com/v1/ip -H "Authorization: Bearer 17nas_live_xxx"

Response fields

200 OK
{
  "ip": "203.0.113.10",
  "protocol": "IPv4",
  "country": { "code": "CN" },
  "region": "Guangdong",
  "city": "Shenzhen",
  "timezone": "Asia/Shanghai",
  "asn": { "number": 4134, "organization": "CHINANET" },
  "edge": { "colo": "HKG" },
  "request_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "generated_at": "2026-07-26T09:00:00.000Z"
}
FieldTypeDescription
ipstringThe public IP of this connection
protocolstringIPv4 or IPv6
country.codestringTwo-letter ISO 3166-1 country code; null when unknown
region / citystringRegion/state and city, in English; accuracy depends on the edge database and may be null
timezonestringIANA time zone name, e.g. Asia/Shanghai
asn.numbernumberAutonomous system number; 0 when unknown
asn.organizationstringNetwork operator name; may be null
edge.colostringThree-letter code of the edge node that served this request
request_idstringUnique ID for this request — include it when reporting a problem

Location fields come from the edge node's GeoIP data and can be wrong or empty on mobile networks, satellite links, corporate NAT egress and some proxies. Treat them as a hint, not a fact — don't use them for hard geographic blocking.

Quota headers

Every successful response carries the current quota state, so there is no separate endpoint to poll.

Response headerDescription
X-RateLimit-LimitRequests allowed per second (QPS)
X-RateLimit-RemainingHow many are left in the current second
X-RateLimit-ResetUnix timestamp when the rate-limit window resets
X-Quota-LimitTotal quota for this period
X-Quota-UsedUsed so far this period
X-Quota-RemainingRemaining this period
X-Quota-ResetWhen this period's quota resets
X-Request-IdID of this request — include it when reporting a problem

Watch X-Quota-Remaining in your code and alert below 10% — don't wait until calls start failing to notice.

Error statuses

401 Key invalid, revoked, or malformed 403 Source IP is not on the allowlist 429 Over the QPS limit, or this period's quota is spent 503 Temporarily unavailable — retry shortly
{
  "error": {
    "code": "quota_exhausted",
    "message": "Monthly quota exhausted",
    "request_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "docs_url": "https://17nas.com/ip-api.php#documentation",
    "upgrade_url": "https://17nas.com/ip-api.php?source=api-error&placement=quota-exhausted#pricing",
    "reset_at": "2026-08-01T00:00:00+08:00"
  }
}

429 has two causes, told apart by the headers. X-RateLimit-Remaining at 0 is a momentary rate limit — wait a second and carry on. X-Quota-Remaining at 0 means the period's quota is spent — wait for reset_at, or top up or upgrade.

Retries and fallbacks

This is a network call, so it will fail sometimes. A few practical rules:

  • Set a timeout under 5 seconds so it can't drag down your main flow
  • Retry on 503 or timeout with exponential backoff (1s, 2s, 4s), at most three times
  • Never retry 401 / 403 — those are configuration problems, and retrying just burns quota
  • If a 429 means the quota is spent, retrying is pointless — go straight to your fallback path
  • On non-critical paths (logging, for instance) just skip it — don't block a legitimate user action because a location lookup failed
  • Cache the answer locally for a few minutes — one server's egress IP doesn't change second by second
FAQ

Things you might want to ask before buying

Can I use the free quota commercially?

No. The free 1,000 calls a month are for personal development and testing only. Anything that serves other people — your website, app, mini program, or a system you deliver to a client — needs a commercial plan. Paid plans show "Commercial use allowed" in the console.

What happens when the quota runs out? Will I be charged automatically?

No. Once the quota is gone the endpoint returns 429 and stops serving. Nothing goes into arrears and nothing is charged automatically. You can wait for the next period to reset, or buy a booster pack and carry on immediately. We deliberately don't do pay-as-you-go, so a runaway loop in your code can never hand you a huge bill.

Does an annual plan hand over the whole year's quota at once?

No — it is released month by month. A yearly API Pro gives you 1,000,000 calls each month for twelve months, not 12,000,000 up front. That protects you from a runaway integration draining the whole year in week one. Unused calls do not roll over.

How soon can I use it after paying?

The new quota reaches every edge node within a minute of a successful payment. Calls made before the sync finishes still count against your old quota, so nothing is lost. When the order shows "Completed" in the console, it is live.

Do you offer refunds?

API quota is a digital good, so once it has been issued we generally can't refund it. That said, if the service was unavailable because of something on our side, or your quota never arrived after payment, bring your order number to the feedback page and we'll work through it case by case. We'd rather you finish your integration on the free tier before paying.

What if my API key leaks?

Revoke it in the console straight away and create a new one; the revocation reaches the edge within a minute. Better still, set up an IP/CIDR allowlist ahead of time — then even a leaked key is useless to anyone outside your server's address.

Can I upgrade mid-term?

Yes. Click "Upgrade plan" in the console and you only pay the difference; the expiry date stays the same. Upgrades keep the current billing cycle (monthly to monthly, annual to annual). To switch from monthly to annual, choose annual at renewal.

Where is the data stored? Do you log what I look up?

We keep aggregate usage statistics only — hourly success/failure counts and average latency — plus an error log. Source IPs in the error log are stored redacted, and retention depends on your plan (from 7 days). We do not store the actual IP data returned by your lookups.

How is availability guaranteed?

The endpoint runs on a global edge network: requests are answered by the node nearest the caller, with no fall-back to a single origin server, so a node failing in one region never takes the whole service down. If you need a contractual SLA, choose the enterprise plan.

Still on the fence?

Then finish the integration on the free tier first. Decide about paying once it works — it costs you nothing to find out.