Skip to main content

Documentation Index

Fetch the complete documentation index at: https://dev.ranked.ai/llms.txt

Use this file to discover all available pages before exploring further.

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  │────>│  Ranked API  │────>│  Your Database  │
│  (Frontend)      │     │  (Data Pull)  │     │  (Cache)        │
└─────────────────┘     └──────────────┘     └─────────────────┘

                              │ Webhooks

                        ┌──────────────┐
                        │  Your Server  │
                        │  (Webhook     │
                        │   Handler)    │
                        └──────────────┘

Step 1: Discover projects

List all SEO projects for the authenticated user:
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:
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:
// 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

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');
});
DataStrategyFrequency
Keyword positionsWebhook keywords.updated triggers API pullDaily (after scan completes)
AI visibilityWebhook prompts.updated triggers API pullMonthly (after analysis)
Audit resultsWebhook audit.completed triggers API pullMonthly (after scan)
Content statusWebhook content.status_changed for real-timeReal-time
BacklinksScheduled API pull (no webhook yet)Weekly cron job
ReportsOn-demand API callAs 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