API Platformu
Task'lar ve Uzun Süren İstekler
Bir dakikadan uzun süren istekler task id ile 202 döner. Sonucu sonra alın, güvenle tekrar deneyin, iki kez ödeme yapmayın.
Bazı istekler, bir HTTP bağlantısının açık kalması gereken süreden uzun sürer. Aşağıdaki endpoint'lerde her istek bir task olarak çalışır. İş 60 saniye içinde biterse sonucu eskisi gibi alırsınız. Bitmezse 202 Accepted ile bir task_id döner; sonucu hazır olduğunda GET /v1/tasks/{task_id} ile alırsınız. İş kaybolmaz, iki kez ücret ödemezsiniz.
Hangi endpoint'ler task olarak çalışır
| Endpoint | service |
|---|---|
POST /v1/serp | serp |
POST /v1/keyword-data | keyword_data |
POST /v1/domain-research | domain_research |
POST /v1/youtube | youtube |
POST /v1/trends | trends |
POST /v1/on-page | on_page |
POST /v1/business-data | business_data |
Backlinks, Google Images ve Technical SEO dahil diğer tüm endpoint'ler şimdilik eskisi gibi senkron yanıt verir. Diğer endpoint'ler de zamanla task'lara geçecek.
Nasıl çalışır?
İsteği her zamanki gibi gönderin
Aynı URL, aynı header'lar, aynı body. Task, iş başlamadan önce kaydedilir; ardından bağlantı en fazla
waitsaniye (varsayılan 60) açık tutulur.Zamanında biterse sonuç gelir
Status code ve body her zamanki gibidir. Tek fark, task id'yi taşıyan yeni
X-Task-Idheader'ıdır.Hâlâ çalışıyorsa 202 gelir
Response'taki
Locationheader'ı task'ın adresini,Retry-Afterheader'ı ise tekrar sormadan önce kaç saniye beklemeniz gerektiğini söyler.Sonucu alın
Retry-Aftersaniye sonraGET /v1/tasks/{task_id}çağırın; status202olmaktan çıkana kadar tekrarlayın. Son response, POST'un döndüreceği response'un aynısıdır.
Task'lardan önce yazılmış entegrasyonlar, bir dakika içinde biten her istekte çalışmaya devam eder. Yine de her istemci 202'yi ele almalıdır: herhangi bir istek wait süresini aşabilir, ayrıca bir deploy sırasında bekleyen istek 202'yi erken alabilir. Örnekler bölümündeki yardımcı fonksiyonlar bunu sizin yerinize halleder.
Task id'yi nerede bulurum?
Her task'ın 4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39 gibi bir UUID'si vardır. Bu id her yanıtla birlikte gelir; böylece bir sonuca her zaman geri dönebilirsiniz:
| Ne aldınız | Task id nerede |
|---|---|
202 Accepted | Body'deki task_id, X-Task-Id header'ı, Location ve poll_url değerlerinin sonu |
POST'tan doğrudan gelen bir sonuç (200) ya da hata | X-Task-Id header'ı. Domain Research, YouTube, Trends, On-Page ve Business Data bunu body'de request_id olarak da verir |
GET /v1/tasks/{task_id} yanıtı | X-Task-Id header'ı |
| Hiçbir şey: süreciniz çöktü ya da yanıtı kaybettiniz | GET /v1/tasks son task'larınızı task_id'leriyle listeler (Task'ları listeleme) |
task_id, request_id, X-Task-Id ve X-Request-Id her zaman aynı değerdir. X-Request-Id, task'lardan önce yazılmış istemciler için duruyor. Her isteğin task id'sini loglayın: bir sonucu tekrar almanın ve destek ekibimizin isteği uçtan uca izlemesinin yolu budur.
Ücretsiz önbellek yanıtlarının task id'si yoktur
Ücretsiz önbellekten dönen bir SERP yanıtı task oluşturmadan anında döner, bu yüzden X-Task-Id
taşımaz. İş yapan ya da ücretlendirilen her isteğin bir task id'si vardır.
202 Response
Task wait saniye sonunda hâlâ çalışıyorsa POST 202 Accepted döner:
| Header | Değer |
|---|---|
Location | /v1/tasks/{task_id}, sorgulanacak yol |
Retry-After | Tekrar sormadan önce beklenecek saniye |
X-Task-Id | Task id |
X-Request-Id | Task id (aynı değer, eski istemciler için) |
X-Task-Status | pending |
HTTP/1.1 202 Accepted
Location: /v1/tasks/4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39
Retry-After: 5
X-Task-Id: 4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39
X-Request-Id: 4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39
X-Task-Status: pending
Content-Type: application/json{
"success": true,
"status": "pending",
"task_id": "4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39",
"request_id": "4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39",
"service": "serp",
"poll_url": "https://data.semust.com/v1/tasks/4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39",
"retry_after": 5,
"created_at": "2026-11-06T13:15:26Z",
"elapsed_seconds": 60,
"deadline_at": "2026-11-06T13:25:26Z",
"deadline_in_seconds": 540
}| Alan | Tip | Açıklama |
|---|---|---|
success | boolean | Her zaman true: istek kabul edildi |
status | string | pending |
task_id | string | Task'ın UUID'si |
request_id | string | task_id ile aynı |
service | string | serp, keyword_data, domain_research, youtube veya trends |
poll_url | string | GET /v1/tasks/{task_id} adresinin tamamı |
retry_after | integer | Retry-After header'ıyla aynı |
created_at | string | Task'ın oluşturulma zamanı, UTC, RFC 3339 |
elapsed_seconds | integer | created_at'ten bu yana geçen saniye |
deadline_at | string | Bitmemiş bir task'tan vazgeçilecek zaman: o an gelince task 504 TIMEOUT ile başarısız olur ve iade edilir. UTC, RFC 3339 |
deadline_in_seconds | integer | deadline_at'e kalan saniye. Her sorgulamada azalır; böylece ne kadar daha sorgulayabileceğinizi bilirsiniz |
success: true sonucun geldiği anlamına gelmez
202 body'sindeki "success": true, isteğin bittiğini değil kabul edildiğini söyler. Kararı
HTTP status code'una göre verin. Yalnızca success alanına bakıp ardından items ya da
organic okuyan bir istemci bu alanları boş bulur.
Task sorgulama
GET /v1/tasks/{task_id} çağrısını POST'ta kullandığınız SEMUST-API-USER ve SEMUST-API-PASSWORD header'larıyla yapın.
| Task durumu | Status code | Body | Header'lar |
|---|---|---|---|
| Çalışıyor | 202 | Yukarıdaki pending body; elapsed_seconds artmaya devam eder | Retry-After, Location, X-Task-Id, X-Task-Status: pending |
| Tamamlandı | POST'un döndüreceği status, genellikle 200 | Endpoint'in normal response'u, bayt bayt aynısı | X-Task-Status: completed, X-Task-Expires-At, X-Task-Expires-In, X-Task-Id |
| Başarısız | Endpoint'in normal hata status'u, ör. 502 veya 404 | Endpoint'in normal hata body'si, ör. REQUEST_FAILED, SERP_FAILED, VIDEO_NOT_FOUND | X-Task-Status: failed, X-Task-Expires-At, X-Task-Expires-In, X-Task-Id |
| Sonuç silinmiş | 410 | TASK_EXPIRED | |
| Bilinmeyen id ya da başka bir API kullanıcısının task'ı | 404 | TASK_NOT_FOUND | |
| UUID değil | 400 | INVALID_TASK_ID |
Biten bir task, POST'un döndüreceği status'u ve body'yi birebir aynı baytlarla döndürür; yani elinizdeki response parser'ı hiçbir değişiklik gerekmeden çalışır. İkisini ayıran tek şey X-Task-* header'larıdır:
X-Task-Status: completedtask'ın başarılı olduğunu ve ücretlendirildiğini söyler.failedise ücret alınmadığı anlamına gelir.X-Task-Expires-Atsonucun silineceği zamanı (RFC 3339),X-Task-Expires-Inise o ana kalan saniyeyi verir.
Bu header'lar, POST'un doğrudan döndürdüğü sonuç dahil her bitmiş yanıtta gelir; böylece ücret alınıp alınmadığını ve sonucu daha ne kadar alabileceğinizi her zaman bilirsiniz:
HTTP/1.1 200 OK
X-Task-Id: 4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39
X-Task-Status: completed
X-Task-Expires-At: 2026-11-09T13:16:41Z
X-Task-Expires-In: 259200Diğer kurallar:
- İstediğiniz kadar alın. Biten bir sonuç, süresi dolana kadar istediğiniz kadar alınabilir. Sorgulamak ücretsizdir.
- Yalnızca oluşturan okuyabilir. Bir task'ı yalnızca onu oluşturan API kullanıcısı okuyabilir. Aynı hesaptaki başka bir API kullanıcısı dahil herkes
404 TASK_NOT_FOUNDalır. - Senkron yanıtlar da birer task'tır. Gerçek iş yapan her istek,
waitiçinde yanıtlananlar dahil, task olarak saklanır.X-Task-Idheader'ı olan her response, saklama süresi boyunca o id ile yeniden alınabilir. - Sorgulama beklemez.
GET /v1/tasks/{task_id}hemen yanıt verir. Çağrılar arasındaRetry-After'a uyun: task'ın ilk 30 saniyesinde 3 saniye, 2 dakikaya kadar 5 saniye, sonrasında 10 saniye.
wait parametresi
?wait=, POST'un 202 dönmeden önce bağlantıyı kaç saniye açık tutacağını belirler.
wait | Davranış |
|---|---|
| Gönderilmezse | 60 saniye |
1–80 | Sonucu en fazla bu kadar saniye bekler |
0 | Hiç beklemeden hemen 202 döner |
| Diğer değerler | "field": "wait" ile 400 INVALID_PARAMETER |
Üst sınır 80'dir, çünkü API'nin önündeki edge katmanı bağlantıları 100 saniyede keser. HTTP istemcinizin okuma timeout'unu wait'ten yüksek tutun, örneğin wait + 30 saniye.
wait=0 "gönder, sonra topla" modelidir: her istek için bağlantı açık tutmadan toplu istek gönderin, sonuçları sorgulayarak ya da listeleyerek toplayın. Önbellekten dönen yanıt gibi anında biten bir istek, yine sonucuyla birlikte hemen yanıtlanır.
POST https://data.semust.com/v1/domain-research?wait=0Task'ları listeleme
GET /v1/tasks, sonucu hâlâ alınabilen son task'larınızı en yeniden eskiye listeler. Aynı header'ları kullanır ve ücretsizdir.
| Query parametresi | Değerler | Varsayılan |
|---|---|---|
status | pending, completed, failed | hepsi |
service | serp, keyword_data, domain_research, youtube, trends | hepsi |
fetched | true veya false | ikisi de |
page | 1 veya daha büyük | 1 |
limit | 1–100 | 50 |
Geçersiz bir status, fetched, page ya da limit değeri 400 INVALID_PARAMETER döndürür; field hangi parametre olduğunu söyler.
curl "https://data.semust.com/v1/tasks?status=completed&service=domain_research&limit=20" \
-H "SEMUST-API-USER: kullanici_adiniz" \
-H "SEMUST-API-PASSWORD: sifreniz"{
"success": true,
"total": 2,
"page": 1,
"limit": 50,
"items": [
{
"task_id": "9d3a6c1e-27b4-4f0a-8e5d-3c1b7a9f2e60",
"service": "serp",
"status": "pending",
"http_status": null,
"cost": null,
"fetched": false,
"poll_url": "https://data.semust.com/v1/tasks/9d3a6c1e-27b4-4f0a-8e5d-3c1b7a9f2e60",
"created_at": "2026-11-06T13:17:02Z",
"completed_at": null,
"expires_at": null,
"expires_in_seconds": null,
"deadline_at": "2026-11-06T13:27:02Z"
},
{
"task_id": "4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39",
"service": "domain_research",
"status": "completed",
"http_status": 200,
"cost": 0.0264,
"fetched": false,
"poll_url": "https://data.semust.com/v1/tasks/4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39",
"created_at": "2026-11-06T13:15:26Z",
"completed_at": "2026-11-06T13:16:41Z",
"expires_at": "2026-11-09T13:16:41Z",
"expires_in_seconds": 259080,
"deadline_at": null
}
]
}| Alan | Tip | Açıklama |
|---|---|---|
task_id | string | Task'ın UUID'si |
service | string | Task'ın ait olduğu endpoint |
status | string | pending, completed veya failed |
http_status | integer | null | Sonucun status code'u; çalışırken null |
cost | number | null | Alınan ücret, USD; tamamlanmadıysa null |
fetched | boolean | Biten sonucun size ulaşıp ulaşmadığı: POST'un kendisinde, bir tekrarda ya da GET /v1/tasks/{task_id} ile |
poll_url | string | Sonucun alınacağı adres |
created_at | string | Task'ın oluşturulma zamanı |
completed_at | string | null | Bitiş zamanı; çalışırken null |
expires_at | string | null | Sonucun silineceği zaman; çalışırken null |
expires_in_seconds | integer | null | expires_at'e kalan saniye; çalışırken null |
deadline_at | string | null | Çalışırken: task'tan vazgeçilip iade edileceği zaman; bittikten sonra null |
total filtreye uyan task sayısıdır; page ve limit uygulanan sayfalamadır.
Kaybolan işleri kurtarma
Süreciniz çöktüyse, bir 202'yi kaçırdıysanız ya da hiç beklemediyseniz, GET /v1/tasks?fetched=false size henüz ulaşmamış biten sonuçları listeler. Her poll_url'yi çağırın; task okunmuş olarak işaretlenir.
POST'un size doğrudan döndürdüğü sonuç da okunmuş sayılır. Böylece fetched=false bir gelen kutusu gibi çalışır: yalnızca kimsenin almadığı sonuçları içerir.
Idempotency
Bir POST'u güvenle tekrarlanabilir yapmak için Idempotency-Key header'ı gönderin. Her mantıksal istek için bir anahtar (ör. bir UUID) üretin; bir ağ hatası ya da istemci timeout'u sonrasında tekrar denerken aynı anahtarı gönderin.
- En fazla 128 karakter. Daha uzun bir anahtar
"field": "Idempotency-Key"ile400 INVALID_PARAMETERdöndürür. - Tüm task endpoint'lerinde çalışır. Domain Research, YouTube ve Trends body'deki
idempotency_keyalanını da kabul etmeye devam eder; ikisi birden gönderilirse body alanı geçerli olur. - Aynı API kullanıcısından aynı endpoint'e gelen aynı anahtar her zaman aynı task'ı gösterir:
| Asıl task | Tekrar gönderilen istek şunu alır |
|---|---|
| Hâlâ çalışıyorsa | O task'ın 202'si |
| Bittiyse | Saklanan sonucu, aynı status ve body ile |
Anahtarı kaç kez gönderirseniz gönderin task bir kez ücretlendirilir. Anahtar, sonuç saklandığı sürece, yani task bittikten sonra 72 saat boyunca, ayrılmış kalır. Bu süreden sonra aynı anahtar yeni bir task başlatır.
Tekrarlanan isteğin body'si asıl istekle karşılaştırılmaz. Parametreleri farklı bir istek için yeni bir anahtar kullanın.
curl -X POST https://data.semust.com/v1/serp \
-H "SEMUST-API-USER: kullanici_adiniz" \
-H "SEMUST-API-PASSWORD: sifreniz" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0c5d2e8a-61f4-4b39-a7de-58c90f2b4e17" \
-d '{"keyword": "seo araçları", "language": "tr", "country": "TR"}'Saklama süresi
| Kural | |
|---|---|
| Sonuç ne kadar saklanır | Task bittikten sonra 72 saat |
| Kesin bitiş zamanı | Her bitmiş yanıtta X-Task-Expires-At ve X-Task-Expires-In (kalan saniye) header'ları; listede expires_at ve expires_in_seconds |
| Süre dolunca | Sonuç silinir; GET /v1/tasks/{task_id} 410 TASK_EXPIRED döndürür |
| Sıkıştırılmış hâli 5 MB'tan büyük sonuçlar | Saklanmaz; sorgulandığında 410 TASK_EXPIRED döner. Bu endpoint'lerde pratikte hiç yaşanmaz |
Daha uzun süre ihtiyaç duyacağınız sonuçları kendi tarafınızda saklayın. Bir süre sonra task kaydının kendisi de silinir; o id artık 404 TASK_NOT_FOUND döndürür.
Ücretlendirme
- Task başarıyla tamamlandığında ücretlendirilir, sonucu hiç sorgulamasanız bile.
- Başarısız bir task'ın ücreti tamamen iade edilir, süresini aşan task'lar dahil.
- Sorgulama ve listeleme ücretsizdir.
- Önbellek eskisi gibi çalışır. Önbellekten dönen yanıtlar hemen gelir ve her endpoint'in kendi sayfasında anlatıldığı gibi ücretlendirilir.
- Endpoint fiyatları değişmedi. Bakiye POST sırasında kontrol edilir ve bloke edilir; bu yüzden
402 INSUFFICIENT_CREDITSbir sorgulamada değil, her zaman POST'ta döner. Task'ın kullanmadığı tutar, task bittiğinde iade edilir.
Sınırlar
| Sınır | Değer | Aşılınca |
|---|---|---|
| POST istekleri | Hesabınızın dakikalık ve günlük limitleri, değişmedi | 429 RATE_LIMIT_EXCEEDED |
GET /v1/tasks ve GET /v1/tasks/{task_id} | API kullanıcısı başına dakikada 600, ayrı bir kovada | 429 RATE_LIMIT_EXCEEDED |
| Bitmemiş task'lar | API kullanıcısı başına 100 | 429 TOO_MANY_PENDING_TASKS, Retry-After: 10 |
Sorgulamalar POST isteklerinizin dakikalık ya da günlük limitlerinden düşmez; sonuç toplamak yeni istek göndermenizi asla engellemez.
Aynı anda çok sayıda task çalışıyorsa her birini ayrı ayrı sorgulamak yerine birkaç saniyede bir GET /v1/tasks?fetched=false çağırın ve yalnızca bitenleri alın.
Endpoint'e özel eşzamanlılık sınırları geçerli
Domain Research ve Trends için API kullanıcısı başına aynı anda en fazla 5, YouTube için en fazla 10 istek
çalışır. Beklemediğiniz task'lar da bitene kadar bu sayıya dahildir; bu yüzden wait=0 ile
gönderilen büyük bir toplu iş Retry-After ile 429 TOO_MANY_CONCURRENT_REQUESTS alır. Biri
bitince sıradaki isteği gönderin.
Güvenilirlik
Şunlara güvenebilirsiniz:
- Task, iş başlamadan önce kaydedilir. Elinize bir
task_idgeçtiyse task vardır. - Sonuçlar kalıcı bir kuyruktan geçer. API'mizin deploy edilmesi ya da yeniden başlatılması bir task'ı kaybettirmez; task tamamlanır ve sonucunu alabilirsiniz.
- Bağlantınız koparsa task durmaz. Beklerken bağlantınız kopsa, timeout'a düşse ya da süreciniz çökse bile task devam eder. Sonucu id ile alın ya da
GET /v1/tasks?fetched=falseile bulun. - Her task'ın 10 dakikalık bir süresi vardır. Bu sürede sonuç gelmezse task, sürenin dolmasından en geç birkaç dakika sonra
504 TIMEOUTile başarısız sayılır ve ücreti iade edilir. Tam 10. dakikada vazgeçmek yerine kesin bir yanıt alana kadar sorgulamaya devam edin. - Para her task için tek bir kez hareket eder. Bir blokaj, ardından tek bir ücretlendirme ya da tek bir iade; kaç kez sorgularsanız ya da aynı
Idempotency-Key'i kaç kez gönderirseniz gönderin.
Hatalar
Hatalar {"error", "code", "hint", "retryable", ...} biçimindedir. Accept-Language: tr gönderirseniz error ve hint Türkçe gelir; code hiç değişmez.
| Durum | Kod | Anlamı | Ne yapmalı |
|---|---|---|---|
| 400 | INVALID_TASK_ID | Task id bir UUID değil | 202 yanıtındaki ya da GET /v1/tasks listesindeki task_id'yi kullanın |
| 400 | INVALID_PARAMETER | wait, Idempotency-Key ya da bir liste filtresi geçersiz; field hangisi olduğunu söyler | Yukarıdaki kurallara bakın |
| 404 | TASK_NOT_FOUND | Böyle bir task yok ya da başka bir API kullanıcısına ait | Task'ı oluşturan API kullanıcısıyla okuyun |
| 410 | TASK_EXPIRED | Sonuç artık saklanmıyor | Sonuçlar task bittikten sonra 72 saat saklanır; isteği yeniden gönderin |
| 429 | TOO_MANY_PENDING_TASKS | Bu API kullanıcısının 100 task'ı hâlâ çalışıyor | Retry-After (10) saniye sonra tekrar deneyin |
| 429 | RATE_LIMIT_EXCEEDED | Bir dakikada 600'den fazla sorgulama ya da POST limitlerinize ulaşıldı | Yavaşlayın |
| 504 | TIMEOUT | Task zamanında bitmedi, ör. 10 dakikalık süresi doldu. Ücret alınmaz | İsteği yeniden gönderin |
TIMEOUT artık yavaş bir HTTP isteği anlamına gelmiyor. Yavaş bir istek 202'ye dönüşür; 504 TIMEOUT ise bitirilemeyen bir task'ın son yanıtıdır. Tam liste için Hata Kodları sayfasına bakın.
{
"error": "Task bulunamadı",
"code": "TASK_NOT_FOUND",
"hint": "Bir task yalnızca onu oluşturan API kullanıcısıyla okunabilir",
"retryable": false
}{
"error": "Bu task'ın sonucu artık saklanmıyor",
"code": "TASK_EXPIRED",
"hint": "Sonuçlar task bittikten sonra 72 saat saklanır",
"retryable": false
}{
"error": "Bu API kullanıcısının bitmemiş task sayısı sınıra ulaştı",
"code": "TOO_MANY_PENDING_TASKS",
"hint": "Aynı anda en fazla 100 task bekleyebilir; bazılarının bitmesini bekleyin",
"retryable": true
}{
"error": "'wait' 0 ile 80 arasında bir tam sayı (saniye) olmalıdır",
"code": "INVALID_PARAMETER",
"field": "wait",
"retryable": false
}Örnekler
Aşağıdaki call(path, body) yardımcısı POST'u gönderir, 202 aldığı sürece Retry-After'a uyarak sorgular ve son response'u döndürür. Her isteğe bir Idempotency-Key eklenir; böylece ağ hatasıyla düşen bir POST güvenle tekrar gönderilir. Dönen response'u task'lardan önce nasıl işliyorsanız öyle işlersiniz.
import time
import uuid
import requests
BASE_URL = "https://data.semust.com"
AUTH = {
"SEMUST-API-USER": "kullanici_adiniz",
"SEMUST-API-PASSWORD": "sifreniz",
"Accept-Language": "tr",
}
def call(path, body, wait=60, give_up_after=900):
"""Semust Data API endpoint'ine POST gönderir ve son response'u döndürür."""
headers = {**AUTH, "Idempotency-Key": str(uuid.uuid4())}
# Aynı Idempotency-Key ile tekrar deneme aynı task'a bağlanır ve bir kez ücretlendirilir.
for attempt in range(3):
try:
response = requests.post(
BASE_URL + path,
params={"wait": wait},
headers=headers,
json=body,
timeout=wait + 30,
)
break
except (requests.ConnectionError, requests.Timeout):
if attempt == 2:
raise
time.sleep(2 ** attempt)
deadline = time.monotonic() + give_up_after
while response.status_code == 202:
task = response.json()
if time.monotonic() > deadline:
raise TimeoutError(f"task {task['task_id']} hâlâ çalışıyor")
time.sleep(int(response.headers.get("Retry-After", task["retry_after"])))
response = requests.get(task["poll_url"], headers=AUTH, timeout=30)
return response
response = call(
"/v1/domain-research",
{"type": "keywords", "domain": "ornek.com.tr", "limit": 100},
)
data = response.json()
if response.status_code != 200:
raise SystemExit(f"{data['code']}: {data['error']} (task {response.headers.get('X-Task-Id')})")
print(f"{data['count']} kelime, ücret ${data['cost']:.6f}")import { randomUUID } from "node:crypto";
const BASE_URL = "https://data.semust.com";
const AUTH = {
"SEMUST-API-USER": "kullanici_adiniz",
"SEMUST-API-PASSWORD": "sifreniz",
"Accept-Language": "tr",
};
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Semust Data API endpoint'ine POST gönderir ve son response'u döndürür.
async function call(path, body, { wait = 60, giveUpAfter = 900 } = {}) {
const headers = {
...AUTH,
"Content-Type": "application/json",
"Idempotency-Key": randomUUID(),
};
// Aynı Idempotency-Key ile tekrar deneme aynı task'a bağlanır ve bir kez ücretlendirilir.
let response;
for (let attempt = 0; ; attempt++) {
try {
response = await fetch(`${BASE_URL}${path}?wait=${wait}`, {
method: "POST",
headers,
body: JSON.stringify(body),
signal: AbortSignal.timeout((wait + 30) * 1000),
});
break;
} catch (err) {
if (attempt === 2) throw err;
await sleep(1000 * 2 ** attempt);
}
}
const deadline = Date.now() + giveUpAfter * 1000;
while (response.status === 202) {
const task = await response.json();
if (Date.now() > deadline) throw new Error(`task ${task.task_id} hâlâ çalışıyor`);
const retryAfter = Number(response.headers.get("Retry-After")) || task.retry_after;
await sleep(retryAfter * 1000);
response = await fetch(task.poll_url, { headers: AUTH });
}
return response;
}
const response = await call("/v1/domain-research", {
type: "keywords",
domain: "ornek.com.tr",
limit: 100,
});
const data = await response.json();
if (!response.ok) {
throw new Error(`${data.code}: ${data.error} (task ${response.headers.get("X-Task-Id")})`);
}
console.log(`${data.count} kelime, ücret $${data.cost}`);# 1. Gönderin. wait=0 ile yanıt hemen 202 olarak döner.
curl -i -X POST "https://data.semust.com/v1/serp?wait=0" \
-H "SEMUST-API-USER: kullanici_adiniz" \
-H "SEMUST-API-PASSWORD: sifreniz" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0c5d2e8a-61f4-4b39-a7de-58c90f2b4e17" \
-d '{"keyword": "seo araçları", "language": "tr", "country": "TR"}'
# HTTP/1.1 202 Accepted
# Location: /v1/tasks/4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39
# Retry-After: 3
# ...
# 2. Toplayın. Retry-After saniye bekleyin; status 202 olduğu sürece tekrarlayın.
# Son yanıt, POST'un döndüreceği response'un aynısıdır.
curl -i https://data.semust.com/v1/tasks/4b7e2f1c-9a3d-4e8b-b6c2-0d5f8a1e7c39 \
-H "SEMUST-API-USER: kullanici_adiniz" \
-H "SEMUST-API-PASSWORD: sifreniz"