Server API
Use the client SDK's device_token to query the device fingerprint and obtain device identification, device information, and risk signals. If client reporting fails, submit the raw SDK geetoken through the same endpoint for fallback analysis. See fp_query fallback mode.
Interface
| Method | Path | Purpose |
|---|---|---|
POST | /api/v2/fp_query/{public_key} | Query device fingerprint |
Where to get input from
| Field | Used in | Where to get |
|---|---|---|
public_key | Path /api/v2/fp_query/{public_key} | Public application identifier from the console, used to locate the application and Region. It is not an authentication key. |
secret | Authorization: Bearer <secret> | Server API authentication secret from the console. |
device_token | Request body | Returned by the client SDK. |
Supported clients
iOS, Android, and Web/H5.
Regional restrictions
Each application belongs to exactly one Region, configured in the dashboard. Send the request to the regional API endpoint for the application's Region.
| Region | Description | API endpoint (HTTPS) |
|---|---|---|
GLB | Global | https://riskct-global.geelabapi.com |
NA | North America | https://riskct-na.geelabapi.com |
EU | Europe | https://riskct-eu.geelabapi.com |
Requests sent to a node in another Region return 404 with message: "integration not found".
Response envelope
Use Content-Type: application/json. The HTTP status code and JSON code have the same value.
Success:
{ "code": 200, "data": {}, "message": "Success" }Failure:
{ "code": 400, "data": {}, "message": "Invalid request" }When validation fails, data contains:
{
"errors": [
{ "field": "device_token", "reason": "Field required", "type": "missing" }
]
}Error code
HTTP / code | message | Meaning |
|---|---|---|
| 400 | Invalid request | The request body is invalid, or device_token is invalid |
| 401 | Invalid bearer token | Authentication failed; check the server-side secret |
| 403 | integration_disabled | The integration is not enabled or has been deleted |
| 403 | application_disabled | The application is not enabled or has been deleted |
| 403 | ip_not_allowed | The server IP allowlist is enabled, but the caller's IP address is not on the list, the list is empty, or a valid caller IP address could not be obtained |
| 403 | degrade_disabled | A raw SDK token was submitted, but fallback mode is disabled |
| 403 | platform_mismatch | The client type submitted by the SDK does not match the integration platform, or the client type is missing or unrecognized |
| 403 | bundle_id_mismatch | The iOS Bundle ID failed the integration check; this check applies to both client reporting and fallback queries |
| 403 | package_name_mismatch | The Android package name failed the integration check; this check applies to both client reporting and fallback queries |
| 403 | sign_mismatch | The Android app signature failed the integration check; this check applies to both client reporting and fallback queries |
| 403 | deactivate | The Device Fingerprint service has expired; access resumes after renewal |
| 403 | quota_exceeded | Usage has exceeded the plan quota; access resumes after a plan upgrade or quota reset |
| 403 | manual_deactivate | The Device Fingerprint service has been suspended; contact GEELAB |
| 404 | integration not found | public_key is invalid, or the request was not sent to the regional API endpoint for the application's Region |
| 429 | Too many requests | Requests exceeded the per-minute or per-hour rate limit |
| 500 | Internal server error | Server error |
POST /api/v2/fp_query/{public_key}
Query the device fingerprint. Send the device_token returned by the client SDK to retrieve trusted device attributes and risk signals.
| Method | POST |
| Path | /api/v2/fp_query/{public_key} |
| Auth | Authorization: Bearer <secret> |
| Content-Type | application/json |
| Success | HTTP 200, code=200, message=Success |
Request
POST /api/v2/fp_query/{public_key}
Authorization: Bearer <secret>
Content-Type: application/jsonPath
| Field | Type | Required | Meaning | Where to get |
|---|---|---|---|---|
public_key | string | Yes | Application public identifier | Dashboard |
Headers
| Field | Required | Meaning | Where to get it |
|---|---|---|---|
Authorization | Yes | Bearer <secret>, server-side query authentication | Get secret from the dashboard |
Content-Type | Yes | application/json | — |
Body
{
"device_token": "<from-client-sdk>",
"event_type": "login",
"event_time": "2026-08-20T10:30:00+08:00"
}| Field | Type | Required | Meaning | Where to get |
|---|---|---|---|---|
device_token | string | Yes | For a standard query, pass the result token. For fallback analysis, pass the raw SDK geetoken. Both use this field. | Client SDK interface |
event_type | string | No | Business event type, such as login | Caller-provided |
event_time | string | No | Event time in RFC 3339 format (with a time zone or Z) | Caller-provided |
Minimum request body:
{ "device_token": "<from-client-sdk>" }fp_query fallback mode
If the client SDK fails to report data or does not obtain a result token, send the raw geetoken generated by the SDK to your backend. The backend can then use the same POST /api/v2/fp_query/{public_key} endpoint to obtain the device fingerprint and risk signals.
Request method
- Continue using the
device_tokenfield and pass the raw SDKgeetokenas-is. No additional field or mode parameter is required. - Use the same URL, Bearer authentication, and optional parameters as a standard query. Send the request to the API endpoint for the application's Region. Keep
secreton the backend and never expose it to clients.
Example for the Global endpoint (replace it with the North America or Europe endpoint when applicable):
curl -sS -X POST "https://riskct-global.geelabapi.com/api/v2/fp_query/{public_key}" \
-H "Authorization: Bearer {secret}" \
-H "Content-Type: application/json" \
-d '{"device_token":"{sdk_raw_geetoken}"}'The response structure is the same as a standard query; see Response (200). Successful fp_query requests in fallback mode are also billable.
Response (200)
{
"code": 200,
"data": {
"request_id": "req_0198c7a1-5b2e-7c10-8d44-1234567890ab",
"event_id": "evt_0198c7a1-5b2e-7c10-8d44-1234567890ab",
"fp_detail": {
"device_id": "GEE4-xxxxxx",
"version": "1.0"
},
"signals": [
{
"signal": "CLOUD_PHONE",
"level": "HIGH",
"confidence": 0.8
}
],
"detail": {
"client_type": "Android",
"device_basic": {
"os": "Android",
"pla": "arm64-v8a",
"mod": "Pixel 8",
"osv": "14"
},
"ip_info": {
"ip": "1.1.1.1",
"ip_geo": {
"country": "China",
"subdivision": "Shanghai",
"city": "Shanghai"
}
}
}
},
"message": "Success"
}data
| Field | Type | Meaning |
|---|---|---|
request_id | string | Analysis ID, formatted as req_ + UUID v7. Standard queries with the same result token return the same value; fallback analysis generates a new value for every request. |
event_id | string | ID of this query, formatted as evt_ + UUID v7. Each query has a different value. |
fp_detail | object | Device fingerprint details |
signals | array | Hit signal list; [] when no signal is hit |
detail | object | Client type, device information, IP address, and IP geolocation |
fp_detail
| Field | Type | Meaning |
|---|---|---|
device_id | string | device fingerprint |
version | string | Fingerprint version, currently"1.0" |
signals[]
| Field | Type | Meaning |
|---|---|---|
signal | string | signal identifier, for exampleCLOUD_PHONE |
level | string | LOW/MEDIUM/HIGH |
confidence | number | 0.0–1.0 |
The following table lists the signal values and descriptions currently supported by the Server API.
Signal Reference (40+)
signal | level | Description | Supported platforms |
|---|---|---|---|
BEING_DEBUGGED | HIGH | The device may be under active debugging, which can enable reverse engineering, tampering, or automated device-farm control | Android, iOS, Web |
DEBUG_MODE_OR_TEST_APP | LOW | The device has debugging enabled, or the app is a debug, modified, or non-store build | Android, iOS |
EMULATOR | HIGH | A traditional emulator environment was detected | Android, iOS, Web |
CLOUD_PHONE | HIGH | A cloud device or ARM server environment was detected | Android |
RUNNING_ON_MACOS | MEDIUM | The app is running on macOS | iOS |
VPN_ENABLED | LOW | A VPN connection is enabled on the device | Android, iOS |
NETWORK_PROXY | MEDIUM | A network proxy is configured on the device | Android, iOS |
INVALID_PACKAGE | LOW | The package name may have been modified, or the app may have been repackaged | Android, iOS |
TOKEN_EXPIRED | MEDIUM | The device_token has expired or was reused | Android, iOS, Web |
LIMIT_EXCEED | MEDIUM | A configured limit was exceeded | Android, iOS, Web |
SCREEN_SHARING | LOW | Screen sharing is active on the device | Android, iOS |
VOICE_CONNECTED | LOW | A voice call is active on the device | Android, iOS |
IP_CRAWLER_OR_BOT | MEDIUM | The IP address is associated with malicious cracking, crawler, or bot activity | Android, iOS, Web |
IP_MALICIOUS_ATTACK | HIGH | The IP address is associated with malicious attack activity | Android, iOS, Web |
IP_SUSPICIOUS_PROXY | MEDIUM | The IP address is suspected of being a proxy | Android, iOS, Web |
IDC_IP | MEDIUM | IDC/data center IP | Android, iOS, Web |
DECLARED_BOT | MEDIUM | The request IP or browser User-Agent matches a known bot catalog, such as search engine crawlers; User-Agent matching applies to Web only | Android, iOS, Web |
DECLARED_AGENT | MEDIUM | The request IP or browser User-Agent matches a known AI agent catalog; User-Agent matching applies to Web only | Android, iOS, Web |
DEVICE_JAILBREAK | HIGH | The iOS device is jailbroken | iOS |
DEVICE_ROOTED | HIGH | The Android device is rooted | Android |
SUSPICIOUS_CUSTOM_ROM | HIGH | The Android device is running a suspicious custom ROM | Android |
SUSPICIOUS_OPEN_SOURCE_ROM | HIGH | The Android device is running an Android Open Source Project-based ROM | Android |
BROWSER_COOKIE_FEATURE_DISABLED | MEDIUM | Browser cookies are unavailable or disabled | Web |
PSEUDO_BROWSER_ENV | HIGH | A spoofed browser environment was detected | Web |
BROWSER_INCOGNITO_MODE | LOW | The browser is running in private or incognito mode | Web |
DEVICE_BOOTLOADER_UNLOCKED | HIGH | The device bootloader is unlocked | Android |
SUSPICIOUS_DEVICE_RESET | HIGH | A suspicious device reset was detected | Android, iOS |
SYSTEM_VERSION_TOO_LOW | MEDIUM | The device operating system version is outdated | Android, iOS |
TIMEZONE_MISMATCH | LOW | The time zone of the request IP does not match the time zone reported by the device; generated only during client reporting, not in fallback mode | Android, iOS, Web |
DEVICE_WITHOUT_SIM | LOW | No SIM card was detected in the device | Android, iOS |
HOOK_TAMPERING_LOW | MEDIUM | Low-risk code hooking or tampering was detected; exclude modifications introduced by your own application before acting on this signal | iOS, Web |
HOOK_TAMPERING_MEDIUM | HIGH | Medium-risk code hooking or tampering capable of altering application logic was detected | Android, iOS, Web |
HOOK_TAMPERING_HIGH | HIGH | High-risk code hooking or tampering was detected | Android, iOS |
SUSPICIOUS_DEVICE_INFO_TAMPERED | HIGH | Device information may have been spoofed, or device attributes do not match the fingerprint | Android, iOS |
ANTIDETECT_BROWSER | HIGH | The device is running an anti-detect browser | Web |
APP_CLONE_TOOL_INSTALLED | LOW | An app-cloning tool is installed on the device | Android, Web |
TAMPER_TOOL_INSTALLED | LOW | A device-tampering tool is installed on the device | Android, iOS |
AUTOMATION_TOOL_INSTALLED | LOW | A device-farm or automation tool capable of operating at scale is installed on the device | Android, iOS |
REVERSE_ENGINEERING_TOOL_INSTALLED | LOW | A reverse-engineering tool is installed on the device | Android, iOS, Web |
NETWORK_TAMPER_TOOL_INSTALLED | LOW | A network-tampering tool is installed or may be in use on the device | Android, iOS |
GAME_TAMPER_TOOL_INSTALLED | LOW | A game modification or cheat tool is installed on the device | Android, iOS |
VIRTUAL_LOCATION_TOOL_INSTALLED | LOW | A location-spoofing tool is installed on the device | Android, iOS |
RUNNING_IN_VIRTUAL_MACHINE | HIGH | The device is running in a virtual machine | Android, iOS, Web |
RUNNING_IN_CLONED_APP | HIGH | The app is running in a cloned-app environment | Android, iOS |
USING_DEVICE_TAMPERING_TOOL | HIGH | A device-tampering or other high-risk tool is currently in use | Android |
USING_AUTOMATION_TOOL | HIGH | A device-farm or automation tool is currently in use | Android, iOS |
USING_REVERSE_ENGINEERING_TOOL | HIGH | A reverse-engineering or tampering tool is currently in use | Android |
USING_VIRTUAL_LOCATION | HIGH | A location-spoofing tool is currently in use | Android, iOS |
RUNNING_IN_SYSTEM_CLONED | LOW | A system-level cloned-app environment was detected | Android |
detail
| Field | Type | Meaning |
|---|---|---|
client_type | string | Android, iOS, or Web/H5 |
device_basic | object | Basic device information; its structure varies by client_type |
ip_info | object | Client IP address and its geolocation; see the table below |
| Field | Type | Meaning |
|---|---|---|
ip_info.ip | string | Client IP address |
ip_info.ip_geo | object | City-level IP geolocation in English, for reference only. This is a new field; a device_token generated before the upgrade may not include it |
ip_info.ip_geo.country | string | Country or region; "" when unknown |
ip_info.ip_geo.subdivision | string | Province or state; "" when unknown |
ip_info.ip_geo.city | string | City; "" when unknown |
If your server validates responses against a strict schema (for example, rejecting unknown fields), allow ip_info.ip_geo first.
Android/iOS device_basic:
{
"os": "Android",
"pla": "arm64-v8a",
"mod": "Pixel 8",
"osv": "14"
}| Field | Meaning |
|---|---|
os | Operating system |
pla | Architecture |
mod | Model |
osv | System version |
Web/H5device_basic:
{
"browser": { "name": "Chrome", "version": "145.0.0.0", "major": "145" },
"device": { "model": "Macintosh", "vendor": "Apple" },
"os": { "name": "macOS", "version": "10.15.7" }
}Error
The HTTP status code equals code. message is the exact string shown in the table below. For 403 responses, message is a stable reason code that your code can branch on; the response never includes expected allowlist values or the caller IP.
| HTTP | message | data | Meaning |
|---|---|---|---|
| 400 | Invalid request | { "errors": [ { "field", "reason", "type" } ] } | Missing field, wrong type, or invalid event_time |
| 400 | Invalid request | { "errors": [{ "field": "device_token", "reason": "Invalid token", "type": "value_error" }] } | device_token is invalid |
| 401 | Invalid bearer token | {} | Authentication failed |
| 403 | integration_disabled | {} | The integration is not enabled or has been deleted |
| 403 | application_disabled | {} | The application is not enabled or has been deleted |
| 403 | ip_not_allowed | {} | The server IP allowlist is enabled, but the caller's IP address is not on the list, the list is empty, or a valid caller IP address could not be obtained |
| 403 | degrade_disabled | {} | A raw SDK token was submitted, but fallback mode is disabled |
| 403 | platform_mismatch | {} | The client type submitted by the SDK does not match the integration platform, or the client type is missing or unrecognized |
| 403 | bundle_id_mismatch | {} | The iOS Bundle ID failed the integration check; this check applies to both client reporting and fallback queries |
| 403 | package_name_mismatch | {} | The Android package name failed the integration check; this check applies to both client reporting and fallback queries |
| 403 | sign_mismatch | {} | The Android app signature failed the integration check; this check applies to both client reporting and fallback queries |
| 403 | deactivate | {} | The Device Fingerprint service has expired; access resumes after renewal |
| 403 | quota_exceeded | {} | Usage has exceeded the plan quota; access resumes after a plan upgrade or quota reset |
| 403 | manual_deactivate | {} | The Device Fingerprint service has been suspended; contact GEELAB |
| 404 | integration not found | {} | public_key is invalid, or the request was not sent to the regional API endpoint for the application's Region |
| 429 | Too many requests | {} | Requests exceeded the per-minute or per-hour rate limit |
| 500 | Internal server error | {} | Server error |
{
"code": 400,
"data": {
"errors": [
{ "field": "device_token", "reason": "Field required", "type": "missing" }
]
},
"message": "Invalid request"
}{
"code": 401,
"data": {},
"message": "Invalid bearer token"
}Example
Copy {public_key} and {secret} from the dashboard. Get {device_token} from the client SDK, and send the request to the regional API endpoint for the application's Region.
curl -sS -X POST "https://{host}/api/v2/fp_query/{public_key}" \
-H "Authorization: Bearer {secret}" \
-H "Content-Type: application/json" \
-d '{
"device_token": "{device_token}",
"event_type": "login",
"event_time": "2026-08-20T10:30:00+08:00"
}'