iSpy Bot API
Complete REST API reference for IMEI checks, order management, and service catalog.
Authentication
All API requests require an API key passed via the X-API-Key header. You can manage your API keys in the Telegram bot under My Profile → API.
$ curl -H "X-API-Key: sk_live_your_api_key" \ https://api.ispy.service/api/v1/balance
Getting an API Key
1. Open @ispyware_bot in Telegram
2. Go to My Profile → API
3. Request API access or create a new key
4. Copy the sk_live_... key for your integrations
Rate Limits
Default rate limit: 100 requests per minute per API key. Rate limit status is returned in response headers.
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests per window |
| X-RateLimit-Remaining | Remaining requests in current window |
| X-RateLimit-Reset | Unix timestamp when window resets |
429 Too Many Requests response. Contact the admin to request a higher limit for your key.
Endpoints
Check API server availability. Does not require authentication.
$ curl https://api.ispy.service/health
{
"status": "ok",
"version": "1.0.0"
}
Returns the current balance and currency for the authenticated account.
$ curl -H "X-API-Key: sk_live_xxx" \ https://api.ispy.service/api/v1/balance
{
"success": true,
"data": {
"balance": 25.50,
"currency": "USD"
}
}
Returns the catalog of available check services with pricing.
$ curl -H "X-API-Key: sk_live_xxx" \ https://api.ispy.service/api/v1/services
{
"success": true,
"data": {
"services": [
{
"id": 1,
"name": "IMEI Check Basic",
"category": "IMEI",
"price": 1.50,
"input_types": ["imei", "serial"],
"accept_file": false,
"description": "Basic IMEI info: model, blacklist, FMI",
"delivery_time": "instant"
},
{
"id": 14,
"name": "FMI ON/OFF [S1]",
"category": "iCloud",
"price": 0.02,
"input_types": ["imei", "serial"],
"accept_file": false,
"description": "Find My iPhone ON/OFF check",
"delivery_time": "instant"
},
{
"id": 42,
"name": "Test: File Echo",
"category": "Tools",
"price": 0.01,
"input_types": ["file"],
"accept_file": true,
"description": "Upload a file, get it back renamed",
"delivery_time": "instant"
}
],
"total": 52
}
}
Submit a new check order. The order cost is deducted from your balance automatically.
Request Body
| Parameter | Type | Description |
|---|---|---|
| service_id required | string | ID of the service from /services (passed as string) |
| input_data required | string | IMEI (15 digits), serial number, or phone number depending on service |
| telegram_id required | integer | Your Telegram user ID (visible in bot under My Profile) |
| caption optional | string | Custom caption for file-based services. Displayed with the result file in Telegram. Max 1024 chars. Only used when the service has accept_file: true. See File-Based Services. |
| webhook_url optional | string | URL to receive result when order completes. Overrides the default webhook set on the API key. Must be http:// or https://, max 500 chars. See Webhooks. |
$ curl -X POST https://api.ispy.service/api/v1/orders \ -H "X-API-Key: sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "service_id": "14", "input_data": "GW9F2X0R50", "telegram_id": 123456 }'
{
"success": true,
"data": {
"order_id": "7147",
"status": "completed",
"service_name": "FMI ON/OFF [S1]",
"input_data": "GW9F2X0R50",
"price": 0.02,
"created_at": "2026-01-27T14:50:15Z"
}
}
status: "completed"). Others require processing time and return status: "pending" — use GET /orders/{id} to poll, or configure a webhook.
input_data value is not validated at order creation time. The order will be accepted with status pending, and if the input is invalid, the worker will return status: "error" after processing. Check the final status via polling or webhook.
{
"code": "VALIDATION_ERROR",
"message": "Telegram ID is required",
"localized_messages": {
"en": "Telegram ID is required",
"ru": "Ошибка валидации"
}
}
Retrieve the status and result of a specific order.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id required | string | Order ID returned from POST /orders |
$ curl -H "X-API-Key: sk_live_xxx" \ https://api.ispy.service/api/v1/orders/7147
{
"success": true,
"data": {
"order_id": "7147",
"status": "completed",
"service_name": "FMI ON/OFF [S1]",
"input_data": "GW9F2X0R50",
"output_data": {
"find_my_watch": "ON"
},
"price": 0.02,
"created_at": "2026-01-27T14:50:14Z",
"updated_at": "2026-01-27T14:50:15Z"
}
}
{
"success": true,
"data": {
"order_id": "7148",
"status": "pending",
"service_name": "GSX Full Report",
"input_data": "353456789012345",
"output_data": null,
"price": 3.50,
"created_at": "2026-01-27T15:12:00Z",
"updated_at": "2026-01-27T15:12:00Z"
}
}
List orders with filtering, sorting, and pagination.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| status optional | string | pending | completed | error |
| service_id optional | integer | Filter by service ID |
| from_date optional | ISO 8601 | Created from date, e.g. 2026-01-01T00:00:00Z |
| to_date optional | ISO 8601 | Created to date |
| sort_by optional | string | created_at | updated_at | price |
| sort_dir optional | string | asc | desc (default: desc) |
| include_output optional | boolean | Include output_data in response (default: true) |
| fields optional | string | Comma-separated field names to return |
| page optional | integer | Page number (default: 1) |
| limit optional | integer | Items per page (default: 20, max: 100) |
$ curl -H "X-API-Key: sk_live_xxx" \ "https://api.ispy.service/api/v1/orders?status=completed&limit=2&sort_by=created_at&sort_dir=desc"
{
"success": true,
"data": {
"orders": [
{
"order_id": "7147",
"status": "completed",
"service_name": "FMI ON/OFF [S1]",
"input_data": "GW9F2X0R50",
"output_data": { "find_my_watch": "ON" },
"price": 0.02,
"created_at": "2026-01-27T14:50:14Z"
},
{
"order_id": "7140",
"status": "completed",
"service_name": "BlackList Check",
"input_data": "353456789012345",
"output_data": { "blacklisted": "Clean" },
"price": 0.05,
"created_at": "2026-01-27T12:30:00Z"
}
],
"total": 5184,
"page": 1,
"limit": 2
}
}
File-Based Services
Some services accept file input (documents, photos) instead of IMEI/serial numbers. These services are marked with accept_file: true in the GET /services response and include "file" in input_types.
How File Input Works
File-based services use Telegram file IDs as input. When a user sends a document or photo to the bot, Telegram assigns a unique file_id. This ID is passed in input_data using the following format:
| Format | Description |
|---|---|
file:document:<file_id> |
Document file (PDF, XLSX, TXT, etc.) |
file:photo:<file_id> |
Photo file (JPG, PNG) |
Creating a File Order via API
$ curl -X POST https://api.ispy.service/api/v1/orders \ -H "X-API-Key: sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "service_id": "42", "input_data": "file:document:BQACAgIAAxkBAAISkWnByZzcVaPB...", "telegram_id": 123456, "caption": "Process this invoice" }'
Request Parameters for File Services
| Parameter | Type | Description |
|---|---|---|
| input_data required | string | file:document:<file_id> or file:photo:<file_id>. The Telegram file ID of the uploaded file. |
| caption optional | string | Custom caption displayed with the result file in Telegram. Max 1024 characters. If omitted, a default localized message is used. |
File Order Response
When a file-based service completes, the response includes a file_name field with the name of the result file.
{
"success": true,
"data": {
"order_id": "7200",
"status": "completed",
"service_name": "Test: File Echo",
"input_data": "file:document:BQACAgIAAxkB...",
"output_data": "File received: file_0.pdf (413883 bytes), renamed to ok.txt",
"file_name": "ok.txt",
"price": 0.01,
"created_at": "2026-03-24T10:00:00Z",
"updated_at": "2026-03-24T10:00:02Z"
}
}
Webhook Payload for File Orders
Webhook notifications for file orders include the file_name field:
{
"order_id": "7200",
"status": "completed",
"input_data": "file:document:BQACAgIAAxkB...",
"output_data": "File received: file_0.pdf, renamed to ok.txt",
"file_name": "ok.txt",
"price": 0.01,
"completed_at": "2026-03-24T10:00:02Z"
}
Identifying File Services
GET /services endpoint and filter by accept_file: true to find all file-accepting services. These services will also have "file" in the input_types array.
file_id obtained from one bot cannot be used with another. The file must have been previously sent to @ispyware_bot.
Webhooks
Get notified via HTTP when an API order completes or fails — no polling required. Webhooks are sent only for orders created through the API (source = api); Telegram notifications are not sent for API orders.
Setting the Webhook URL
There are two ways to specify where the result is delivered. Per-request URL takes priority.
| Method | Priority | Description |
|---|---|---|
| Per-request | Higher | Pass webhook_url in the POST /api/v1/orders body. Applies only to that order. |
| Default (API key) | Lower | Set once in the Telegram bot: My Profile → API → select key → Webhook URL. Used when the request body has no webhook_url. |
webhook_url per request when you need results routed to a different endpoint.
Delivery Details
| Property | Value |
|---|---|
| HTTP method | POST |
| Content-Type | application/json |
| Timeout | 30 seconds |
| Success | Any 2xx status code |
| Trigger | Order completes (completed) or fails (error) |
| Delivery | Asynchronous — does not block order processing |
Payload
{
"order_id": "7147",
"status": "completed",
"input_data": "GW9F2X0R50",
"output_data": {
"find_my_watch": "ON"
},
"price": 0.02,
"completed_at": "2026-01-27T14:50:15Z"
}
{
"order_id": "7148",
"status": "error",
"input_data": "353456789012345",
"output_data": null,
"price": 3.50,
"completed_at": "2026-01-27T15:14:22Z"
}
Payload Fields
| Field | Type | Description |
|---|---|---|
| order_id | string | Unique order identifier — use for idempotency (duplicates possible on network retries) |
| status | string | completed or error |
| input_data | string | IMEI / serial / phone submitted with the order |
| output_data | object | null | Check result. null when status is error |
| file_name | string | null | Result file name for file-based services (e.g. "ok.txt"). Omitted for non-file services. See File-Based Services |
| price | number | Amount charged for this order (USD) |
| completed_at | ISO 8601 | Timestamp when the order was finalized |
Example: Creating an Order with Webhook
$ curl -X POST https://api.ispy.service/api/v1/orders \ -H "X-API-Key: sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "service_id": "14", "input_data": "GW9F2X0R50", "telegram_id": 123456, "webhook_url": "https://example.com/ispy-webhook" }'
{
"success": true,
"data": {
"order_id": "7150",
"status": "pending",
"price": 0.02,
"created_at": "2026-01-27T16:00:00Z"
}
}
Limitations
• Only http:// and https:// URLs are accepted (max 500 characters).
• HTTPS is strongly recommended for production endpoints.
• The HTTP client timeout is 30 seconds; responses with 2xx status are treated as success.
• Duplicate deliveries are possible on network failures — use order_id for idempotent processing.
Managing the Default Webhook in Bot
In @ispyware_bot:
My Profile → API Access → select key
• Webhook URL — set or update the default URL
• Reset webhook — clear the URL (disables webhook for this key)
Error Codes
All error responses follow a consistent JSON format:
{
"code": "ERROR_CODE",
"message": "Human-readable description",
"localized_messages": {
"en": "English message",
"ru": "Сообщение на русском"
}
}
| HTTP Code | Error Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST_BODY | Malformed JSON or missing required fields |
| 400 | VALIDATION_ERROR | Request body fields failed validation (e.g. missing telegram_id) |
| 401 | unauthorized | Missing or invalid API key |
| 402 | insufficient_balance | Not enough balance to complete the order |
| 403 | forbidden | API key does not have permission for this action |
| 404 | not_found | Order or service not found |
| 422 | validation_error | Input data doesn't match service requirements |
| 429 | rate_limited | Too many requests, try again later |
| 500 | internal_error | Server error, contact support |
| 502 | upstream_error | External provider returned an error |
| 503 | service_unavailable | API is temporarily down for maintenance |
Code Examples
Quick-start examples for common integrations.
Full IMEI Check Flow
# 1. Check balance $ curl -s -H "X-API-Key: sk_live_xxx" \ https://api.ispy.service/api/v1/balance # 2. List available services $ curl -s -H "X-API-Key: sk_live_xxx" \ https://api.ispy.service/api/v1/services # 3. Create an order $ curl -s -X POST https://api.ispy.service/api/v1/orders \ -H "X-API-Key: sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{"service_id": "14", "telegram_id": 123456, "input_data": "GW9F2X0R50"}' # 4. Check order status (if pending) $ curl -s -H "X-API-Key: sk_live_xxx" \ https://api.ispy.service/api/v1/orders/7147
import requests import time API_KEY = "sk_live_xxx" BASE = "https://api.ispy.service/api/v1" headers = {"X-API-Key": API_KEY} # Check balance balance = requests.get(f"{BASE}/balance", headers=headers).json() print(f"Balance: {balance['data']['balance']} {balance['data']['currency']}") # Create order order = requests.post(f"{BASE}/orders", headers=headers, json={ "service_id": "14", "telegram_id": 123456, "input_data": "GW9F2X0R50" }).json() order_id = order["data"]["order_id"] print(f"Order {order_id}: {order['data']['status']}") # Poll if pending while order["data"]["status"] == "pending": time.sleep(5) order = requests.get( f"{BASE}/orders/{order_id}", headers=headers ).json() print("Result:", order["data"]["output_data"])
const API_KEY = "sk_live_xxx"; const BASE = "https://api.ispy.service/api/v1"; const headers = { "X-API-Key": API_KEY, "Content-Type": "application/json" }; // Check balance const bal = await fetch(`${BASE}/balance`, { headers }).then(r => r.json()); console.log(`Balance: ${bal.data.balance} ${bal.data.currency}`); // Create order const order = await fetch(`${BASE}/orders`, { method: "POST", headers, body: JSON.stringify({ service_id: "14", telegram_id: 123456, input_data: "GW9F2X0R50" }) }).then(r => r.json()); console.log(`Order ${order.data.order_id}: ${order.data.status}`); // Poll if pending let result = order; while (result.data.status === "pending") { await new Promise(r => setTimeout(r, 5000)); result = await fetch( `${BASE}/orders/${order.data.order_id}`, { headers } ).then(r => r.json()); } console.log("Result:", result.data.output_data);
<?php $apiKey = 'sk_live_xxx'; $base = 'https://api.ispy.service/api/v1'; function apiGet($url, $key) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["X-API-Key: {$key}"], ]); $res = curl_exec($ch); curl_close($ch); return json_decode($res, true); } function apiPost($url, $key, $data) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($data), CURLOPT_HTTPHEADER => [ "X-API-Key: {$key}", "Content-Type: application/json", ], ]); $res = curl_exec($ch); curl_close($ch); return json_decode($res, true); } // Check balance $balance = apiGet("{$base}/balance", $apiKey); echo "Balance: " . $balance['data']['balance'] . "\n"; // Create order $order = apiPost("{$base}/orders", $apiKey, [ 'service_id' => '14', 'telegram_id' => 123456, 'input_data' => 'GW9F2X0R50', ]); $orderId = $order['data']['order_id']; echo "Order {$orderId}: " . $order['data']['status'] . "\n"; // Poll if pending while ($order['data']['status'] === 'pending') { sleep(5); $order = apiGet("{$base}/orders/{$orderId}", $apiKey); } print_r($order['data']['output_data']);
Webhook Handler
from flask import Flask, request, jsonify app = Flask(__name__) processed = set() # idempotency guard @app.route("/ispy-webhook", methods=["POST"]) def handle_webhook(): data = request.json order_id = data["order_id"] # Deduplicate (retries may send the same order_id) if order_id in processed: return jsonify({"ok": True}), 200 processed.add(order_id) print(f"Order {order_id}: {data['status']}") print(f" Input: {data['input_data']}") print(f" Price: {data['price']}") if data["status"] == "completed": print(f" Result: {data['output_data']}") else: print(" Order failed") return jsonify({"ok": True}) app.run(port=8080)
const express = require("express"); const app = express(); const processed = new Set(); app.use(express.json()); app.post("/ispy-webhook", (req, res) => { const { order_id, status, input_data, output_data, price } = req.body; // Deduplicate if (processed.has(order_id)) { return res.json({ ok: true }); } processed.add(order_id); console.log(`Order ${order_id}: ${status}`); console.log(` Input: ${input_data}, Price: ${price}`); if (status === "completed") { console.log(" Result:", output_data); } else { console.log(" Order failed"); } res.json({ ok: true }); }); app.listen(8080);
<?php // ispy-webhook.php header('Content-Type: application/json'); $body = file_get_contents('php://input'); $data = json_decode($body, true); if (!$data || empty($data['order_id'])) { http_response_code(400); echo json_encode(['error' => 'invalid payload']); exit; } $orderId = $data['order_id']; $status = $data['status']; // TODO: check idempotency (e.g. DB lookup by order_id) error_log("[webhook] Order {$orderId}: {$status}"); if ($status === 'completed') { $result = $data['output_data']; // Process result... error_log("[webhook] Result: " . json_encode($result)); } echo json_encode(['ok' => true]);
Batch IMEI Check
import requests, time API_KEY = "sk_live_xxx" BASE = "https://api.ispy.service/api/v1" headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"} imeis = ["353456789012345", "359876543210987", "GW9F2X0R50"] # Submit all orders order_ids = [] for imei in imeis: resp = requests.post(f"{BASE}/orders", headers=headers, json={ "service_id": "14", "telegram_id": 123456, "input_data": imei }).json() if resp.get("success"): order_ids.append(resp["data"]["order_id"]) print(f"Submitted {imei} -> order {resp['data']['order_id']}") # Collect results for oid in order_ids: for _ in range(20): r = requests.get(f"{BASE}/orders/{oid}", headers=headers).json() if r["data"]["status"] != "pending": print(f"Order {oid}: {r['data']['output_data']}") break time.sleep(3)