API reference/ip

Look up an address from the browser with a publishable key

POST/api/v1/ip/public

authentication
Publishable key in the body, checked against the browser Origin header
billing
Bills the key owner like a single lookup; capped by the key's daily credit cap when one is set.

The browser counterpart of POST /api/v1/ip, for localising a page or screening a form on the client without exposing a secret key. It is authenticated by a publishable pk_ key in the body plus the browser's Origin header, which must match one of the domains the key is locked to. A missing Origin is rejected on purpose: servers use a secret key. Leave ip out to look up the visitor's own address, which is the usual case.

The lookup bills the key's owner under the normal rules and answers { "success", "data" } with no meta block, so page visitors never see the owner's balance. Because the key sits in page source, give it a daily credit cap in the dashboard: once the cap is spent the endpoint answers 429 KEY_SPEND_CAP_REACHED until the next day. Free answers (reserved ranges, cache hits) never count toward the cap. A key that carries a Cloudflare Turnstile pair requires a confirmed turnstile_token and answers 403 TURNSTILE_FAILED without one, before any credit is spent.

Throttled at 20 requests per minute per IP.

Parameters

name in type description
OriginrequiredheaderstringSent by browsers automatically. Its host must be on the key's allowed-domain list.

Request body

field type description
keyrequiredstringA publishable key, which starts with `pk_`. At most 64 characters.
ipstring | nullThe address to look up. Omit it for the visitor's own address.
turnstile_tokenstring | nullRequired when the key carries a Turnstile pair. A single-use token from the Turnstile widget on the page. At most 2,048 characters.

Example request

curl -X POST https://spaw.co/api/v1/ip/public \
  -H "Origin: https://www.example.com" \
  -H "Content-Type: application/json" \
  -d '{
  "key": "pk_live_…"
}'
const response = await fetch('https://spaw.co/api/v1/ip/public', {
  method: 'POST',
  headers: {
    'Origin': 'https://www.example.com',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "key": "pk_live_…"
  }),
});
const result = await response.json();
import requests

response = requests.post(
    'https://spaw.co/api/v1/ip/public',
    headers={'Origin': 'https://www.example.com'},
    json={
        'key': 'pk_live_…'
    },
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/ip/public');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => ['Origin: https://www.example.com', 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'key' => 'pk_live_…'
    ]),
]);
$result = json_decode(curl_exec($ch), true);

Responses

200The same fields as `POST /api/v1/ip`, without a meta block.

{
    "success": true,
    "data": {
        "ip": "203.0.113.9",
        "version": 4,
        "reason": null,
        "country": "DE",
        "country_name": "Germany",
        "is_eu": true,
        "city": "Berlin",
        "timezone": "Europe/Berlin",
        "currency": "EUR",
        "is_anonymous": false,
        "risk_score": 0,
        "risk_level": "low"
    }
}

401The publishable key does not exist or was revoked.

{
    "success": false,
    "error": {
        "code": "INVALID_PUBLISHABLE_KEY",
        "message": "That publishable key does not exist or was revoked.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

402The balance is empty. The lookup did not run.

{
    "success": false,
    "error": {
        "code": "INSUFFICIENT_CREDITS",
        "message": "Your credit balance is empty.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

403The page's origin is not on the key's allowed-domain list (`ORIGIN_NOT_ALLOWED`), or the key requires a Turnstile token that was missing or not confirmed (`TURNSTILE_FAILED`).

{
    "success": false,
    "error": {
        "code": "ORIGIN_NOT_ALLOWED",
        "message": "This publishable key cannot be used from this origin.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

422The request body could not be validated; `error.errors` lists the fields.

{
    "success": false,
    "error": {
        "code": "VALIDATION_FAILED",
        "message": "The email field is required.",
        "errors": {
            "email": [
                "The email field is required."
            ]
        },
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

429The key has spent its owner's daily credit cap (`KEY_SPEND_CAP_REACHED`), or the IP exceeded 20 requests per minute (`RATE_LIMITED`).

{
    "success": false,
    "error": {
        "code": "KEY_SPEND_CAP_REACHED",
        "message": "This publishable key has reached its daily credit cap. The counter resets each day.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

Error codes

Failures answer { success: false, error: { code, message, request_id } }. Each code has its own page.

markdown version·openapi.json