MIYAIP / DEVELOPERS

Crawler API documentation

Discover interfaces, submit one crawler task and retrieve its result with cURL or Python. Authentication, parameters, credits and error handling.

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/List

2. 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/Detail

3. 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/Invoke

4. 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/TaskResult

After 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 600
Read / 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"
  }
}
ConditionAction
HTTP 401 / business 401Log in again and replace the console token. Do not use an MCP key.
Other HTTP failuresCheck 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/200Treat as failure even when HTTP is 200. Read message; do not parse it as a successful result.
failed / timeoutTerminal task states. Read error and inspect the call log. A refund is not guaranteed.
Local waiting timeoutKeep the taskId and query later. Ending the script does not cancel the server task.
Invalid or unexpected responseKeep 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.