İçeriğe geç
Approvalens

Geliştiriciler · API v1

REST API

Hesap sayfanın bir site portföyü için yaptığı her şey, JSON olarak: siteleri toplu ekle, etiketle ve çıkar; erişilebilirliği, ads.txt, robots ve noindex sinyallerini, sunucuları ve güvenlik durumlarını oku; tarama başlat ve raporları oku.

Temel adres

https://approvalens.com/api/v1

Her ücretli plana dahil · her site ve sunucu planına dahil.

Anahtar oluşturOpenAPI 3.1

Hızlı başlangıç

Temel adres

https://approvalens.com/api/v1

Her uç nokta JSON döner ve yalnızca anahtarın sahibi olan hesaba dokunur: sitelerine, sunucularına, etiketlerine ve e‑posta adresinle taranan ya da satın alınan raporlara.

  1. 1

    Oturum aç. Her ücretli plan API'yi içerir: site planı da sunucu planı da.

  2. 2

    Hesap → API anahtarları bölümünü aç ve bir anahtar oluştur. Betiğin bir şeyleri değiştirecekse "Okuma ve yazma"yı seç. Anahtarı kopyala: yalnızca bir kez gösterilir.

  3. 3

    Anahtarı bir ortam değişkenine koy ve her istekte bearer token olarak gönder.

1. Anahtarı dene: kime ait, yetkileri ne, kaç boş site yerin var
export APPROVALENS_KEY=al_live_…

curl https://approvalens.com/api/v1/account \
  -H "Authorization: Bearer $APPROVALENS_KEY"
2. Tek seferde 300'e kadar site ekle, bir etiketle (önce dry_run ile önizle)
curl -X POST https://approvalens.com/api/v1/sites \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["example.com", "news.example.net"], "tags": ["client:acme"], "dry_run": true}'
3. Sorunu olan siteleri bul, örneğin ads.txt'si eksik olanları
curl "https://approvalens.com/api/v1/sites?signal=ads_txt_problem" \
  -H "Authorization: Bearer $APPROVALENS_KEY"
4. Bir tarama başlat ve status done olana kadar yokla
id=$(curl -s -X POST https://approvalens.com/api/v1/scans -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Content-Type: application/json" -d '{"url": "example.com"}' | jq -r .scan.id)

while [ "$(curl -s https://approvalens.com/api/v1/scans/$id -H "Authorization: Bearer $APPROVALENS_KEY" | jq -r .scan.status)" != "done" ]; do sleep 15; done

Kimlik doğrulama ve erişim

Her ücretli plana dahil

API erişimi her ücretli planla gelir: site planı (ayda 4,99 $'dan başlar) ya da sunucu planı; plan etkinken ya da iptal edilmiş ama henüz bitmemişken. Tam rapora dahil bir aylık izleme API içermez. Plan yoksa anahtarların silinmez ama her istek 403 subscription_required döner; plan yeniden etkin olunca aynı anahtarlar çalışır.

Anahtarlar al_live_ ile başlar, ardından 32 karakter gelir. Authorization başlığında gönder:

Authorization: Bearer al_live_••••••••••••••••••••••••••••••••

Bir hesabın en fazla 5 etkin anahtarı olabilir. Hesap sayfasında iptal ettiğin anahtar hemen çalışmaz olur. Her anahtarın yalnızca SHA-256 parmak izini saklarız; kaybolan anahtar yeniden gösterilemez, yenisini oluştur.

API sunucular ve betikler içindir. CORS başlığı göndermez; başka bir sitedeki web sayfası onu ziyaretçinin tarayıcısından çağıramaz. Anahtarı asla ön yüz koduna koyma.

Yetkiler: okuma ve yazma

Her anahtarın, oluştururken seçtiğin bir yetkisi vardır:

  • okuma: tüm GET uç noktaları ve ücretsiz yapay zekâ botu kontrolü (hesabında hiçbir şeyi değiştirmez).
  • okuma ve yazma: okumanın yaptığı her şey, artı site ekleme, etiketleme ve çıkarma, etiketler, sunucu kayıt anahtarları, tarama başlatma ve hesabında zaten olan bir krediyi kullanma.

7 Ekim 2026'dan önce oluşturulan anahtarlar yalnızca okuma yetkilidir: o zaman yazma erişimi yoktu ve eski bir betikteki eski bir anahtarın sessizce bu yetkiyi kazanmasını istemedik. Bir şeyleri değiştiren betikler için yazma yetkili yeni bir anahtar oluştur. Yalnızca okuma yetkili bir anahtar yazma uç noktasını çağırırsa 403 insufficient_scope alır.

403
{
  "error": "insufficient_scope",
  "message": "This key is read-only. Make a key with the write scope on your account page to POST here.",
  "details": {
    "required": "write"
  }
}

Hiçbir uç nokta senden ücret almaz. POST /api/v1/scans/:id/unlock yalnızca hesabında zaten olan bir site kredisini kullanır; kredin kalmadıysa 402 no_credits döner.

İstek sınırları

Anahtar başına dakikada 60 istek; kayan bir dakika üzerinden sayılır ve herhangi bir saniyede en fazla 10. Her yanıtta şunlar bulunur:

  • X-RateLimit-Limit dakikalık sınır
  • X-RateLimit-Remaining geçerli dakikada kalan istek
  • X-RateLimit-Reset en eski isteğin pencereden çıkacağı an (Unix saniyesi)
  • X-RateLimit-Burst saniyelik üst sınır
  • X-Request-Id bu isteğin kimliği; bize yazarsan bunu belirt

Bir sınır aşılınca saniye cinsinden Retry-After başlığıyla 429 rate_limited dönülür. Bazı uç noktaların, sitedekiyle aynı, kendi ek sınırları vardır:

  • POST /ai-checks: anahtar başına dakikada 6; son 10 dakikada kontrol edilmiş bir site o sonucu geri alır (cached: true).
  • POST /scans: anahtar başına dakikada 8, hesap başına da ücretsiz taramanın saatlik ve günlük sınırları. Son 20 dakikada taranmış bir site o taramayı geri verir.
  • POST /sites: hesap başına 10 dakikada 30 çağrı ve saatte 1.500 eklenen site; hesap sayfasındaki içe aktarmayla ortak.
  • POST /sites/remove: hesap başına 10 dakikada 20 çağrı. POST /scans/:id/unlock: 10 dakikada 12.
  • Yanlış anahtarlı istekler: IP adresi başına dakikada 20.
429
{
  "error": "rate_limited",
  "message": "Rate limit of 60 requests per minute per key reached. Retry in 12 s."
}

Sayfalama

Listeler (siteler, sunucular, taramalar, raporlar) sayfa sayfa gelir ve sen okurken bir sayfa kaymasın diye sıralıdır: siteler ve sunucular kimliğe göre, taramalar en yeni önce. limit sayfa boyutudur (varsayılan 100, en fazla 300; varsayılan en büyük site planını kapsar, yani hiç sayfalamayan istemciler de her siteyi görür).

Her yanıtta next_cursor bulunur. Sonraki sayfa için onu aynı filtrelerle, hiç değiştirmeden ?cursor= olarak geri gönder; son sayfada null olur. İmleçler opaktır, kendin oluşturma. total (siteler, sunucular) filtrelere uyan tüm öğeleri sayar.

cursor=""
while :; do
  page=$(curl -s "https://approvalens.com/api/v1/sites?limit=300${cursor:+&cursor=$cursor}" -H "Authorization: Bearer $APPROVALENS_KEY")
  echo "$page" | jq -r '.sites[].host'
  cursor=$(echo "$page" | jq -r '.next_cursor // empty')
  [ -z "$cursor" ] && break
done

Idempotency anahtarları

Her POST ile bir Idempotency-Key başlığı gönder (en iyisi bir UUID). Bağlantı koparsa aynı anahtarla yeniden dene: istek bir kez çalışır, yeniden deneme ilk yanıtı Idempotent-Replayed: true ile geri alır. Yanıtlar 24 saat saklanır.

Bir anahtarı farklı bir istek için yeniden kullanırsan 422 idempotency_key_reused, ilki hâlâ çalışırken yeniden denersen 409 idempotency_in_progress döner. Bizim tarafımızda başarısız olan (5xx) bir istek saklanmaz, yeniden deneme onu tekrar çalıştırır. Zaten var olan bir siteyi eklemek ve zaten açık bir siteyi açmak her durumda güvenle tekrarlanabilir.

Kurallar

  • Zamanlar UTC'de, kesirsiz ISO-8601'dir: 2026-10-06T09:14:03Z. Olmayan zaman null'dır.
  • İnsanların okuyacağı metinler (kesinti açıklamaları, uyarı satırları, bulgu başlıkları) için ?lang=tr ekle; varsayılan İngilizcedir.
  • Sitelere alan adıyla erişilir ve www ile de www olmadan da eşleşir; sunuculara, etiketlere ve anahtarlara sayısal kimlikleriyle.
  • v1 yanıtlarına alan eklenebilir; mevcut alanların adı ve anlamı değişmez. Geriye uyumsuz değişiklikler yolda yeni bir sürümle gelir.

Hatalar

Hatalar HTTP durum kodunu ve tek bir JSON biçimini kullanır: makinenin okuyacağı bir kod, insanlar için bir cümle ve işe yarayacaksa details (bir sınır, planının doluluğu, gereken yetki):

409
{
  "error": "plan_full",
  "message": "Your site plan is full. Remove a site or move to a larger plan.",
  "details": {
    "plan": {
      "limit": 25,
      "used": 25,
      "free": 0
    }
  }
}
KodDurumAnlamı
bad_request400Bir parametre ya da JSON gövdesi geçersiz; message alanın adını verir.
bad_url400Tarayabileceğimiz ya da izleyebileceğimiz herkese açık bir web adresi değil.
invalid_key401Anahtar hatalı biçimde, bilinmiyor ya da iptal edilmiş.
missing_key401Authorization: Bearer başlığı yok.
no_credits402Açmak için site kredisi gerekir ve hesabında hiç yok. Ücret alınmadı.
insufficient_scope403Anahtar yalnızca okuma yetkili, uç nokta ise bir şey değiştirir. details.required gereken yetkiyi söyler.
subscription_required403Hesabın etkin bir ücretli planı yok. Plan olunca aynı anahtarlar yeniden çalışır.
not_found404Böyle bir uç nokta yok ya da hesabında bu kimlikte veya alan adında bir şey yok.
idempotency_in_progress409Bu Idempotency-Key ile gönderilen istek hâlâ çalışıyor.
limit_reached409Çok fazla etkin kayıt anahtarı var.
no_plan409Site eklemek için etkin bir site planı gerekir.
not_ready409Tarama henüz bitmedi.
plan_full409Site planının bütün yerleri dolu; sayılar details.plan içinde.
tag_exists409Bu adda bir etiket zaten var.
tag_limit409Hesabın 100'den fazla etiketi olurdu.
idempotency_key_reused422Bu Idempotency-Key farklı bir istek için kullanılmış.
unreachable422Sitenin adı çözümlenmiyor ya da site yanıt vermedi.
rate_limited429Çok fazla istek: Retry-After kadar saniye bekle.
unavailable503Bizim tarafımızda bir sorun oldu. Biraz sonra tekrar dene.

Uç noktalar

Biçimler birebir aynıdır; değerler örnek bir hesaptır. Her istek Authorization başlığı ister.

Hesap

Anahtar, yetkileri, planların ve boş yerleri.

GET/api/v1/accountYetki: read

Hesabın

Anahtarın ait olduğu e‑posta, yetkileri, site ve sunucu planların ile dolu ve boş yerleri ve kredi bakiyen. Toplu eklemeden önce boş yerlerine buradan bak.

Örnek istek
curl https://approvalens.com/api/v1/account \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "account": {
    "email": "you@example.com",
    "key": {
      "id": 9,
      "prefix": "al_live_7Gk2",
      "scopes": [
        "read",
        "write"
      ]
    },
    "plans": {
      "site": {
        "status": "active",
        "limit": 25,
        "used": 2,
        "free": 23,
        "renews_at": "2026-10-25T08:00:00Z",
        "ends_at": null
      },
      "server": {
        "status": "active",
        "limit": 5,
        "used": 2,
        "free": 3,
        "renews_at": "2026-10-25T08:00:00Z",
        "ends_at": null
      }
    },
    "sites": {
      "limit": 25,
      "used": 2,
      "free": 23,
      "over_plan": 0,
      "included": 0
    },
    "servers": {
      "quota": 6,
      "used": 2
    },
    "credits": {
      "balance": 3,
      "billing": "live"
    }
  }
}

Hatalar: invalid_key, missing_key, subscription_required, rate_limited, unavailable

Siteler

İzlenen siteler: filtreli liste, toplu ekleme ve çıkarma, etiketler, erişilebilirlik, kesintiler ve günlük AdSense sinyalleri.

GET/api/v1/sitesYetki: readsayfalı

Siteleri listele

Hesabındaki her site ve şu anki durumu: ayakta mı, 30 günlük erişilebilirlik, son yanıt süresi, TLS, ads.txt, günlük kontrolün AdSense sinyalleri (ads.txt, Mediapartners ve Googlebot erişimi, noindex, yeni engellenen yapay zekâ botları), etiketler, plan kapsamı ve varsa süren kesinti. Etikete, duruma, izlenmeye, sinyale ya da alan adının bir kısmına göre filtrele.

Parametreler

tag
yalnızca bu etiketi taşıyanlar; birkaçından herhangi biri için tekrarla ya da virgülle ayır
status
up, down ya da unknown (henüz yoklanmadı)
monitoring
true ya da false: şu an izleniyor mu
signal
yalnızca bu sorunu olan siteler: down, ads_txt_problem, noindex, mediapartners_blocked, googlebot_blocked, ai_blocked_new, tls_expiring (14 gün ya da daha az), over_plan, not_monitored
q
alan adının bir kısmı
limit
sayfa boyutu, 1–300 (varsayılan 100)
cursor
önceki sayfanın next_cursor değeri
lang
en (varsayılan) ya da tr: metin alanlarının dili
Örnek istek
curl "https://approvalens.com/api/v1/sites?tag=wordpress&signal=ads_txt_problem&limit=50" \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "sites": [
    {
      "id": 1,
      "host": "example.com",
      "url": "https://example.com/",
      "status": "up",
      "monitoring": true,
      "in_plan": true,
      "plan": {
        "status": "active",
        "source": "plan",
        "state": "active",
        "renews_at": "2026-10-25T08:00:00Z",
        "ends_at": null,
        "included_until": null,
        "test": false
      },
      "tags": [
        "client:acme",
        "wordpress"
      ],
      "uptime_30d": 99.94,
      "response_ms": null,
      "tls_valid": true,
      "tls_days_left": 41,
      "tls_expires_at": "2026-11-16T12:00:00Z",
      "ads_txt": "found",
      "signals": {
        "ads_txt": "ok",
        "pubs_missing": [],
        "mediapartners": "ok",
        "googlebot": "ok",
        "noindex": false,
        "ai_blocked_new": [
          "GPTBot"
        ],
        "ai_blocked_at": "2026-10-04T08:00:00Z"
      },
      "ai_allowed": 11,
      "ai_total": 14,
      "last_check": "2026-10-06T03:00:00Z",
      "next_check": "2026-10-07T03:00:00Z",
      "last_uptime_probe": "2026-10-06T07:58:00Z",
      "open_incident": null,
      "alerts_enabled": true
    },
    {
      "id": 2,
      "host": "recipes.example.org",
      "url": "https://recipes.example.org/",
      "status": "up",
      "monitoring": true,
      "in_plan": true,
      "plan": {
        "status": "active",
        "source": "plan",
        "state": "active",
        "renews_at": "2026-10-25T08:00:00Z",
        "ends_at": null,
        "included_until": null,
        "test": false
      },
      "tags": [
        "wordpress"
      ],
      "uptime_30d": 100,
      "response_ms": null,
      "tls_valid": true,
      "tls_days_left": 41,
      "tls_expires_at": "2026-11-16T12:00:00Z",
      "ads_txt": "missing",
      "signals": {
        "ads_txt": "missing",
        "pubs_missing": [],
        "mediapartners": "ok",
        "googlebot": "ok",
        "noindex": false,
        "ai_blocked_new": [],
        "ai_blocked_at": null
      },
      "ai_allowed": 14,
      "ai_total": 14,
      "last_check": "2026-10-06T03:00:00Z",
      "next_check": "2026-10-07T03:00:00Z",
      "last_uptime_probe": "2026-10-06T07:58:30Z",
      "open_incident": null,
      "alerts_enabled": true
    }
  ],
  "total": 2,
  "next_cursor": null
}

Hatalar: bad_request, invalid_key, missing_key, subscription_required, rate_limited, unavailable

POST/api/v1/sitesYetki: write

Site ekle

Site planına bir site (url) ya da 300'e kadar site (urls) ekler; hesap sayfasındaki içe aktarmanın aynısı: her giriş denetlenir (biçim, DNS, herkese açık adres), tekrarlar birleştirilir ve yalnızca planındaki boş yerler kullanılır; hiçbir şey satın alınmaz. Etiketler eklenen ya da zaten olan her siteye konur. Tek url ile: eklendiyse 201, zaten varsa 200, değilse 409 no_plan ya da plan_full. urls ile: 200 ve her giriş için sırasıyla bir sonuç (added, already, invalid, duplicate, no_slot, no_plan). dry_run: true eklemeden önizler.

Parametreler

lang
en (varsayılan) ya da tr: metin alanlarının dili
Örnek istek
curl -X POST https://approvalens.com/api/v1/sites \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"urls":["example.com","news.example.net","https://shop.example.net/products","not a site"],"tags":["client:acme"]}'
Örnek yanıt · 200
{
  "dry_run": false,
  "results": [
    {
      "input": "example.com",
      "host": "example.com",
      "status": "already",
      "id": 1
    },
    {
      "input": "news.example.net",
      "host": "news.example.net",
      "status": "added",
      "id": 31
    },
    {
      "input": "https://shop.example.net/products",
      "host": "shop.example.net",
      "status": "added",
      "id": 32
    },
    {
      "input": "not a site",
      "host": null,
      "status": "invalid",
      "reason": "bad_url"
    }
  ],
  "summary": {
    "already": 1,
    "added": 2,
    "invalid": 1
  },
  "plan": {
    "limit": 25,
    "used": 4,
    "free": 21
  }
}
Şunu da döner · 201
{
  "dry_run": false,
  "results": [
    {
      "input": "news.example.net",
      "host": "news.example.net",
      "status": "added",
      "id": 31
    }
  ],
  "summary": {
    "added": 1
  },
  "plan": {
    "limit": 25,
    "used": 3,
    "free": 22
  }
}

Hatalar: bad_request, bad_url, invalid_key, missing_key, insufficient_scope, subscription_required, idempotency_in_progress, no_plan, plan_full, tag_limit, idempotency_key_reused, unreachable, rate_limited, unavailable

POST/api/v1/sites/removeYetki: write

Siteleri çıkar

Tek çağrıda 300'e kadar siteyi planından çıkarır (DELETE'in tek site için yaptığı gibi). Hesabında olmayan alan adları not_found içinde döner.

Örnek istek
curl -X POST https://approvalens.com/api/v1/sites/remove \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"hosts":["recipes.example.org","news.example.net","old.example.com"]}'
Örnek yanıt · 200
{
  "removed": [
    "recipes.example.org",
    "news.example.net"
  ],
  "deleted": [
    "recipes.example.org",
    "news.example.net"
  ],
  "not_found": [
    "old.example.com"
  ]
}

Hatalar: bad_request, invalid_key, missing_key, insufficient_scope, subscription_required, idempotency_in_progress, idempotency_key_reused, rate_limited, unavailable

GET/api/v1/sites/{host}Yetki: read

Tek site

Listedeki her şey, artı yapay zekâ botu dökümü, günlük güvenlik kontrolleri (HTTPS yönlendirmesi, HSTS, başlıklar, açıkta kalan .env ya da .git), en son 5 kesinti ve en son 10 uyarı.

Parametreler

host
sitenin alan adı, www ile ya da www olmadan
lang
en (varsayılan) ya da tr: metin alanlarının dili
Örnek istek
curl https://approvalens.com/api/v1/sites/example.com \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "site": {
    "id": 1,
    "host": "example.com",
    "url": "https://example.com/",
    "status": "up",
    "monitoring": true,
    "in_plan": true,
    "plan": {
      "status": "active",
      "source": "plan",
      "state": "active",
      "renews_at": "2026-10-25T08:00:00Z",
      "ends_at": null,
      "included_until": null,
      "test": false
    },
    "tags": [
      "client:acme",
      "wordpress"
    ],
    "uptime_30d": 99.94,
    "response_ms": null,
    "tls_valid": true,
    "tls_days_left": 41,
    "tls_expires_at": "2026-11-16T12:00:00Z",
    "ads_txt": "found",
    "signals": {
      "ads_txt": "ok",
      "pubs_missing": [],
      "mediapartners": "ok",
      "googlebot": "ok",
      "noindex": false,
      "ai_blocked_new": [
        "GPTBot"
      ],
      "ai_blocked_at": "2026-10-04T08:00:00Z"
    },
    "ai_allowed": 11,
    "ai_total": 14,
    "last_check": "2026-10-06T03:00:00Z",
    "next_check": "2026-10-07T03:00:00Z",
    "last_uptime_probe": "2026-10-06T07:58:00Z",
    "open_incident": null,
    "alerts_enabled": true,
    "ai_crawlers": {
      "allowed": 11,
      "blocked_by_robots": 2,
      "blocked_by_server": 1,
      "unknown": 0,
      "total": 14
    },
    "security": {
      "https_redirect": true,
      "hsts": true,
      "headers": [
        "nosniff",
        "frame"
      ],
      "files_checked": [
        "env",
        "git_head"
      ],
      "exposed": []
    },
    "pending_confirmation": [],
    "latest_check_url": "https://approvalens.com/ai/Xk2v9QmL0pRa",
    "incidents": [
      {
        "id": 12,
        "started_at": "2026-10-03T08:00:00Z",
        "ended_at": "2026-10-03T08:25:00Z",
        "ongoing": false,
        "duration_seconds": 1500,
        "reason": "http_502",
        "http_status": 502,
        "description": "HTTP 502"
      }
    ],
    "alerts": [
      {
        "id": 87,
        "at": "2026-10-03T08:25:00Z",
        "kind": "up",
        "emailed": true,
        "lines": [
          "Your homepage answers again. It was down for 25 minutes (Oct 3, 2026 08:00 UTC – Oct 3, 2026 08:25 UTC). Reason seen: HTTP 502."
        ],
        "check_id": null
      },
      {
        "id": 86,
        "at": "2026-10-03T08:10:00Z",
        "kind": "down",
        "emailed": true,
        "lines": [
          "example.com is down · HTTP 502"
        ],
        "check_id": null
      }
    ]
  }
}

Hatalar: bad_request, invalid_key, missing_key, subscription_required, not_found, rate_limited, unavailable

PATCH/api/v1/sites/{host}Yetki: write

Siteyi etiketle, uyarılarını aç/kapat

Etiket ekler ve çıkarır (olmayan adlar oluşturulur) ve sitenin e‑posta uyarılarını açar ya da kapatır. Siteyi GET'in döndüğü gibi döner.

Parametreler

host
sitenin alan adı, www ile ya da www olmadan
lang
en (varsayılan) ya da tr: metin alanlarının dili
Örnek istek
curl -X PATCH https://approvalens.com/api/v1/sites/example.com \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"add_tags":["client:acme"],"remove_tags":["old-theme"],"alerts_enabled":true}'
Örnek yanıt · 200
{
  "site": {
    "id": 1,
    "host": "example.com",
    "url": "https://example.com/",
    "status": "up",
    "monitoring": true,
    "in_plan": true,
    "plan": {
      "status": "active",
      "source": "plan",
      "state": "active",
      "renews_at": "2026-10-25T08:00:00Z",
      "ends_at": null,
      "included_until": null,
      "test": false
    },
    "tags": [
      "client:acme",
      "wordpress"
    ],
    "uptime_30d": 99.94,
    "response_ms": null,
    "tls_valid": true,
    "tls_days_left": 41,
    "tls_expires_at": "2026-11-16T12:00:00Z",
    "ads_txt": "found",
    "signals": {
      "ads_txt": "ok",
      "pubs_missing": [],
      "mediapartners": "ok",
      "googlebot": "ok",
      "noindex": false,
      "ai_blocked_new": [
        "GPTBot"
      ],
      "ai_blocked_at": "2026-10-04T08:00:00Z"
    },
    "ai_allowed": 11,
    "ai_total": 14,
    "last_check": "2026-10-06T03:00:00Z",
    "next_check": "2026-10-07T03:00:00Z",
    "last_uptime_probe": "2026-10-06T07:58:00Z",
    "open_incident": null,
    "alerts_enabled": true,
    "ai_crawlers": {
      "allowed": 11,
      "blocked_by_robots": 2,
      "blocked_by_server": 1,
      "unknown": 0,
      "total": 14
    },
    "security": {
      "https_redirect": true,
      "hsts": true,
      "headers": [
        "nosniff",
        "frame"
      ],
      "files_checked": [
        "env",
        "git_head"
      ],
      "exposed": []
    },
    "pending_confirmation": [],
    "latest_check_url": "https://approvalens.com/ai/Xk2v9QmL0pRa",
    "incidents": [
      {
        "id": 12,
        "started_at": "2026-10-03T08:00:00Z",
        "ended_at": "2026-10-03T08:25:00Z",
        "ongoing": false,
        "duration_seconds": 1500,
        "reason": "http_502",
        "http_status": 502,
        "description": "HTTP 502"
      }
    ],
    "alerts": [
      {
        "id": 87,
        "at": "2026-10-03T08:25:00Z",
        "kind": "up",
        "emailed": true,
        "lines": [
          "Your homepage answers again. It was down for 25 minutes (Oct 3, 2026 08:00 UTC – Oct 3, 2026 08:25 UTC). Reason seen: HTTP 502."
        ],
        "check_id": null
      },
      {
        "id": 86,
        "at": "2026-10-03T08:10:00Z",
        "kind": "down",
        "emailed": true,
        "lines": [
          "example.com is down · HTTP 502"
        ],
        "check_id": null
      }
    ]
  }
}

Hatalar: bad_request, invalid_key, missing_key, insufficient_scope, subscription_required, not_found, tag_limit, rate_limited, unavailable

DELETE/api/v1/sites/{host}Yetki: write

Siteyi çıkar

Siteyi planından çıkarır; yeri hemen boşalır. Ücretli bir rapor ayı onu sonuna kadar izlenir tutar (monitoring: true); artık hiçbir şeyin kapsamadığı bir site geçmişiyle birlikte silinir (deleted: true).

Parametreler

host
sitenin alan adı, www ile ya da www olmadan
Örnek istek
curl -X DELETE https://approvalens.com/api/v1/sites/recipes.example.org \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "id": 2,
  "host": "recipes.example.org",
  "deleted": true,
  "monitoring": false
}

Hatalar: bad_request, invalid_key, missing_key, insufficient_scope, subscription_required, not_found, rate_limited, unavailable

GET/api/v1/sites/{host}/uptimeYetki: read

Sitenin erişilebilirliği

5 dakikalık erişilebilirlik kontrolü: şu anki durum, 30 günlük erişilebilirlik, son yanıt süresi (ölçülene kadar null) ve son 30 UTC günü tek tek, en eskisi önce. Site eklenmeden önceki günler null'dır.

Parametreler

host
sitenin alan adı, www ile ya da www olmadan
Örnek istek
curl https://approvalens.com/api/v1/sites/example.com/uptime \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "host": "example.com",
  "status": "up",
  "monitoring": true,
  "uptime_30d": 99.94,
  "response_ms": null,
  "last_probe": "2026-10-06T07:58:00Z",
  "monitored_since": "2026-08-26T08:00:00Z",
  "days": [
    {
      "date": "2026-09-07",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-08",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-09",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-10",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-11",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-12",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-13",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-14",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-15",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-16",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-17",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-18",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-19",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-20",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-21",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-22",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-23",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-24",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-25",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-26",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-27",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-28",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-29",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-09-30",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-10-01",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-10-02",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-10-03",
      "uptime_pct": 98.26,
      "downtime_seconds": 1500
    },
    {
      "date": "2026-10-04",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-10-05",
      "uptime_pct": 100,
      "downtime_seconds": 0
    },
    {
      "date": "2026-10-06",
      "uptime_pct": 100,
      "downtime_seconds": 0
    }
  ]
}

Hatalar: bad_request, invalid_key, missing_key, subscription_required, not_found, rate_limited, unavailable

GET/api/v1/sites/{host}/incidentsYetki: read

Sitenin kesintileri

since'ten sonra biten ya da hâlâ süren kesintiler, en yenisi önce. Üst üste iki başarısız kontrolden sonra kesinti açılır.

Parametreler

host
sitenin alan adı, www ile ya da www olmadan
since
ISO-8601 tarih ya da saat; varsayılan 30 gün önce
limit
1–500, varsayılan 100
lang
en (varsayılan) ya da tr: metin alanlarının dili
Örnek istek
curl "https://approvalens.com/api/v1/sites/example.com/incidents?since=2026-09-06" \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "host": "example.com",
  "since": "2026-09-06T08:00:00Z",
  "incidents": [
    {
      "id": 12,
      "started_at": "2026-10-03T08:00:00Z",
      "ended_at": "2026-10-03T08:25:00Z",
      "ongoing": false,
      "duration_seconds": 1500,
      "reason": "http_502",
      "http_status": 502,
      "description": "HTTP 502"
    }
  ]
}

Hatalar: bad_request, invalid_key, missing_key, subscription_required, not_found, rate_limited, unavailable

Etiketler

Sitelere ve sunuculara koyduğun kendi etiketlerin: oluştur, yeniden adlandır, sil ve tek seferde çok öğeye koy.

GET/api/v1/tagsYetki: read

Etiketleri listele

Etiketlerin ve onları taşıyan site ve sunucuların kimlikleri.

Örnek istek
curl https://approvalens.com/api/v1/tags \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "tags": [
    {
      "id": 3,
      "name": "client:acme",
      "color": "blue",
      "created_at": "2026-09-06T08:00:00Z",
      "site_ids": [
        1
      ],
      "server_ids": []
    },
    {
      "id": 4,
      "name": "wordpress",
      "color": "green",
      "created_at": "2026-09-06T08:00:00Z",
      "site_ids": [
        1,
        2
      ],
      "server_ids": [
        21
      ]
    }
  ]
}

Hatalar: invalid_key, missing_key, subscription_required, rate_limited, unavailable

POST/api/v1/tagsYetki: write

Etiket oluştur

Yeni bir etiket. Adlar küçük harfle saklanır: 1–32 harf, rakam, boşluk ya da - _ : . / &.

Örnek istek
curl -X POST https://approvalens.com/api/v1/tags \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"client:acme","color":"blue"}'
Örnek yanıt · 201
{
  "tag": {
    "id": 3,
    "name": "client:acme",
    "color": "blue",
    "created_at": "2026-09-06T08:00:00Z",
    "site_ids": [
      1
    ],
    "server_ids": []
  }
}

Hatalar: bad_request, invalid_key, missing_key, insufficient_scope, subscription_required, idempotency_in_progress, tag_exists, tag_limit, idempotency_key_reused, rate_limited, unavailable

PATCH/api/v1/tags/{id}Yetki: write

Etiketi yeniden adlandır ya da rengini değiştir

Adı, rengi ya da ikisini birden değiştirir.

Parametreler

id
etiket kimliği
Örnek istek
curl -X PATCH https://approvalens.com/api/v1/tags/3 \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"color":"blue"}'
Örnek yanıt · 200
{
  "tag": {
    "id": 3,
    "name": "client:acme",
    "color": "blue",
    "created_at": "2026-09-06T08:00:00Z",
    "site_ids": [
      1
    ],
    "server_ids": []
  }
}

Hatalar: bad_request, invalid_key, missing_key, insufficient_scope, subscription_required, not_found, tag_exists, rate_limited, unavailable

POST/api/v1/tags/assignYetki: write

Çok sayıda site ve sunucuyu etiketle

Tek çağrıda 300'e kadar siteye (alan adıyla) ve sunucuya (kimlikle) etiket koyar (op add; olmayan adlar oluşturulur) ya da kaldırır (op remove). Bilinmeyen alan adları ve kimlikler not_found içinde döner.

Örnek istek
curl -X POST https://approvalens.com/api/v1/tags/assign \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"op":"add","tags":["wordpress"],"sites":["example.com","recipes.example.org","old.example.com"],"servers":[21]}'
Örnek yanıt · 200
{
  "op": "add",
  "tag_ids": [
    4
  ],
  "sites": {
    "updated": [
      "example.com",
      "recipes.example.org"
    ],
    "not_found": [
      "old.example.com"
    ]
  },
  "servers": {
    "updated": [
      21
    ],
    "not_found": []
  }
}

Hatalar: bad_request, invalid_key, missing_key, insufficient_scope, subscription_required, not_found, idempotency_in_progress, tag_limit, idempotency_key_reused, rate_limited, unavailable

Sunucular

Ajanın çalıştığı sunucular: şu anki değerleri, açık uyarılar, tüm ayrıntı ve makine eklemek için kayıt anahtarları.

GET/api/v1/serversYetki: readsayfalı

Sunucuları listele

Sunucuların ve şu anki değerleri (CPU, yük, bellek, en dolu disk ve ne zaman dolacağı), kaç uyarı, güvenlik sinyali ve bildirimin açık olduğu, etiketler ve planlarının sunucu kotası.

Parametreler

status
pending, up, silent ya da paused
tag
yalnızca bu etiketi taşıyanlar; birkaçından herhangi biri için tekrarla ya da virgülle ayır
limit
sayfa boyutu, 1–300 (varsayılan 100)
cursor
önceki sayfanın next_cursor değeri
Örnek istek
curl https://approvalens.com/api/v1/servers \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "servers": [
    {
      "id": 21,
      "name": "web-1",
      "status": "up",
      "paused_reason": null,
      "hostname": "web-1.example.net",
      "os": "Ubuntu 24.04.1 LTS",
      "arch": "x86_64",
      "agent_version": "0.3.0",
      "created_at": "2026-09-16T08:00:00Z",
      "first_seen_at": "2026-09-16T08:05:00Z",
      "last_seen_at": "2026-10-06T07:59:20Z",
      "learning_until": null,
      "tags": [
        "wordpress"
      ],
      "current": {
        "at": "2026-10-06T07:59:20Z",
        "cpu_pct": 23.4,
        "load": [
          0.82,
          0.71,
          0.64
        ],
        "cores": 4,
        "mem_pct": 61.2,
        "swap_pct": 3.1,
        "disk_max_pct": 74,
        "disk_full_in_hours": 4600,
        "uptime_seconds": 1814400,
        "processes": 214
      },
      "open": {
        "alerts": 0,
        "security_signals": 1,
        "notices": 1
      }
    },
    {
      "id": 22,
      "name": "db-1",
      "status": "silent",
      "paused_reason": null,
      "hostname": "db-1.example.net",
      "os": "Ubuntu 24.04.1 LTS",
      "arch": "x86_64",
      "agent_version": "0.3.0",
      "created_at": "2026-09-16T08:00:00Z",
      "first_seen_at": "2026-09-16T08:05:00Z",
      "last_seen_at": "2026-10-06T06:00:00Z",
      "learning_until": null,
      "tags": [],
      "current": {
        "at": "2026-10-06T07:59:20Z",
        "cpu_pct": 23.4,
        "load": [
          0.82,
          0.71,
          0.64
        ],
        "cores": 4,
        "mem_pct": 61.2,
        "swap_pct": 3.1,
        "disk_max_pct": 74,
        "disk_full_in_hours": 4600,
        "uptime_seconds": 1814400,
        "processes": 214
      },
      "open": {
        "alerts": 0,
        "security_signals": 0,
        "notices": 0
      }
    }
  ],
  "quota": 6,
  "used": 2,
  "total": 2,
  "next_cursor": null
}

Hatalar: bad_request, invalid_key, missing_key, subscription_required, rate_limited, unavailable

GET/api/v1/servers/{id}Yetki: read

Tek sunucu

Listedeki kayıt, artı her disk, metinleriyle açık uyarılar, güvenlik sinyalleri ve bildirimler, son olaylar ve güvenlik ayarları kontrolü.

Parametreler

id
sunucu kimliği
Örnek istek
curl https://approvalens.com/api/v1/servers/21 \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "server": {
    "id": 21,
    "name": "web-1",
    "status": "up",
    "paused_reason": null,
    "hostname": "web-1.example.net",
    "os": "Ubuntu 24.04.1 LTS",
    "arch": "x86_64",
    "agent_version": "0.3.0",
    "created_at": "2026-09-16T08:00:00Z",
    "first_seen_at": "2026-09-16T08:05:00Z",
    "last_seen_at": "2026-10-06T07:59:20Z",
    "learning_until": null,
    "tags": [
      "wordpress"
    ],
    "current": {
      "at": "2026-10-06T07:59:20Z",
      "cpu_pct": 23.4,
      "load": [
        0.82,
        0.71,
        0.64
      ],
      "cores": 4,
      "mem_pct": 61.2,
      "swap_pct": 3.1,
      "disk_max_pct": 74,
      "disk_full_in_hours": 4600,
      "uptime_seconds": 1814400,
      "processes": 214
    },
    "open": {
      "alerts": 0,
      "security_signals": 1,
      "notices": 1
    },
    "disks": [
      {
        "mount": "/",
        "fs": "ext4",
        "total_bytes": 160000000000,
        "used_bytes": 118400000000,
        "pct": 74,
        "full_in_hours": 4600
      }
    ],
    "alerts": [],
    "security_signals": [
      {
        "id": 501,
        "kind": "new_port",
        "at": "2026-10-06T03:00:00Z",
        "ended_at": null,
        "acknowledged_at": null,
        "emailed": true,
        "security": true,
        "lines": [
          "A new public port is open: 8080/tcp (node)."
        ]
      }
    ],
    "notices": [
      {
        "id": 495,
        "kind": "updates",
        "at": "2026-10-05T08:00:00Z",
        "ended_at": null,
        "acknowledged_at": null,
        "emailed": false,
        "security": false,
        "lines": [
          "12 package updates are waiting, 3 of them security updates."
        ]
      }
    ],
    "recent_events": [
      {
        "id": 480,
        "kind": "silent",
        "at": "2026-10-02T08:00:00Z",
        "ended_at": "2026-10-02T08:20:00Z",
        "acknowledged_at": null,
        "emailed": true,
        "security": false,
        "lines": [
          "web-1 sent nothing for 20 minutes."
        ]
      }
    ],
    "posture": [
      {
        "key": "ssh_password",
        "status": "warn",
        "label": "SSH accepts passwords",
        "detail": "PasswordAuthentication is yes in sshd_config.",
        "acknowledged": false
      }
    ],
    "posture_at": "2026-10-06T07:00:00Z"
  }
}

Hatalar: invalid_key, missing_key, subscription_required, not_found, rate_limited, unavailable

GET/api/v1/servers/{id}/detailYetki: read

Sunucu ayrıntısı

Sunucu sayfasının gösterdiği her şey, kendi sözleşmesiyle: genel bakış (erişilebilirlik, kesintiler, yeniden başlatmalar), güvenlik (açıklamalı portlar, SSH, oturum açmalar, güvenlik duvarı, güncellemeler, öneriler, puan), aralık için grafik serileriyle kaynaklar, olaylar ve ajan. İçerideki zamanlar Unix saniyesidir.

Parametreler

id
sunucu kimliği
range
grafik aralığı: 1h, 24h (varsayılan), 7d ya da 30d
lang
en (varsayılan) ya da tr: metin alanlarının dili
Örnek istek
curl "https://approvalens.com/api/v1/servers/21/detail?range=7d" \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "detail": {
    "id": 21,
    "name": "web-1",
    "range": "24h",
    "overview": {
      "hostname": "web-1.example.net",
      "os": "Ubuntu 24.04.1 LTS",
      "kernel": "6.8.0-45-generic",
      "arch": "x86_64",
      "virt": "kvm",
      "cores": 4,
      "mem_total": 8000000000,
      "uptime_s": 1814400,
      "first_seen": 1789545900,
      "last_seen": 1791273560,
      "status": "up",
      "availability": {
        "d1": 100,
        "d7": 99.9,
        "d30": 99.8
      },
      "outages": [],
      "reboots": []
    },
    "security": {
      "summary": {
        "level": "warn",
        "bad": 0,
        "warn": 2,
        "ok": 14
      },
      "score": {
        "value": 82,
        "grade": "B"
      },
      "ports": [
        {
          "proto": "tcp",
          "port": 22,
          "addr": "0.0.0.0",
          "public": true,
          "process": "sshd",
          "service": "SSH",
          "purpose": "Remote login.",
          "advice": "Keep it; allow keys only.",
          "level": "ok",
          "reachable": true
        }
      ]
    },
    "resources": {
      "current": {
        "cpu": 23.4,
        "load": [
          0.82,
          0.71,
          0.64
        ],
        "mem_pct": 61.2,
        "swap_pct": 3.1,
        "procs": 214
      },
      "series": {
        "cpu": [
          [
            1791273000,
            21.2
          ],
          [
            1791273300,
            23.4
          ]
        ]
      }
    },
    "events": [
      {
        "id": 501,
        "at": 1791255600,
        "kind": "new_port",
        "level": "warn",
        "title": "New public port 8080/tcp",
        "acked": false,
        "emailed": true
      }
    ],
    "agent": {
      "version": "0.3.0",
      "latest": "0.3.0",
      "mode": "detailed",
      "cpu_pct": 0.05,
      "rss": 17000000,
      "sent_kb_day": 240
    }
  }
}

Hatalar: bad_request, invalid_key, missing_key, subscription_required, not_found, rate_limited, unavailable

GET/api/v1/servers/enroll-keysYetki: read

Kayıt anahtarlarını listele

Kayıt anahtarların: tek anahtar ajanı çok sayıda makineye kurar, her biri bir sunucu olur. Gizli değer asla listelenmez.

Örnek istek
curl https://approvalens.com/api/v1/servers/enroll-keys \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "keys": [
    {
      "id": 7,
      "name": "ansible",
      "prefix": "alek_Qm81xZ",
      "created_at": "2026-09-26T08:00:00Z",
      "last_used_at": "2026-10-04T08:00:00Z",
      "uses": 14,
      "revoked_at": null,
      "active": true
    }
  ]
}

Hatalar: invalid_key, missing_key, subscription_required, rate_limited, unavailable

POST/api/v1/servers/enroll-keysYetki: write

Kayıt anahtarı oluştur

Yeni bir kayıt anahtarı. Gizli değeri ve kurulum satırı yalnızca bu yanıtta bulunur; satırı her makinede çalıştır (Ansible, cloud-init…). Sunucu kotanı aşan makineler duraklatılır.

Örnek istek
curl -X POST https://approvalens.com/api/v1/servers/enroll-keys \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"ansible"}'
Örnek yanıt · 201
{
  "key": {
    "id": 7,
    "name": "ansible",
    "prefix": "alek_Qm81xZ",
    "created_at": "2026-09-26T08:00:00Z",
    "last_used_at": "2026-10-04T08:00:00Z",
    "uses": 14,
    "revoked_at": null,
    "active": true
  },
  "secret": "alek_Qm81xZ••••••••••••••••••••••••••••••••",
  "install_command": "curl -fsSL https://approvalens.com/agent/install.sh | sudo bash -s -- --enroll alek_Qm81xZ••••••••••••••••••••••••••••••••"
}

Hatalar: bad_request, invalid_key, missing_key, insufficient_scope, subscription_required, idempotency_in_progress, limit_reached, idempotency_key_reused, rate_limited, unavailable

DELETE/api/v1/servers/enroll-keys/{id}Yetki: write

Kayıt anahtarını iptal et

Onunla yeni makine kaydolamaz; daha önce oluşturduğu sunucular bildirmeye devam eder.

Parametreler

id
kayıt anahtarı kimliği
Örnek istek
curl -X DELETE https://approvalens.com/api/v1/servers/enroll-keys/7 \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "key": {
    "id": 7,
    "name": "ansible",
    "prefix": "alek_Qm81xZ",
    "created_at": "2026-09-26T08:00:00Z",
    "last_used_at": "2026-10-04T08:00:00Z",
    "uses": 14,
    "revoked_at": "2026-10-06T08:00:00Z",
    "active": false
  }
}

Hatalar: bad_request, invalid_key, missing_key, insufficient_scope, subscription_required, not_found, rate_limited, unavailable

Taramalar ve raporlar

Ücretsiz tarama başlat, ilerlemesini izle, raporları oku ve birini zaten sahip olduğun bir krediyle aç.

GET/api/v1/scansYetki: readsayfalı

Taramaları listele

API'den ya da oturum açıkken başlattığın taramalar ve e‑posta adresinle satın alınan raporlar, en yenisi önce: puan ve karar (bitene kadar ya da site bizi engellediyse null), neyin ne zamana kadar açık olduğu ve bir açmanın başlattığı derin tarama.

Parametreler

status
queued, running, done, error ya da blocked
host
yalnızca bu alan adının taramaları
limit
sayfa boyutu, 1–300 (varsayılan 100)
cursor
önceki sayfanın next_cursor değeri
Örnek istek
curl https://approvalens.com/api/v1/scans \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "scans": [
    {
      "id": "q7RkT2mYw9Lb3xNc",
      "host": "example.com",
      "url": "https://example.com/",
      "status": "done",
      "score": 78,
      "verdict": "almost",
      "created_at": "2026-10-04T08:00:00Z",
      "finished_at": "2026-10-04T08:06:50Z",
      "unlocked": true,
      "unlocked_until": "2026-11-03T08:00:00Z",
      "ai_unlocked_until": null,
      "deep_scan": {
        "id": "Hn4sP0dVe8KqZ1aY",
        "status": "done",
        "progress": 100
      },
      "report_url": "https://approvalens.com/scan/q7RkT2mYw9Lb3xNc"
    }
  ],
  "next_cursor": null
}

Hatalar: bad_request, invalid_key, missing_key, subscription_required, rate_limited, unavailable

POST/api/v1/scansYetki: write

Tarama başlat

Bir sitenin ücretsiz taramasını ana sayfanın yaptığı gibi başlatır. Birkaç dakika sürer: GET /scans/:id ile yokla. Son 20 dakikada taranmış bir site o taramayı geri verir (cached: true).

Örnek istek
curl -X POST https://approvalens.com/api/v1/scans \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://recipes.example.org"}'
Örnek yanıt · 202
{
  "scan": {
    "id": "Zt8mQ2xLp4Vn6RcA",
    "host": "recipes.example.org",
    "url": "https://recipes.example.org/",
    "status": "queued",
    "score": null,
    "verdict": null,
    "created_at": "2026-10-06T07:58:30Z",
    "finished_at": null,
    "progress": {
      "stage": "",
      "percent": 0,
      "queue_position": 1
    },
    "unlocked": false,
    "unlocked_until": null,
    "ai_unlocked": false,
    "ai_unlocked_until": null,
    "deep_scan": null,
    "report_url": "https://approvalens.com/scan/Zt8mQ2xLp4Vn6RcA",
    "summary": null
  },
  "cached": false
}

Hatalar: bad_request, bad_url, invalid_key, missing_key, insufficient_scope, subscription_required, idempotency_in_progress, idempotency_key_reused, unreachable, rate_limited, unavailable

GET/api/v1/scans/{id}Yetki: read

Tek tarama

Tarama sürerken ilerleme (aşama, yüzde, kuyruktaki sıra), sonra puan, karar ve rapor özeti. Bir bulgunun arkasındaki sayfalar ve parametreler yalnızca hesabında site açıksa (ya da her rapor ücretsizken) gelir.

Parametreler

id
tarama ya da rapor kimliği
lang
en (varsayılan) ya da tr: metin alanlarının dili
Örnek istek
curl https://approvalens.com/api/v1/scans/q7RkT2mYw9Lb3xNc \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "scan": {
    "id": "q7RkT2mYw9Lb3xNc",
    "host": "example.com",
    "url": "https://example.com/",
    "status": "done",
    "score": 78,
    "verdict": "almost",
    "created_at": "2026-10-04T08:00:00Z",
    "finished_at": "2026-10-04T08:06:50Z",
    "progress": null,
    "unlocked": true,
    "unlocked_until": "2026-11-03T08:00:00Z",
    "ai_unlocked": true,
    "ai_unlocked_until": null,
    "deep_scan": {
      "id": "Hn4sP0dVe8KqZ1aY",
      "status": "done",
      "progress": 100
    },
    "report_url": "https://approvalens.com/scan/q7RkT2mYw9Lb3xNc",
    "summary": {
      "counts": {
        "blocker": 0,
        "critical": 1,
        "warning": 3,
        "notice": 2,
        "pass": 74
      },
      "categories": [
        {
          "id": "content",
          "score": 71
        },
        {
          "id": "trust",
          "score": 90
        }
      ],
      "pages_scanned": 50,
      "findings": [
        {
          "id": "content.thin_pages",
          "title": "Thin articles",
          "category": "content",
          "severity": "critical",
          "level": "official",
          "evidence_total": 26,
          "unlocked": true,
          "params": {
            "thin": 26,
            "very_thin": 9,
            "of": 42,
            "pct": 62,
            "median": 131,
            "tools": 0
          },
          "evidence": [
            {
              "url": "https://example.com/recipes/quick-salsa/",
              "detail": "96 words"
            },
            {
              "url": "https://example.com/recipes/lemonade/",
              "detail": "118 words (+240 words of menus/footer)"
            }
          ]
        },
        {
          "id": "trust.contact_missing",
          "title": "Contact information",
          "category": "trust",
          "severity": "warning",
          "level": "official",
          "evidence_total": 0,
          "unlocked": true,
          "params": {},
          "evidence": []
        }
      ]
    }
  }
}
Şunu da döner · 200
{
  "scan": {
    "id": "Zt8mQ2xLp4Vn6RcA",
    "host": "recipes.example.org",
    "url": "https://recipes.example.org/",
    "status": "running",
    "score": null,
    "verdict": null,
    "created_at": "2026-10-06T07:58:30Z",
    "finished_at": null,
    "progress": {
      "stage": "pages",
      "percent": 46,
      "queue_position": null
    },
    "unlocked": false,
    "unlocked_until": null,
    "ai_unlocked": false,
    "ai_unlocked_until": null,
    "deep_scan": null,
    "report_url": "https://approvalens.com/scan/Zt8mQ2xLp4Vn6RcA",
    "summary": null
  }
}

Hatalar: invalid_key, missing_key, subscription_required, not_found, rate_limited, unavailable

POST/api/v1/scans/{id}/unlockYetki: write

Raporu krediyle aç

Bitmiş bir taramanın tam raporunu hesabında zaten olan bir site kredisiyle açar: 30 gün, derin yeniden tarama ve sınırsız yeniden tarama. Asla ücret almaz: zaten açık bir site hiçbir şeye mal olmaz (already: true), kredin kalmadıysa 402 no_credits döner.

Parametreler

id
tarama ya da rapor kimliği
Örnek istek
curl -X POST https://approvalens.com/api/v1/scans/q7RkT2mYw9Lb3xNc/unlock \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
Örnek yanıt · 200
{
  "unlock": {
    "host": "example.com",
    "already": false,
    "free": false,
    "unlocked_until": "2026-11-05T08:00:00Z",
    "credits_left": 2,
    "deep_scan_id": "Hn4sP0dVe8KqZ1aY"
  }
}

Hatalar: bad_request, invalid_key, missing_key, no_credits, insufficient_scope, subscription_required, not_found, idempotency_in_progress, not_ready, idempotency_key_reused, rate_limited, unavailable

GET/api/v1/creditsYetki: read

Kredilerin

Site kredisi bakiyen ve kredilerin açtığı siteler. Her rapor ücretsizken billing free, balance null olur.

Örnek istek
curl https://approvalens.com/api/v1/credits \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "credits": {
    "billing": "live",
    "balance": 2,
    "unlocks": [
      {
        "host": "example.com",
        "scan_id": "q7RkT2mYw9Lb3xNc",
        "until": "2026-11-05T08:00:00Z",
        "active": true
      }
    ]
  }
}

Hatalar: invalid_key, missing_key, subscription_required, rate_limited, unavailable

GET/api/v1/reportsYetki: readsayfalı

Raporları listele

GET /scans ile aynı liste; ondan önce yazılmış istemciler için korunuyor.

Parametreler

limit
sayfa boyutu, 1–300 (varsayılan 100)
cursor
önceki sayfanın next_cursor değeri
Örnek istek
curl https://approvalens.com/api/v1/reports \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "reports": [
    {
      "id": "q7RkT2mYw9Lb3xNc",
      "host": "example.com",
      "url": "https://example.com/",
      "status": "done",
      "score": 78,
      "verdict": "almost",
      "created_at": "2026-10-04T08:00:00Z",
      "finished_at": "2026-10-04T08:06:50Z",
      "unlocked": true,
      "unlocked_until": "2026-11-03T08:00:00Z",
      "ai_unlocked_until": null,
      "deep_scan": {
        "id": "Hn4sP0dVe8KqZ1aY",
        "status": "done",
        "progress": 100
      },
      "report_url": "https://approvalens.com/scan/q7RkT2mYw9Lb3xNc"
    }
  ],
  "next_cursor": null
}

Hatalar: bad_request, invalid_key, missing_key, subscription_required, rate_limited, unavailable

GET/api/v1/reports/{id}Yetki: read

Tek rapor

Puan, karar, önem derecesine göre sorun sayıları, kategori puanları ve başlığı ile önem derecesiyle her başarısız bulgu; kanıtlar yalnızca hesabında site açıksa.

Parametreler

id
tarama ya da rapor kimliği
lang
en (varsayılan) ya da tr: metin alanlarının dili
Örnek istek
curl https://approvalens.com/api/v1/reports/q7RkT2mYw9Lb3xNc \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "report": {
    "id": "q7RkT2mYw9Lb3xNc",
    "host": "example.com",
    "url": "https://example.com/",
    "status": "done",
    "score": 78,
    "verdict": "almost",
    "created_at": "2026-10-04T08:00:00Z",
    "finished_at": "2026-10-04T08:06:50Z",
    "unlocked": true,
    "unlocked_until": "2026-11-03T08:00:00Z",
    "ai_unlocked": true,
    "ai_unlocked_until": null,
    "report_url": "https://approvalens.com/scan/q7RkT2mYw9Lb3xNc",
    "counts": {
      "blocker": 0,
      "critical": 1,
      "warning": 3,
      "notice": 2,
      "pass": 74
    },
    "categories": [
      {
        "id": "content",
        "score": 71
      },
      {
        "id": "trust",
        "score": 90
      }
    ],
    "pages_scanned": 50,
    "findings": [
      {
        "id": "content.thin_pages",
        "title": "Thin articles",
        "category": "content",
        "severity": "critical",
        "level": "official",
        "evidence_total": 26,
        "unlocked": true,
        "params": {
          "thin": 26,
          "very_thin": 9,
          "of": 42,
          "pct": 62,
          "median": 131,
          "tools": 0
        },
        "evidence": [
          {
            "url": "https://example.com/recipes/quick-salsa/",
            "detail": "96 words"
          },
          {
            "url": "https://example.com/recipes/lemonade/",
            "detail": "118 words (+240 words of menus/footer)"
          }
        ]
      },
      {
        "id": "trust.contact_missing",
        "title": "Contact information",
        "category": "trust",
        "severity": "warning",
        "level": "official",
        "evidence_total": 0,
        "unlocked": true,
        "params": {},
        "evidence": []
      }
    ]
  }
}

Hatalar: invalid_key, missing_key, subscription_required, not_found, rate_limited, unavailable

Yapay zekâ kontrolü

Ücretsiz “Yapay zekâ siteni görebiliyor mu?” kontrolü.

POST/api/v1/ai-checksYetki: read

Yapay zekâ botu kontrolü çalıştır

Bir ana sayfanın ücretsiz “Yapay zekâ siteni görebiliyor mu?” kontrolünü çalıştırır (yaklaşık 20 saniye) ve tam sonucu döner: her yapay zekâ botunun robots.txt kuralı ve benzetilmiş isteği, meta yönergeleri, llms.txt ve site haritası. Yeni kontrol için 201, önbellekteki için 200. Yalnızca okuma yetkili anahtarlar da çalıştırabilir.

Örnek istek
curl -X POST https://approvalens.com/api/v1/ai-checks \
  -H "Authorization: Bearer $APPROVALENS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'
Örnek yanıt · 201
{
  "check": {
    "id": "aic_7Qm2xK9pLs",
    "url": "greenleaf-journal.example",
    "final_url": "https://www.greenleaf-journal.example/",
    "host": "www.greenleaf-journal.example",
    "checked_at": "2026-10-06T12:45:00Z",
    "duration_ms": 17420,
    "cached": false,
    "headline": {
      "key": "blocked_server",
      "bot": "ChatGPT-User",
      "product": "ChatGPT"
    },
    "summary": {
      "allowed": 6,
      "blocked_robots": 4,
      "blocked_server": 3,
      "unknown": 1,
      "total": 14
    },
    "bots": [
      {
        "id": "oai-searchbot",
        "token": "OAI-SearchBot",
        "name": "OAI-SearchBot",
        "vendor": "OpenAI",
        "product": "ChatGPT",
        "purpose": "search",
        "kind": "fetcher",
        "doc_url": "https://developers.openai.com/api/docs/bots",
        "robots_respected": true,
        "robots": {
          "allowed": true,
          "group": "*",
          "rule": null
        },
        "http": {
          "simulated": true,
          "status": 200,
          "verdict": "ok",
          "reason": "200 OK, same page as a browser",
          "similarity": 0.98,
          "headers": {
            "content-type": "text/html; charset=utf-8"
          }
        },
        "verdict": "allowed"
      }
    ],
    "robots": {
      "url": "https://www.greenleaf-journal.example/robots.txt",
      "status": 200,
      "found": true,
      "unavailable": false,
      "excerpt": "User-agent: *\nDisallow: /wp-admin/\nAllow: /wp-admin/admin-ajax.php\n\nUser-agent: GPTBot\nDisallow: /\n\nUser-agent: CCBot\nDisallow: /\n\nUser-agent: Bytespider\nDisallow: /\n\nUser-agent: Google-Extended\nDisallow: /\n\nSitemap: https://www.greenleaf-journal.example/sitemap_index.xml"
    },
    "directives": {
      "meta": [
        {
          "name": "robots",
          "content": "index, follow, noai, noimageai"
        }
      ],
      "x_robots_tag": [],
      "flags": {
        "noai": true,
        "noimageai": true,
        "noindex": false,
        "nosnippet": false
      }
    },
    "js": {
      "raw_words": 412,
      "rendered_words": 1860,
      "ratio": 0.22,
      "verdict": "js_dependent",
      "reason": ""
    },
    "llms": {
      "url": "https://www.greenleaf-journal.example/llms.txt",
      "status": 200,
      "found": true,
      "content_type": "text/html; charset=utf-8",
      "h1": "Greenleaf Journal",
      "summary": null,
      "sections": 3,
      "links": 18,
      "problems": [
        {
          "code": "no_summary",
          "detail": "No blockquote summary after the H1",
          "line": 2
        },
        {
          "code": "content_type",
          "detail": "Served as text/html; expected text/plain or text/markdown",
          "line": null
        },
        {
          "code": "bad_list_item",
          "detail": "Several links on one list line",
          "line": 14
        }
      ]
    },
    "sitemap": {
      "sitemaps": [
        {
          "url": "https://www.greenleaf-journal.example/sitemap_index.xml",
          "status": 200,
          "valid": true,
          "blocked_for": [
            "gptbot",
            "ccbot",
            "bytespider",
            "google-extended"
          ]
        },
        {
          "url": "https://www.greenleaf-journal.example/post-sitemap.xml",
          "status": 200,
          "valid": true,
          "blocked_for": [
            "gptbot",
            "ccbot",
            "bytespider",
            "google-extended"
          ]
        }
      ],
      "sampled": 20,
      "resolved": 19,
      "problems": [
        {
          "code": "blocked_for_ai",
          "detail": "robots.txt blocks 4 AI crawlers from the URLs this sitemap lists"
        }
      ]
    },
    "result_url": "https://approvalens.com/ai/aic_7Qm2xK9pLs"
  }
}
Şunu da döner · 200
{
  "check": {
    "id": "aic_7Qm2xK9pLs",
    "url": "greenleaf-journal.example",
    "final_url": "https://www.greenleaf-journal.example/",
    "host": "www.greenleaf-journal.example",
    "checked_at": "2026-10-06T12:45:00Z",
    "duration_ms": 17420,
    "cached": false,
    "headline": {
      "key": "blocked_server",
      "bot": "ChatGPT-User",
      "product": "ChatGPT"
    },
    "summary": {
      "allowed": 6,
      "blocked_robots": 4,
      "blocked_server": 3,
      "unknown": 1,
      "total": 14
    },
    "bots": [
      {
        "id": "oai-searchbot",
        "token": "OAI-SearchBot",
        "name": "OAI-SearchBot",
        "vendor": "OpenAI",
        "product": "ChatGPT",
        "purpose": "search",
        "kind": "fetcher",
        "doc_url": "https://developers.openai.com/api/docs/bots",
        "robots_respected": true,
        "robots": {
          "allowed": true,
          "group": "*",
          "rule": null
        },
        "http": {
          "simulated": true,
          "status": 200,
          "verdict": "ok",
          "reason": "200 OK, same page as a browser",
          "similarity": 0.98,
          "headers": {
            "content-type": "text/html; charset=utf-8"
          }
        },
        "verdict": "allowed"
      }
    ],
    "robots": {
      "url": "https://www.greenleaf-journal.example/robots.txt",
      "status": 200,
      "found": true,
      "unavailable": false,
      "excerpt": "User-agent: *\nDisallow: /wp-admin/\nAllow: /wp-admin/admin-ajax.php\n\nUser-agent: GPTBot\nDisallow: /\n\nUser-agent: CCBot\nDisallow: /\n\nUser-agent: Bytespider\nDisallow: /\n\nUser-agent: Google-Extended\nDisallow: /\n\nSitemap: https://www.greenleaf-journal.example/sitemap_index.xml"
    },
    "directives": {
      "meta": [
        {
          "name": "robots",
          "content": "index, follow, noai, noimageai"
        }
      ],
      "x_robots_tag": [],
      "flags": {
        "noai": true,
        "noimageai": true,
        "noindex": false,
        "nosnippet": false
      }
    },
    "js": {
      "raw_words": 412,
      "rendered_words": 1860,
      "ratio": 0.22,
      "verdict": "js_dependent",
      "reason": ""
    },
    "llms": {
      "url": "https://www.greenleaf-journal.example/llms.txt",
      "status": 200,
      "found": true,
      "content_type": "text/html; charset=utf-8",
      "h1": "Greenleaf Journal",
      "summary": null,
      "sections": 3,
      "links": 18,
      "problems": [
        {
          "code": "no_summary",
          "detail": "No blockquote summary after the H1",
          "line": 2
        },
        {
          "code": "content_type",
          "detail": "Served as text/html; expected text/plain or text/markdown",
          "line": null
        },
        {
          "code": "bad_list_item",
          "detail": "Several links on one list line",
          "line": 14
        }
      ]
    },
    "sitemap": {
      "sitemaps": [
        {
          "url": "https://www.greenleaf-journal.example/sitemap_index.xml",
          "status": 200,
          "valid": true,
          "blocked_for": [
            "gptbot",
            "ccbot",
            "bytespider",
            "google-extended"
          ]
        },
        {
          "url": "https://www.greenleaf-journal.example/post-sitemap.xml",
          "status": 200,
          "valid": true,
          "blocked_for": [
            "gptbot",
            "ccbot",
            "bytespider",
            "google-extended"
          ]
        }
      ],
      "sampled": 20,
      "resolved": 19,
      "problems": [
        {
          "code": "blocked_for_ai",
          "detail": "robots.txt blocks 4 AI crawlers from the URLs this sitemap lists"
        }
      ]
    },
    "result_url": "https://approvalens.com/ai/aic_7Qm2xK9pLs"
  }
}

Hatalar: bad_request, bad_url, invalid_key, missing_key, subscription_required, idempotency_in_progress, idempotency_key_reused, unreachable, rate_limited, unavailable

Uyarılar

Uyarılarının nereye gittiği (yalnızca okuma).

GET/api/v1/alert-channelsYetki: read

Uyarı hedeflerini listele

Uyarılarının nereye gittiği: hesap e‑postası, ek e‑posta adresleri, Slack, Telegram, Discord ve webhook'lar; her birinin uyarı türleri, en düşük önem derecesi, etiketleri, sessiz saatleri ve özeti. Gizli değerler asla gönderilmez: hedefler maskelenir. Değiştirmek için hesap sayfasını kullan.

Parametreler

lang
en (varsayılan) ya da tr: metin alanlarının dili
Örnek istek
curl https://approvalens.com/api/v1/alert-channels \
  -H "Authorization: Bearer $APPROVALENS_KEY"
Örnek yanıt · 200
{
  "channels": [
    {
      "id": 1,
      "kind": "account",
      "name": "",
      "target": "you@example.com",
      "status": "active",
      "primary": true,
      "events": [
        "uptime",
        "ssl",
        "domain",
        "adstxt",
        "robots",
        "noindex",
        "aibots",
        "security",
        "reputation",
        "server",
        "server_security"
      ],
      "min_severity": "info",
      "tags": [],
      "quiet_hours": null,
      "digest": "weekly",
      "digest_hour": 8,
      "instant": true,
      "created_at": "2026-08-26T08:00:00Z",
      "last_ok_at": "2026-10-03T08:00:00Z",
      "last_error": null,
      "failures": 0
    },
    {
      "id": 5,
      "kind": "webhook",
      "name": "Ops webhook",
      "target": "hooks.example.net/…/x9Kq",
      "status": "active",
      "primary": false,
      "events": [
        "uptime",
        "server"
      ],
      "min_severity": "warning",
      "tags": [
        "client:acme"
      ],
      "quiet_hours": {
        "start": "23:00",
        "end": "07:00"
      },
      "digest": "off",
      "digest_hour": null,
      "instant": true,
      "created_at": "2026-09-27T08:00:00Z",
      "last_ok_at": "2026-10-03T08:00:00Z",
      "last_error": null,
      "failures": 0
    }
  ],
  "timezone": "Europe/Istanbul",
  "webhooks": {
    "signature_header": "X-Approvalens-Signature",
    "docs_url": "https://approvalens.com/api#webhooks"
  }
}

Hatalar: invalid_key, missing_key, subscription_required, rate_limited, unavailable

Webhook imzaları

Webhook hedefleri (hesap sayfanda kurulur, GET /alert-channels listeler) her uyarıyı bir JSON POST olarak alır; webhook'u oluştururken ya da yenilerken gösterilen gizli değerle imzalanır. X-Approvalens-Signature başlığı t=1791273600,v1=5f2c… biçimindedir: t gönderildiği Unix zamanı, v1 ise anahtar olarak gizli değerin kullanıldığı, zamanın, bir noktanın ve ham gövdenin hex HMAC-SHA256'sıdır.

Aynı HMAC'i ham gövde üzerinden (ayrıştırmadan önce) hesapla, sabit zamanlı karşılaştır ve 5 dakikadan eski bir t'yi reddet. X-Approvalens-Delivery yeniden denemelerde aynı kalır; tekrarları bununla ayıklayabilirsin.

Node.js
import crypto from "node:crypto";

// header: the X-Approvalens-Signature value; raw: the request body as received (a Buffer)
export function verify(secret, header, raw) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
  const t = Number(parts.t);
  if (!t || Date.now() / 1000 - t > 300) return false; // older than 5 minutes
  const want = crypto.createHmac("sha256", secret).update(`${t}.`).update(raw).digest("hex");
  const got = String(parts.v1 || "");
  return got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want));
}
Python
import hashlib, hmac, time

def verify(secret: str, header: str, raw: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    t = int(parts.get("t", "0") or 0)
    if not t or time.time() - t > 300:  # older than 5 minutes
        return False
    want = hmac.new(secret.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(parts.get("v1", ""), want)
Webhook kur

OpenAPI

API'nin tamamı bir OpenAPI 3.1 belgesi olarak: her uç nokta, parametre, gövde ve yanıt şemasıyla ve örneğiyle; bu sayfayla aynı koddan üretilir. Postman'e, Insomnia'ya ya da bir kod üreticisine yükle.

/api/openapi.json'ı aç

Değişiklikler

  1. v1.1 · 2026-10-07Yazma erişimi ve fazlası: siteleri toplu ekleme, etiketleme ve çıkarma, site filtreleri ve erişilebilirlik, etiketler, sunucular ve kayıt anahtarları, taramalar ve krediyle açma, hesap, uyarı hedefleri. Anahtar yetkileri (önceki anahtarlar yalnızca okuma yetkili), saniyelik sınır, idempotency anahtarları, imleçler ve OpenAPI. API artık sunucu planları dahil her ücretli planla geliyor.
  2. v1 · 2026-10-06İlk sürüm: siteler, kesintiler, raporlar, rapor özeti, yapay zekâ kontrolleri.
  3. —Geriye uyumsuz değişiklikler /api/v2 olarak gelecek ve önce burada duyurulacak.