- 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 |
|---|---|---|---|
| Originrequired | header | string | Sent by browsers automatically. Its host must be on the key's allowed-domain list. |
Request body
| field | type | description |
|---|---|---|
| keyrequired | string | A publishable key, which starts with `pk_`. At most 64 characters. |
| ip | string | null | The address to look up. Omit it for the visitor's own address. |
| turnstile_token | string | null | Required 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_…"
}'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.