The Zapi MCP server exposes the whole scraper catalog to your agent through 8 tools — search, schemas, runs, bulk jobs, account, and usage. Copy the config for your client, paste your API key, done.
Zapi runs a remote MCP server named zapi (version 1.2.0) at https://mcp.zpi.web.id/mcp. Transport is Streamable HTTP, stateless — POST carries JSON-RPC, GET opens SSE, DELETE tears down. There are no resources and no prompts: exactly 8 tools, documented in full below.
MCP (Model Context Protocol) is how AI clients — Claude, Cursor, VS Code Copilot, Gemini, Windsurf, and others — call external tools. Once connected, your agent can discover a scraper and run it without you writing any HTTP code.
Authenticate with your Zapi API key (zpi_...) via any of three headers: Authorization: Bearer, x-api-key, or api-key. Missing or invalid keys get a 401. Create a key at Dashboard → API Keys — free, no credit card required.
# Zapi MCP endpoint (Streamable HTTP)
https://mcp.zpi.web.id/mcp
# Auth via header — any of these three:
Authorization: Bearer zpi_xxx
x-api-key: zpi_xxx
api-key: zpi_xxx▸ There is no OAuth server — clients whose connector UI is OAuth-only (Claude Desktop, claude.ai web) can't send a header directly and need the mcp-remote bridge shown in the setup section. Your key is a secret: keep it in your client's config store, never in a repo, and prefer a dedicated revocable key for MCP.
Find your client, copy its config, and replace zpi_xxx with your key. The endpoint is already baked into every snippet. Clients differ in the config key they expect for a remote HTTP server — the wrong key fails silently:
| Config key | Clients |
|---|---|
| url | Cursor, Kiro, Cline, Zed (context_servers), JetBrains |
| httpUrl | Gemini CLI |
| serverUrl | Antigravity, Windsurf |
| servers + type: http | VS Code / Copilot |
claude mcp addkey: --transport httpclaude mcp add --transport http zapi https://mcp.zpi.web.id/mcp \
--header "Authorization: Bearer zpi_xxx".cursor/mcp.jsonkey: url{
"mcpServers": {
"zapi": {
"url": "https://mcp.zpi.web.id/mcp",
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
}~/.gemini/settings.jsonkey: httpUrl{
"mcpServers": {
"zapi": {
"httpUrl": "https://mcp.zpi.web.id/mcp",
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
}mcp_config.jsonkey: serverUrl{
"mcpServers": {
"zapi": {
"serverUrl": "https://mcp.zpi.web.id/mcp",
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
}.kiro/settings/mcp.jsonkey: url{
"mcpServers": {
"zapi": {
"url": "https://mcp.zpi.web.id/mcp",
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
}.vscode/mcp.jsonkey: servers + type: http{
"servers": {
"zapi": {
"type": "http",
"url": "https://mcp.zpi.web.id/mcp",
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
}~/.codeium/windsurf/mcp_config.jsonkey: serverUrl{
"mcpServers": {
"zapi": {
"serverUrl": "https://mcp.zpi.web.id/mcp",
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
}MCP settings JSONkey: url{
"mcpServers": {
"zapi": {
"url": "https://mcp.zpi.web.id/mcp",
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
}config.yaml / .continue (verify: docs.continue.dev)key: mcpServers[] + type: streamable-http{
"mcpServers": [
{
"name": "zapi",
"type": "streamable-http",
"url": "https://mcp.zpi.web.id/mcp",
"requestOptions": {
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
]
}settings.jsonkey: context_servers{
"context_servers": {
"zapi": {
"url": "https://mcp.zpi.web.id/mcp",
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
}mcp.jsonkey: url{
"mcpServers": {
"zapi": {
"url": "https://mcp.zpi.web.id/mcp",
"headers": {
"Authorization": "Bearer zpi_xxx"
}
}
}
}Claude's connector UI is OAuth-only — there's nowhere to enter a header. Use mcp-remote, a stdio bridge that forwards requests with your auth header:
{
"mcpServers": {
"zapi": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.zpi.web.id/mcp",
"--header",
"Authorization: Bearer zpi_xxx"
]
}
}
}In args, --header and its value are two separate array elements — don't combine them into one string.
From TypeScript, skip the client config entirely — zpi-sdk/mcp is a zero-dependency MCP client for the same server: JSON-RPC over Streamable HTTP, protocol version 2025-06-18, session tracked via the mcp-session-id header. The initialize handshake runs lazily on first call.
import { createMcpClient } from "zpi-sdk/mcp";
const mcp = createMcpClient({
apiKey: "zpi_...",
baseURL: "https://api.zpi.web.id",
});
const tools = await mcp.listTools();
const result = await mcp.callTool("run_scraper", {
scraper: "instagram-scraper",
endpoint: "profile",
params: { username: "zaadevofc" },
});listTools() returns the 8 tool definitions; callTool(name, args) invokes one. RPC failures throw ZpiMcpError (with code and data). See the SDK docs for the full client reference.
All 8 tools, in registration order. Read-only tools are free; run_scraper bills per call and bulk_submit bills per item. The recommended workflow:
# The recommended 3-step workflow for your agent:
search_scrapers query="instagram profile" # 1) find an endpoint
get_scraper_schema scraper="..." endpoint="..." # 2) only if flagged needsSchemaCall
run_scraper scraper="..." endpoint="profile" params={ "username": "zaadevofc" }▸ search_scrapers results carry each endpoint's params inline, so your agent can usually go straight to run_scraper — call get_scraper_schema only when a result is flagged needsSchemaCall.
Natural-language fuzzy search over the catalog. Zero results return did-you-mean suggestions. Each row includes the endpoint's params inline plus minPlan and bulkEnabled. Categories: ai, bypass-tools, downloader, food, games, search-tools, social-media, web-tools.
| Param | Type | Required | Purpose |
|---|---|---|---|
| query | string | no | Free-text search, e.g. "instagram profile". |
| category | string | no | Filter to one category. |
| cursor | string | no | Pagination cursor. |
Full parameter schema for one endpoint: fields with name / type / required / example, plus minPlan and bulkEnabled. Unknown scraper or endpoint returns an error steering back to search_scrapers.
| Param | Type | Required | Purpose |
|---|---|---|---|
| scraper | string | yes | Scraper slug. |
| endpoint | string | yes | Endpoint slug. |
No input. Returns every catalog category with its scraper count — useful for scoping before a search.
Executes one scraper endpoint. Marked readOnlyHint: false — clients should not auto-approve it. Path params in params fill the URL template automatically and the HTTP method is chosen for you.
| Param | Type | Required | Purpose |
|---|---|---|---|
| scraper | string | yes | Scraper slug. |
| endpoint | string | yes | Endpoint slug. |
| params | object | no | Endpoint params; path params (username, slug, …) go here too. |
Success returns { data, project, cache, meta } where meta is { billed, costUnits, remaining, resetAt }. Failures set isError with a code from the error table — including plan_upgrade_required when the endpoint's minPlan is above your tier.
No input. Returns your tier, planStatus (free | active | expired), planExpiresAt, and the API key label ({ name, keyPrefix, enabled }). The key secret is never returned. Quota numbers live in get_usage, not here.
No input. Returns a summary ({ totalRequests, requests24h, errors24h, successRate24h }) plus your quota ({ tier, used, limit, remaining, resetAt }— resetAt is the 1st of next month, UTC). Check it before run_scraper to avoid burning quota blind.
Submits a batch job. Requires a Pro+ plan and bulkEnabled on the target endpoint, with per-tier item caps. Returns immediately with { jobId, status: "queued", total } — it never waits; poll with bulk_status.
| Param | Type | Required | Purpose |
|---|---|---|---|
| scraper | string | yes | Scraper slug. |
| endpoint | string | yes | Endpoint slug. |
| urls | array | yes | URL strings or per-item param objects; duplicates are removed. |
| idempotencyKey | string | no | Same key returns the same job — no double submission. |
Polls one job. Returns { jobId, status, total, succeeded, failed, scraperSlug, endpointSlug, items } where each item is { url, status, error? }. Unknown or other-account jobs return job_not_found — job ids can't be enumerated.
| Param | Type | Required | Purpose |
|---|---|---|---|
| jobId | string | yes | Id from bulk_submit. |
MCP shares rate limits with the REST API. The per-minute window counts all requests; the per-month window counts only billable ones. Free tier: 100 requests/minute, 2,000 billable requests/month — paid tiers on Pricing. A 429 response is JSON with window: "minute" | "month" telling you which limit you hit.
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Your plan's limit for the window. |
| X-RateLimit-Remaining-Minute | Requests left in the current minute. |
| X-RateLimit-Remaining-Month | Billable requests left this month. |
| X-Plan-Expired | Present when a paid plan has lapsed — free limits apply. |
minPlan — calls below it fail with plan_upgrade_required (including requiredPlan and upgradeUrl).bulkEnabled on the endpoint, and staying under your tier's per-job item cap.get_account (planStatus) if calls start rate-limiting unexpectedly.Tool failures set isError with a machine code in structuredContent.code:
| Code | Meaning |
|---|---|
| unauthenticated | Missing or invalid API key (401). |
| plan_upgrade_required | Endpoint needs a higher tier; includes requiredPlan + upgradeUrl. |
| invalid_params | Input failed validation; includes per-field details. |
| not_found | Unknown scraper or endpoint. |
| job_not_found | bulk_status jobId unknown or owned by another account. |
| disabled | Endpoint is temporarily disabled. |
| method_not_allowed | Endpoint doesn't support the resolved HTTP method. |
| stream_unsupported | Streaming endpoint — not runnable through run_scraper. |
| module_not_registered | Scraper module not loaded on the worker. |
| scraper_error | The scraper ran and failed upstream. |
| client_error | Other 4xx from the API. |
| internal_error | Unexpected server failure — retry later. |
Authorization: Bearer, x-api-key, api-key) and rotate at /dashboard/keys.window: "minute" | "month") and the X-RateLimit headers; a minute limit clears itself, a month limit means upgrading.minPlan is above your tier. See Pricing.