Entegrasyon API'si

seopanel sitenizdeki SEO sorunlarını bulur ve bitmiş düzeltme talimatları üretir. Bu API o talimatları kendi backend'inize çekmeniz, kendi içerik şemanıza eşlemeniz ve kalıcı yazmanız içindir. Uygulayan taraf sizin kodunuzdur.

Temel adres: https://ordinaryus.net/api/seo-repair/v1 v1.1.0

OpenAPI belgesini indir (JSON)Tüm uçları, izinleri ve hata kodlarını içeren OpenAPI 3.1 belgesi; Postman, Insomnia veya kod üreticilerine doğrudan aktarılabilir.

Etkileşimli API Referansı →

Başlangıç

  1. Panelden bir API anahtarı oluşturun (SEO Hata Düzeltme → Kanal & Kurulum → Geliştirici API'si). Anahtar, SEO Hata Düzeltme kapalıyken de oluşturulabilir.
  2. POST /handshake ile anahtarı sınayın.
  3. GET /rules/{projectId} ile düzeltmeleri çekin. İlk çağrıda since vermeyin; sonrakilerde dönen cursor değerini since olarak gönderin.
  4. Her düzeltmeyi id ile eşleyerek kendi tarafınıza kalıcı yazın.
  5. POST /rules/{projectId}/applied ile hangilerini uyguladığınızı bildirin.
  6. Bitirince aşağıdaki kurulum kontrol listesini madde madde geçin.

Senkron döngüsü (zorunlu)

seopanel düzeltmeleri hazırlar ama sitenize kendisi yazmaz. Sisteminiz düzeltmeleri düzenli olarak çekmedikçe hiçbir düzeltme sitenize ulaşmaz ve panel bunu "Siten düzeltmeleri almıyor" uyarısıyla gösterir.

  • Zamanlanmış görev: döngüyü en az 15 dakikada bir çalıştırın. Hız sınırı anahtar başına dakikada 300 istektir.
  • cursor değerini kalıcı saklayın. Kaybederseniz sonraki çağrı tüm seti yeniden getirir; zararsızdır, id ile tekilleştirin.
  • removedIds gelirse o düzeltmeleri kendi tarafınızda geri alın.
  • Olay adresleri (webhook) tanımladıysanız "Yeni düzeltme hazır" (fix.ready) bildirimi gelir; içerik bildirimde değil, uçtan çekilir. Bildirimi kaçırmanıza karşı zamanlanmış çekimi yine de sürdürün.

Kimlik doğrulama

Her istekte proje anahtarınızı gönderin. İki biçim de kabul edilir:

X-SP-Auth: sp_…

Authorization: Bearer sp_…

Kodunuzun yazıldığı API sürümünü X-SP-Api-Version başlığıyla bildirin (ör. 1.0.0). Panel, bu değere göre eksik güncellemeleri gösterir; başlık yoksa "siteniz sürüm bildirmiyor" der. Yanıtlar da güncel sürümü aynı başlıkla döner.

X-SP-Api-Version: 1.1.0

Anahtar yönetimi

  • Göster: mevcut anahtarı tekrar gösterir, değiştirmez; çalışan entegrasyon etkilenmez.
  • Yeniden üret: eski anahtar anında geçersiz olur; bakım penceresinde yapın.
  • Bağlantıyı kes: anahtar iptal edilir, sonraki istekler 401 alır.

IP izin listesi

İsteğe bağlı. Açarsanız listede olmayan adresten gelen istek 403 SP_API_IP_DENIED alır; hata metni isteğin geldiği adresi söyler ve panelde "Listeye ekle" ile eklenebilir.

Hız sınırı

Anahtar başına dakikada yaklaşık 300 istek. Aşılırsa 429 SP_API_RATE_LIMITED ve Retry-After başlığı döner.

Hata biçimi

Hatalar her zaman şu gövdeyle döner: success false, error (insan-okur metin), code (makine-okur kod).

{
  "success": false,
  "error": "API anahtarı geçersiz.",
  "code": "SP_API_INVALID_KEY"
}
KodDurumAnlamı
SP_API_NO_KEY401Anahtar gönderilmedi.
SP_API_INVALID_KEY401Anahtar geçersiz ya da iptal edilmiş.
SP_API_KEY_PROJECT_MISMATCH401Yoldaki projectId anahtarın projesi değil.
SP_API_IP_DENIED403İstek IP izin listesinde olmayan bir adresten geldi.
SEO_REPAIR_DISABLED403Proje için SEO Hata Düzeltme kapalı.
PROJECT_INACTIVE403Proje duraklatılmış ya da etkin değil.
SP_API_SINCE_INVALID400since ISO 8601 tarih değil.
SP_API_LIMIT_INVALID400limit 1-500 arasında tam sayı değil.
SP_API_APPLIED_IDS_REQUIRED400Gövdede ids dizisi yok ya da boş.
SP_API_RATE_LIMITED429İstek sınırı aşıldı.
INTEGRATION_SCOPE_MISSING403Anahtarda bu uç için izin açık değil.
INTEGRATION_MODULE_INACTIVE403Uç bir modül gerektiriyor ve modül sitede etkin değil.
INTEGRATION_IDEMPOTENCY_CONFLICT409Aynı Idempotency-Key farklı bir istekle kullanıldı.
INTEGRATION_IDEMPOTENCY_KEY_INVALID400Idempotency-Key boş ya da 128 karakterden uzun.
INTEGRATION_NOT_IMPLEMENTED501Modül açık ama bu uç için işleyici henüz yayında değil.
MODULE_DISABLED403Paketinizde bot takibi kapalı; ziyaretler kaydedilmez.

Uç noktalar

Tüm uçlar JSON konuşur. SEO uçları /api/seo-repair/v1, entegrasyon uçları /api/integrations/v1 altındadır.

Form, sohbet, randevu ve yorum modülleri bu sitede etkin olmadığı sürece "Modül gerekir" rozetli uçlar 403 INTEGRATION_MODULE_INACTIVE döner. Gerçek bir yanıt uydurulmaz.

Bağlantı

  • POST/api/seo-repair/v1/handshake
    Anahtarı doğrula
  • GET/api/seo-repair/v1/status
    Senkron durumu

Düzeltmeler

  • GET/api/seo-repair/v1/rules/{projectId}
    Düzeltmeleri çekSEO Hata Düzeltme açık olmalı
  • POST/api/seo-repair/v1/rules/{projectId}/applied
    Uyguladığını bildirSEO Hata Düzeltme açık olmalıIdempotency-Key
  • GET/api/seo-repair/v1/content/{projectId}
    Yayına hazır içerikleri çek
  • POST/api/seo-repair/v1/content/{projectId}/applied
    İçeriğin sitede uygulandığını bildirIdempotency-Key

Bot ziyaretleri

  • POST/api/seo-repair/v1/bot-visits/{projectId}
    Bot ziyaretlerini gönderIdempotency-Key

MCP

  • POST/api/seo-repair/v1/mcp
    MCP sunucusu (JSON-RPC)

Entegrasyon

  • GET/api/integrations/v1/me
    Anahtar, izinler, açık modüller

Randevular · modül gerekir

  • GET/api/integrations/v1/bookings/offerings
    Hizmetleri listeleModül gerekirİzin: bookings:read
  • GET/api/integrations/v1/bookings/availability
    Boş saatlerModül gerekirİzin: bookings:read
  • GET/api/integrations/v1/bookings/appointments
    Randevuları listeleModül gerekirİzin: bookings:read
  • POST/api/integrations/v1/bookings/appointments
    Randevu açModül gerekirİzin: bookings:writeIdempotency-Key
  • GET/api/integrations/v1/bookings/appointments/{appointmentId}
    Randevuyu okuModül gerekirİzin: bookings:read
  • POST/api/integrations/v1/bookings/appointments/{appointmentId}/approve
    OnaylaModül gerekirİzin: bookings:writeIdempotency-Key
  • POST/api/integrations/v1/bookings/appointments/{appointmentId}/cancel
    İptal etModül gerekirİzin: bookings:writeIdempotency-Key

Bot ziyaretleri

Sitenizin sunucusunun gördüğü istekleri toplu gönderin (adres, user-agent, zaman, istemci IP'si). Bot olmayan istekler bizde elenir; yalnız yapay zekâ ve arama motoru botları kaydedilir ve panelde Bot Ziyaretleri sayfasında görünür. Sunucu tarafında sayıldığı için JavaScript ya da görsel çalıştırmayan botlar da görünür; piksel bu ziyaretleri göremez.

  • Tek çağrıda en fazla 500 kayıt işlenir, fazlası yok sayılır. İstekleri bellekte biriktirip birkaç saniyede bir ya da 100 istekte bir tek çağrıyla gönderin.
  • ip önerilir: doğrulama için gereklidir. ip göndermeyen kayıtlar doğrulanmamış ("–") olarak saklanır ve panelde görünür; geçersiz biçimli ip ise reddedilir. IP'yi kendi proxy/CDN başlığınızdan alın (örn. CF-Connecting-IP); istemcinin gönderdiği X-Forwarded-For taklit edilebilir. IP bot ağından gelmiyorsa ziyaret "Doğrulandı" değil "–" olarak görünür.
  • ts epoch milisaniye ya da ISO 8601 olabilir; yoksa, geçersizse ya da gelecekteyse alındığı an kullanılır. 7 günden eski kayıtlar saklanmaz. accepted işlenen kayıt sayısı, stored bot olarak tanınıp kaydedilen ziyaret sayısıdır.
  • Anahtar yalnız kendi projesinin adresine yazabilir (başka projectId için 401 SP_API_KEY_PROJECT_MISMATCH). SEO Hata Düzeltme kapalı olsa da çalışır; aynı gövdeyi yeniden göndermemek için Idempotency-Key ekleyin.
Örnek istek
curl -X POST https://ordinaryus.net/api/seo-repair/v1/bot-visits/PROJE_KIMLIGI \
  -H "X-SP-Auth: $SP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: toplu-2026-10-27-1" \
  -d '{"hits":[{"url":"/hakkimizda","ua":"Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)","ts":1790000000000,"ip":"20.171.207.1"}]}'
Yanıt
{
  "success": true,
  "accepted": 120,
  "stored": 7
}

Hatalar: 401 anahtar yok/geçersiz/başka proje, 403 IP reddi ya da paketinizde bot takibi kapalı (MODULE_DISABLED), 400 geçersiz gövde, 429 hız sınırı.

Sunucu günlüğünden gönderme

Aşağıdaki bağımlılıksız Node betiği nginx access_log dosyasını kaldığı yerden okur, bot benzeri satırları süzer ve 500'lük gruplar halinde gönderir. Cron ile 5 dakikada bir çalıştırın.

sp-bot-visits.js
const fs = require('fs')
const crypto = require('crypto')

const LOG = process.env.ACCESS_LOG || '/var/log/nginx/access.log'
const STATE = process.env.STATE_FILE || '/var/tmp/sp-bot-visits.offset'
const PROJECT_ID = process.env.SP_PROJECT_ID
const API_KEY = process.env.SP_API_KEY
const ENDPOINT = 'https://ordinaryus.net/api/seo-repair/v1/bot-visits/' + PROJECT_ID
const LINE = /^(\S+) \S+ \S+ \[([^\]]+)\] "(?:GET|HEAD|POST) (\S+) [^"]*" \d+ \S+ "[^"]*" "([^"]*)"/
const BOTISH = /bot|crawl|spider|slurp|gpt|claude|perplexity|anthropic|bytespider|amazonbot|applebot|meta-external|duckassist|mistral|cohere|ccbot/i

function parseTime(s) {
  const m = /^(\d+)\/(\w+)\/(\d+):(\d+):(\d+):(\d+) ([+-]\d{4})$/.exec(s)
  if (!m) return Date.now()
  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
  const utc = Date.UTC(+m[3], months.indexOf(m[2]), +m[1], +m[4], +m[5], +m[6])
  const off = (m[7][0] === "-" ? -1 : 1) * (parseInt(m[7].slice(1, 3), 10) * 60 + parseInt(m[7].slice(3), 10))
  return utc - off * 60000
}

async function send(hits) {
  const key = crypto.createHash('sha256').update(JSON.stringify(hits)).digest('hex').slice(0, 64)
  const res = await fetch(ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-SP-Auth': API_KEY, 'Idempotency-Key': key },
    body: JSON.stringify({ hits }),
  })
  if (!res.ok) throw new Error('bot-visits ' + res.status + ' ' + (await res.text()))
}

(async () => {
  let offset = 0
  try { offset = parseInt(fs.readFileSync(STATE, 'utf8'), 10) || 0 } catch {}
  const size = fs.statSync(LOG).size
  if (size < offset) offset = 0
  const fd = fs.openSync(LOG, "r")
  const buf = Buffer.alloc(size - offset)
  fs.readSync(fd, buf, 0, buf.length, offset)
  fs.closeSync(fd)
  const text = buf.toString("utf8")
  const end = text.lastIndexOf("\n") + 1
  const hits = []
  for (const line of text.slice(0, end).split("\n")) {
    const m = LINE.exec(line)
    if (!m || !BOTISH.test(m[4])) continue
    hits.push({ ip: m[1], ts: parseTime(m[2]), url: m[3], ua: m[4] })
  }
  for (let i = 0; i < hits.length; i += 500) await send(hits.slice(i, i + 500))
  fs.writeFileSync(STATE, String(offset + Buffer.byteLength(text.slice(0, end))))
})().catch((e) => { console.error(e.message); process.exit(1) })
Cron satırı (5 dakikada bir)
*/5 * * * * SP_PROJECT_ID=PROJE_KIMLIGI SP_API_KEY=sp_... /usr/bin/node /opt/sp-bot-visits.js >> /var/log/sp-bot-visits.log 2>&1

İnsan ziyaretçilerin kaydı tutulmaz: yalnız user-agent'ı tanınan botların adresi, user-agent'ı ve IP'si saklanır.

Uç ayrıntıları

POST /handshake

Anahtarın geçerli olduğunu ve hangi projeye bağlı olduğunu gösterir. Durum değiştirmez; kurulumda sağlık kontrolü olarak kullanın.

{
  "success": true,
  "ok": true,
  "projectId": "665f1c2a9b1e4a0012a3b4c5",
  "domain": "ornek.com"
}

GET /status

Senkron sağlığı: mod, aktif kanal, son çekim, düzeltmelerin alınıp alınmadığı, durum sayıları ve bildirdiğiniz API sürümü.

GET /rules/{projectId}

Uygulanmaya hazır düzeltmeleri döner. İlk senkronda since vermeyin; artımlı senkronda sakladığınız cursor'ı since olarak gönderin. hasMore true ise dönen cursor ile tekrar çağırın. Boş mutations arıza demek değildir; sebebini emptyReason söyler. Çekilen düzeltmeler çekildi olarak işaretlenir.

Sorgu parametreleri: since (ISO 8601), limit (1-500, varsayılan 100), url (tek sayfa).

{
  "success": true,
  "mutations": [
    {
      "id": "665f1c2a9b1e4a0012a3b4c5",
      "url": "https://ornek.com/hakkimizda",
      "updatedAt": "2026-10-26T10:12:00.000Z",
      "rules": [
        { "selector": "meta[name=description]", "action": "set_attribute", "attribute": "content", "value": "Şirketimizin kuruluşu, ekibi ve hizmet alanları." }
      ]
    }
  ],
  "removedIds": [],
  "textFixes": [],
  "removedTextFixIds": [],
  "cursor": "2026-10-26T10:12:00.000Z",
  "hasMore": false,
  "mode": "full",
  "emptyReason": null
}

emptyReason değerleri

  • up_to_date — since cursor'ından beri yeni düzeltme yok. Normal; aynı cursor ile tekrar çağırın.
  • awaiting_manual_generation — Proje yarı otonom modda, henüz düzeltme üretilmedi. Panelden üretin ya da tam otonom moda alın.
  • awaiting_approval — Düzeltmeler var ama onaylanmadı. Panelden onaylayın.
  • in_progress — Bu tur hazırlanıyor. Bekleyip cursor ile tekrar çağırın.
  • none_yet — Bu proje için henüz düzeltme hazırlanmadı. Sonraki taramadan sonra tekrar çağırın.
  • inactive_channel — Projenin aktif kanalı Geliştirici API'si değil; düzeltmeler başka bir kanaldan gidiyor.

POST /rules/{projectId}/applied

Kendi sisteminize yazdığınız düzeltmelerin id'lerini bildirir; düzeltme Uygulandı kademesine geçer ve doğrulama taraması neyi arayacağını bilir. İdempotenttir. Blog yazısı ve yeni sayfa için urls ile sitedeki adresi de gönderin; projenin alan adı dışındaki adres yok sayılır.

{ "ids": ["665f1c2a9b1e4a0012a3b4c5"], "urls": { "665f1c2a9b1e4a0012a3b4c5": "https://ornek.com/blog/seo-nedir" } }

Yanıt: acknowledged (ilk kez işaretlenen), requested (gönderdiğiniz), invalid (bu projede karşılığı olmayan), ignored (zaten uygulanmış ya da şu an uygulanamaz durumda olan).

{ "success": true, "acknowledged": 1, "requested": 1, "invalid": 0, "ignored": 0 }

POST /mcp

JSON-RPC 2.0 konuşan MCP ucu (Streamable HTTP, tek yanıt). initialize, ping, tools/list ve tools/call desteklenir; araçlar test_connection, get_seo_fixes, report_applied ve get_status. GET 405 döner.

Talimat türleri

Her kuralın action alanı ne yapacağınızı söyler. Tanımadığınız talimatı atlayabilirsiniz; doğrulama taraması eksik kalanı raporlar.

actionGelen alanlarSizde ne yapılır
set_attributeselector, attribute, valueİlgili elemanın niteliğini yazın. meta[name=description] + content meta açıklama, link[rel=canonical] + href canonical, meta[name=robots] + content robots.
set_htmlselector, valueSeçilen elemanın iç HTML'ini value ile değiştirin (h1 sayfa başlık alanı).
replace_htmlselector, valueset_html ile aynı.
replace_textselector, matchText, valueElemanın metninde matchText'i bulup value ile değiştirin.
remove_elementselectorElemanı kaldırın ya da gizleyin.
add_classselector, valueSınıf ekleyin (görsel; çoğu backend'de atlanabilir).
remove_classselector, valueSınıfı çıkarın (görsel; çoğu backend'de atlanabilir).
remove_attributeselector, attributeNiteliği silin.
change_tagselector, valueElemanın etiketini değiştirin (div → h2). Şablon katmanında ele alın.
create_if_missingcreateObject, positionEksikse yeni eleman ekleyin (ör. eksik meta etiketi).
add_redirectfrom, to, statusKalıcı yönlendirme kuralı yazın.
remove_redirectfromDaha önce yazdığınız yönlendirmeyi kaldırın.
inject_related_linkslinks[]İçeriğe "ilgili sayfalar" iç linkleri ekleyin.
create_pagetitle, content, slug, excerpt, postType, tags, featuredImageUrl, statusYeni sayfa ya da blog yazısı oluşturun. Yayın adresini applied bildiriminde urls ile gönderin.
set_http_headerheadersHTTP yanıt başlığı ekleyin (ör. X-Robots-Tag). Sunucu ya da CDN katmanında uygulanır.
inject_csscssSite geneli CSS ekleyin.
put_filepath, valuepath'teki dosyayı value içeriğiyle oluşturun ya da güncelleyin (robots.txt, sitemap.xml).
optimize_mediasrc, maxWidth, maxHeightsrc'teki görseli en-boy oranını koruyarak kutuya sığdırıp daha hafif biçimde yayınlayın. Orijinali silmeyin.
insert_content_blockblockId, position, htmlİçeriğin başına (start) ya da sonuna (end) html bloğunu ekleyin. Aynı blockId ikinci kez gelirse eskisini değiştirin.
replace_phraseselector, matchText, valueselector içindeki görünen metinde matchText ifadesini value ile değiştirin; boşluk farkları yok sayılır.

selector bir CSS/DOM seçicisidir. DOM'u olmayan bir sistemde onu anlamına göre kendi alanınıza eşleyin: meta[name=description] meta açıklama, title sayfa başlığı, link[rel=canonical] canonical. Şablona bağlı serbest seçicileri çözemiyorsanız atlayın.

Örnekler

curl ile anahtarı sınama ve düzeltmeleri çekme
curl -X POST https://ordinaryus.net/api/seo-repair/v1/handshake \
  -H "X-SP-Auth: $SP_API_KEY" \
  -H "X-SP-Api-Version: 1.1.0"

curl "https://ordinaryus.net/api/seo-repair/v1/rules/$SP_PROJECT_ID?limit=100" \
  -H "Authorization: Bearer $SP_API_KEY" \
  -H "X-SP-Api-Version: 1.1.0"

curl -X POST https://ordinaryus.net/api/seo-repair/v1/rules/$SP_PROJECT_ID/applied \
  -H "X-SP-Auth: $SP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids":["<id>"],"urls":{"<id>":"https://ornek.com/blog/seo-nedir"}}'
Node.js senkron döngüsü (18+, bağımlılıksız)
const BASE = 'https://ordinaryus.net/api/seo-repair/v1';
const PROJECT_ID = process.env.SP_PROJECT_ID;
const HEADERS = {
  'X-SP-Auth': process.env.SP_API_KEY,
  'X-SP-Api-Version': '1.1.0',
  'Content-Type': 'application/json',
};

async function syncFixes(store) {
  let since = await store.getCursor();
  let hasMore = true;
  while (hasMore) {
    const qs = new URLSearchParams({ limit: '100', ...(since ? { since } : {}) });
    const res = await fetch(`${BASE}/rules/${PROJECT_ID}?${qs}`, { headers: HEADERS });
    if (res.status === 429) return;
    if (!res.ok) throw new Error(`pull ${res.status}`);
    const body = await res.json();

    const applied = [];
    for (const m of body.mutations) {
      await store.applyRules(m.url, m.rules);
      applied.push(m.id);
    }
    for (const id of body.removedIds || []) await store.revert(id);

    if (applied.length) {
      await fetch(`${BASE}/rules/${PROJECT_ID}/applied`, {
        method: 'POST', headers: HEADERS, body: JSON.stringify({ ids: applied }),
      });
    }
    since = body.cursor;
    await store.saveCursor(since);
    hasMore = body.hasMore;
  }
}
PHP senkron döngüsü
<?php
$base = 'https://ordinaryus.net/api/seo-repair/v1';
$projectId = getenv('SP_PROJECT_ID');
$headers = [
    'X-SP-Auth: ' . getenv('SP_API_KEY'),
    'X-SP-Api-Version: 1.1.0',
    'Content-Type: application/json',
];

function sp_request(string $method, string $url, array $headers, ?array $body = null): array {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_POSTFIELDS => $body === null ? null : json_encode($body),
    ]);
    $raw = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    return [$status, json_decode((string) $raw, true) ?? []];
}

$since = file_exists('cursor.txt') ? trim(file_get_contents('cursor.txt')) : null;
do {
    $query = http_build_query(array_filter(['limit' => 100, 'since' => $since]));
    [$status, $data] = sp_request('GET', "$base/rules/$projectId?$query", $headers);
    if ($status !== 200) break;
    $applied = [];
    foreach ($data['mutations'] as $m) {
        apply_rules($m['url'], $m['rules']);
        $applied[] = $m['id'];
    }
    foreach ($data['removedIds'] as $id) revert_fix($id);
    if ($applied) sp_request('POST', "$base/rules/$projectId/applied", $headers, ['ids' => $applied]);
    $since = $data['cursor'];
    if ($since) file_put_contents('cursor.txt', $since);
} while (!empty($data['hasMore']));

Döngüyü 15 dakikada bir çalıştırın. Blog yazıları ve sayfa düzeltmeleri aynı döngüden gelir; ayrı zamanlama gerekmez.

Zamanlama (cron)
*/15 * * * * cd /var/www/site && node scripts/seopanel-sync.js >> /var/log/seopanel-sync.log 2>&1

MCP sunucusu

Yapay zekâ asistanınızı (Claude Desktop, Cursor) aynı anahtarla bağlayın. Araçlar: test_connection, get_seo_fixes, report_applied, get_status.

Sunucu JSON-RPC 2.0 konuşur: POST ile tek yanıt, bildirimler için 202; GET 405 döner.

Cursor (Streamable HTTP)
{
  "mcpServers": {
    "seopanel": {
      "url": "https://ordinaryus.net/api/seo-repair/v1/mcp",
      "headers": {
        "X-SP-Auth": "sp_…"
      }
    }
  }
}
Claude Desktop (mcp-remote köprüsü)
{
  "mcpServers": {
    "seopanel": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://ordinaryus.net/api/seo-repair/v1/mcp",
        "--header",
        "X-SP-Auth:sp_…"
      ]
    }
  }
}
Yerel stdio sunucusu
{
  "mcpServers": {
    "seopanel": {
      "command": "npx",
      "args": [
        "tsx",
        "mcp/server.ts"
      ],
      "env": {
        "SEOPANEL_API_URL": "https://ordinaryus.net",
        "SEOPANEL_API_KEY": "sp_…"
      }
    }
  }
}

Depo yerelde çalışıyorsa asistanın bağlanacağı komut: npx tsx mcp/server.ts. SEOPANEL_API_URL ve SEOPANEL_API_KEY ortam değişkenlerini verin.

Claude web arayüzündeki "özel bağlayıcı ekle" yalnızca OAuth kabul eder; bu yüzden MCP sunucusu oradan eklenemez.

Entegrasyon

GET /api/integrations/v1/me anahtarın projesini, açık izinleri, sitede etkin modülleri ve olay adlarını döner. Kurulumda sağlık kontrolü olarak kullanın.

İstek
curl https://ordinaryus.net/api/integrations/v1/me \
  -H "X-SP-Auth: $SP_API_KEY"
Yanıt
{
  "success": true,
  "data": {
    "project": {
      "id": "665f1c2a9b1e4a0012a3b4c5",
      "domain": "ornek.com"
    },
    "scopes": [
      "forms:read"
    ],
    "modules": [],
    "events": []
  }
}

modules alanı bu sitede gerçekten açık modüllerin listesidir (conversations, forms, bookings, comments, reviews). Modül yoksa boş liste döner; bu bir hata değildir.

Modül uçları · modül gerekir

Randevu uçları yalnız Online Randevu modülü sitenizde açıksa ve anahtarda ilgili izin açıksa çalışır. Modülü kullanmıyorsanız bu uçları kurmanız gerekmez; SEO düzeltmeleri ve blog teslimi bunlara bağlı değildir.

GrupİzinlerGereken modül
Randevularbookings:read, bookings:writeOnline Randevu
Modül sitede açık değilken yanıt
{
  "success": false,
  "error": "Bu uç \"forms\" modülünü gerektirir; modül bu sitede etkin değil.",
  "code": "INTEGRATION_MODULE_INACTIVE"
}

Modül açık değilken bu uçlar boş liste ile 200 dönmez; 403 INTEGRATION_MODULE_INACTIVE döner. Anahtarda izin yoksa 403 INTEGRATION_SCOPE_MISSING döner.

İçerik API'si (hesap anahtarı)

Hesap anahtarı tüm projelerinize erişen kişisel anahtardır; proje anahtarından ayrıdır. Hesap → API Erişimi sayfasından seçtiğiniz izinlerle oluşturun, Authorization başlığında Bearer olarak gönderin. Anahtar yalnız oluşturulurken gösterilir.

Örnek istek
curl https://ordinaryus.net/api/public/v1/me \
  -H "Authorization: Bearer spt_…"

Anahtar başına dakikada 120 istek, bunun 30 tanesi yazma olabilir. Yanıtlarda önbellek kapalıdır.

Uç noktaİzinAçıklama
GET /api/public/v1/meaccount:readHesap, plan ve kredi bakiyesi
GET /api/public/v1/projectsprojects:readErişebildiğiniz projeler
GET /api/public/v1/projects/{projectId}/articlescontent:readProjenin yazılarını sayfalı listeler; status ile süzülür
POST /api/public/v1/projects/{projectId}/articlescontent:writeYazı üretimini başlatır; bakiyeden düşer, 202 ile kimlik döner
GET /api/public/v1/articles/{articleId}content:readYazıyı metni, SEO alanları ve denetim sonuçlarıyla getirir
PATCH /api/public/v1/articles/{articleId}content:writeYazıyı düzenler; yayındaki yazıda siteye gitmez, sync ile gönderilir
POST /api/public/v1/articles/{articleId}/publishcontent:publishYazıyı bağlı kanala taslak veya yayın olarak gönderir
POST /api/public/v1/articles/{articleId}/synccontent:publishYayındaki yazının düzenlemesini siteye gönderir
GET /api/public/v1/projects/{projectId}/reportreports:readSon 30 günün içerik sayıları, harcanan kredi ve bu ayın blog planı

Yayın uçları paneldeki denetimlerin aynısından geçer: bloklayan kontrolü olan yazı yayınlanmaz, yazma ve yayın için ayrı izin gerekir, ekip üyesinin rolü burada da geçerlidir. POST isteklerinde Idempotency-Key başlığıyla tekrar güvenli hale gelir. Hata gövdesi success, error ve code alanlarını taşır; 401 anahtar geçersiz, 403 izin yok, 402 kredi yetersiz, 422 denetim engeli, 429 sınır aşıldı demektir.

Webhooks

Seçtiğiniz olaylar olduğu anda verdiğiniz https adresine imzalı JSON olarak POST edilir. Adresleri panelde Entegrasyonlar → Olay adresleri bölümünden eklersiniz; en çok 5 adres. Modülü sitede açık olmayan olaylar seçilemez.

OlayNe zamanModül gerekir
fix.readyYeni düzeltme hazırModül gerekmez
form.submission.createdYeni form kaydıFormlar
chat.conversation.startedYeni konuşma başladıKonuşmalar
chat.lead.capturedZiyaretçi bilgilerini bıraktıKonuşmalar
chat.handoff.requestedZiyaretçi bir yetkiliyle görüşmek istediKonuşmalar
chat.message.createdYeni mesaj (ziyaretçi, bot ya da ekip)Konuşmalar
chat.conversation.closedKonuşma kapatıldıKonuşmalar
booking.appointment.createdYeni randevuRandevular
booking.appointment.confirmedRandevu onaylandıRandevular
booking.appointment.rescheduledRandevu yeni saate alındıRandevular
booking.appointment.cancelledRandevu iptal edildiRandevular
social.comment.createdYeni Instagram yorumuYorumlar
review.createdYeni Google yorumuGoogle Yorumları
content.publishedİçerik yayınlandıModül gerekmez
content.updatedYayındaki içerik güncellendiModül gerekmez
Gövde zarfı
{
  "id": "evt_9f2c1a7b3d5e8a4c01",
  "type": "fix.ready",
  "occurredAt": "2026-10-26T09:15:00.000Z",
  "projectId": "clx0abc123",
  "data": {
    "fixId": "clx8k2m0a0001",
    "pageUrl": "https://ornek.com/hakkimizda",
    "ruleCount": 3
  }
}

content.published ve content.updated olaylarının data alanında yazının tamamı gelir: contentId, op, title, slug, url, html, excerpt, metaTitle, metaDescription, keywords, faqJsonLd, featuredImage ve publishedAt. Görseller gövdeye gömülmez; imzalı adres olarak gelir ve 7 gün geçerlidir, kalıcı kullanım için kendi sunucunuza kopyalayın. content.published aynı yazı için bir kez gönderilir; Blogger'da zamanlanan yazıda publishedAt ileri tarihlidir.

Her teslimde X-SP-Signature (t=zaman damgası, v1=HMAC-SHA256 imzası), X-SP-Event (olay adı), X-SP-Event-Id (tekrar eden teslimi ayıklamak için) ve X-SP-Delivery (teslim kimliği) başlıkları gönderilir. İmza, t + "." + ham gövde üzerinden olay adresinin imza anahtarıyla (whsec_…) hesaplanır; her denemede t yenilenir. Gövdeyi ayrıştırmadan, gelen ham metin üzerinden doğrulayın ve t değeri 5 dakikadan eskiyse reddedin.

X-SP-Signature: t=1790000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-SP-Event: fix.ready
X-SP-Event-Id: evt_9f2c1a7b3d5e8a4c01
X-SP-Delivery: clx8k2m0a0001
İmza doğrulama örneği (Node)
const crypto = require('crypto')

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false
  const expected = crypto.createHmac('sha256', secret).update(parts.t + '.' + rawBody).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(parts.v1 || "")
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

İmza anahtarı adres eklenirken ya da yenilenirken yalnız bir kez gösterilir; sonradan yalnız son 4 hanesi görünür. Anahtarı yenilerseniz eskisi anında geçersiz olur.

Panelde "Deneme gönder" ile ping olayı yollayabilirsiniz; gerçek olaylarla aynı imzayı taşır, yeniden denenmez ve hata sayacına yazılmaz.

Teslim kuralları

  • Yalnız 2xx yanıt başarılı sayılır.
  • Yönlendirmeler izlenmez; 3xx yanıt başarısız sayılır.
  • Yanıt için 10 saniye beklenir.
  • Başarısız teslim artan aralıkla toplam 8 kez denenir (ilk deneme hemen; sonra 1 dk, 5 dk, 15 dk, 1 sa, 3 sa, 6 sa ve 12 sa sonra). 8. denemeden sonra vazgeçilir; panelde "Yeniden gönder" ile yeniden sıraya alınabilir.
  • Art arda 50 başarısız denemede olay adresi kapatılır ve bildirim gönderilir. API'den yapılan yazımlar olay üretmez (döngü oluşmaz).
  • Aynı olay aynı X-SP-Event-Id ile yeniden gelebilir; tekilleştirmek için bu kimliği kullanın.
  • Adres yalnız herkese açık bir https adresi olabilir; iç ağ adreslerine gönderim yapılmaz.
  • Teslim kayıtları 30 gün saklanır.

İzinler ve tekrar koruması

Modül uçları anahtarda yalnızca panelde açtığınız izinlerle çalışır: conversations:read|write, forms:read|write, bookings:read|write, comments:read|write, reviews:read. İzni olmayan uç 403 INTEGRATION_SCOPE_MISSING döner. SEO uçları izin sormaz.

POST ve PATCH isteklerine Idempotency-Key başlığı ekleyin (en çok 128 karakter). Aynı anahtarla gelen ikinci istek işi tekrar yapmaz, ilk yanıtı Idempotent-Replayed: true başlığıyla döner; aynı anahtar farklı bir istekle kullanılırsa 409 INTEGRATION_IDEMPOTENCY_CONFLICT. Anahtar 24 saat saklanır.

GET /me anahtarın projesini, açık izinleri, sitede gerçekten açık modülleri ve olay adlarını döner. Modül listesi mağaza aboneliklerinden okunur; hiç modül açık değilse boştur.

Liste uçları limit (en çok 100) ve cursor alır; nextCursor bir sonraki sayfanın imlecidir.

Idempotency-Key ile mesaj gönderme
curl -X POST https://ordinaryus.net/api/integrations/v1/conversations/chat_123/messages \
  -H "X-SP-Auth: $SP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: yanit-8841" \
  -d '{"text":"Merhaba"}'

Tüm liste uçlarında limit en çok 100'dür. cursor/nextCursor ile sayfalayın; since verirseniz yalnız o andan sonra değişenler gelir.

https://ordinaryus.net/api/integrations/v1

Sürüm notları

Entegrasyonunuz yazıldıktan sonra eklenen her yenilik burada sürümüyle durur. "Yapmanız gereken" boş değilse o yenilikten yararlanmak için kodunuza ekleme gerekir; eklemezseniz mevcut akış bozulmaz.

SürümTarihYenilikYapmanız gereken
1.0.02026-10-26İlk sürüm: handshake, status, rules (cursor/since, removedIds), applied (urls ile yazı adresi), MCP sunucusu ve modül uçları için izin/modül kapısı.Her isteğe kodunuzun yazıldığı sürümü X-SP-Api-Version başlığıyla ekleyin; blog yazısı ve yeni sayfa (create_page) için applied bildirimine urls ekleyin.
1.1.02026-10-27Bot ziyaretleri ucu (POST /bot-visits/{projectId}) yayında; /me sitede gerçekten açık modülleri döner; modül uçlarında modül kapısı (modül yokken 403 INTEGRATION_MODULE_INACTIVE); yazma isteklerinde Idempotency-Key.—

Kurulum kontrol listesi

Entegrasyonu bitirdiğinizi düşündüğünüzde her maddeyi kontrol edin. Biri eksikse düzeltmeler sitenize geç ulaşır ya da hiç ulaşmaz.

  • API anahtarı: POST /handshake başarılı dönüyor.
  • Sürüm başlığı: her istek X-SP-Api-Version taşıyor.
  • İlk çekim: GET /rules/{projectId} ile gelen düzeltmeler veritabanınıza yazıldı ve sitede görünüyor.
  • Zamanlanmış senkron: döngü en az 15 dakikada bir çalışıyor ve cursor kalıcı saklanıyor.
  • Uygulandı bildirimi: uyguladığınız düzeltmeler için POST /rules/{projectId}/applied çağrılıyor; create_page için urls ile adres de gidiyor.
  • removedIds: geri çekilen düzeltmeleri kendi tarafınızda geri alıyorsunuz.
  • Panel: "Siten düzeltmeleri almıyor" uyarısı görünmüyor ve uygulanan düzeltmeler yeniden taramada Doğrulandı durumuna geçiyor.

CRM kurulum kontrol listesi

  • GET /me başarılı dönüyor ve gerekli izinler scopes içinde görünüyor.
  • Kullandığınız modül modules listesinde var; yoksa modül uçları 403 INTEGRATION_MODULE_INACTIVE dönecektir.
  • POST ve PATCH isteklerinde Idempotency-Key gönderiyorsunuz.
  • Liste uçlarını limit ve cursor ile sayfalıyorsunuz.