# Ranked AI Developer Docs > Build on Ranked AI's SEO platform: a REST API for keyword rankings, AI visibility, site audits, backlinks, content calendars and reports; a TypeScript SDK; an MCP server for ChatGPT, Claude and Cursor; and signed webhooks. # Ranked AI Developer Docs Source: https://www.ranked.ai/developers > Build on top of Ranked AI with our REST API, MCP integration, and webhooks ## Welcome Ranked AI provides a complete SEO data platform that you can integrate into your own tools, dashboards, and workflows. Access keyword rankings, AI visibility data, site audits, backlinks, content calendars, and more. - [REST API](https://www.ranked.ai/developers/quickstart): Pull SEO data into your applications with our REST API. Keywords, audits, backlinks, AI visibility, and more. - [TypeScript SDK](https://www.ranked.ai/developers/sdk): Get started fast with our typed SDK. Auto-pagination, error handling, and webhook verification built in. - [MCP Integration](https://www.ranked.ai/developers/mcp/overview): Connect Ranked AI to ChatGPT, Claude, Cursor, and other AI tools via the Model Context Protocol. - [Webhooks](https://www.ranked.ai/developers/webhooks/overview): Receive real-time notifications when keyword positions update, content changes, or audits complete. ## Quick Links - [Get your API key](https://www.ranked.ai/developers/authentication): Create an API key from your dashboard to start making requests. - [API Reference](https://www.ranked.ai/developers/api-reference/introduction): Full endpoint documentation with request/response examples. - [TypeScript SDK](https://www.ranked.ai/developers/sdk): Typed client with auto-pagination and webhook verification. - [Build an agency dashboard](https://www.ranked.ai/developers/guides/agency-dashboard): Step-by-step guide for agencies building custom client dashboards. - [Webhook events](https://www.ranked.ai/developers/webhooks/events): All available webhook events and their payload schemas. --- # Quickstart Source: https://www.ranked.ai/developers/quickstart > Make your first API call in under 2 minutes ## 1. Create an API key - [Create an API Key](https://app.ranked.ai/dashboard/settings?tab=api-keys): Go to Settings > API in your Ranked AI dashboard. Choose your permission level: - **Read Only** (default) -- fetch keywords, audits, backlinks, AI visibility, content, and reports - **Read + Write** -- everything above, plus create reports, manage webhooks, and update preferences > **Warning:** API keys are shown only once when created. Copy and store it securely. Keys are tied to the project owner account and can only access projects you own directly. ## 2. Find your project ID Every API call requires a project ID. List your projects to find it: ```bash cURL curl https://app.ranked.ai/api/v1/projects \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```javascript JavaScript const response = await fetch('https://app.ranked.ai/api/v1/projects', { headers: { 'Authorization': 'Bearer rk_live_your_api_key' } }); const { data } = await response.json(); data.forEach(project => { console.log(`${project.name}: ${project.id}`); }); ``` ```python Python import requests response = requests.get( 'https://app.ranked.ai/api/v1/projects', headers={'Authorization': 'Bearer rk_live_your_api_key'} ) for project in response.json()['data']: print(f"{project['name']}: {project['id']}") ``` Response: ```json { "data": [ { "id": "40596405-c27c-4dfc-89e4-142c87846d66", "name": "My Website", "status": "active", "serviceType": "seo", "productMode": "managed", "websiteUrl": "https://example.com" } ] } ``` Copy the `id` value -- you'll use it in all subsequent calls. `productMode` tells you whether Ranked AI's team runs the project (`managed`) or you run it yourself with the software (`software`); the data endpoints work the same for both, and software projects simply have no content calendar. > **Tip:** No project yet? A Read + Write key can [create a software project](https://www.ranked.ai/developers/api-reference/projects/create) from a website URL, no plan or payment needed. ## 3. Fetch your data Use the project ID to pull keyword rankings, audit results, AI visibility, and more. ### Keyword rankings ```bash cURL curl "https://app.ranked.ai/api/v1/projects/YOUR_PROJECT_ID/rankings/keywords?limit=10" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```javascript JavaScript const base = `https://app.ranked.ai/api/v1/projects/${projectId}`; const headers = { 'Authorization': `Bearer ${apiKey}` }; const response = await fetch(`${base}/rankings/keywords?limit=10`, { headers }); const { data } = await response.json(); data.forEach(kw => { console.log(`${kw.keyword}: Desktop ${kw.desktop_position}, Mobile ${kw.mobile_position}, Net Change ${kw.net_change > 0 ? '+' : ''}${kw.net_change}`); }); ``` ```python Python import requests base = f'https://app.ranked.ai/api/v1/projects/{project_id}' headers = {'Authorization': f'Bearer {api_key}'} response = requests.get(f'{base}/rankings/keywords', headers=headers, params={'limit': 10}) for kw in response.json()['data']: print(f"{kw['keyword']}: Desktop {kw['desktop_position']}, Net Change {kw['net_change']}") ``` Each keyword includes positions across four channels: | Field | Description | |-------|-------------| | `desktop_position` | Google Desktop rank | | `mobile_position` | Google Mobile rank | | `ai_mode_position` | Google AI Mode rank | | `maps_position` | Google Maps rank | | `net_change` | Combined position change across all channels | ## What else is available | Endpoint | What it returns | |----------|----------------| | `GET /rankings/keywords` | Keyword positions across Desktop, Mobile, AI Mode, Maps | | `GET /prompts` | AI visibility across ChatGPT, Claude, Gemini, Perplexity, Grok, Meta | | `GET /audits/latest` | Latest site audit results with issue counts | | `GET /backlinks/summary` | Backlink profile with referring domains | | `GET /content` | Content calendar with status and scheduling | | `GET /reports` | Shareable SEO report links | | `POST /webhooks` | Real-time notifications when data changes | | `POST /projects` | Create a self-serve software project (no plan needed to create it) | See the [full API reference](https://www.ranked.ai/developers/api-reference/introduction) for all endpoints and parameters. ## Build with AI Copy this prompt into Cursor, Claude, or ChatGPT to build an integration: **Give this to your AI coding tool to build a Ranked AI integration.** ```text I need to integrate with the Ranked AI REST API. Here's everything you need: Base URL: https://app.ranked.ai/api/v1 Auth: Bearer token in Authorization header (format: rk_live_...) Docs: https://www.ranked.ai/developers How it works: 1. GET /projects → returns array of projects with id, name, websiteUrl, status 2. Use project ID in all other endpoints: /projects/{projectId}/... Available endpoints: - GET /projects → list SEO projects (each has productMode "managed" or "software"; ?product_mode= filters) - POST /projects → create a self-serve software project from { website_url, name? } (needs write key; response has plan.active + plan.manageUrl) - GET /projects/{id}/rankings/keywords?limit=1000 → keyword positions (desktop_position, mobile_position, ai_mode_position, maps_position, net_change, location, last_checked) - GET /projects/{id}/rankings/keywords/{keywordId}/history → daily position history - GET /projects/{id}/prompts?limit=200 → AI visibility across ChatGPT, Claude, Gemini, Perplexity, Grok, Meta (visibility_percentage, average_position, latest_responses per model) - GET /projects/{id}/prompts/{promptId}/history → full AI model responses with citations - GET /projects/{id}/audits/latest → latest site audit (total_issues, critical_issues, warning_issues, notice_issues) - GET /projects/{id}/audits/{auditId}/issues → individual audit issues with severity and affected_count - GET /projects/{id}/backlinks/summary → total backlinks, referring domains - GET /projects/{id}/backlinks/domains?limit=100 → referring domains with domain_rank - GET /projects/{id}/content?limit=100 → content calendar (title, status, scheduled_date) - GET /projects/{id}/reports → shareable report links - POST /projects/{id}/reports → create report (needs write key) All responses: { success: true, data: [...], meta: { pagination: { total, limit, offset, has_more } } } Webhooks (needs write key): - POST /webhooks → create subscription with url, project_id, events array - Events: keywords.updated, content.status_changed, content.created, audit.started, audit.completed, prompts.updated - Payloads include X-Webhook-Signature header (HMAC-SHA256) for verification Rate limits: 200/min, 5000/hr, 50000/day per key Max limits: 1000 keywords, 200 prompts, 500 projects per request ``` ## Next steps - [API Reference](https://www.ranked.ai/developers/api-reference/introduction): Full endpoint documentation with examples. - [Webhooks](https://www.ranked.ai/developers/webhooks/overview): Get notified when keyword positions update, content changes, or audits complete. - [MCP Integration](https://www.ranked.ai/developers/mcp/overview): Connect Ranked AI to ChatGPT, Claude, or Cursor. - [Agency Dashboard](https://www.ranked.ai/developers/guides/agency-dashboard): Build a custom client dashboard with the API and webhooks. --- # Authentication Source: https://www.ranked.ai/developers/authentication > Authenticate API requests with Bearer tokens ## API Keys All API requests require a Bearer token in the `Authorization` header. - [Create an API Key](https://app.ranked.ai/dashboard/settings?tab=api-keys): Go to Settings > API in your dashboard to create and manage API keys. ```bash curl https://app.ranked.ai/api/v1/projects \ -H "Authorization: Bearer rk_live_your_api_key" ``` ### Key types | Scope | Permissions | |-------|------------| | **Read Only** (default) | Fetch all data: keywords, audits, backlinks, prompts, content, reports | | **Read + Write** | Everything above, plus: create reports, manage webhooks, update content preferences | ### Key format API keys follow the format `rk_live_` followed by a random string. Example: ``` rk_live_PJcyKvCPW1lSdCtC0-Gh0MrwCv3poWF5 ``` > **Warning:** Keys are shown only once when created. Store them securely -- you cannot retrieve a key after closing the creation dialog. ## Response format All responses follow a consistent envelope: ### Success ```json { "success": true, "data": { ... }, "meta": { "request_id": "req_abc123", "rate_limit": { "limit": 100, "remaining": 99, "reset": 1778891947 }, "pagination": { "total": 20, "limit": 50, "offset": 0, "has_more": false } } } ``` ### Error ```json { "success": false, "error": { "code": "UNAUTHORIZED", "message": "Invalid API key" }, "meta": { "request_id": "req_abc123" } } ``` ### Error codes | Code | HTTP Status | Description | |------|------------|-------------| | `UNAUTHORIZED` | 401 | Missing or invalid API key | | `FORBIDDEN` | 403 | Valid key but insufficient permissions (e.g., read-only key attempting a write) | | `NOT_FOUND` | 404 | Resource not found | | `VALIDATION_ERROR` | 400 | Invalid request parameters | | `RATE_LIMITED` | 429 | Too many requests | | `INTERNAL_ERROR` | 500 | Server error | ## Pagination List endpoints support `limit` and `offset` query parameters: ```bash # Get 20 keywords starting from the 40th curl "https://app.ranked.ai/api/v1/projects/{id}/rankings/keywords?limit=20&offset=40" \ -H "Authorization: Bearer rk_live_..." ``` - **Default limit**: 50 - **Maximum limit**: 1000 (keywords), 200 (prompts), 500 (projects) - **Offset**: 0-based The `meta.pagination` object indicates `total` count and `has_more` flag. ## Rate limits | Window | Limit | |--------|-------| | Per minute | 200 requests | | Per hour | 5,000 requests | | Per day | 50,000 requests | Rate limit headers are included in every response via `meta.rate_limit`: ```json { "limit": 100, "remaining": 97, "reset": 1778891947 } ``` When rate limited, you'll receive a `429` response. Wait until `reset` (Unix timestamp) before retrying. --- # MCP Overview Source: https://www.ranked.ai/developers/mcp/overview > Connect Ranked AI to ChatGPT, Claude, Cursor, and other AI tools ## What is MCP? The **Model Context Protocol** (MCP) lets AI tools like ChatGPT, Claude, and Cursor interact with your Ranked AI data directly. Ask questions about your SEO performance in natural language. ## Capabilities | Category | What you can do | |----------|----------------| | **Keywords** | View positions across Desktop, Mobile, AI Mode, and Maps with position changes over 7d, 30d, 60d, 90d, 180d, 365d, or all time | | **AI Visibility** | Check brand visibility across 6 AI models with per-model mention rates and citation counts | | **Audits** | Get audit results with issue counts by severity. Run new audits | | **Backlinks** | View referring domains, new/lost tracking, dofollow/nofollow breakdown | | **Content** | Browse content calendar, approve articles, request revisions, submit topics | | **Traffic** | Google Search Console impressions, clicks, and top queries; Google Analytics sessions, users, and channels | | **Reports** | Generate shareable SEO report links | | **Heatmaps** | View local SEO ranking heatmap data | ## Available tools ### Read tools (11) | Tool | Description | |------|-------------| | `ranked_get_project_overview` | List all projects or get detailed metrics for one project | | `ranked_get_keyword_rankings` | Keyword positions with per-device changes and net change | | `ranked_get_ai_visibility` | Brand visibility across ChatGPT, Claude, Gemini, Perplexity, Grok, Meta Llama | | `ranked_get_audit_summary` | Latest audit issue counts scoped to most recent audit | | `ranked_get_audit_details` | Affected URLs for a specific audit issue | | `ranked_get_backlink_summary` | Backlink profile with dofollow/nofollow counts | | `ranked_get_content_calendar` | Content items filtered by status with scheduling info | | `ranked_get_heatmaps` | Local SEO ranking and competition heatmaps | | `ranked_get_sitemap_indexing` | Sitemap URLs and Google indexing status | | `ranked_get_search_console_metrics` | Google Search Console impressions, clicks, CTR, and top queries over any window | | `ranked_get_analytics_metrics` | Google Analytics sessions, users, page views, and channel breakdown over any window | ### Write tools (11) | Tool | Description | |------|-------------| | `ranked_create_software_project` | Create a self-serve SEO Software project from a website URL | | `ranked_add_keywords` | Add keywords to track in search results | | `ranked_remove_keywords` | Remove tracked keywords by ID | | `ranked_add_prompts` | Add AI visibility prompts for brand monitoring | | `ranked_request_topic` | Submit a content topic request | | `ranked_approve_content` | Approve content for publishing | | `ranked_request_revision` | Request a content revision with notes | | `ranked_generate_report` | Create a shareable SEO report link | | `ranked_run_audit` | Start a new site audit | | `ranked_update_content_preferences` | Update the project's content writing preferences | | `ranked_update_publishing_preferences` | Update the project's publishing preferences | > **Info:** Projects come in two product modes. **Managed** projects are run by Ranked AI's team and every tool applies. **Software** projects are self-serve (the account runs the software itself, no content pipeline), so the content tools (`ranked_request_topic`, `ranked_approve_content`, `ranked_request_revision`, `ranked_update_content_preferences`, `ranked_update_publishing_preferences`) explain the upgrade to the managed service instead of acting. `ranked_get_project_overview` reports each project's `productMode`, the account's software plan and, per software project, an `addServiceUrl` for that upgrade. --- # MCP Setup Source: https://www.ranked.ai/developers/mcp/setup > Connect your Ranked AI account to AI tools via MCP ## Install the Ranked AI skill Install our documentation skill so your AI tools have full context on the Ranked AI API: **Install the Ranked AI skill for AI-assisted development.** ```text npx skills add https://www.ranked.ai ``` ## ChatGPT 1. Open **ChatGPT Settings > Connected Apps** 2. Search for **Ranked AI** and click **Connect** 3. Sign in with your Ranked AI account to authorize access 4. Start asking about your SEO data in any ChatGPT conversation ### Example prompts ``` How are my keyword rankings doing this month? ``` ``` What's my AI visibility across all models? ``` ``` Show me my latest audit results ``` ## Claude 1. Open **Settings > Connectors** (claude.ai or Claude Desktop) 2. Click **Add custom connector** 3. Enter `https://app.ranked.ai/api/mcp` as the server URL 4. Sign in with your Ranked AI account when prompted ## Cursor 1. Open **Cursor Settings > MCP** 2. Add a new MCP server with URL: `https://app.ranked.ai/api/mcp` 3. Authenticate when prompted ## Server URL The MCP endpoint is: ``` https://app.ranked.ai/api/mcp ``` The previous URL, `https://app.ranked.ai/api/mcp/sse`, keeps working forever — existing connections don't need to change. Use `/api/mcp` for new setups. The server supports Streamable HTTP and the stateless 2026-07-28 MCP revision, and negotiates the protocol version your client requests. ## Authentication The MCP integration uses **OAuth 2.0** with authorization code flow. When you first connect, you'll be redirected to sign in with your Ranked AI account. Access tokens are automatically refreshed. - Access tokens expire after 30 days - Refresh tokens last 1 year - Scopes: `read:projects`, `read:keywords`, `read:audits`, `read:backlinks`, `read:content`, `read:ai` > **Info:** The MCP integration has read and write access to your project data. Write actions (like approving content) show a confirmation prompt before executing. --- # MCP Tool Reference Source: https://www.ranked.ai/developers/mcp/tools > Detailed reference for all MCP tools ## Read tools ## ranked_get_project_overview List all your projects, or get detailed metrics for one project. Call this first — every other tool needs a `project_id` from it. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | No | Project UUID. Omit to list all accessible projects | ### Response includes - Without `project_id`: every accessible project with keyword, AI prompt, and audit issue counts, plus each project's `productMode` (`managed` or `software`). When the account has software projects the list also carries `softwarePlan` (capacity, usage, price, renewal) and a `productModes` explainer. - With `project_id`: average position, AI visibility, audit issue counts by severity, backlink totals, and content calendar counts. Software projects add an `addServiceUrl`, the link that upgrades the project to the managed service. --- ## ranked_get_keyword_rankings Get keyword positions across all search channels with position changes. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `limit` | number | No | Max keywords to return (default: 50, max: 1000) | | `date_range` | string | No | Comparison period: `7d`, `30d`, `60d`, `90d`, `180d`, `365d`, `all` (default: `30d`) | ### Response fields Each keyword includes: | Field | Description | |-------|-------------| | `desktopPosition` | Current Google Desktop rank | | `mobilePosition` | Current Google Mobile rank | | `aiModePosition` | Current Google AI Mode rank | | `mapsPosition` | Current Google Maps rank | | `desktopChange` | Position change for the date range | | `mobileChange` | Position change for the date range | | `aiModeChange` | Position change for the date range | | `mapsChange` | Position change for the date range | | `netChange` | Sum of all channel changes (positive = improved) | | `location` | Target location name | ### Net change A positive `netChange` means the keyword improved. It is the sum of position changes across all four channels for the selected date range. --- ## ranked_get_ai_visibility Get brand visibility across 6 AI models. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `limit` | number | No | Max prompts to return (default: 20, max: 200) | ### Response includes - Overall visibility percentage and average position - Per-prompt breakdown with visibility change indicators - Per-model mention rates and average positions - AI search volume estimates per prompt - Per-model status (mentioned, position, citation count) from latest analysis --- ## ranked_get_audit_summary Get the latest audit results scoped to the most recent audit task. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `limit` | number | No | Max issues to return (default: 20, max: 50) | | `severity` | string | No | Filter: `critical`, `warning`, or `notice` | --- ## ranked_get_audit_details Get the affected URLs for a specific audit issue. Use `ranked_get_audit_summary` first to get issue IDs. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `issue_id` | string | Yes | Audit issue UUID from `ranked_get_audit_summary` | | `limit` | number | No | Max affected items to return (default: 20, max: 50) | --- ## ranked_get_backlink_summary Get backlink profile overview. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `limit` | number | No | Max referring domains (default: 20, max: 50) | ### Response includes - Total backlinks and referring domains - DoFollow/NoFollow breakdown - New and lost backlinks in last 30 days - Top referring domains --- ## ranked_get_content_calendar Get content calendar items. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `limit` | number | No | Max items (default: 30, max: 50) | | `status` | string | No | Filter by status name (e.g., `Approved`, `Published`) | --- ## ranked_get_heatmaps Get local SEO heatmaps showing how the business ranks across a geographic grid in Google Maps. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `limit` | number | No | Max heatmaps to return (default: 10, max: 20) | | `heatmap_type` | string | No | Filter: `competition` or `ranking` | --- ## ranked_get_sitemap_indexing Get sitemap URLs and their Google indexing status. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `limit` | number | No | Max URLs to return (default: 50, max: 100) | | `indexed_status` | string | No | Filter: `submitted`, `indexed`, or `not_indexed` | --- ## ranked_get_search_console_metrics Get Google Search Console traffic data. Requires the project to have Google Search Console connected. GSC keeps around 16 months of history, and the most recent 2-3 days can be partial while Google finalizes them. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `days` | number | No | Trailing window in days (default: 30, max: 500). Ignored when `start_date` is set | | `start_date` | string | No | `YYYY-MM-DD` exact window start, overrides `days` | | `end_date` | string | No | `YYYY-MM-DD` exact window end (default: today) | | `limit` | number | No | Max top queries to return (default: 15, max: 100) | ### Response includes - Total impressions and clicks for the window (computed from daily property totals) - Average CTR and average position - Top queries with per-query impressions, clicks, CTR, and position - Daily impressions/clicks breakdown For period comparisons ("this month vs last month"), call the tool twice with two exact windows and compare the results. --- ## ranked_get_analytics_metrics Get Google Analytics traffic data. Requires the project to have Google Analytics connected. GA aggregates in the property's timezone, so today and yesterday can be partial. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `days` | number | No | Trailing window in days (default: 30, max: 500). Ignored when `start_date` is set | | `start_date` | string | No | `YYYY-MM-DD` exact window start, overrides `days` | | `end_date` | string | No | `YYYY-MM-DD` exact window end (default: today) | ### Response includes - Sessions, users, page views, and bounce rate for the window - Daily sessions/users breakdown - Channel breakdown (organic, direct, referral, ...) with sessions, users, and new users --- ## Write tools ## ranked_create_software_project Create a self-serve SEO Software project for the connected account from a website URL. No plan or payment is needed to create it: the project exists immediately and appears in the dashboard's Software Suite. Software projects are run by the account itself (rank tracking, AI visibility prompts, audits, backlinks, heatmaps, integrations, reports); Ranked AI's team does no content, publishing or optimization work on them. Adding keywords or prompts and running scans needs the account's SEO Software plan ($4.99/month per 100 tracked keywords and per 100 AI prompts, one plan for all software projects) and the response says whether one is active and where to add it. > **Info:** Managed-service projects (Ranked AI does the work, from $99/month with a free trial, software suite included) cannot be created here. They start from the dashboard's Add Project flow. The AI should confirm the website URL with you before calling this tool. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `website_url` | string | Yes | The website to track, e.g. `acme.com` or `https://acme.com` | | `name` | string | No | Project name; defaults to the domain | ### Response includes - The new project's id, name and dashboard link - Whether the account's software plan is active, its remaining capacity, and the page to add or change the plan - An `addServiceUrl` that opens the "Add our SEO service" plan picker on the project --- ## ranked_add_keywords Add keywords to track in Google search results. Duplicates are automatically skipped. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `keywords` | string[] | Yes | Keyword strings to track | | `location` | string | No | Target location, e.g. `Waterbury, Connecticut, United States` (default: `United States`) | --- ## ranked_remove_keywords Remove tracked keywords by ID. Use `ranked_get_keyword_rankings` first to get keyword IDs. > **Warning:** This permanently deletes the keywords and their position history. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `keyword_ids` | string[] | Yes | Keyword UUIDs to remove | --- ## ranked_add_prompts Add prompts to track brand visibility across 6 AI models. Prompts should be natural questions someone would ask an AI assistant, without mentioning the brand name. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `prompts` | string[] | Yes | Prompt questions to track | | `target_location` | string | No | Target location (default: `Global`) | --- ## ranked_request_topic > **Info:** This and the other content tools (`ranked_approve_content`, `ranked_request_revision`, `ranked_update_content_preferences`, `ranked_update_publishing_preferences`) only act on **managed** projects. On a self-serve software project they return an explanation of the managed-service upgrade (with the project's `addServiceUrl`) instead of "no content found". Submit a topic request for content generation. The topic is queued for article creation. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `topic` | string | Yes | The topic or article title to request | | `description` | string | No | Optional context or brief for the content | --- ## ranked_approve_content Approve a content item for publishing. Sets the status to `Approved` and adds it to the publishing queue. Use `ranked_get_content_calendar` first to get content IDs. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `content_id` | string | Yes | Content calendar item UUID | --- ## ranked_request_revision Request a revision on a content item. Sets the status to `Revising` and attaches the revision notes. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `content_id` | string | Yes | Content calendar item UUID | | `notes` | string | Yes | What needs to be changed | --- ## ranked_generate_report Generate a shareable SEO report link covering keywords, AI visibility, audits, backlinks, and analytics. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `date_range` | string | No | `7days`, `30days`, `90days`, or `lastMonth` | | `title` | string | No | Custom report title | --- ## ranked_run_audit Start a technical SEO audit that crawls the project website for issues. Results are available via `ranked_get_audit_summary` once the crawl completes. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `max_pages` | number | No | Maximum pages to crawl (default: 100, max: 500) | --- ## ranked_update_content_preferences Update the project's content preferences (tone, style, topics, guidelines). This overwrites the existing content preferences text. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `preferences` | string | Yes | The new content preferences text | --- ## ranked_update_publishing_preferences Update the project's publishing preferences (schedule, formatting, SEO guidelines). This overwrites the existing publishing preferences text. ### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `project_id` | string | Yes | Project UUID | | `publishing_preferences` | string | Yes | The new publishing preferences text | --- # Webhooks Overview Source: https://www.ranked.ai/developers/webhooks/overview > Receive real-time notifications when data changes in your Ranked AI projects ## How webhooks work Instead of polling the API for changes, webhooks push notifications to your server when events occur. When a keyword position updates, content gets approved, or an audit completes, we send an HTTP POST to your registered URL. ``` Your App Ranked AI | | | POST /api/v1/webhooks | | (subscribe to events) | |-------------------------->| | | | | keyword positions update | | -----------------------> | POST https://your.app | | (event payload) | |<--------------------------| | | | 200 OK | |-------------------------->| ``` ## Quick setup ### 1. Create a webhook subscription Requires a **Read + Write** API key: ```bash curl -X POST https://app.ranked.ai/api/v1/webhooks \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{ "name": "My Dashboard", "url": "https://your-app.com/webhooks/ranked", "project_id": "your-project-uuid", "events": ["keywords.updated", "content.status_changed", "audit.completed"] }' ``` ### 2. Handle incoming webhooks ```javascript app.post('/webhooks/ranked', (req, res) => { const event = req.body; switch (event.event) { case 'keywords.updated': // Sync latest keyword positions syncKeywords(event.project_id, event.data.keywords_updated); break; case 'content.status_changed': // Update content tracker updateContent(event.data.content_id, event.data.new_status); break; case 'audit.completed': // Pull latest audit results fetchAuditResults(event.data.audit_id); break; } res.status(200).send('OK'); }); ``` ### 3. Verify the signature Every webhook includes an HMAC-SHA256 signature for verification. See [Webhook Security](https://www.ranked.ai/developers/webhooks/security). ## Webhook payload format ```json { "event": "keywords.updated", "timestamp": "2026-05-16T00:10:17.701Z", "project_id": "40596405-c27c-4dfc-89e4-142c87846d66", "data": { "keywords_updated": 20, "job_id": "abc-123" } } ``` ## Delivery behavior | Behavior | Details | |----------|---------| | **Retries** | 3 attempts with exponential backoff (1s, 2s, 4s) | | **Timeout** | 30 seconds per delivery attempt | | **Auto-disable** | Subscription disabled after 5 consecutive failures | | **Re-enable** | Update the subscription via API to set `is_active: true` | ## Requirements - Webhook URL must use **HTTPS** - Your endpoint must return a **2xx** status code within 30 seconds - Payload is sent as **JSON** with `Content-Type: application/json` --- # Webhook Events Source: https://www.ranked.ai/developers/webhooks/events > All available webhook events and their payload schemas ## Event types Subscribe to any combination of these events when creating a webhook. ### Content events | Event | Fires when | |-------|-----------| | `content.created` | A new content item is added to the calendar | | `content.status_changed` | Content status changes (approved, revising, published) | #### content.created ```json { "event": "content.created", "timestamp": "2026-05-16T00:10:17.701Z", "project_id": "40596405-c27c-4dfc-89e4-142c87846d66", "data": { "content_id": "abc-123", "title": "Best Tree Care Tips for Spring", "content_type": "Blog Post", "scheduled_date": "2026-05-20T00:00:00Z" } } ``` #### content.status_changed ```json { "event": "content.status_changed", "timestamp": "2026-05-16T00:10:17.701Z", "project_id": "40596405-c27c-4dfc-89e4-142c87846d66", "data": { "content_id": "abc-123", "title": "Best Tree Care Tips for Spring", "new_status": "Approved" } } ``` ### Audit events | Event | Fires when | |-------|-----------| | `audit.started` | A site audit begins crawling | | `audit.completed` | A site audit finishes and results are ready | #### audit.started ```json { "event": "audit.started", "timestamp": "2026-05-16T00:10:17.701Z", "project_id": "40596405-c27c-4dfc-89e4-142c87846d66", "data": { "audit_id": "abc-123", "target_url": "https://example.com", "max_crawl_pages": 200 } } ``` #### audit.completed ```json { "event": "audit.completed", "timestamp": "2026-05-16T01:15:00.000Z", "project_id": "40596405-c27c-4dfc-89e4-142c87846d66", "data": { "audit_id": "abc-123", "target_url": "https://example.com", "pages_crawled": 34 } } ``` > **Tip:** After receiving `audit.completed`, call `GET /api/v1/projects/{id}/audits/latest` to fetch the full audit results. ### Ranking events | Event | Fires when | |-------|-----------| | `keywords.updated` | Daily keyword scan completes for a project | | `prompts.updated` | AI visibility analysis completes for a prompt | #### keywords.updated Fires once per project when the daily keyword position scan finishes (not per keyword). ```json { "event": "keywords.updated", "timestamp": "2026-05-16T06:30:00.000Z", "project_id": "40596405-c27c-4dfc-89e4-142c87846d66", "data": { "keywords_updated": 20, "job_id": "abc-123" } } ``` > **Tip:** After receiving `keywords.updated`, call `GET /api/v1/projects/{id}/rankings/keywords` to fetch the latest positions. #### prompts.updated Fires when all AI models have responded for a prompt analysis. ```json { "event": "prompts.updated", "timestamp": "2026-05-16T00:30:51.000Z", "project_id": "40596405-c27c-4dfc-89e4-142c87846d66", "data": { "prompt_id": "abc-123", "visibility_percentage": 50, "average_position": 5, "best_model": "openai/gpt-5.4-nano", "models_checked": 6 } } ``` --- # Webhook Security Source: https://www.ranked.ai/developers/webhooks/security > Verify webhook signatures to ensure payloads are authentic ## Getting your webhook secret When you create a webhook subscription, the API response includes a `secret` field. This is the only time the secret is shown -- store it immediately in your environment variables or secrets manager. ```bash # Create a webhook subscription curl -X POST https://app.ranked.ai/api/v1/webhooks \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{ "name": "My Dashboard", "url": "https://your-app.com/webhooks/ranked", "project_id": "your-project-uuid", "events": ["keywords.updated", "audit.completed"] }' ``` The response contains the secret: ```json { "data": { "id": "3a2e02d7-106f-4425-9518-9597dbf5a23a", "url": "https://your-app.com/webhooks/ranked", "secret": "whsec_daca66d72437cbe7767ec3c3bea5fe359637832f800ca4d284ed39ffc624a611", ... } } ``` Save `whsec_daca66d...` as an environment variable (e.g., `RANKED_WEBHOOK_SECRET`). You'll use it to verify every incoming delivery. > **Warning:** The secret is only shown once when the subscription is created. If you lose it, delete the webhook and create a new one. ## Verifying signatures Every webhook delivery includes an HMAC-SHA256 signature in the `X-Webhook-Signature` header. Use your stored secret to verify the payload hasn't been tampered with. ### Headers sent with each delivery | Header | Description | |--------|-------------| | `X-Webhook-Signature` | `sha256=` followed by the HMAC-SHA256 hex digest | | `X-Webhook-Timestamp` | Unix timestamp when the webhook was sent | | `X-Webhook-Event` | Event type (e.g., `keywords.updated`) | | `X-Webhook-Delivery-Attempt` | Attempt number (1, 2, or 3) | | `User-Agent` | `RankedAI-Webhooks/1.0` | ### Verifying in Node.js ```javascript const crypto = require('crypto'); function verifyWebhook(payload, signature, secret) { const expectedSignature = 'sha256=' + crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } // Express middleware app.post('/webhooks/ranked', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-webhook-signature']; const payload = req.body.toString(); if (!verifyWebhook(payload, signature, process.env.RANKED_WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(payload); // Process event... res.status(200).send('OK'); }); ``` ### Verifying in Python ```python import hmac import hashlib def verify_webhook(payload: bytes, signature: str, secret: str) -> bool: expected = 'sha256=' + hmac.new( secret.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) # Flask example @app.route('/webhooks/ranked', methods=['POST']) def handle_webhook(): signature = request.headers.get('X-Webhook-Signature') payload = request.get_data() if not verify_webhook(payload, signature, WEBHOOK_SECRET): return 'Invalid signature', 401 event = request.get_json() # Process event... return 'OK', 200 ``` ## Best practices - Always verify signatures before processing webhooks - Use `crypto.timingSafeEqual` (Node.js) or `hmac.compare_digest` (Python) to prevent timing attacks - Return a `200` response quickly, then process the event asynchronously - Implement idempotency -- the same event may be delivered more than once during retries - Check the `X-Webhook-Timestamp` to reject old payloads (e.g., older than 5 minutes) --- # Storing Webhook Data Source: https://www.ranked.ai/developers/webhooks/storing-data > Best practices for persisting webhook events in your database ## Why store webhook data Webhook events are delivered once (with retries). If your server is down or you need historical data, you'll want to persist events in your own database. This is especially important for: - **Building dashboards** -- cached data means faster page loads and no API calls per user visit - **Historical tracking** -- compare keyword positions over time beyond what the API returns - **Audit trails** -- record when content was approved, audits completed, etc. ## What to store ### Keyword position snapshots When you receive a `keywords.updated` event, fetch and store the full keyword data: ```sql CREATE TABLE keyword_snapshots ( id SERIAL PRIMARY KEY, project_id TEXT NOT NULL, keyword_id TEXT NOT NULL, keyword TEXT NOT NULL, desktop_position INTEGER, mobile_position INTEGER, ai_mode_position INTEGER, maps_position INTEGER, net_change INTEGER, location TEXT, recorded_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_snapshots_project ON keyword_snapshots(project_id, recorded_at); CREATE INDEX idx_snapshots_keyword ON keyword_snapshots(keyword_id, recorded_at); ``` ### Webhook event log Store every webhook event for debugging and replay: ```sql CREATE TABLE webhook_events ( id SERIAL PRIMARY KEY, event_type TEXT NOT NULL, project_id TEXT NOT NULL, payload JSONB NOT NULL, signature TEXT, processed_at TIMESTAMP DEFAULT NOW(), status TEXT DEFAULT 'received' ); CREATE INDEX idx_events_type ON webhook_events(event_type, processed_at); ``` ## Processing pattern Always store the raw event first, then process it. This ensures you never lose data even if processing fails: ```javascript app.post('/webhooks/ranked', express.raw({ type: 'application/json' }), async (req, res) => { const signature = req.headers['x-webhook-signature']; const payload = req.body.toString(); // Verify signature if (!verifyWebhook(payload, signature, process.env.RANKED_WEBHOOK_SECRET)) { return res.status(401).send(); } const event = JSON.parse(payload); // 1. Store raw event immediately await db.query( 'INSERT INTO webhook_events (event_type, project_id, payload, signature) VALUES ($1, $2, $3, $4)', [event.event, event.project_id, event, signature] ); // 2. Return 200 quickly res.status(200).send('OK'); // 3. Process asynchronously try { await processEvent(event); await db.query("UPDATE webhook_events SET status = 'processed' WHERE payload->>'timestamp' = $1", [event.timestamp]); } catch (err) { await db.query("UPDATE webhook_events SET status = 'failed' WHERE payload->>'timestamp' = $1", [event.timestamp]); console.error('Failed to process event:', err); } }); ``` ## Handling duplicates The same event may be delivered more than once during retries. Use the event timestamp and type as a deduplication key: ```javascript async function processEvent(event) { // Check if already processed const existing = await db.query( "SELECT id FROM webhook_events WHERE event_type = $1 AND payload->>'timestamp' = $2 AND status = 'processed'", [event.event, event.timestamp] ); if (existing.rows.length > 0) { console.log('Duplicate event, skipping'); return; } // Process the event... } ``` ## Data retention Consider how long you need to keep data: | Data | Suggested retention | Reason | |------|-------------------|--------| | Keyword snapshots | 12+ months | Track long-term ranking trends | | Webhook event log | 30-90 days | Debugging and replay | | Audit results | 6+ months | Compare site health over time | | Content events | 90 days | Activity audit trail | For long-term keyword data, consider aggregating old daily snapshots into weekly or monthly summaries to save storage. ## Database recommendations | Use case | Recommended | |----------|-------------| | Simple setup | [Supabase](https://supabase.com/) (PostgreSQL, free tier available) | | Already using Postgres | Your existing PostgreSQL database | | High volume analytics | [ClickHouse](https://clickhouse.com/) for time-series data | | Serverless | [Neon](https://neon.tech/) or [PlanetScale](https://planetscale.com/) | --- # Building an Agency Dashboard Source: https://www.ranked.ai/developers/guides/agency-dashboard > How to use the API and webhooks to build a custom client-facing dashboard ## Overview Agencies can build custom dashboards on top of Ranked AI's data using the REST API for data retrieval and webhooks for real-time updates. This guide walks through the recommended architecture. ## Architecture Your dashboard pulls data from the API and caches it locally. Webhooks push updates so you don't need to poll. **API pulls data into your database** Your server calls the Ranked AI API to fetch keywords, audits, backlinks, and other data, then stores it in your database. **Webhooks push real-time updates** When keyword positions update or content changes, Ranked AI sends a webhook to your server with the event details. **Your dashboard reads from the cache** Your frontend reads from your local database instead of calling the API on every page load. Fast and always available. ## Step 1: Discover projects List all SEO projects for the authenticated user: ```javascript const response = await fetch('https://app.ranked.ai/api/v1/projects', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const { data: projects } = await response.json(); // Store project IDs for subsequent calls projects.forEach(project => { db.upsert('projects', { ranked_id: project.id, name: project.name, website: project.websiteUrl, status: project.status, }); }); ``` ## Step 2: Initial data sync Pull the full dataset for each project: ```javascript async function syncProject(projectId) { const headers = { 'Authorization': `Bearer ${apiKey}` }; const base = `https://app.ranked.ai/api/v1/projects/${projectId}`; // Fetch all data in parallel const [keywords, audits, backlinks, prompts, content] = await Promise.all([ fetch(`${base}/rankings/keywords?limit=1000`, { headers }).then(r => r.json()), fetch(`${base}/audits/latest`, { headers }).then(r => r.json()), fetch(`${base}/backlinks/summary`, { headers }).then(r => r.json()), fetch(`${base}/prompts?limit=200`, { headers }).then(r => r.json()), fetch(`${base}/content?limit=100`, { headers }).then(r => r.json()), ]); // Store in your database await db.upsertKeywords(projectId, keywords.data); await db.upsertAudit(projectId, audits.data); await db.upsertBacklinks(projectId, backlinks.data); await db.upsertPrompts(projectId, prompts.data); await db.upsertContent(projectId, content.data); } ``` ## Step 3: Set up webhooks for real-time updates Instead of polling, subscribe to events: ```javascript // Create webhook for each project for (const project of projects) { await fetch('https://app.ranked.ai/api/v1/webhooks', { method: 'POST', headers: { 'Authorization': `Bearer ${writeApiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: `Dashboard - ${project.name}`, url: 'https://your-dashboard.com/webhooks/ranked', project_id: project.id, events: [ 'keywords.updated', 'content.status_changed', 'content.created', 'audit.completed', 'prompts.updated', ], }), }); } ``` ## Step 4: Handle webhook events ```javascript app.post('/webhooks/ranked', async (req, res) => { // Verify signature first (see Webhook Security docs) if (!verifySignature(req)) return res.status(401).send(); const { event, project_id, data } = req.body; switch (event) { case 'keywords.updated': // Re-sync keyword positions from the API const keywords = await fetch( `https://app.ranked.ai/api/v1/projects/${project_id}/rankings/keywords?limit=1000`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ).then(r => r.json()); await db.upsertKeywords(project_id, keywords.data); break; case 'audit.completed': // Fetch the latest audit results const audit = await fetch( `https://app.ranked.ai/api/v1/projects/${project_id}/audits/latest`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ).then(r => r.json()); await db.upsertAudit(project_id, audit.data); break; case 'content.status_changed': // Update content status in your DB await db.updateContentStatus(data.content_id, data.new_status); break; case 'prompts.updated': // Refresh AI visibility data for this prompt const prompt = await fetch( `https://app.ranked.ai/api/v1/projects/${project_id}/prompts/${data.prompt_id}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ).then(r => r.json()); await db.upsertPrompt(project_id, prompt.data); break; } res.status(200).send('OK'); }); ``` ## Recommended sync strategy | Data | Strategy | Frequency | |------|----------|-----------| | Keyword positions | Webhook `keywords.updated` triggers API pull | Daily (after scan completes) | | AI visibility | Webhook `prompts.updated` triggers API pull | Monthly (after analysis) | | Audit results | Webhook `audit.completed` triggers API pull | Monthly (after scan) | | Content status | Webhook `content.status_changed` for real-time | Real-time | | Backlinks | Scheduled API pull (no webhook yet) | Weekly cron job | | Reports | On-demand API call | As needed | ## Tips - Use **Read Only** API keys for data fetching and a separate **Read + Write** key for webhook management - Cache API responses in your database rather than calling the API on every page load - The `keywords.updated` webhook fires once per daily scan, not per keyword -- use it as a signal to re-sync - Implement retry logic for your API calls with exponential backoff --- # Rate Limits Source: https://www.ranked.ai/developers/guides/rate-limits > API rate limits and best practices for staying within them ## Limits | Window | Limit | |--------|-------| | Per minute | 200 requests | | Per hour | 5,000 requests | | Per day | 50,000 requests | Rate limits are tracked per API key. Every response includes the current rate limit status: ```json { "meta": { "rate_limit": { "limit": 100, "remaining": 97, "reset": 1778891947 } } } ``` ## When rate limited You'll receive a `429 Too Many Requests` response: ```json { "success": false, "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded. Try again later." } } ``` Wait until the `reset` Unix timestamp before making more requests. ## Pagination limits | Resource | Maximum per request | |----------|-------------------| | Keywords | 1,000 | | AI prompts | 200 | | Projects | 500 | | All other resources | 1,000 | ## Best practices ### Use webhooks instead of polling Instead of calling `GET /rankings/keywords` every 5 minutes to check for changes, subscribe to the `keywords.updated` webhook and only fetch data when positions actually update. ### Batch your requests When syncing multiple data types for a project, fetch them in parallel: ```javascript const [keywords, audits, backlinks] = await Promise.all([ fetch(`${base}/rankings/keywords?limit=1000`, { headers }), fetch(`${base}/audits/latest`, { headers }), fetch(`${base}/backlinks/summary`, { headers }), ]); ``` This uses 3 of your rate limit instead of making sequential calls that take longer. ### Cache responses Store API responses in your database and serve your UI from the cache. Only re-fetch when: - A webhook event signals new data is available - The user manually requests a refresh - A scheduled sync interval passes (e.g., daily for backlinks which don't have webhooks yet) ### Use pagination efficiently For large datasets, use `limit` and `offset` to page through results: ```javascript async function fetchAllKeywords(projectId) { let allKeywords = []; let offset = 0; const limit = 1000; while (true) { const response = await fetch( `${base}/rankings/keywords?limit=${limit}&offset=${offset}`, { headers } ).then(r => r.json()); allKeywords.push(...response.data); if (!response.meta.pagination.has_more) break; offset += limit; } return allKeywords; } ``` --- # Keyword Ranking Monitor Source: https://www.ranked.ai/developers/recipes/ranking-monitor > Build a simple ranking monitor that tracks position changes over time ## Overview A Node.js script that pulls keyword rankings daily, stores them in a local database, and generates a summary of changes. ## Full example ```javascript const Database = require('better-sqlite3'); const API_KEY = process.env.RANKED_API_KEY; const PROJECT_ID = process.env.RANKED_PROJECT_ID; const BASE = `https://app.ranked.ai/api/v1/projects/${PROJECT_ID}`; const headers = { 'Authorization': `Bearer ${API_KEY}` }; // Initialize SQLite database const db = new Database('rankings.db'); db.exec(` CREATE TABLE IF NOT EXISTS snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, keyword_id TEXT, keyword TEXT, desktop INTEGER, mobile INTEGER, ai_mode INTEGER, maps INTEGER, net_change INTEGER, recorded_at TEXT DEFAULT CURRENT_TIMESTAMP ) `); async function fetchAndStore() { let offset = 0; const limit = 1000; let total = 0; while (true) { const response = await fetch( `${BASE}/rankings/keywords?limit=${limit}&offset=${offset}`, { headers } ); const { data, meta } = await response.json(); const insert = db.prepare(` INSERT INTO snapshots (keyword_id, keyword, desktop, mobile, ai_mode, maps, net_change) VALUES (?, ?, ?, ?, ?, ?, ?) `); const batch = db.transaction((keywords) => { for (const kw of keywords) { insert.run( kw.id, kw.keyword, kw.desktop_position, kw.mobile_position, kw.ai_mode_position, kw.maps_position, kw.net_change ); } }); batch(data); total += data.length; if (!meta.pagination.has_more) break; offset += limit; } console.log(`Stored ${total} keyword snapshots`); } function generateReport() { const today = new Date().toISOString().split('T')[0]; const improved = db.prepare(` SELECT keyword, net_change FROM snapshots WHERE date(recorded_at) = date(?) AND net_change > 0 ORDER BY net_change DESC LIMIT 10 `).all(today); const declined = db.prepare(` SELECT keyword, net_change FROM snapshots WHERE date(recorded_at) = date(?) AND net_change < 0 ORDER BY net_change ASC LIMIT 10 `).all(today); console.log('\n--- Daily Ranking Report ---\n'); if (improved.length > 0) { console.log('Top Improvements:'); improved.forEach(kw => console.log(` ${kw.keyword}: +${kw.net_change}`)); } if (declined.length > 0) { console.log('\nBiggest Declines:'); declined.forEach(kw => console.log(` ${kw.keyword}: ${kw.net_change}`)); } if (improved.length === 0 && declined.length === 0) { console.log('No ranking changes today.'); } } // Run fetchAndStore().then(generateReport); ``` ## Running daily Use cron (Linux/Mac) or Task Scheduler (Windows): ```bash # Run daily at 7am 0 7 * * * cd /path/to/project && node monitor.js >> /var/log/rankings.log 2>&1 ``` Or use the `keywords.updated` webhook to trigger automatically when the scan completes instead of running on a fixed schedule. --- # Slack Ranking Alerts Source: https://www.ranked.ai/developers/recipes/slack-alerts > Get Slack notifications when keyword positions change ## Overview Set up a webhook that sends Slack messages when your keyword positions update. You'll receive a daily summary after each scan completes. ## Prerequisites - A Ranked AI API key with **Read + Write** access - A [Slack Incoming Webhook URL](https://api.slack.com/messaging/webhooks) ## Step 1: Create a webhook endpoint Build a simple server that receives Ranked AI webhooks and forwards to Slack: ```javascript const express = require('express'); const crypto = require('crypto'); const app = express(); const RANKED_WEBHOOK_SECRET = process.env.RANKED_WEBHOOK_SECRET; const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL; const RANKED_API_KEY = process.env.RANKED_API_KEY; function verifySignature(payload, signature) { const expected = 'sha256=' + crypto .createHmac('sha256', RANKED_WEBHOOK_SECRET) .update(payload) .digest('hex'); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } app.post('/webhooks/ranked', express.raw({ type: 'application/json' }), async (req, res) => { const signature = req.headers['x-webhook-signature']; if (!verifySignature(req.body.toString(), signature)) { return res.status(401).send(); } const event = JSON.parse(req.body); if (event.event === 'keywords.updated') { await handleKeywordsUpdated(event); } res.status(200).send('OK'); }); async function handleKeywordsUpdated(event) { // Fetch latest keyword data const response = await fetch( `https://app.ranked.ai/api/v1/projects/${event.project_id}/rankings/keywords?limit=1000`, { headers: { 'Authorization': `Bearer ${RANKED_API_KEY}` } } ); const { data: keywords } = await response.json(); // Find significant changes const improved = keywords.filter(kw => kw.net_change > 0); const declined = keywords.filter(kw => kw.net_change < 0); if (improved.length === 0 && declined.length === 0) return; // Build Slack message const blocks = [ { type: 'header', text: { type: 'plain_text', text: 'Keyword Rankings Update' } } ]; if (improved.length > 0) { const top5 = improved.sort((a, b) => b.net_change - a.net_change).slice(0, 5); blocks.push({ type: 'section', text: { type: 'mrkdwn', text: `*Improved (${improved.length})*\n` + top5.map(kw => `${kw.keyword}: +${kw.net_change} positions`).join('\n') } }); } if (declined.length > 0) { const top5 = declined.sort((a, b) => a.net_change - b.net_change).slice(0, 5); blocks.push({ type: 'section', text: { type: 'mrkdwn', text: `*Declined (${declined.length})*\n` + top5.map(kw => `${kw.keyword}: ${kw.net_change} positions`).join('\n') } }); } // Send to Slack await fetch(SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ blocks }) }); } app.listen(3001, () => console.log('Webhook server running on port 3001')); ``` ## Step 2: Register the webhook ```bash curl -X POST https://app.ranked.ai/api/v1/webhooks \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{ "name": "Slack Alerts", "url": "https://your-server.com/webhooks/ranked", "project_id": "your-project-id", "events": ["keywords.updated"] }' ``` Save the `secret` from the response as your `RANKED_WEBHOOK_SECRET` environment variable. ## Result You'll receive a Slack message once per day after the keyword scan completes, showing which keywords improved or declined. --- # Sync to Google Sheets Source: https://www.ranked.ai/developers/recipes/google-sheets > Automatically sync keyword rankings to a Google Sheet ## Overview Pull keyword ranking data from the Ranked AI API and write it to a Google Sheet on a daily schedule using Google Apps Script. ## Step 1: Create the Apps Script Open your Google Sheet, go to **Extensions > Apps Script**, and paste: ```javascript const API_KEY = 'rk_live_your_api_key'; const PROJECT_ID = 'your-project-id'; const BASE_URL = 'https://app.ranked.ai/api/v1'; function syncKeywords() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Keywords'); if (!sheet) { SpreadsheetApp.getActiveSpreadsheet().insertSheet('Keywords'); sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Keywords'); } // Fetch keywords from API const response = UrlFetchApp.fetch( `${BASE_URL}/projects/${PROJECT_ID}/rankings/keywords?limit=1000`, { headers: { 'Authorization': `Bearer ${API_KEY}` }, muteHttpExceptions: true } ); const data = JSON.parse(response.getContentText()); if (!data.success) { Logger.log('API error: ' + JSON.stringify(data.error)); return; } // Clear and write headers sheet.clear(); sheet.appendRow([ 'Keyword', 'Desktop', 'Mobile', 'AI Mode', 'Maps', 'Net Change', 'Location', 'Last Checked' ]); // Write keyword data data.data.forEach(kw => { sheet.appendRow([ kw.keyword, kw.desktop_position || '—', kw.mobile_position || '—', kw.ai_mode_position || '—', kw.maps_position || '—', kw.net_change, kw.location, kw.last_checked ? new Date(kw.last_checked).toLocaleDateString() : '—' ]); }); // Format header row const headerRange = sheet.getRange(1, 1, 1, 8); headerRange.setFontWeight('bold'); headerRange.setBackground('#f3f4f6'); Logger.log(`Synced ${data.data.length} keywords`); } ``` ## Step 2: Set up a daily trigger In Apps Script, go to **Triggers** (clock icon) and create a time-driven trigger: - Function: `syncKeywords` - Event source: Time-driven - Type: Day timer - Time: 6:00 AM - 7:00 AM (after the daily scan completes) ## Step 3: Run manually to test Click the **Run** button in Apps Script to test. Your Google Sheet should populate with all keyword data. ## Adding AI Visibility data Add another function to sync AI prompt data to a second sheet: ```javascript function syncPrompts() { let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('AI Visibility'); if (!sheet) { SpreadsheetApp.getActiveSpreadsheet().insertSheet('AI Visibility'); sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('AI Visibility'); } const response = UrlFetchApp.fetch( `${BASE_URL}/projects/${PROJECT_ID}/prompts?limit=200`, { headers: { 'Authorization': `Bearer ${API_KEY}` }, muteHttpExceptions: true } ); const data = JSON.parse(response.getContentText()); if (!data.success) return; sheet.clear(); sheet.appendRow([ 'Prompt', 'Visibility %', 'Avg Position', 'Best Model', 'AI Search Volume', 'Citations' ]); data.data.forEach(prompt => { sheet.appendRow([ prompt.prompt, prompt.visibility_percentage + '%', prompt.average_position || '—', prompt.best_model || '—', prompt.ai_search_volume || '—', prompt.total_citations ]); }); const headerRange = sheet.getRange(1, 1, 1, 6); headerRange.setFontWeight('bold'); headerRange.setBackground('#f3f4f6'); } ``` --- # API Reference Source: https://www.ranked.ai/developers/api-reference/introduction > Complete reference for the Ranked AI REST API v1 ## Base URL ``` https://app.ranked.ai/api/v1 ``` ## Authentication All requests require a Bearer token in the `Authorization` header: ```bash curl https://app.ranked.ai/api/v1/projects \ -H "Authorization: Bearer rk_live_your_api_key" ``` Create API keys from **Settings > API** in your [dashboard](https://app.ranked.ai/dashboard/settings?tab=api-keys). ### Permissions | Key Type | Can do | |----------|--------| | **Read Only** (default) | Fetch all data: keywords, audits, backlinks, AI visibility, content, reports | | **Read + Write** | Everything above, plus: create reports, manage webhooks, update content preferences | All `GET` endpoints work with read-only keys. `POST`, `PATCH`, and `DELETE` require read+write. ## How it works All data is organized by **project**. A project represents one website you're tracking in Ranked AI. **Step 1** -- Get your project IDs: ```bash curl https://app.ranked.ai/api/v1/projects \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```json { "data": [ { "id": "40596405-c27c-...", "name": "My Website", "status": "active", "websiteUrl": "https://example.com" } ] } ``` **Step 2** -- Use the project ID to fetch any data: ```bash curl "https://app.ranked.ai/api/v1/projects/40596405-c27c-.../rankings/keywords?limit=5" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```json { "data": [ { "keyword": "tree care services", "desktop_position": 5, "mobile_position": 3, "ai_mode_position": 3, "maps_position": 3, "net_change": 93, "location": "Sioux City, Iowa, United States" } ] } ``` **Step 3** (optional) -- Set up webhooks to get notified when data changes: ```bash curl -X POST https://app.ranked.ai/api/v1/webhooks \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{"url":"https://your-app.com/webhook","project_id":"40596405-c27c-...","events":["keywords.updated"]}' ``` > **Info:** Each API key can access all projects owned by the account that created it. If you manage multiple client websites, they'll all appear in the projects list. ## Key concepts | Term | What it means | |------|--------------| | **Keywords** | Search terms you're tracking in Google. Positions tracked across Desktop, Mobile, AI Mode (Google's AI search), and Google Maps. | | **AI Visibility** | How often your brand appears when people ask AI tools (ChatGPT, Claude, Gemini, Perplexity, Grok, Meta) questions related to your business. Each tracked question is called a "prompt". | | **Net Change** | The total position improvement across all four search channels (Desktop + Mobile + AI Mode + Maps). Positive = you moved up. | | **Audits** | Automated site crawls that find SEO issues like missing tags, broken pages, and performance problems. | | **Product mode** | Every project is `managed` (Ranked AI's team does the work; the software suite is included in the service plan) or `software` (self-serve SEO Software, billed as prepaid keyword and prompt blocks, no content pipeline). Software projects can be created through the API; managed projects start from the dashboard. | ## Endpoints ### Projects | Method | Path | Description | |--------|------|-------------| | `GET` | `/projects` | List your SEO projects (`?product_mode=` `managed` or `software` to filter) | | `POST` | `/projects` | Create a self-serve software project from a website URL | ### Keywords | Method | Path | Description | |--------|------|-------------| | `GET` | `/projects/{id}/rankings/keywords` | Keyword positions across all channels | | `GET` | `/projects/{id}/rankings/keywords/{keywordId}/history` | Daily position history for a keyword | ### AI Visibility | Method | Path | Description | |--------|------|-------------| | `GET` | `/projects/{id}/prompts` | AI visibility prompts with per-model data | | `GET` | `/projects/{id}/prompts/{promptId}` | Single prompt detail | | `GET` | `/projects/{id}/prompts/{promptId}/history` | Model response history with citations | ### Audits | Method | Path | Description | |--------|------|-------------| | `GET` | `/projects/{id}/audits` | List audit tasks | | `GET` | `/projects/{id}/audits/latest` | Most recent completed audit | | `GET` | `/projects/{id}/audits/{auditId}/issues` | Issues for a specific audit | ### Backlinks | Method | Path | Description | |--------|------|-------------| | `GET` | `/projects/{id}/backlinks` | Tracked backlink targets | | `GET` | `/projects/{id}/backlinks/summary` | Total backlinks and referring domains | | `GET` | `/projects/{id}/backlinks/domains` | Referring domains with domain rank | | `GET` | `/projects/{id}/backlinks/history` | Daily new/lost backlink counts | | `GET` | `/projects/{id}/backlinks/anchors` | Anchor text analysis | ### Content | Method | Path | Description | |--------|------|-------------| | `GET` | `/projects/{id}/content` | Content calendar items | | `GET` | `/projects/{id}/content/{contentId}` | Full content with article body | | `GET` `PATCH` | `/projects/{id}/content/preferences` | Content preferences | ### Reports | Method | Path | Description | |--------|------|-------------| | `GET` `POST` | `/projects/{id}/reports` | List or create report links | | `GET` `DELETE` | `/projects/{id}/reports/{reportId}` | Get or delete a report | ### Webhooks | Method | Path | Description | |--------|------|-------------| | `GET` `POST` | `/webhooks` | List or create webhook subscriptions | | `GET` `PATCH` `DELETE` | `/webhooks/{webhookId}` | Manage a webhook | | `POST` | `/webhooks/{webhookId}/test` | Send a test delivery | --- # List Projects Source: https://www.ranked.ai/developers/api-reference/projects/list `GET https://app.ranked.ai/api/v1/projects` > List all SEO projects for the authenticated user Returns active SEO projects owned by the API key holder. Only projects with `trial`, `active`, or `past_due` status are included. Every project carries a `productMode`: | Value | Meaning | |-------|---------| | `managed` | Ranked AI's team does the work (content, publishing, optimization, outreach, Google Ads). The full software suite is included in the service plan and never billed separately. | | `software` | Self-serve SEO Software: the account runs it themselves. Rank tracking, AI visibility prompts, audits, backlinks, heatmaps, integrations and reports, billed as prepaid keyword and prompt blocks. No content pipeline, so the content endpoints return nothing for these projects. | ### Query parameters - `limit` (query, number, default 50): Maximum number of projects to return (max: 500) - `offset` (query, number, default 0): Number of projects to skip for pagination - `product_mode` (query, string): Only return one kind of project: `managed` or `software`. Omit for both. **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects?product_mode=managed" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```javascript JavaScript const response = await fetch('https://app.ranked.ai/api/v1/projects', { headers: { 'Authorization': 'Bearer rk_live_your_api_key' } }); const { data } = await response.json(); for (const project of data) { console.log(`${project.name} (${project.productMode}): ${project.id}`); } ``` ```python Python import requests response = requests.get( 'https://app.ranked.ai/api/v1/projects', headers={'Authorization': 'Bearer rk_live_your_api_key'}, params={'product_mode': 'software'}, ) for project in response.json()['data']: print(f"{project['name']} ({project['productMode']}): {project['id']}") ``` **Response** ```json 200 { "success": true, "data": [ { "id": "40596405-c27c-4dfc-89e4-142c87846d66", "name": "Sioux City Tree Co", "status": "active", "serviceType": "seo", "productMode": "managed", "websiteUrl": "https://siouxcitytreeco.com", "createdAt": "2025-08-28T08:47:05.816Z" }, { "id": "8c1f2a4e-5b7d-4c3e-9a1b-2d3e4f5a6b7c", "name": "acme.com", "status": "active", "serviceType": "seo", "productMode": "software", "websiteUrl": "https://acme.com", "createdAt": "2026-09-14T10:12:44.102Z" } ], "meta": { "pagination": { "total": 2, "limit": 50, "offset": 0, "has_more": false } } } ``` ### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | string | Project UUID, used in every `/projects/{projectId}/...` path | | `name` | string | Project name | | `status` | string | `trial`, `active` or `past_due` | | `serviceType` | string | Always `seo` for API-visible projects | | `productMode` | string | `managed` or `software` (see above) | | `websiteUrl` | string or null | The tracked website | | `createdAt` | string | ISO 8601 creation time | > **Info:** Need a new project? Software projects can be created with [Create Project](https://www.ranked.ai/developers/api-reference/projects/create). Managed-service projects start from the dashboard's Add Project flow, where the plan and free trial are set up. --- # Create Project Source: https://www.ranked.ai/developers/api-reference/projects/create `POST https://app.ranked.ai/api/v1/projects` > Create a self-serve SEO Software project from a website URL Creates a **software** project for the account behind the API key. This is the only project type that can be created through the API: no plan or payment is needed, the project exists immediately and appears in the dashboard's Software Suite. Managed-service projects (where Ranked AI's team does the work) start from the dashboard's Add Project flow, where the plan and free trial are set up. Requires a **Read + Write** API key. > **Info:** Creating the project is free. Adding keywords or AI prompts and running scans needs a live SEO Software plan on the account: $4.99/month per 100 tracked keywords and $4.99/month per 100 AI prompts, prepaid, one plan for every software project on the account, unlimited websites and users. The response tells you whether a plan is active (`plan.active`) and where to add one (`plan.manageUrl`). ### Body parameters - `website_url` (body, string, required): The website to track, e.g. `acme.com` or `https://acme.com`. Invalid URLs return a validation error. - `name` (body, string): Project name. Defaults to the domain. - `product_mode` (body, string, default software): Only `software` is accepted. Any other value returns a validation error explaining that managed projects start from the dashboard. **Request** ```bash cURL curl -X POST https://app.ranked.ai/api/v1/projects \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{"website_url": "acme.com", "name": "Acme"}' ``` ```javascript JavaScript const response = await fetch('https://app.ranked.ai/api/v1/projects', { method: 'POST', headers: { 'Authorization': 'Bearer rk_live_your_write_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ website_url: 'acme.com', name: 'Acme' }), }); const { data } = await response.json(); console.log(data.id, data.plan.active ? 'ready to track' : `add a plan: ${data.plan.manageUrl}`); ``` ```python Python import requests response = requests.post( 'https://app.ranked.ai/api/v1/projects', headers={'Authorization': 'Bearer rk_live_your_write_key'}, json={'website_url': 'acme.com', 'name': 'Acme'}, ) project = response.json()['data'] print(project['id'], project['plan']['active']) ``` **Response** ```json 201 { "success": true, "data": { "id": "8c1f2a4e-5b7d-4c3e-9a1b-2d3e4f5a6b7c", "name": "Acme", "status": "active", "serviceType": "seo", "productMode": "software", "websiteUrl": "https://acme.com", "createdAt": "2026-09-16T09:41:12.512Z", "dashboardUrl": "https://app.ranked.ai/dashboard/projects/8c1f2a4e-5b7d-4c3e-9a1b-2d3e4f5a6b7c?tab=keywords&subtab=keywords", "addServiceUrl": "https://app.ranked.ai/dashboard/projects/8c1f2a4e-5b7d-4c3e-9a1b-2d3e4f5a6b7c?addService=1", "plan": { "active": true, "capacity": { "keywords": 300, "prompts": 100 }, "remaining": { "keywords": 180, "prompts": 64 }, "pricing": "$4.99/month per 100 tracked keywords and $4.99/month per 100 AI prompts, prepaid; one plan covers every software project on the account; ...", "manageUrl": "https://app.ranked.ai/dashboard/projects?suite=software&addPlan=1" }, "ownerUserId": "2f5e9c7a-1b3d-4e6f-8a9b-0c1d2e3f4a5b" }, "meta": { "request_id": "req_01J8X2Y3Z4", "rate_limit": { "limit": 200, "remaining": 199, "reset": 1789000000 } } } ``` ```json 400 { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": { "errors": [ { "field": "product_mode", "message": "Only \"software\" projects can be created through the API. Managed-service projects start from the dashboard (Add Project), where the plan and free trial are set up." } ] } } } ``` ```json 403 { "success": false, "error": { "code": "FORBIDDEN", "message": "This API key does not have the required permissions. Generate a new API key with write access from Settings > API." } } ``` ### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | string | The new project's UUID. Use it in every `/projects/{projectId}/...` call. | | `productMode` | string | Always `software` | | `dashboardUrl` | string | Deep link to the project's Rankings tab | | `addServiceUrl` | string | Opens the "Add our SEO service" plan picker on the project, for upgrading it to the managed service later | | `plan.active` | boolean | Whether the account has a live SEO Software plan. `false` means keywords, prompts and scans are blocked until one is added. | | `plan.capacity` | object | Total tracked keywords and AI prompts the plan allows across all software projects | | `plan.remaining` | object | Capacity left after existing software projects' usage | | `plan.manageUrl` | string | Where to add or change the plan | | `ownerUserId` | string | The account the project landed under. Usually the API key holder; if their account's software subscription belongs to another team member, the project pools there. | ### After creating - Add keywords with the dashboard, MCP (`ranked_add_keywords`) or the support chat. Adds beyond the plan's capacity are refused with a message pointing at the plan page; raise the plan's blocks and retry. - Everything on a software project scans the same way as on a managed one: keyword positions, AI prompts, audits and backlinks (5 manual audits and 5 manual backlink scans per project per rolling 24 hours). - Content endpoints (`/content`, `/content/preferences`) return no items for software projects. There is no content pipeline without the service. - To hand the project to Ranked AI's team, open `addServiceUrl`. Once upgraded, `productMode` becomes `managed` and the software suite is simply part of the service plan. --- # List Keywords Source: https://www.ranked.ai/developers/api-reference/keywords/list `GET https://app.ranked.ai/api/v1/projects/{projectId}/rankings/keywords` > Get keyword positions across Desktop, Mobile, AI Mode, and Maps Returns tracked keywords with current positions across all search channels, net position change, and metadata. ### Path parameters - `projectId` (path, string, required): Project UUID ### Query parameters - `limit` (query, number, default 50): Maximum keywords to return (max: 1000) - `offset` (query, number, default 0): Number of keywords to skip - `device` (query, string, default all): Filter by device: `all`, `desktop`, or `mobile` - `date_from` (query, string): Start date for position change calculation (ISO 8601, e.g., `2026-04-01`) - `date_to` (query, string): End date for position change calculation (ISO 8601) **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/rankings/keywords?limit=10" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": [ { "id": "0e982fa1-a846-4e61-98ba-8811fafe892c", "keyword": "tree care services sioux city", "location": "Sioux City, Iowa, United States", "location_code": 1016030, "target_url": "https://siouxcitytreeco.com", "device": "desktop", "language_code": "en", "desktop_position": 8, "mobile_position": 3, "ai_mode_position": 3, "maps_position": 3, "desktop_url": "https://siouxcitytreeco.com/", "mobile_url": "https://siouxcitytreeco.com/", "ai_mode_url": "https://siouxcitytreeco.com/", "maps_url": "https://siouxcitytreeco.com/", "featured_snippet": false, "local_pack_position": null, "monthly_search_volume": null, "net_change": 93, "tags": [], "last_checked": "2026-05-16T00:10:17.701+00:00", "created_at": "2026-03-14T18:24:05.660Z" } ], "meta": { "pagination": { "total": 20, "limit": 10, "offset": 0, "has_more": true } } } ``` ### Response fields | Field | Type | Description | |-------|------|-------------| | `desktop_position` | number or null | Current Google Desktop rank | | `mobile_position` | number or null | Current Google Mobile rank | | `ai_mode_position` | number or null | Current Google AI Mode rank | | `maps_position` | number or null | Current Google Maps rank | | `net_change` | number | Sum of position changes across all channels. Positive = improved. | | `featured_snippet` | boolean | Whether the keyword has a featured snippet | | `monthly_search_volume` | number or null | Estimated monthly search volume | | `last_checked` | string | When positions were last scanned | --- # Keyword History Source: https://www.ranked.ai/developers/api-reference/keywords/history `GET https://app.ranked.ai/api/v1/projects/{projectId}/rankings/keywords/{keywordId}/history` > Get daily position history for a specific keyword Returns daily position data across all search channels for a tracked keyword. ### Path parameters - `projectId` (path, string, required): Project UUID - `keywordId` (path, string, required): Keyword UUID (from the keywords list endpoint) ### Query parameters - `date_from` (query, string): Start date (ISO 8601). Defaults to 90 days ago. - `date_to` (query, string): End date (ISO 8601). Defaults to today. **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/rankings/keywords/{keywordId}/history" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": { "keyword_id": "abc-123", "keyword": "tree care services sioux city", "history": [ { "check_date": "2026-05-14", "desktop_position": 5, "mobile_position": 3, "ai_mode_position": 3, "maps_position": 3, "featured_snippet": false, "local_pack_position": null } ], "summary": { "best_position": 1, "worst_position": 12, "data_points": 30 } } } ``` --- # List Prompts Source: https://www.ranked.ai/developers/api-reference/prompts/list `GET https://app.ranked.ai/api/v1/projects/{projectId}/prompts` > Get AI visibility prompts with per-model visibility data Returns active AI visibility prompts with brand mention rates, positions, and citation counts across AI models. ### Path parameters - `projectId` (path, string, required): Project UUID ### Query parameters - `limit` (query, number, default 50): Maximum prompts to return (max: 200) - `offset` (query, number, default 0): Number of prompts to skip **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/prompts?limit=5" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": [ { "id": "bd17920c-61ff-4298-9116-f0a8035aabf3", "prompt": "Who provides tree services for HOA properties in Sioux City, Iowa?", "target_location": "Sioux City, Iowa, United States", "brand_name": "Sioux City Tree Co", "visibility_percentage": 50, "average_position": 5, "best_model": "openai/gpt-5.4-nano", "ai_search_volume": 60, "total_citations": 46, "latest_responses": { "openai/gpt-5.4-nano": { "is_visible": true, "position": 3, "citations_count": 15, "response_excerpt": "Tree services for HOA properties in Sioux City..." }, "perplexity/sonar": { "is_visible": true, "position": 5, "citations_count": 6, "response_excerpt": "Several companies in Sioux City offer..." } }, "last_checked": "2026-05-10T00:30:51.79+00:00", "created_at": "2026-03-12T21:57:35.036Z" } ] } ``` --- # Prompt Detail Source: https://www.ranked.ai/developers/api-reference/prompts/detail `GET https://app.ranked.ai/api/v1/projects/{projectId}/prompts/{promptId}` > Get detailed AI visibility data for a specific prompt Returns full details for a single AI visibility prompt including per-model responses. ### Path parameters - `projectId` (path, string, required): Project UUID - `promptId` (path, string, required): Prompt UUID **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/prompts/{promptId}" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": { "id": "bd17920c-61ff-4298-9116-f0a8035aabf3", "prompt": "Who provides tree services for HOA properties in Sioux City, Iowa?", "target_location": "Sioux City, Iowa, United States", "ai_models": ["openai/gpt-5.4-nano", "anthropic/claude-3-haiku", "google/gemini-2.5-flash-lite", "perplexity/sonar", "xai/grok-4.1-fast-non-reasoning", "meta/llama-4-scout"], "brand_name": "Sioux City Tree Co", "visibility_percentage": 50, "average_position": 5, "best_model": "openai/gpt-5.4-nano", "ai_search_volume": 60, "total_citations": 46, "latest_responses": { ... }, "last_checked": "2026-05-10T00:30:51.79+00:00", "last_analyzed": "2026-05-10T00:30:51.728+00:00", "last_volume_update": "2026-04-15T12:00:00.000Z", "created_at": "2026-03-12T21:57:35.036Z" } } ``` --- # Prompt History Source: https://www.ranked.ai/developers/api-reference/prompts/history `GET https://app.ranked.ai/api/v1/projects/{projectId}/prompts/{promptId}/history` > Get AI model response history with citations for a prompt Returns the full response history from each AI model, including the response content, brand position, and cited sources. ### Path parameters - `projectId` (path, string, required): Project UUID - `promptId` (path, string, required): Prompt UUID ### Query parameters - `limit` (query, number, default 50): Max responses to return - `offset` (query, number, default 0): Number to skip - `date_from` (query, string): Start date (ISO 8601). Defaults to 90 days ago. - `date_to` (query, string): End date (ISO 8601) **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/prompts/{promptId}/history?limit=5" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": { "prompt_id": "bd17920c-61ff-4298-9116-f0a8035aabf3", "prompt": "Who provides tree services for HOA properties in Sioux City, Iowa?", "responses": [ { "model": "perplexity/sonar", "is_visible": true, "position": 5, "citations_count": 6, "citations": [ { "url": "https://siouxcitytreeco.com", "title": "siouxcitytreeco.com", "domain": "siouxcitytreeco.com" } ], "response_excerpt": "Several companies in Sioux City, IA, offer professional tree services...", "checked_at": "2026-05-10T00:30:51.679+00:00" } ] }, "meta": { "pagination": { "total": 36, "limit": 5, "offset": 0, "has_more": true } } } ``` --- # List Audits Source: https://www.ranked.ai/developers/api-reference/audits/list `GET https://app.ranked.ai/api/v1/projects/{projectId}/audits` > List site audit tasks for a project Returns audit tasks ordered by most recent first. ### Path parameters - `projectId` (path, string, required): Project UUID ### Query parameters - `limit` (query, number, default 50): Max audits to return - `offset` (query, number, default 0): Number to skip - `status` (query, string): Filter by status: `pending`, `crawling`, `processing`, `completed`, `failed` **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/audits" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": [ { "id": "2e8b1269-d954-4b3b-a5e5-233bd44dfc28", "target_url": "https://siouxcitytreeco.com/", "status": "completed", "crawl_progress": 100, "pages_crawled": 34, "total_issues": 0, "critical_issues": 0, "warning_issues": 0, "notice_issues": 0, "started_at": "2026-05-03T20:06:21.861+00:00", "completed_at": "2026-05-03T21:04:36.6+00:00", "created_at": "2026-05-03T20:06:21.998Z" } ] } ``` --- # Latest Audit Source: https://www.ranked.ai/developers/api-reference/audits/latest `GET https://app.ranked.ai/api/v1/projects/{projectId}/audits/latest` > Get the most recent completed audit with issue summary Returns the latest completed audit for a project with issue counts. Issue counts reflect the current state after any dismissals. ### Path parameters - `projectId` (path, string, required): Project UUID **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/audits/latest" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": { "id": "2e8b1269-d954-4b3b-a5e5-233bd44dfc28", "target_url": "https://siouxcitytreeco.com/", "status": "completed", "crawl_progress": 100, "pages_crawled": 34, "pages_in_queue": 0, "total_issues": 0, "critical_issues": 0, "warning_issues": 0, "notice_issues": 0, "started_at": "2026-05-03T20:06:21.861+00:00", "completed_at": "2026-05-03T21:04:36.6+00:00", "created_at": "2026-05-03T20:06:21.998Z", "issues_summary": { "passed": 11, "failed": 0, "total": 11 } } } ``` Returns `404` if no completed audit exists for the project. --- # Audit Issues Source: https://www.ranked.ai/developers/api-reference/audits/issues `GET https://app.ranked.ai/api/v1/projects/{projectId}/audits/{auditId}/issues` > Get issues for a specific audit sorted by severity Returns audit issues sorted by severity (critical, warning, notice) then by affected count. ### Path parameters - `projectId` (path, string, required): Project UUID - `auditId` (path, string, required): Audit task UUID ### Query parameters - `limit` (query, number, default 50): Max issues to return - `offset` (query, number, default 0): Number to skip - `severity` (query, string): Filter: `critical`, `warning`, or `notice` - `status` (query, string): Filter: `passed` or `failed` **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/audits/{auditId}/issues?severity=critical" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": [ { "id": "a27fd070-0b6f-47bf-8efe-2b130db0111d", "type": "no_h1_tag", "severity": "critical", "title": "Missing H1 Tags", "description": "Pages without H1 tags", "affected_count": 3, "status": "failed", "created_at": "2026-05-03T21:04:36.6+00:00" } ], "meta": { "pagination": { "total": 11, "limit": 50, "offset": 0, "has_more": false } } } ``` --- # List Backlink Targets Source: https://www.ranked.ai/developers/api-reference/backlinks/overview `GET https://app.ranked.ai/api/v1/projects/{projectId}/backlinks` > Get tracked backlink targets for a project Returns all active tracked backlink targets for a project with their latest metrics. - `projectId` (path, string, required): Project UUID **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/backlinks" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```javascript JavaScript const response = await fetch( `https://app.ranked.ai/api/v1/projects/${projectId}/backlinks`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data } = await response.json(); ``` ```python Python import requests response = requests.get( f'https://app.ranked.ai/api/v1/projects/{project_id}/backlinks', headers={'Authorization': f'Bearer {api_key}'} ) targets = response.json()['data'] ``` **Response** ```json 200 { "success": true, "data": [ { "id": "abc-123", "target_domain": "siouxcitytreeco.com", "total_backlinks": 58, "total_referring_domains": 54, "rank": 0, "last_summary_update": "2026-05-02" } ] } ``` --- # Backlink Summary Source: https://www.ranked.ai/developers/api-reference/backlinks/summary `GET https://app.ranked.ai/api/v1/projects/{projectId}/backlinks/summary` > Get aggregated backlink metrics for a project Returns total backlinks, referring domains, and dofollow/nofollow breakdown. - `projectId` (path, string, required): Project UUID **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/backlinks/summary" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```javascript JavaScript const response = await fetch( `https://app.ranked.ai/api/v1/projects/${projectId}/backlinks/summary`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data } = await response.json(); console.log(`${data.total_backlinks} backlinks from ${data.total_referring_domains} domains`); ``` ```python Python import requests response = requests.get( f'https://app.ranked.ai/api/v1/projects/{project_id}/backlinks/summary', headers={'Authorization': f'Bearer {api_key}'} ) summary = response.json()['data'] ``` **Response** ```json 200 { "success": true, "data": { "project_id": "40596405-c27c-4dfc-89e4-142c87846d66", "total_backlinks": 58, "total_referring_domains": 54, "average_domain_rank": 0, "dofollow_backlinks": 0, "nofollow_backlinks": 0, "broken_backlinks": 0, "tracked_domains_count": 1, "last_updated": "2026-05-02" } } ``` --- # Referring Domains Source: https://www.ranked.ai/developers/api-reference/backlinks/domains `GET https://app.ranked.ai/api/v1/projects/{projectId}/backlinks/domains` > Get referring domains with domain rank, backlink count, and follow status Returns paginated referring domains sorted by domain rank. - `projectId` (path, string, required): Project UUID - `limit` (query, number, default 50): Maximum domains to return (max: 1000) - `offset` (query, number, default 0): Number to skip for pagination - `sort_by` (query, string, default domain_rank): Sort by: `domain_rank`, `backlinks_count`, or `first_seen` - `sort_order` (query, string, default desc): `asc` or `desc` **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/backlinks/domains?limit=10&sort_by=domain_rank" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```javascript JavaScript const response = await fetch( `https://app.ranked.ai/api/v1/projects/${projectId}/backlinks/domains?limit=10&sort_by=domain_rank`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data, meta } = await response.json(); ``` ```python Python import requests response = requests.get( f'https://app.ranked.ai/api/v1/projects/{project_id}/backlinks/domains', headers={'Authorization': f'Bearer {api_key}'}, params={'limit': 10, 'sort_by': 'domain_rank'} ) domains = response.json()['data'] ``` **Response** ```json 200 { "success": true, "data": [ { "domain": "www.bbb.org", "backlinks_count": 2, "domain_rank": 89, "is_dofollow": false, "first_seen": "2025-10-03T02:54:17+00:00" }, { "domain": "www.iheart.com", "backlinks_count": 1, "domain_rank": 82, "is_dofollow": true, "first_seen": "2026-01-15T10:22:00+00:00" } ], "meta": { "pagination": { "total": 57, "limit": 10, "offset": 0, "has_more": true } } } ``` --- # Backlink History Source: https://www.ranked.ai/developers/api-reference/backlinks/history `GET https://app.ranked.ai/api/v1/projects/{projectId}/backlinks/history` > Get daily new and lost backlink counts over time Returns daily backlink change data for trend analysis. - `projectId` (path, string, required): Project UUID **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/backlinks/history" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```javascript JavaScript const response = await fetch( `https://app.ranked.ai/api/v1/projects/${projectId}/backlinks/history`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data } = await response.json(); ``` ```python Python import requests response = requests.get( f'https://app.ranked.ai/api/v1/projects/{project_id}/backlinks/history', headers={'Authorization': f'Bearer {api_key}'} ) history = response.json()['data'] ``` **Response** ```json 200 { "success": true, "data": { "history": [ { "check_date": "2026-05-01", "new_backlinks": 3, "lost_backlinks": 1, "new_referring_domains": 2, "lost_referring_domains": 0, "net_backlinks_change": 2, "net_domains_change": 2 }, { "check_date": "2026-04-24", "new_backlinks": 5, "lost_backlinks": 0, "new_referring_domains": 4, "lost_referring_domains": 1, "net_backlinks_change": 5, "net_domains_change": 3 } ], "summary": { "total_new": 8, "total_lost": 1, "net_change": 7 } } } ``` --- # Anchor Text Source: https://www.ranked.ai/developers/api-reference/backlinks/anchors `GET https://app.ranked.ai/api/v1/projects/{projectId}/backlinks/anchors` > Get anchor text analysis showing the most common anchor texts in your backlink profile Returns aggregated anchor text data with counts and percentages. - `projectId` (path, string, required): Project UUID - `limit` (query, number, default 50): Maximum anchor texts to return (max: 1000) - `offset` (query, number, default 0): Number to skip for pagination **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/backlinks/anchors?limit=10" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```javascript JavaScript const response = await fetch( `https://app.ranked.ai/api/v1/projects/${projectId}/backlinks/anchors?limit=10`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data, meta } = await response.json(); ``` ```python Python import requests response = requests.get( f'https://app.ranked.ai/api/v1/projects/{project_id}/backlinks/anchors', headers={'Authorization': f'Bearer {api_key}'}, params={'limit': 10} ) anchors = response.json()['data'] ``` **Response** ```json 200 { "success": true, "data": [ { "anchor_text": "tree service sioux city", "count": 8, "percentage": 13.8, "is_dofollow": true }, { "anchor_text": "siouxcitytreeco.com", "count": 5, "percentage": 8.6, "is_dofollow": true } ], "meta": { "pagination": { "total": 9, "limit": 10, "offset": 0, "has_more": false } } } ``` --- # List Content Source: https://www.ranked.ai/developers/api-reference/content/list `GET https://app.ranked.ai/api/v1/projects/{projectId}/content` > Get content calendar items for a project Returns content calendar items with status, type, and scheduling information. - `projectId` (path, string, required): Project UUID - `limit` (query, number, default 50): Max items to return - `offset` (query, number, default 0): Number to skip - `status` (query, string): Filter by status name (e.g., `Approved`, `Published`, `Needs Approval`) - `date_from` (query, string): Start date filter (ISO 8601) - `date_to` (query, string): End date filter (ISO 8601) **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/content?limit=5" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": [ { "id": "acc6b6c5-3573-488e-b1c2-4994350c4a18", "title": "How to Rebuild Intimacy After Porn Addiction", "description": null, "scheduled_date": "2025-10-27T00:00:00+00:00", "due_date": null, "status": "Published", "status_color": "green", "content_type": "Blog Post", "content_type_color": "green", "priority": 3, "document_url": "https://docs.google.com/document/d/...", "source_url": "https://docs.google.com/document/d/...", "featured_image_url": null, "created_at": "2025-09-30T15:36:19.196Z", "updated_at": "2025-11-19T13:33:38.079Z" } ] } ``` --- # Content Detail Source: https://www.ranked.ai/developers/api-reference/content/detail `GET https://app.ranked.ai/api/v1/projects/{projectId}/content/{contentId}` > Get full details for a content item including the article body Returns complete content item data including the full content body, metadata, and scheduling information. - `projectId` (path, string, required): Project UUID - `contentId` (path, string, required): Content item UUID **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/content/{contentId}" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ```javascript JavaScript const response = await fetch( `https://app.ranked.ai/api/v1/projects/${projectId}/content/${contentId}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data } = await response.json(); console.log(data.title, data.status); ``` ```python Python import requests response = requests.get( f'https://app.ranked.ai/api/v1/projects/{project_id}/content/{content_id}', headers={'Authorization': f'Bearer {api_key}'} ) content = response.json()['data'] ``` **Response** ```json 200 { "success": true, "data": { "id": "acc6b6c5-3573-488e-b1c2-4994350c4a18", "title": "Best Tree Care Tips for Spring", "description": null, "scheduled_date": "2026-05-20T00:00:00+00:00", "due_date": null, "status": "Published", "status_color": "green", "content_type": "Blog Post", "content_type_color": "green", "priority": 3, "document_url": "https://docs.google.com/document/d/...", "source_url": "https://docs.google.com/document/d/...", "featured_image_url": null, "content_body": "
Spring is the perfect time to...
", "meta_data": { "word_count": 1250, "reading_time": "5 min" }, "created_at": "2026-04-15T10:00:00.000Z", "updated_at": "2026-05-18T14:30:00.000Z" } } ``` ### Response fields | Field | Type | Description | |-------|------|-------------| | `content_body` | string or null | Full HTML content of the article | | `meta_data` | object or null | Additional metadata (word count, reading time, revision notes) | | `document_url` | string or null | Link to the content document | | `featured_image_url` | string or null | Featured image URL if set | --- # Content Preferences Source: https://www.ranked.ai/developers/api-reference/content/preferences `GET https://app.ranked.ai/api/v1/projects/{projectId}/content/preferences` > Get or update content preferences for a project ### GET - Retrieve preferences - `projectId` (path, string, required): Project UUID **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/content/preferences" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ### PATCH - Update preferences Requires a **Read + Write** API key. **Request** ```bash cURL curl -X PATCH "https://app.ranked.ai/api/v1/projects/{projectId}/content/preferences" \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{ "preferences": "Write in a professional but friendly tone. Focus on local tree care topics." }' ``` --- # Reports Source: https://www.ranked.ai/developers/api-reference/reports/list `GET https://app.ranked.ai/api/v1/projects/{projectId}/reports` > List or create shareable SEO report links ### GET - List reports - `projectId` (path, string, required): Project UUID - `limit` (query, number, default 50): Max reports to return - `offset` (query, number, default 0): Number to skip **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/reports" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ### POST - Create a report Requires a **Read + Write** API key. - `title` (body, string, required): Report title - `date_range` (body, string, required): `7days`, `30days`, `90days`, or `custom` - `custom_start_date` (body, string): Start date (ISO 8601). Required when `date_range` is `custom`. - `custom_end_date` (body, string): End date (ISO 8601). Required when `date_range` is `custom`. - `description` (body, string): Optional report description **Request** ```bash Preset range curl -X POST "https://app.ranked.ai/api/v1/projects/{projectId}/reports" \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{ "title": "Monthly SEO Report", "date_range": "30days" }' ``` ```bash Custom range curl -X POST "https://app.ranked.ai/api/v1/projects/{projectId}/reports" \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{ "title": "Q1 2026 Report", "date_range": "custom", "custom_start_date": "2026-01-01", "custom_end_date": "2026-03-31" }' ``` --- # Report Detail Source: https://www.ranked.ai/developers/api-reference/reports/detail `GET https://app.ranked.ai/api/v1/projects/{projectId}/reports/{reportId}` > Get or delete a specific report ### GET - Get report details - `projectId` (path, string, required): Project UUID - `reportId` (path, string, required): Report slug ID **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/projects/{projectId}/reports/{reportId}" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ### DELETE - Delete a report Requires a **Read + Write** API key. Soft-deletes the report link. **Request** ```bash cURL curl -X DELETE "https://app.ranked.ai/api/v1/projects/{projectId}/reports/{reportId}" \ -H "Authorization: Bearer rk_live_your_write_key" ``` --- # List Webhooks Source: https://www.ranked.ai/developers/api-reference/webhooks/list `GET https://app.ranked.ai/api/v1/webhooks` > List webhook subscriptions or create a new one ### GET - List subscriptions Returns all webhook subscriptions for the authenticated user. - `project_id` (query, string): Filter by project UUID - `limit` (query, number, default 50): Max subscriptions to return - `offset` (query, number, default 0): Number to skip **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/webhooks" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ### POST - Create subscription Requires a **Read + Write** API key. - `name` (body, string): Display name for this subscription - `url` (body, string, required): Webhook destination URL (must be HTTPS) - `project_id` (body, string, required): Project UUID to monitor - `events` (body, string[], required): Events to subscribe to. Options: `content.created`, `content.status_changed`, `audit.started`, `audit.completed`, `keywords.updated`, `prompts.updated` **Request** ```bash cURL curl -X POST "https://app.ranked.ai/api/v1/webhooks" \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{ "name": "My Dashboard", "url": "https://your-app.com/webhooks/ranked", "project_id": "your-project-uuid", "events": ["keywords.updated", "content.status_changed", "audit.completed"] }' ``` **Response** ```json 200 { "success": true, "data": { "id": "3a2e02d7-106f-4425-9518-9597dbf5a23a", "project_id": "40596405-c27c-4dfc-89e4-142c87846d66", "name": "My Dashboard", "url": "https://your-app.com/webhooks/ranked", "events": ["keywords.updated", "content.status_changed", "audit.completed"], "is_active": true, "secret": "whsec_daca66d72437cbe7767ec3c3bea5fe359637832f...", "created_at": "2026-05-16T01:06:02.042Z" } } ``` > **Warning:** The `secret` is only returned when the subscription is created. Store it securely for signature verification. --- # Manage Webhook Source: https://www.ranked.ai/developers/api-reference/webhooks/manage `GET https://app.ranked.ai/api/v1/webhooks/{webhookId}` > Get, update, or delete a webhook subscription ### GET - Get subscription details - `webhookId` (path, string, required): Webhook subscription UUID **Request** ```bash cURL curl "https://app.ranked.ai/api/v1/webhooks/{webhookId}" \ -H "Authorization: Bearer rk_live_your_api_key" ``` ### PATCH - Update subscription Requires a **Read + Write** API key. - `name` (body, string): Updated display name - `url` (body, string): Updated webhook URL (must be HTTPS) - `events` (body, string[]): Updated event list - `is_active` (body, boolean): Enable or disable the subscription **Request** ```bash cURL curl -X PATCH "https://app.ranked.ai/api/v1/webhooks/{webhookId}" \ -H "Authorization: Bearer rk_live_your_write_key" \ -H "Content-Type: application/json" \ -d '{ "is_active": false }' ``` ### DELETE - Delete subscription Requires a **Read + Write** API key. **Request** ```bash cURL curl -X DELETE "https://app.ranked.ai/api/v1/webhooks/{webhookId}" \ -H "Authorization: Bearer rk_live_your_write_key" ``` --- # Test Webhook Source: https://www.ranked.ai/developers/api-reference/webhooks/test `POST https://app.ranked.ai/api/v1/webhooks/{webhookId}/test` > Send a test payload to verify your webhook endpoint Sends a test payload to the webhook URL to verify connectivity and signature verification. - `webhookId` (path, string, required): Webhook subscription UUID **Request** ```bash cURL curl -X POST "https://app.ranked.ai/api/v1/webhooks/{webhookId}/test" \ -H "Authorization: Bearer rk_live_your_api_key" ``` **Response** ```json 200 { "success": true, "data": { "success": true, "message": "Test webhook delivered successfully", "status_code": 200 } } ``` The test payload sent to your URL looks like: ```json { "event": "content.created", "timestamp": "2026-05-16T01:06:30.000Z", "project_id": "test-project-id", "data": { "test": true, "message": "This is a test webhook from Ranked AI" } } ``` It includes the same `X-Webhook-Signature` header as real deliveries, signed with your subscription's secret. --- # TypeScript SDK Source: https://www.ranked.ai/developers/sdk > Lightweight TypeScript client for the Ranked AI REST API v1. A zero-dependency, single-file TypeScript SDK for the Ranked AI REST API v1. Works anywhere `fetch` is available - Node 18+, Deno, Bun, and modern browsers. ## Installation The SDK is a single file with no external dependencies. Download it straight into your project: ```bash curl -o lib/ranked-ai.ts https://www.ranked.ai/developers/sdk/ranked-ai.ts ``` You can also [view the source](https://www.ranked.ai/developers/sdk/ranked-ai.ts) and copy it by hand. Or, when the package is published to npm: ```bash npm install @ranked-ai/sdk ``` Then import the client: ```typescript import { RankedAI } from './lib/ranked-ai'; // or: import { RankedAI } from '@ranked-ai/sdk'; ``` ## Quick start ```typescript import { RankedAI } from './lib/ranked-ai'; const client = new RankedAI('rai_your_api_key'); // List all projects const { data: projects } = await client.listProjects(); console.log(projects); // Get keywords for a project const { data: keywords } = await client.listKeywords(projects[0].id, { device: 'desktop', }); console.log(keywords); ``` ## Configuration ```typescript const client = new RankedAI('rai_your_api_key', { // Override the base URL (useful for staging / self-hosted) baseUrl: 'https://app.ranked.ai', }); ``` ## Error handling Every failed request throws a `RankedAIError` with the HTTP status, error code, and request ID: ```typescript import { RankedAI, RankedAIError } from './lib/ranked-ai'; const client = new RankedAI('rai_your_api_key'); try { await client.getLatestAudit('invalid-id'); } catch (err) { if (err instanceof RankedAIError) { console.error(err.status); // 404 console.error(err.code); // "NOT_FOUND" console.error(err.requestId); // "req_abc123..." console.error(err.message); // "No completed audit found for this project" } } ``` ## Pagination All list endpoints return paginated responses. The `meta.pagination` object tells you whether more data is available: ```typescript const res = await client.listKeywords('proj_1', { limit: 25, offset: 0 }); console.log(res.meta.pagination); // { total: 142, limit: 25, offset: 0, has_more: true } ``` ### Manual pagination ```typescript let offset = 0; const limit = 50; let hasMore = true; while (hasMore) { const { data, meta } = await client.listKeywords('proj_1', { limit, offset }); for (const keyword of data) { console.log(keyword.keyword, keyword.desktop_position); } hasMore = meta.pagination?.has_more ?? false; offset += limit; } ``` ### Auto-pagination iterator The SDK provides a built-in `paginate` helper that yields every item across all pages: ```typescript for await (const keyword of client.paginate( (p) => client.listKeywords('proj_1', p), 100, // page size (default: 100) )) { console.log(keyword.keyword, keyword.desktop_position); } ``` This works with any paginated method: ```typescript // Iterate all prompts for await (const prompt of client.paginate( (p) => client.listPrompts('proj_1', p), )) { console.log(prompt.prompt, prompt.visibility_percentage); } // Iterate all audit issues for await (const issue of client.paginate( (p) => client.getAuditIssues('proj_1', 'audit_1', { ...p, severity: 'critical' }), )) { console.log(issue.title, issue.affected_count); } ``` ## Endpoints ### Projects ```typescript // List all projects const { data } = await client.listProjects({ limit: 50, offset: 0 }); ``` ### Keywords (Rankings) ```typescript // List keywords with optional filters const { data } = await client.listKeywords('proj_1', { limit: 100, offset: 0, device: 'desktop', // 'all' | 'desktop' | 'mobile' date_from: '2025-01-01', date_to: '2025-03-31', }); ``` ### AI Prompts ```typescript // List prompts const { data: prompts } = await client.listPrompts('proj_1'); // Get a single prompt with full detail const { data: prompt } = await client.getPrompt('proj_1', 'prompt_1'); console.log(prompt.visibility_percentage, prompt.latest_responses); // Get prompt response history const { data: history } = await client.getPromptHistory('proj_1', 'prompt_1', { date_from: '2025-01-01', limit: 50, }); for (const entry of history.responses) { console.log(entry.model, entry.is_visible, entry.position); } ``` ### Audits ```typescript // List audits const { data: audits } = await client.listAudits('proj_1', { status: 'completed', }); // Get the latest completed audit const { data: latest } = await client.getLatestAudit('proj_1'); console.log(latest.total_issues, latest.issues_summary); // Get issues for a specific audit const { data: issues } = await client.getAuditIssues('proj_1', latest.id, { severity: 'critical', status: 'failed', }); ``` ### Backlinks ```typescript // Get aggregated backlink summary const { data: summary } = await client.getBacklinkSummary('proj_1'); console.log(summary.total_backlinks, summary.average_domain_rank); // List referring domains const { data: domains } = await client.listBacklinkDomains('proj_1', { status: 'active', sort_by: 'domain_rank', sort_order: 'desc', limit: 50, }); ``` ### Content ```typescript // List content calendar items const { data: items } = await client.listContent('proj_1', { status: 'Published', date_from: '2025-01-01', date_to: '2025-06-30', }); ``` ### Reports ```typescript // List existing reports const { data: reports } = await client.listReports('proj_1'); // Create a new report const { data: report } = await client.createReport('proj_1', { title: 'Monthly SEO Report — May 2025', date_range: '30days', config: { include_rankings: true, include_audits: true, include_prompts: true, include_backlinks: true, include_content: false, }, }); console.log(report.share_url); // https://agencyreport.ai/r/... // Delete a report await client.deleteReport('proj_1', report.id); ``` ### Webhooks ```typescript // List webhooks const { data: hooks } = await client.listWebhooks({ project_id: 'proj_1' }); // Create a webhook const { data: hook } = await client.createWebhook({ project_id: 'proj_1', url: 'https://example.com/webhooks/ranked', events: ['audit.completed', 'keywords.updated'], name: 'Production webhook', }); // Save hook.secret — it is only returned once! console.log(hook.secret); // whsec_... // Test a webhook const { data: result } = await client.testWebhook(hook.id); console.log(result.success); // Delete a webhook await client.deleteWebhook(hook.id); ``` ## Webhook signature verification When your server receives a webhook, verify the signature to confirm it was sent by Ranked AI. The signature is in the `X-Webhook-Signature` header as `sha256=