An integration against an asynchronous financial research API usually breaks in one of three places: testing a new API key by creating a billable job, passing a stock ticker string where the backend expects an issuer UUID, or retrying a timed-out POST with a fresh idempotency key so the same job runs twice.
Eqvero exposes US and Hong Kong company data, original disclosure search, deep research, and financial analytics through both a Streamable HTTP MCP server (https://eqvero.com/api/mcp) and HTTP RPC endpoints (POST /api/v1/operations/<OPERATION>). Both interfaces share the same parameter schemas and tool names (HTTP uses dots such as companies.search, while MCP uses underscores such as companies_search). Wiring the setup, verification, and timeout recovery paths properly prevents accidental charges and dropped tasks.
Client configuration and key isolation
API and MCP access requires an Eqvero account on a Basic or higher plan and a dedicated API key created under Keys and usage. Keys are scoped by role (such as read-only lookups versus research and analytics writes) and shown once on creation. Eqvero provides neither OAuth sign-in nor keyless access; every client sends Authorization: Bearer <EQVERO_API_KEY>. Clients that only support OAuth or local stdio transports need a compatible HTTP MCP adapter rather than copying these snippets directly.
In Claude Code, register the server from the terminal and keep the key in local secure storage rather than a shared repository:
claude mcp add --transport http eqvero https://eqvero.com/api/mcp --header "Authorization: Bearer <EQVERO_API_KEY>"
In Cursor, merge the eqvero block under mcpServers without overwriting existing servers:
{
"mcpServers": {
"eqvero": {
"url": "https://eqvero.com/api/mcp",
"headers": {
"Authorization": "Bearer <EQVERO_API_KEY>"
}
}
}
}
In VS Code, the root key is servers and the transport field is explicit:
{
"servers": {
"eqvero": {
"type": "http",
"url": "https://eqvero.com/api/mcp",
"headers": {
"Authorization": "Bearer <EQVERO_API_KEY>"
}
}
}
}
Never place real API keys in URLs, shell logs, chat transcripts, browser client bundles, or committed config files.
Verify with a read-only lookup and keep the issuer UUID
Saving a configuration file or seeing "connected" in a UI does not confirm the key and permissions work. Reload the client if needed, list the available server tools, and test with a single read-only search:
companies_search({"query": "AAPL", "limit": 1})
The equivalent HTTP check posts to https://eqvero.com/api/v1/operations/companies.search:
{
"data": {
"items": [
{
"id": "COMPANY_UUID",
"name": "Apple Inc.",
"ticker": "AAPL"
}
],
"total": 1
}
}
Never call research_create or analytics_create just to verify installation. If the read-only check fails, status 401 points to an invalid or revoked key, 402 indicates plan access or remaining allowance limits, and 403 indicates missing permission scopes on that key.
One common input bug happens right after the first search succeeds. Downstream endpoints (financials_metrics, financials_query, evidence_search, events_list, ownership_list, prices_list, and research_create) take the returned id UUID as issuerId, not the ticker string "AAPL". Monetary amounts and ratios are also returned as decimal strings so client pipelines can parse them into Decimal objects without binary floating-point rounding drift.
Recover dropped async tasks without double billing
Research (research.create) and quantitative calculations (analytics.create) run asynchronously in the background. When accepted, the endpoint returns HTTP 202 Accepted with {"data": {"id": "TASK_UUID", "status": "queued"}}. Closing a page or disconnecting an MCP session does not cancel an accepted job; tasks continue through queued, running, waiting for data, awaiting input, completed, failed, and cancelled.
If a network blip or MCP client timeout drops the 202 response before your caller records TASK_UUID, do not generate a new idempotencyKey and submit again. Query the original submission record first using research.submission (or MCP research_submission) with the original key:
import json
import os
import urllib.error
import urllib.request
import uuid
BASE = "https://eqvero.com/api/v1/operations"
API_KEY = os.environ["EQVERO_API_KEY"]
def post_op(operation: str, payload: dict) -> dict:
req = urllib.request.Request(
f"{BASE}/{operation}",
data=json.dumps(payload).encode("utf-8"),
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req, timeout=20) as resp:
return json.loads(resp.read().decode("utf-8"))
def submit_or_recover_research(
issuer_id: str,
question: str,
idem_key: str | None = None,
) -> str:
key = idem_key or str(uuid.uuid4())
payload = {
"mode": "deep",
"outputLocale": "en",
"issuerId": issuer_id,
"question": question,
"idempotencyKey": key,
}
try:
res = post_op("research.create", payload)
return res["data"]["id"]
except (urllib.error.URLError, TimeoutError):
lookup = post_op(
"research.submission",
{"operation": "research.create", "idempotencyKey": key},
)
data = lookup.get("data", {})
if data.get("found"):
task_id = data.get("taskId") or data.get("id")
if task_id:
return task_id
raise
Two rules keep this recovery safe. First, reuse the same idempotencyKey only when the operation inputs are identical; changing the question or issuer with an existing key triggers HTTP 409. Second, on 503 service-unavailable errors, look up existing tasks by their original ID or submission key rather than spawning a duplicate job. Once you have the TASK_UUID, read results via research.get or subscribe to progress updates from GET /api/v1/research/TASK_ID/events?after=EVENT_ID using your last seen event cursor on reconnect.
Quote calculations first and respect cancellation state
Quantitative jobs follow a three-step guard before execution: inspect available models and field contracts with analytics_methods, request a cost and input check with analytics_quote, and only then call analytics_create to read final values from analytics_get. Pass proportions as decimals (0.05 for 5%). When required data is missing, the API reports the gap explicitly instead of filling in zeros.
For failed tasks, resume with research_retry against the original taskId and a dedicated retry identifier instead of restarting a separate research_create loop. Call research_cancel or analytics_cancel only when stopping a job on purpose, and check executionActive in the response: status: "cancelled" with executionActive: true means the background worker is still shutting down.
If you want research-method instructions layered over the raw MCP tools, the optional eqvero-skill repository bundles SKILL.md conventions for US and Hong Kong disclosure checks. For a broader look at the research workspace and published filing investigations, see Eqvero: investigate US and Hong Kong stocks through the original disclosures and the Eqvero developer documentation.
Part of the Eqvero: Product Guides & Updates series.
Top comments (0)