01 / QUICK START
From interface to result
Base URL: https://miyaip.com. Use your own console login token as Authorization: Bearer <LOGIN_TOKEN>. This is a session credential, not a permanent public API key. An MCP key only authenticates the MCP service.
Sign in to the console. In your browser developer tools, inspect your own /api/SysLogin/GetUserInfo request and copy only the token after Bearer in its Authorization header. Keep it private and replace it after your session expires. This documentation never asks you to paste it into the page. Open console →
The shell examples below use Bash, cURL with --fail-with-body support, and Python 3.8+. Export the same environment variables in your shell on Windows. Requests run against your account and task creation may consume credits.
1. Discover available interfaces
# Bash: enter your own console login token without echoing it.
read -rsp 'Console token: ' MIYA_CONSOLE_TOKEN; echo
export MIYA_CONSOLE_TOKEN
export MIYA_INTERFACE_KEY='replace-with-a-key-from-the-list'
curl --fail-with-body -sS --max-time 30 \
-H "Authorization: Bearer $MIYA_CONSOLE_TOKEN" \
https://miyaip.com/api/CrawlerInterface/List2. Read the chosen parameter schema
curl --fail-with-body -sS --max-time 30 --get \
-H "Authorization: Bearer $MIYA_CONSOLE_TOKEN" \
--data-urlencode "interfaceKey=$MIYA_INTERFACE_KEY" \
https://miyaip.com/api/CrawlerInterface/Detail3. Submit exactly one task
# Save ONLY the selected interface's parameter object in params.json.
python -c 'import json,os; p=json.load(open("params.json",encoding="utf-8")); print(json.dumps({"InterfaceKey":os.environ["MIYA_INTERFACE_KEY"],"Params":p}))' > request.json
curl --fail-with-body -sS --max-time 30 \
-H "Authorization: Bearer $MIYA_CONSOLE_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @request.json \
https://miyaip.com/api/Crawler/Invoke4. Query the returned task ID
export MIYA_TASK_ID='replace-with-returned-taskId'
curl --fail-with-body -sS --max-time 30 --get \
-H "Authorization: Bearer $MIYA_CONSOLE_TOKEN" \
--data-urlencode "taskId=$MIYA_TASK_ID" \
https://miyaip.com/api/Crawler/TaskResultAfter step 2, create params.json using that interface’s exact field names and required values. Use {} only if it has no required parameters. Check both the HTTP status and business code. The first response contains a taskId; repeat only TaskResult while status is running, normally at five-second intervals.
02 / PYTHON
A complete, bounded task runner
Standard library only. It checks the interface definition, submits once, then polls. stdout contains the final task envelope; stderr includes the task ID. It exits with code 1 on failure. Unknown statuses stop with an error instead of waiting indefinitely.
Download crawler_api.py ↓python crawler_api.py "$MIYA_INTERFACE_KEY" params.json --wait-seconds 600Read / copy the full Python script
crawler_api.py
"""MIYAIP crawler example. Python 3.8+, standard library only.
Set MIYA_CONSOLE_TOKEN, then run:
python crawler_api.py INTERFACE_KEY params.json
Inspect available interfaces first with the cURL request in the public docs.
"""
import argparse
import json
import math
import os
import sys
import time
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode, urlsplit
from urllib.request import HTTPRedirectHandler, Request, build_opener
class NoRedirect(HTTPRedirectHandler):
# Do not forward login credentials to a redirected host.
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
def field(value, name, default=None):
return value.get(name, value.get(name[0].upper() + name[1:], default))
def request(base, token, path, data=None, timeout=30):
req = Request(base + path, headers={"Authorization": "Bearer " + token})
if data is not None:
req.data = json.dumps(data).encode("utf-8")
req.add_header("Content-Type", "application/json")
try:
with build_opener(NoRedirect()).open(req, timeout=timeout) as response:
payload = json.load(response)
except HTTPError as error:
raise RuntimeError("HTTP %s; check authentication (401), permissions or service availability. Do not blindly resubmit." % error.code) from None
except (URLError, TimeoutError, OSError):
raise RuntimeError("Network error; check call logs before resubmitting an uncertain creation request.") from None
except (ValueError, UnicodeError):
raise RuntimeError("Invalid JSON response") from None
if isinstance(payload, dict):
if "code" in payload and (type(payload["code"]) is not int or payload["code"] not in (0, 200)):
raise RuntimeError("Business error %s: %s" % (payload.get("code"), payload.get("message", "Request failed")))
return payload.get("body", payload.get("Body", payload))
return payload
def run(base, token, interface_key, params, wait_seconds=600, interval=5):
parsed = urlsplit(base)
if parsed.username or parsed.password or parsed.query or parsed.fragment or parsed.path not in ("", "/"):
raise ValueError("Base URL must be an origin without credentials, path or query")
if parsed.scheme != "https" and not (parsed.scheme == "http" and parsed.hostname in ("localhost", "127.0.0.1")):
raise ValueError("HTTPS is required (HTTP loopback is allowed for local tests)")
if not token.strip() or not interface_key.strip() or not isinstance(params, dict):
raise ValueError("Token, interface key and a JSON parameter object are required")
if not math.isfinite(wait_seconds) or not math.isfinite(interval) or wait_seconds <= 0 or interval <= 0:
raise ValueError("Wait and interval must be finite and positive")
base = base.rstrip("/")
mapping = request(base, token, "/api/CrawlerInterface/Detail?" + urlencode({"interfaceKey": interface_key}))
if not isinstance(mapping, dict) or field(mapping, "interfaceKey") != interface_key:
raise RuntimeError("Interface definition missing or mismatched")
if mapping.get("isEnabled") is False or mapping.get("IsEnabled") is False:
raise RuntimeError("Interface is disabled")
# Params must follow the returned ParamSchema. The server validates the contract.
created = request(base, token, "/api/Crawler/Invoke", {"InterfaceKey": interface_key, "Params": params})
task_id = field(created, "taskId") if isinstance(created, dict) else None
if not isinstance(task_id, str) or not task_id.strip():
raise RuntimeError("Missing taskId; inspect call logs before resubmitting")
print("Task ID: " + task_id, file=sys.stderr)
deadline = time.monotonic() + wait_seconds
while time.monotonic() < deadline:
remaining = deadline - time.monotonic()
if remaining <= 0:
break
result = request(base, token, "/api/Crawler/TaskResult?" + urlencode({"taskId": task_id}), timeout=min(30, remaining))
if not isinstance(result, dict) or not isinstance(field(result, "status"), str):
raise RuntimeError("Invalid task response; query the existing task ID later")
status = field(result, "status").strip().lower()
if status in ("success", "succeeded", "completed", "complete"):
return result
if status in ("failed", "fail", "error", "timeout", "timedout", "time_out"):
raise RuntimeError("Task %s: %s" % (status, field(result, "error", "No error detail")))
if status != "running":
raise RuntimeError("Unknown task status: " + status)
time.sleep(min(interval, max(0, deadline - time.monotonic())))
raise RuntimeError("Waiting timed out. Task %s may still be running; check TaskResult or call logs. No new task was submitted." % task_id)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("interface_key")
parser.add_argument("params_file")
parser.add_argument("--wait-seconds", type=float, default=600)
args = parser.parse_args()
try:
with open(args.params_file, encoding="utf-8") as source:
params = json.load(source)
result = run("https://miyaip.com", os.environ.get("MIYA_CONSOLE_TOKEN", ""), args.interface_key, params, args.wait_seconds)
print(json.dumps(result, ensure_ascii=False, indent=2))
except (OSError, ValueError, RuntimeError) as error:
print(str(error), file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
03 / REFERENCE
Five endpoints, one task lifecycle
JSON responses normally use { code, message, body }. Numeric codes 0 and 200 are successful. Responses can use body/Body and camelCase/PascalCase model fields. Preserve the documented request casing.
GET /api/CrawlerInterface/List
Request: No parameters.
Response: body: interface array. InterfaceKey, DisplayName, Description, ParamSchema, CreditCostBase, CreditCostParam, CreditCostMultiplier, IsEnabled.
GET /api/CrawlerInterface/Detail
Request: query: interfaceKey (string)
Response: body: one interface definition, including its current parameter schema and cost metadata.
POST /api/Crawler/Invoke
Request: JSON: { "InterfaceKey": string, "Params": object }
Response: body: { taskId, status: "running", costCredits }. Creates a task, not a final result.
GET /api/Crawler/TaskResult
Request: query: taskId (string)
Response: body: taskId, interfaceKey, interfaceName, status, result, error, costCredits, totalCostMs, startedAt, finishedAt.
GET /api/Crawler/CallLogPage
Request: query: PageNo=1, PageSize=10, KeyWord, InterfaceKey, Status, SearchBeginTime, SearchEndTime
Response: body: records and totalRows (also Records, TotalRows, total or Total). Records include taskId, requestJson, resultJson, errorMessage, status, costCredits, totalCostMs and refunded.
Log filters are optional except the pagination values shown. Status accepts running/success/failed/timeout. Send date boundaries as YYYY-MM-DD 00:00:00 and YYYY-MM-DD 23:59:59; confirm the server timezone for your reporting. UserId is an administrator-only filter; it does not grant access to another user’s records.
04 / INPUT
Use the interface’s ParamSchema
ParamSchema is a JSON-encoded string containing an array of field definitions, not a standard JSON Schema document. Decode it before inspecting the fields. The illustration below is not the contract of a specific live interface.
[
{
"name": "url",
"label": "URL",
"type": "string",
"required": true
},
{
"name": "limit",
"label": "Limit",
"type": "int",
"min": 1,
"max": 100,
"default": 10
}
]- name is the submitted key; label is display text. required/default control required input and defaults.
- Types: string, int, number, float, bool, enum, string[]. Check finite numeric values, integer requirements, min/max and enum options.
- showWhen defines conditional fields: AND across keys, OR across accepted values. Omit fields whose condition is not met. hidden/advanced/group are display hints, not proof that a value should be omitted.
- Use JSON booleans and arrays in requests. Only the returned interface definition can confirm available render, session or output options.
05 / OUTPUT
A created task is not a completed crawl
Stop polling at success, failed or timeout. Parse result after success and validate that it contains the data your application needs. result may be an object, array, string or null; totalCostMs is milliseconds. This is an illustrative final response, not a fixed result schema.
{
"code": 200,
"message": "",
"body": {
"taskId": "example-task-id",
"interfaceKey": "example-interface",
"status": "success",
"result": {
"items": []
},
"error": null,
"costCredits": 2,
"totalCostMs": 3200,
"startedAt": "2026-09-05T10:00:00Z",
"finishedAt": "2026-09-05T10:00:03.200Z"
}
}| Condition | Action |
|---|---|
| HTTP 401 / business 401 | Log in again and replace the console token. Do not use an MCP key. |
| Other HTTP failures | Check the status, permissions and service response. A network timeout after Invoke does not prove that creation failed; inspect logs before retrying. |
| Business code other than 0/200 | Treat as failure even when HTTP is 200. Read message; do not parse it as a successful result. |
| failed / timeout | Terminal task states. Read error and inspect the call log. A refund is not guaranteed. |
| Local waiting timeout | Keep the taskId and query later. Ending the script does not cancel the server task. |
| Invalid or unexpected response | Keep the task ID if available. Report the problem without sharing your token; do not automatically resubmit. |
SignalR is optional: the console subscribes to PublicCrawlerTaskStatus using the login token. Hub URLs depend on the deployment. Use the HTTP polling workflow unless your deployment supplies a confirmed Hub URL. Polling and push return the same task lifecycle; do not create another task to reconnect.
06 / COSTS
Read costs from the task
CreditCostBase + Params[CreditCostParam] × CreditCostMultiplier
This estimates credits where a cost parameter and multiplier exist; otherwise use the base cost. Read costCredits and the call log for the returned charge. refunded is a recorded status, not a guarantee that failures are free. Querying an existing task is distinct from creating another task; this contract does not publish a separate polling tariff.
Concurrency limits simultaneous tasks, not the amount of useful data in each result. Task retention, exact refund timing and rate-limit error codes are not specified here. The downloaded runner uses a 30-second per-request timeout and a 600-second polling deadline; these are client settings, not a service SLA.