{
  "openapi": "3.1.0",
  "info": {
    "title": "IndexZero API",
    "version": "1.0.0",
    "summary": "SEO research, monitoring, and AI-visibility tools for agents.",
    "description": "IndexZero exposes its whole product surface as Model Context Protocol tools over a single\nStreamable HTTP endpoint at `https://app.indexzero.site/mcp`. There are 36 tools covering keyword\nresearch, domain and backlink analysis, SERP and local results, rank tracking, site audits,\nSearch Console / Google Analytics / PostHog reporting, and AI-visibility measurement.\n\nAuthentication is OAuth 2.1 with PKCE and Dynamic Client Registration (RFC 7591) for\ninteractive clients, or an `iz_` API key in the `x-api-key` header for headless ones. The\n`mcp` scope is required on every grant; `offline_access` adds a refresh token.\n\nMost tools spend workspace credits. Call `whoami` first for the plan and remaining balance,\nand `estimate_rank_tracker_cost` before a rank-tracker run. Tools that read data you already\nown — Search Console, Analytics, PostHog, saved results — are free.\n\nMachine-readable index: https://indexzero.site/llms.txt. MCP manifest: https://indexzero.site/.well-known/mcp.json.",
    "termsOfService": "https://indexzero.site/terms-of-service",
    "license": {
      "name": "IndexZero Terms of Service",
      "url": "https://indexzero.site/terms-of-service"
    },
    "contact": {
      "name": "IndexZero support",
      "email": "support@indexzero.site",
      "url": "https://indexzero.site/contact"
    }
  },
  "externalDocs": {
    "description": "IndexZero documentation",
    "url": "https://indexzero.site/docs"
  },
  "servers": [
    {
      "url": "https://app.indexzero.site",
      "description": "IndexZero production API"
    }
  ],
  "security": [
    {
      "mcpOAuth": [
        "mcp",
        "offline_access"
      ]
    },
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "MCP",
      "description": "The Model Context Protocol endpoint and its tool catalog."
    },
    {
      "name": "Discovery",
      "description": "Unauthenticated documents an agent reads before it connects."
    }
  ],
  "paths": {
    "/mcp": {
      "post": {
        "tags": [
          "MCP"
        ],
        "operationId": "callMcp",
        "summary": "Send a JSON-RPC message to the MCP server",
        "description": "Model Context Protocol endpoint, Streamable HTTP transport (MCP spec revision 2025-06-18). The body is a single JSON-RPC 2.0 request, notification, or response, or a batch array of them. Send `Accept: application/json, text/event-stream`; the server replies with a JSON body for simple calls and an SSE stream when a call produces progress notifications. After `initialize`, echo the `Mcp-Session-Id` response header on every subsequent request.",
        "parameters": [
          {
            "name": "Mcp-Session-Id",
            "in": "header",
            "required": false,
            "description": "Session identifier returned by `initialize`. Required on every request after it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "required": false,
            "description": "Negotiated MCP protocol revision, e.g. `2025-06-18`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/JsonRpcRequest"
                  },
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/JsonRpcRequest"
                    }
                  }
                ]
              },
              "examples": {
                "listTools": {
                  "summary": "List the tools this workspace can call",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list",
                    "params": {}
                  }
                },
                "callTool": {
                  "summary": "Research keywords from a seed term",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "research_keywords",
                      "arguments": {
                        "seeds": [
                          "seo platform"
                        ],
                        "limit": 50
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response, or an SSE stream when the call emits progress notifications.",
            "headers": {
              "Mcp-Session-Id": {
                "description": "Session identifier. Returned on initialize; echo it on later requests.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Server-Sent Events; each `data:` line is a JSON-RPC message."
                }
              }
            }
          },
          "202": {
            "description": "Accepted. Returned for notifications and responses, which carry no reply."
          },
          "400": {
            "description": "Malformed JSON-RPC message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or expired credentials. The `WWW-Authenticate` header points at the protected-resource metadata to start discovery.",
            "headers": {
              "WWW-Authenticate": {
                "description": "e.g. `Bearer error=\"invalid_token\", resource_metadata=\"…/.well-known/oauth-protected-resource/mcp\"`",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "402": {
            "description": "Out of credits, or the plan does not include the feature. The body names the plan that unblocks it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but the token does not carry the `mcp` scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "API key request limit reached.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "MCP"
        ],
        "operationId": "openMcpStream",
        "summary": "Open the server-to-client SSE stream",
        "description": "Long-lived Server-Sent Events stream for messages the server initiates. Requires the `Mcp-Session-Id` returned by initialize.",
        "parameters": [
          {
            "name": "Mcp-Session-Id",
            "in": "header",
            "required": false,
            "description": "Session identifier returned by `initialize`. Required on every request after it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "required": false,
            "description": "Negotiated MCP protocol revision, e.g. `2025-06-18`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "An SSE stream of JSON-RPC messages.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or expired credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "405": {
            "description": "This server does not offer a stream for this session."
          }
        }
      },
      "delete": {
        "tags": [
          "MCP"
        ],
        "operationId": "endMcpSession",
        "summary": "Terminate an MCP session",
        "parameters": [
          {
            "name": "Mcp-Session-Id",
            "in": "header",
            "required": false,
            "description": "Session identifier returned by `initialize`. Required on every request after it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Session terminated."
          },
          "401": {
            "description": "Missing, invalid, or expired credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp/catalog": {
      "get": {
        "tags": [
          "MCP"
        ],
        "operationId": "listMcpToolCatalog",
        "summary": "List every MCP tool, with its JSON Schema",
        "description": "Unauthenticated. The same catalog the MCP server registers, including each tool's input schema, so a client can decide what to call before authenticating. Which tools a given workspace may run still depends on its plan.",
        "security": [],
        "responses": {
          "200": {
            "description": "The tool catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpToolCatalog"
                }
              }
            }
          },
          "405": {
            "description": "This resource is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "406": {
            "description": "No representation matches the request's Accept header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getHealth",
        "summary": "Service health",
        "description": "Unauthenticated liveness check. Served by both origins; the app origin additionally reports database reachability.",
        "security": [],
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                }
              }
            }
          },
          "404": {
            "description": "Unknown API path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "This resource is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "406": {
            "description": "No representation matches the request's Accept header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getAuthorizationServerMetadata",
        "summary": "OAuth 2.0 authorization server metadata (RFC 8414)",
        "description": "Issuer, endpoints, supported grants, PKCE methods, and `scopes_supported`. The marketing origin redirects to this document.",
        "security": [],
        "responses": {
          "200": {
            "description": "Authorization server metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "405": {
            "description": "This resource is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "406": {
            "description": "No representation matches the request's Accept header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getProtectedResourceMetadata",
        "summary": "OAuth 2.0 protected resource metadata (RFC 9728)",
        "description": "Names the `/mcp` resource, the authorization servers that protect it, and its `scopes_supported`.",
        "security": [],
        "responses": {
          "200": {
            "description": "Protected resource metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "405": {
            "description": "This resource is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "406": {
            "description": "No representation matches the request's Accept header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "servers": [
        {
          "url": "https://indexzero.site",
          "description": "IndexZero marketing and discovery origin"
        }
      ],
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getOpenApiDocument",
        "summary": "This document",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "405": {
            "description": "This resource is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "406": {
            "description": "No representation matches the request's Accept header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "servers": [
        {
          "url": "https://indexzero.site",
          "description": "IndexZero marketing and discovery origin"
        }
      ],
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getLlmsTxt",
        "summary": "Agent instructions and link index (llms.txt)",
        "description": "What IndexZero is for, when an agent should reach for it, when it should not, and where every other machine-readable file lives.",
        "security": [],
        "responses": {
          "200": {
            "description": "Markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "405": {
            "description": "This resource is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "406": {
            "description": "No representation matches the request's Accept header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "servers": [
        {
          "url": "https://indexzero.site",
          "description": "IndexZero marketing and discovery origin"
        }
      ],
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getMcpManifest",
        "summary": "MCP server manifest",
        "description": "Endpoint, transport, authentication, scopes, and tool groups for the IndexZero MCP server, in one document.",
        "security": [],
        "responses": {
          "200": {
            "description": "The manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpManifest"
                }
              }
            }
          },
          "405": {
            "description": "This resource is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "406": {
            "description": "No representation matches the request's Accept header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "mcpOAuth": {
        "type": "oauth2",
        "description": "OAuth 2.1 with PKCE (S256). Clients may register dynamically at the registration endpoint (RFC 7591) rather than being provisioned by hand. Request the narrowest scope you need: `mcp` alone for a short-lived session, `mcp offline_access` only if the agent must survive access-token expiry.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://app.indexzero.site/api/auth/oauth2/authorize",
            "tokenUrl": "https://app.indexzero.site/api/auth/oauth2/token",
            "refreshUrl": "https://app.indexzero.site/api/auth/oauth2/token",
            "scopes": {
              "mcp": "Call IndexZero MCP tools on behalf of the authorizing workspace. Required on every grant; a token without it authenticates but reaches no tools.",
              "offline_access": "Issue a refresh token so a long-running agent keeps working after the access token expires."
            }
          }
        }
      },
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "An `iz_`-prefixed workspace API key, created self-serve at https://app.indexzero.site/ai-mcp. Also accepted as `Authorization: Bearer iz_…`. Keys are confined to `/mcp`: a key can never be exchanged for a session that reaches account or billing endpoints. Requires a plan that includes MCP access."
      }
    },
    "schemas": {
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "description": "Omit for notifications.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "method": {
            "type": "string",
            "description": "An MCP method, e.g. `initialize`, `tools/list`, `tools/call`.",
            "examples": [
              "initialize",
              "tools/list",
              "tools/call"
            ]
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              },
              "data": {}
            }
          }
        }
      },
      "ApiError": {
        "type": "object",
        "description": "The error shape every IndexZero HTTP endpoint returns. Machine-readable `code` first, human `message` second, `hint` when there is a concrete next step.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable, screaming-snake or lower-snake identifier.",
                "examples": [
                  "not_found",
                  "INSUFFICIENT_CREDITS",
                  "PLAN_GATE"
                ]
              },
              "message": {
                "type": "string"
              },
              "hint": {
                "type": "string",
                "description": "How to resolve it, when there is a single answer."
              },
              "documentation_url": {
                "type": "string",
                "format": "uri"
              },
              "denial": {
                "type": "object",
                "description": "Present on 402s: the blocked feature, the current plan, and the plan that unblocks it.",
                "additionalProperties": true
              }
            }
          }
        }
      },
      "OAuthError": {
        "type": "object",
        "description": "RFC 6749 error shape, used by the credential layer in front of `/mcp`.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "examples": [
              "invalid_token",
              "invalid_api_key",
              "rate_limited",
              "usage_exceeded"
            ]
          },
          "error_description": {
            "type": "string"
          }
        }
      },
      "HealthStatus": {
        "type": "object",
        "required": [
          "ok",
          "service",
          "timestamp"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "service": {
            "type": "string",
            "examples": [
              "indexzero-web",
              "indexzero-app"
            ]
          },
          "env": {
            "type": "string"
          },
          "database": {
            "type": "string",
            "enum": [
              "ok",
              "error"
            ]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "McpToolCatalog": {
        "type": "object",
        "required": [
          "endpoint",
          "version",
          "toolCount",
          "tools"
        ],
        "properties": {
          "endpoint": {
            "type": "string",
            "format": "uri"
          },
          "version": {
            "type": "string"
          },
          "toolCount": {
            "type": "integer"
          },
          "tools": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/McpToolDefinition"
            }
          }
        }
      },
      "McpToolDefinition": {
        "type": "object",
        "description": "One tool, in the shape an LLM function-calling API expects: a unique name, a description, and a JSON Schema for its arguments.",
        "required": [
          "name",
          "description",
          "inputSchema"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Unique tool identifier.",
            "examples": [
              "research_keywords"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "inputSchema": {
            "type": "object",
            "description": "JSON Schema for the tool's arguments.",
            "additionalProperties": true
          },
          "outputSchema": {
            "type": "object",
            "additionalProperties": true
          },
          "costsCredits": {
            "type": "boolean",
            "description": "True when calling the tool spends workspace credits."
          },
          "readOnly": {
            "type": "boolean"
          }
        }
      },
      "McpManifest": {
        "type": "object",
        "required": [
          "name",
          "endpoint",
          "transport",
          "authentication"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "endpoint": {
            "type": "string",
            "format": "uri"
          },
          "transport": {
            "type": "string",
            "enum": [
              "streamable-http"
            ]
          },
          "authentication": {
            "type": "object",
            "additionalProperties": true
          },
          "toolCount": {
            "type": "integer"
          },
          "toolGroups": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      }
    }
  }
}
