API REFERENCE

接口使用说明

快速开始

  1. 确认权限:查看我的权限;尚未开通所需数据时,前往权限中心联系开通。
  2. 获取密钥:创建或复制 API Key,请求时放入 X-Api-Key 请求头。
  3. 开始查询:从已开通接口进入对应文档,按示例发起请求并查看返回;也可选择MCP 接入或Python SDK。

使用前请查看调用规则与标准调用代码。不要把 API Key 写进浏览器前端、公开仓库或截图。

基础地址:https://jinniu.lingxideai.com。日期使用 YYYYMMDD,例如 20260805。
所有 API 的响应压缩:客户端请求头带 Accept-Encoding: gzip 时,服务端使用 gzip 压缩传输;未携带时按默认 JSON 返回。该请求头不是业务参数,不需要写入各接口的请求示例。

MCP 接入

支持无状态 Streamable HTTP MCP。将服务地址设为 https://jinniu.lingxideai.com/mcp,并在请求头中使用 Authorization: Bearer YOUR_API_KEY;也兼容 X-Api-Key。

MCP 复用当前账号的数据权限。

复制给AI,一键添加MCP
复制后将 YOUR_API_KEY 替换为个人中心生成的Key。
请帮我添加并连接这个MCP服务:
名称:金牛行情数据
类型:Streamable HTTP
地址:https://jinniu.lingxideai.com/mcp
鉴权:Authorization: Bearer YOUR_API_KEY

安装后请读取tools/list验证连接;如果不能自动添加,请告诉我配置入口。
调用建议:最多3个并行工作线程,每个线程内约3秒调用1次,错峰发起,所有线程与客户端合计不超过60次/分钟。以后台实际限制和Retry-After为准,不修改或绕过后台限额。

连接验证

curl -X POST 'https://jinniu.lingxideai.com/mcp' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"example","version":"1.0"}}}'

连接后通过 tools/list 获取当前工具及输入参数说明;通过 tools/call 查询数据。返回数据使用 data.fields + data.items 结构。

MCP 调用建议:最多 3 个并行工作线程,每个线程内约 3 秒调用 1 次,错峰发起,合计不超过 60 次/分钟。这是客户端建议,不锁定后台;实际限制更严格时以后台配置及 Retry-After 为准。

faq_search 可搜索已核验、可解释的FAQ,参数为 q(关键词,可省略)和 page(默认1),固定每页20条。未确认的问题不返回,查询不扣行情日额度;答案适用范围见返回的 scope,分页信息见 data.meta。已连接的AI如看不到新工具,请刷新工具列表或重新连接MCP。

调用规则

调用频率与并发

以下为文档建议调用配置,不锁定后台设置。管理员可在后台调整实际限额;后台要求更严格时,必须按实际限额调用。

  • 历史、实时及 MCP 调用均为每账户每分钟最多 60 次。
  • 建议同一账户最多同时发起 3 个并发请求。每个线程内的调用频率约为 3 秒 1 次:线程 A 可在第 0、3、6 秒调用,线程 B 在第 1、4、7 秒调用,线程 C 在第 2、5、8 秒调用。不是所有线程共用一个 3 秒间隔;所有线程和客户端合计每分钟不超过 60 次。
  • 请求尚未完成时,不要为了追赶频率增加并发。达到实际限制时返回 429,以响应中的 Retry-After 为准等待;网关并发限制通常返回 Retry-After: 2。
  • 账户数据权限到期、权限关闭或超限时,接口会拒绝请求。删除或重新生成 API Key 不会重置账户调用次数。

单次返回与批量查询

适用接口单次返回上限查询方式
股票、ETF、交易所指数历史日线;股票复权因子;股票、ETF、交易所指数、期货及期权历史分钟线;期权日线6000 条按代码、日期或时间区间分批查询。
公募基金列表6000 条按市场、状态筛选,使用 offset 分页。
基金经理5000 条按基金、姓名或公告日期筛选。
基金管理人、净值、分红及持仓2000 条按支持的筛选条件与 offset 分页;持仓为报告期披露。
期货合约信息6000 条按交易所、产品筛选,使用 offset 分页。
期货日线、主力映射、持仓排名2000 条按合约、日期或 offset 分页。
期货复权因子、复权日线3000 条主力合约换月复权。
期货仓单日报1000 条按产品、日期或 offset 分页。
申万指数历史分钟线5000 条单个指数代码,按时间区间分批查询。
股票基础信息:股票列表、交易日历、ST 历史、名称变更、公司主体、北交所代码映射;期权合约信息5,000 条通过各接口支持的代码、日期等条件筛选。
申万指数基本信息、行业分类、行业成分及历史成员;ETF 复权因子2,000 条通过代码、分类、日期等条件筛选。
申万指数历史日线4,000 条按指数代码与日期区间查询。
股票、ETF、交易所指数、期货实时分钟线;股票及 ETF 实时日线;申万指数实时行情1,000 条每次最多查询 100 个代码或合约。
股票日内分钟线;ETF、交易所指数及期货盘中分钟线1,000 条每次仅查询 1 个代码或合约。
  • 分钟线不限制固定自然日跨度;超过单次返回量时请按时间范围分批查询。
  • 所有 API 支持可选 gzip 响应压缩:带 Accept-Encoding: gzip 时返回压缩内容;不带时按默认 JSON 返回。常见浏览器和 HTTP 客户端会自动协商并解压。
  • 请妥善保管 API Key。发现泄露时,在个人中心轮换 Key,旧 Key 会立即失效。
  • 数据仅限研究、学习和个人使用,使用者应自行核验并遵守适用的法律、平台与数据使用规则。

规范调用建议

  • 按代码和时间范围分批查询,并通过 fields 仅请求所需字段;已成功获取的数据请保存在本地,避免重复拉取。
  • 收到 429 时必须按照 Retry-After 等待后再重试,不要立即连续重发;网络异常建议间隔 2、5、10 秒逐步重试,最多 3 次。
  • 大批量历史数据建议安排在非交易时段执行;实时接口仅查询当前业务需要的代码,不要循环请求全市场。
Python 标准调用示例
代码可在窗口内滚动,也可直接复制完整内容。
import json
import math
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime

import requests

API_KEY = "YOUR_API_KEY"
URL = "https://jinniu.lingxideai.com/api/stk_mins"
# Client recommendations only; use lower values if your account requires them.
WORKERS = 3
SECONDS_PER_THREAD = 3.0
TOTAL_REQUESTS_PER_MINUTE = 60


class CallControl:
    def __init__(self):
        self.lock = threading.Lock()
        self.local = threading.local()
        self.stop = threading.Event()
        self.next_global = 0.0
        self.cooldown_until = 0.0

    def wait(self):
        while not self.stop.is_set():
            with self.lock:
                if self.stop.is_set():
                    break
                now = time.monotonic()
                due = max(getattr(self.local, "next_at", 0.0),
                          self.next_global, self.cooldown_until)
                if now >= due:
                    self.local.next_at = now + SECONDS_PER_THREAD
                    self.next_global = now + 60.0 / TOTAL_REQUESTS_PER_MINUTE
                    return
            self.stop.wait(min(due - now, 0.25))
        raise RuntimeError("Batch stopped; keep completed files and fix the error.")

    def cooldown(self, seconds):
        with self.lock:
            self.cooldown_until = max(self.cooldown_until, time.monotonic() + seconds)


control = CallControl()


def retry_after(response, body):
    waits = []
    for value in (response.headers.get("Retry-After"),
                  (body.get("data") or {}).get("retry_after_seconds")):
        try:
            seconds = float(value)
            if math.isfinite(seconds) and seconds >= 0:
                waits.append(seconds)
                continue
        except (TypeError, ValueError):
            pass
        try:
            date = parsedate_to_datetime(str(value))
            date = date if date.tzinfo else date.replace(tzinfo=timezone.utc)
            waits.append(max(0.0, (date - datetime.now(timezone.utc)).total_seconds()))
        except (TypeError, ValueError, OverflowError):
            pass
    return max(waits, default=2.0)


def query_minutes(session, trade_date):
    payload = {
        "ts_code": "000001.SZ", "freq": "1min",
        "start_date": f"{trade_date} 09:30:00",
        "end_date": f"{trade_date} 15:00:00",
        "fields": "ts_code,trade_time,open,close,high,low,vol,amount",
        "limit": 4000,
    }
    for attempt in range(4):
        control.wait()  # Every HTTP attempt, including retries, is paced.
        try:
            with session.post(URL, json=payload, timeout=(5, 60)) as response:
                if response.status_code in (400, 401, 403):
                    control.stop.set()
                    raise RuntimeError(f"HTTP {response.status_code}: fix parameters, key or permission.")
                if response.status_code == 429:
                    try:
                        body = response.json()
                    except ValueError:
                        body = {}
                    if (body.get("data") or {}).get("limit_type") == "daily":
                        control.stop.set()
                        raise RuntimeError("Daily quota exhausted; resume after reset.")
                    if attempt == 3:
                        raise RuntimeError("Rate limit persists after 3 retries.")
                    control.cooldown(retry_after(response, body))
                    continue
                response.raise_for_status()
                result = response.json()
                if not isinstance(result, dict) or result.get("success") is not True:
                    raise RuntimeError("API returned an unsuccessful response.")
                return result
        except requests.RequestException:
            if attempt == 3:
                raise
            if control.stop.wait((2, 5, 10)[attempt]):
                raise RuntimeError("Batch stopped.") from None
    raise RuntimeError("No successful response.")


def worker(index, dates, epoch):
    control.local.next_at = epoch + index
    # Each worker owns its Session and reuses its own connections.
    with requests.Session() as session:
        session.headers.update({"X-Api-Key": API_KEY, "Accept-Encoding": "gzip"})
        try:
            for trade_date in dates[index::WORKERS]:
                result = query_minutes(session, trade_date)
                with open(f"000001.SZ_{trade_date}.json", "w", encoding="utf-8") as file:
                    json.dump(result, file, ensure_ascii=False)
        except Exception:
            control.stop.set()
            raise


def main():
    dates = ["2026-09-08", "2026-09-09", "2026-09-10"]
    epoch = time.monotonic()
    with ThreadPoolExecutor(max_workers=WORKERS) as pool:
        futures = [pool.submit(worker, index, dates, epoch) for index in range(WORKERS)]
        for future in as_completed(futures):
            future.result()


if __name__ == "__main__":
    main()

错误码

400 参数错误,401 API Key 无效或已删除,403 账户期限或数据权限不满足,429 请求超过限额,503 行情数据暂不可用。错误响应包含 code、message 和 data 字段。

请求频率超限时,HTTP 429 响应头会返回 Retry-After;响应体的 data 会提供 limit_type、retry_after_seconds 和 reset_at,客户端应等待后再重试。