Skip to main content

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.
1

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.
2

Webhooks push real-time updates

When keyword positions update or content changes, Ranked AI sends a webhook to your server with the event details.
3

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:
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