Dokumentacja API
Trzy endpointy, jeden nagłówek uwierzytelniający, rozliczenie za rozstrzygnięte cytowanie. Poniżej wszystko, czego potrzeba do integracji — każdy przykład został uruchomiony na produkcji.
Uwierzytelnianie
Klucz w nagłówku Bearer. Nieznany, nieaktywny i brakujący klucz dają ten sam wynik 401 — nie podpowiadamy, która z tych rzeczy zawiodła.
Authorization: Bearer cyt_live_…Dwa rodzaje kluczy
Klucz z prefiksem cyt_test_ korzysta z tego samego korpusu i zwraca te same odpowiedzi, ale nie jest rozliczany — ${CYTOWANIA_TESTOWE} cytowań miesięcznie na integrację i sprawdzenie obsługi błędów. Klucz cyt_live_ obsługuje ruch produkcyjny i liczy się do pakietu. Klucz widzisz jeden jedyny raz przy wydaniu; w bazie trzymamy wyłącznie jego skrót.
Za co płacisz
Jednostką rozliczeniową jest ROZSTRZYGNIĘTE CYTOWANIE — jedno zdanie, do którego znaleźliśmy źródło z numerem strony. Odmowa (found: false) jest bezpłatna, a w /chapter-sources bezpłatny jest też materiał TŁA (trafność poniżej 7) — płacisz wyłącznie za źródła wprost o temacie. Osobno liczy się NOWY TEMAT: zbudowanie korpusu dla zagadnienia, którego jeszcze nie mamy — trwa minuty i kosztuje dwa rzędy wielkości więcej, dlatego uruchamiasz je świadomie przez harvestOnMiss. Powyżej pakietu wywołania działają dalej po stawkach nadwyżki (doliczane do następnej faktury) — ale najwyżej do DODATKOWYCH 100% pakietu; potem 429 do 1. dnia miesiąca albo do przejścia na wyższy pakiet. Rachunek nie może cię więc zaskoczyć bardziej niż dwukrotnością pakietu.
| Pakiet | Cena / mies. | Cytowania | Nowe tematy | Ponad pakiet |
|---|---|---|---|---|
| Test | za darmo | 100 | 3 | — |
| Basic | $19 | 400 | 5 | $0.04 / $1.00 |
| Start | $49 | 1200 | 15 | $0.04 / $1.00 |
| Pro | $99 | 3000 | 40 | $0.04 / $1.00 |
Endpointy
GET /usage
Stan pakietu i zużycie w bieżącym miesiącu. Nic nie kosztuje i niczego nie zużywa — najtańszy sposób sprawdzenia, czy klucz działa.
curl -s -H "Authorization: Bearer $CYTADO_KEY" \
https://cytado.com/api/ext/usageimport os, requests
r = requests.get(
"https://cytado.com/api/ext/usage",
headers={"Authorization": f"Bearer {os.environ['CYTADO_KEY']}"},
timeout=60,
)
print(r.json())const r = await fetch("https://cytado.com/api/ext/usage", {
headers: { Authorization: `Bearer ${process.env.CYTADO_KEY}` },
});
console.log(await r.json());POST /find-source
Zdanie na wejściu, źródło z numerem strony na wyjściu. To jedyne wywołanie, za które płacisz — i tylko wtedy, gdy coś znajdzie. Do 60 pozycji w żądaniu; nadmiar odrzucamy JAWNIE. Pole hint to temat pracy: bez niego pytamy „czy to źródło potwierdza to zdanie”, a z nim „czy ktoś piszący TĘ pracę mógłby je zacytować”. Każde trafienie ma pole wsparcie — czy ZWRÓCONY fragment sam broni tezy: wprost (cytuj śmiało), posrednie (przeczytaj i zaznacz kontekst), brak (fragment nie stawia tego twierdzenia — poszukaj innego miejsca), null (nie oceniono).
curl -s -X POST https://cytado.com/api/ext/find-source \
-H "Authorization: Bearer $CYTADO_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"id": "a1",
"claim": "Procrastination is accompanied by guilt and psychological discomfort.",
"hint": "procrastination among university students"
}
],
"langs": [
"en"
],
"harvestOnMiss": false
}'import os, requests
r = requests.post(
"https://cytado.com/api/ext/find-source",
headers={"Authorization": f"Bearer {os.environ['CYTADO_KEY']}"},
json={
"items": [
{
"id": "a1",
"claim": "Procrastination is accompanied by guilt and psychological discomfort.",
"hint": "procrastination among university students"
}
],
"langs": [
"en"
],
"harvestOnMiss": False
},
timeout=600,
)
r.raise_for_status()
print(r.json())const r = await fetch("https://cytado.com/api/ext/find-source", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CYTADO_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"items": [
{
"id": "a1",
"claim": "Procrastination is accompanied by guilt and psychological discomfort.",
"hint": "procrastination among university students"
}
],
"langs": [
"en"
],
"harvestOnMiss": false
}),
});
if (!r.ok) throw new Error(`cytado ${r.status}: ${await r.text()}`);
console.log(await r.json());POST /chapter-sources
Temat rozdziału na wejściu, zestaw cytowalnych źródeł na wyjściu, uszeregowany pokryciem tematu. Pole exclude pozwala podać źródła już wykorzystane — pokrycie liczymy wtedy po tych, które zostały, więc kolejne rozdziały nie dostają w kółko tego samego. Naliczamy WYŁĄCZNIE źródła wprost o temacie (trafność ≥ 7) — i domyślnie tylko takie zwracamy; materiał tła (trafność 5-6, gratis) dostaniesz po jawnym minRelevance: 5. Każda pozycja ma pole wsparcie: czy zwracany fragment sam broni rozdziału (wprost / posrednie / brak, null = nie oceniono). Pole chapter jest opcjonalne: bez niego źródła dobierane są pod cały temat, z nim oceny kotwiczą się w tym rozdziale. limit ma sufit 15; limit: 0 (albo ujemny) to bezpłatny no-op — naturalny dla pętli `limit: docelowo - mam`.
curl -s -X POST https://cytado.com/api/ext/chapter-sources \
-H "Authorization: Bearer $CYTADO_KEY" \
-H "Content-Type: application/json" \
-d '{
"topic": "screen time in preschool children",
"chapter": "Introduction",
"exclude": []
}'import os, requests
r = requests.post(
"https://cytado.com/api/ext/chapter-sources",
headers={"Authorization": f"Bearer {os.environ['CYTADO_KEY']}"},
json={
"topic": "screen time in preschool children",
"chapter": "Introduction",
"exclude": []
},
timeout=600,
)
r.raise_for_status()
print(r.json())const r = await fetch("https://cytado.com/api/ext/chapter-sources", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CYTADO_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"topic": "screen time in preschool children",
"chapter": "Introduction",
"exclude": []
}),
});
if (!r.ok) throw new Error(`cytado ${r.status}: ${await r.text()}`);
console.log(await r.json());Kształt odpowiedzi /find-source
{
"results": [
{
"id": "a1", // twoje id, oddane bez zmian
"claim": "…", // echo tezy, o która pytales
"found": true,
"source": { // UWAGA: pola zrodla sa TUTAJ, nie plasko
"title": "…",
"authors": "…",
"year": 2021,
"url": "https://doi.org/10.12740/pp/95085",
"doi": "10.12740/pp/95085",
"page_from": 1088,
"page_to": 1090,
"snippet": "…",
"relevance": 9
}
},
{ "id": "a2", "claim": "…", "found": false }
],
"billing": { "citations_charged": 1, "topics_charged": 0 },
"harvested": 0
}Brak źródła to po prostu brak pola source. Sześćdziesiąt pozycji w jednym żądaniu jest w porządku, ale licz około 1,6 s na tezę — komplet zajmie ~95 s. Ustaw timeout klienta z zapasem; brama daje 800 s.
Pola odpowiedzi, które warto rozumieć
- found
- false znaczy, że korpus nie ma czym poprzeć tej tezy. Nie zwracamy wtedy najbliższego dopasowania i nie pobieramy opłaty.
- page_from / page_to
- Numer strony DRUKOWANEJ — ten sam, na który trafi promotor sprawdzający przypis, a nie numer kartki w pliku PDF.
- page_from_label
- Wypełnione tylko przy numeracji nietypowej (np. „E136”). Puste pole przy wypełnionym page_from znaczy „numer drukowany to po prostu 152”, a nie „brak paginacji”.
- relevance
- 9–10: wprost o tej tezie i tej grupie. 7–8: to samo zjawisko w pokrewnym ujęciu — inna grupa zawodowa, inny wiek, inny kraj; cytowalne, ale zaznacz kontekst.
- url
- Odnośnik do oryginału — DOI albo bezpośredni adres. Ma go KAŻDE zwrócone źródło; przy ponad połowie korpusu prowadzi do pełnego tekstu, bo są to publikacje otwarte.
- snippet
- Krótki fragment w ramach dozwolonego użytku. Pełnych tekstów źródeł, których nie wolno nam republikować, nie zwracamy nigdy — zwracamy odnośnik, pod którym czytelnik przeczyta je u wydawcy.
- billing
- Co dokładnie policzyliśmy w tym żądaniu: citations_charged (naliczone), already_paid (opłacone wcześniej w tym temacie) oraz free_background — ile zwróconych pozycji to bezpłatny materiał tła (trafność poniżej 7). Zero w free_background znaczy, że wszystko, co wróciło, było wprost o temacie.
Limity i błędy
- 401 unauthorized
- Nieznany, nieaktywny albo brakujący klucz.
- 400 invalid-json
- Ciało żądania nie jest poprawnym JSON-em.
- 400 missing-items
- Brakuje wymaganego pola `items` (tablica).
- 400 unknown-field
- Nieznane pole w żądaniu — z podpowiedzią, jeśli wygląda na literówkę (`min_relevance` → „czy chodziło o minRelevance?”). Nie ignorujemy po cichu pól, których nie znamy: literówka w nazwie parametru nie może cicho kosztować pieniędzy.
- 422 invalid_request
- Żądanie poprawne składniowo, ale zła WARTOŚĆ znanego pola — odpowiedź wskazuje pole. Np. temat krótszy niż 8 albo dłuższy niż 4000 znaków, langs niebędące tablicą znanych kodów, exclude niebędące tablicą napisów, minRelevance niebędące liczbą.
- 429 rate_limited
- Przekroczony limit. Nagłówek Retry-After mówi, za ile sekund wrócić. Bezpiecznik minutowy mówi „zwolnij”, limit dobowy „na dziś koniec”, sufit nadwyżki (pakiet + 100%) „do 1. dnia miesiąca albo wyższy pakiet” — komunikat je rozróżnia.
- 402
- Wyczerpany pakiet na operacji, która wymaga opłaconego dostępu.
Długie operacje: harvest i odpytywanie
Odczyt gotowego korpusu trwa kilka sekund. Zbudowanie korpusu dla tematu, którego jeszcze nie mamy, to 5-30 minut: pobieramy i czytamy prawdziwe pliki PDF. Nie da się na to czekać w jednym żądaniu HTTP — po drodze stoi nginx z własnym limitem, a Twój klient ma swój. Dlatego /corpus ma tryb asynchroniczny: zlecasz i odpytujesz.
# 1. Enqueue. Returns immediately.
curl -s https://cytado.com/api/ext/corpus \
-H "Authorization: Bearer $CYTADO_KEY" \
-H "Content-Type: application/json" \
-d '{"topic":"persuasion in advertising","langs":["en"],"minCoverage":6,"async":true}'
# {"jobId":82,"coverage":7,"coverageWprost":1,"corpusReady":false,"async":true}
# jobId = null -> corpus already sufficient, nothing was started, nothing billed.
# 2. Poll every 20-30 s. Free: polling is never billed.
curl -s https://cytado.com/api/ext/corpus/status \
-H "Authorization: Bearer $CYTADO_KEY" \
-H "Content-Type: application/json" \
-d '{"jobId":82,"topic":"persuasion in advertising","langs":["en"]}'
# {"jobId":82,"status":"running","ready":false,"coverage":9,"coverageWprost":4}
# status: queued | running | done | failed | unknown
# stop when "ready" is true, then call /chapter-sourcesOdpytuj co 20-30 sekund i przewiduj limit własnego oczekiwania — status failed jest legalną odpowiedzią, nie awarią połączenia. Gdy zadanie padnie, korpus zostaje taki, jaki był, i możesz pracować na tym, co już masz.
To samo w Pythonie — gotowe do skopiowania:
import os, time, requests
BASE = "https://cytado.com/api/ext"
HEAD = {"Authorization": f"Bearer {os.environ['CYTADO_KEY']}"}
def ensure_corpus(topic, langs=("pl", "en"), min_coverage=6, limit_s=1800):
"""Ensure the corpus covers a topic. Returns final coverage.
Blocks for up to limit_s. A harvest is billed once as a New topic;
polling is free. jobId None means the corpus was already sufficient
and nothing was started or charged.
"""
r = requests.post(f"{BASE}/corpus", headers=HEAD, timeout=120, json={
"topic": topic, "langs": list(langs),
"minCoverage": min_coverage, "async": True,
})
r.raise_for_status()
state = r.json()
if not state.get("jobId"):
return state.get("coverage", 0) # already covered, no charge
deadline = time.time() + limit_s
while time.time() < deadline:
time.sleep(25) # 20-30 s is the sane cadence
try:
s = requests.post(f"{BASE}/corpus/status", headers=HEAD, timeout=60,
json={"jobId": state["jobId"], "topic": topic,
"langs": list(langs)})
s.raise_for_status()
except requests.RequestException:
continue # transient: keep polling
state2 = s.json()
if state2.get("ready"): # done OR failed — both final
return state2.get("coverage", 0)
return state.get("coverage", 0) # our deadline, not an error
def chapter_sources(topic, chapter, used=(), limit=12):
"""Sources for one chapter. Pass previously used URLs/DOIs in 'used'
so the next chapter gets NEW material instead of the same top hits."""
r = requests.post(f"{BASE}/chapter-sources", headers=HEAD, timeout=600, json={
"topic": topic, "chapter": chapter, "langs": ["pl", "en"],
"limit": limit, "exclude": list(used),
})
r.raise_for_status()
return r.json()
ensure_corpus("persuasion in advertising")
data = chapter_sources("persuasion in advertising", "Chapter 1")
print(data["coverage"], data["coverageWprost"], data["billing"])Windows i PowerShell: wyślij ciało jako UTF-8
PowerShell 5.1 przy Invoke-RestMethod -Body <napis> koduje ciało domyślną stroną kodową systemu, nie UTF-8. Znaki spoza ASCII — polskie ogonki, niemieckie umlauty, cyrylica — giną, zanim żądanie wyjdzie z komputera. Temat dociera do nas zniekształcony, wyszukiwanie działa na złym tekście, a nic tego nie sygnalizuje. Przekaż BAJTY zamiast napisu:
$body = @{ topic = "calibration of pressure transducers" } | ConvertTo-Json -Compress
$bytes = [System.Text.Encoding]::UTF8.GetBytes($body) # <-- required
Invoke-RestMethod -Uri "$BASE/corpus" -Method Post -Headers $h -Body $bytesPowerShell 7 i curl.exe robią to poprawnie same. Problem dotyczy wyłącznie wbudowanego PowerShella 5.1 z Windowsa.
Kolejne rozdziały: parametr exclude
Bez exclude drugi rozdział dostanie te same najlepsze źródła, co pierwszy — bo obiektywnie są najlepsze. Przekaż URL-e albo DOI już zużytych pozycji, a pokrycie zostanie policzone po tym, czym da się jeszcze napisać KOLEJNY rozdział, nie po całym temacie.
curl -s https://cytado.com/api/ext/chapter-sources \
-H "Authorization: Bearer $CYTADO_KEY" \
-H "Content-Type: application/json" \
-d '{
"topic": "persuasion in advertising",
"chapter": "Chapter 2: emotional appeals",
"langs": ["en"],
"limit": 12,
"exclude": ["https://doi.org/10.1234/abc", "10.5678/def"]
}'
# {
# "sources": [ ... ],
# "coverage": 9, # documents still available (excluded ones removed)
# "coverageWprost": 5, # of which are squarely ON the topic, not adjacent
# "billing": { "citations_charged": 9, "already_paid": 3 }
# }coverageWprost jest istotniejsze od coverage. Rozjazd „coverage 6, coverageWprost 0” znaczy, że korpus ma materiał z tej DZIEDZINY, ale nic o tej konkretnej rzeczy — i to jest moment, w którym warto zlecić harvest zamiast pisać z tego, co jest.
Sprawdź koszt, zanim zlecisz
To samo wywołanie /corpus może odczytać gotowy korpus (grosze) albo zbudować go od zera (nowy temat, kilkanaście minut). O tym, co się stanie, decyduje stan korpusu, którego z zewnątrz nie widzisz. Dodaj dryRun, a policzymy dokładnie tę samą bramkę i powiemy, co BY się stało — nie tworząc zadania i nie naliczając nic.
curl -s https://cytado.com/api/ext/corpus \
-H "Authorization: Bearer $CYTADO_KEY" \
-H "Content-Type: application/json" \
-d '{"topic":"persuasion in advertising","langs":["en"],
"minCoverage":6,"dryRun":true}'
# {
# "dryRun": true,
# "coverage": 9,
# "coverageWprost": 5,
# "corpusReady": true,
# "wouldHarvest": false, # a real call would NOT start a harvest
# "wouldBill": null # ...and would bill nothing
# }
#
# When the corpus is thin:
# "wouldHarvest": true,
# "wouldBill": { "kind": "topic", "count": 1 }Podgląd jest bezpłatny i nieograniczony. Używaj go w pętli decyzyjnej: najpierw dryRun, potem — jeśli koszt Ci odpowiada — to samo wywołanie bez tego pola.
Klucz testowy a nowe tematy
Klucz cyt_test_ może zbudować 3 NOWE TEMATY miesięcznie — tyle, żeby sprawdzić tryb asynchroniczny, odpytywanie i webhooki, czyli najtrudniejszą część integracji. Po wyczerpaniu tego przydziału odczyt korpusu i weryfikacja działa dalej bez zmian; zablokowane jest wyłącznie budowanie korpusu dla nowego tematu, bo to najdroższa operacja w całym API. Do ruchu produkcyjnego użyj klucza cyt_live_.
Bez Postmana: konsola w panelu
Wszystkie trzy endpointy — /corpus, /chapter-sources i /find-source — możesz wywołać z panelu (Konto → API → Konsola), bez wklejania klucza i bez konfigurowania czegokolwiek. Każde pole ma opis mówiący, co się stanie, a nie jakiego jest typu. Pod formularzem stoi to samo wywołanie jako curl, gotowe do skopiowania do kodu.
Koszt pokazujemy PRZED wysłaniem, ale w każdym endpoincie inaczej — bo w każdym bierze się skądinąd. /corpus ma dwustopniowy przycisk: pierwszy sprawdza koszt i nie może nic naliczyć, drugi (ze wskazaną ceną) odsłania się dopiero pod odpowiedzią. /chapter-sources i /find-source podają sufit od razu, bo ogranicza go liczba, którą sam wpisujesz: liczba źródeł albo liczba tez. Uwaga na jedno pole: harvestOnMiss w /find-source potrafi jednym kliknięciem zbudować do 24 nowych tematów, dlatego domyślnie jest wyłączone.
maxNewTopics — ogranicz własny rachunek
W /find-source pole harvestOnMiss uruchamia dociąganie źródeł z sieci dla tez, których nie da się rozstrzygnąć z gotowego korpusu. Każde takie dociągnięcie to NOWY TEMAT, czyli pozycja ~25× droższa od cytowania. Bez tego pola budujemy najwyżej 6 tematów na żądanie; twardy sufit serwera to 24. Podaj maxNewTopics, żeby ustawić własną granicę — także wyższą, aż do 24. Wartość spoza zakresu przycinamy do 0-24, zamiast odrzucać żądanie.
{ "items": [...], "langs": ["en"],
"harvestOnMiss": true,
"maxNewTopics": 3 } # at most 3 new topics in this requestCzym konsola NIE jest
Konsola wchodzi w ten sam kod co żądanie z zewnątrz — te same limity, ten sam sufit klucza testowego, to samo naliczanie i ten sam wpis w logach. Wywołanie z panelu kosztuje dokładnie tyle samo co wywołanie z Twojego serwera; nie jest to tryb darmowy. Pomija za to warstwę HTTP, więc nie sprawdzi błędu w nagłówkach ani w serializacji ciała żądania — jeśli debugujesz problem transportowy, użyj curl-a spod formularza, bo tylko on przechodzi przez pełny stos.
Serwer MCP — dla agentów AI
Całe API jest też dostępne jako serwer MCP (Model Context Protocol) — agent podłącza się raz i dostaje narzędzia z opisami, które mówią mu, kiedy je wołać i co kosztuje. Ten sam klucz co REST, to samo naliczanie, to samo zużycie w panelu. Transport: Streamable HTTP, bez sesji.
# Claude Code
claude mcp add cytado --transport http https://cytado.com/api/mcp \
--header "Authorization: Bearer $CYTADO_KEY"
# Claude API (MCP connector)
"mcp_servers": [{ "type": "url", "url": "https://cytado.com/api/mcp",
"name": "cytado", "authorization_token": "$CYTADO_KEY" }]
# stdio-only clients
npx -y mcp-remote https://cytado.com/api/mcp \
--header "Authorization: Bearer $CYTADO_KEY"ground_citation— tezy → zweryfikowane źródło z drukowaną stroną i cytatem, albo uczciwe found:false; naliczane jak /find-sourceget_chapter_sources— temat rozdziału → źródła z fragmentami znaczonymi stronami; naliczane jak /chapter-sourcescheck_corpus— darmowy podgląd pokrycia; z provision:true startuje harvest w tle i zwraca jobIdcheck_harvest_status— stan harvestu (darmowe); agent sam odpytuje co 30-60 s
Narzędzia MCP nigdy nie harvestują synchronicznie — ground_citation odpowiada z korpusu w sekundach, a dociąganie nowych tematów zawsze idzie w tle przez jobId. Semantyka rozliczeń stoi wprost w opisach narzędzi, więc agent wie, co kosztuje, zanim zawoła.
Podłączenie w oknie czatu (claude.ai)
cytado możesz dodać jako własny konektor (custom connector) bezpośrednio w claude.ai — wtedy narzędzia są dostępne w zwykłej rozmowie, bez terminala i bez kodu:
- W claude.ai wejdź w Ustawienia → Konektory (na planach Team/Enterprise robi to właściciel organizacji w ustawieniach organizacji) i kliknij „Dodaj własny konektor” (Add custom connector).
- Jako adres serwera podaj:
https://cytado.com/api/mcp - W sekcji nagłówków żądania (Request headers) dodaj nagłówek Authorization o wartości „Bearer ” + Twój klucz API z panelu (Konto → API). Claude zapisuje wartość bezpiecznie i wysyła ją przy każdym żądaniu.
- W rozmowie kliknij „+” → Konektory i włącz cytado. Od tej chwili możesz poprosić: „napisz akapit o X i ugruntuj twierdzenia w źródłach przez cytado” — agent sam zawoła narzędzia.
Uwaga: pole nagłówków żądania w claude.ai jest funkcją beta udostępnianą stopniowo — jeśli go nie widzisz, Twoje konto obsługuje na razie wyłącznie konektory OAuth. Logowanie OAuth dla cytado jest w przygotowaniu; do tego czasu użyj Claude Code albo konektora MCP w Claude API (przykłady wyżej). W ChatGPT konektor dodasz analogicznie w trybie deweloperskim (Settings → Connectors), podając ten sam adres i nagłówek.
Powiadomienia HTTP (webhooki)
Zamiast odpytywać nas o stan pakietu, możesz podać adres, na który wyślemy podpisany POST: przy 80% i 100% pakietu, przy zamknięciu okresu rozliczeniowego, po wystawieniu faktury oraz po zakończeniu dociągania korpusu. Adres i sekret ustawiasz w panelu.
plan.80,plan.100— wykorzystano 80% i 100% puli Pakietuperiod.closed— zamknięcie okresu rozliczeniowegoinvoice.issued— faktura gotowajob.completed,job.failed— koniec dociągania korpusu; dzięki nim nie musisz odpytywać w pętli
POST https://your-app.example/cytado-hook
X-Cytado-Event: job.completed
X-Cytado-Timestamp: 1785858025
X-Cytado-Signature: 9f2c... # HMAC-SHA256 over "<timestamp>.<raw body>"
{
"event": "job.completed",
"at": 1785858025,
"data": {
"jobId": 83,
"topic": "persuasion in advertising",
"coverage": 15,
"coverageWprost": 10
}
}
# Verify in Node — sign the RAW body, never re-serialised JSON:
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = createHmac("sha256", SECRET)
.update(`${req.headers["x-cytado-timestamp"]}.${rawBody}`)
.digest("hex");
const provided = req.headers["x-cytado-signature"];
const ok =
provided.length === expected.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(provided));Podpisuj RAW BODY, nie ponownie zserializowany JSON — przestawienie kluczy zmieni podpis. Odrzucaj żądania starsze niż kilka minut. Wysyłamy jedną próbę na zdarzenie: webhook jest wygodą, a nie źródłem prawdy — tym pozostaje panel i faktura.
Zmiany w API
Nie wersjonujemy adresów. Zamiast tego trzymamy się jednej obietnicy: pola nigdy nie znikają ani nie zmieniają znaczenia, a nowe mogą dochodzić w każdej chwili. Parsuj odpowiedzi tolerancyjnie — nieznane pole zignoruj, nie traktuj go jako błędu.
- 2026-08-05 — chapter-sources: pole chapter naprawdę wpływa na dobór (wcześniej było ignorowane), nowy parametr minRelevance (5-10), naliczanie WYŁĄCZNIE źródeł wprost (trafność ≥ 7; tło gratis — patrz billing.free_background). Oceny trafności są zapamiętywane na dobę, więc identyczne wywołanie zwraca ten sam zestaw i retry nie kosztuje drugi raz. Nazwy pól odpowiedzi przeszły na angielskie (billing, ready, url+doi, event/at/data); find-source przyjmuje exclude i zwraca claim. Nieznane pole w żądaniu to teraz 400 unknown-field z podpowiedzią, zamiast cichego zignorowania.
- 2026-08-04 — doszło coverageWprost (/corpus, /corpus/status, /chapter-sources) oraz blok billing w /chapter-sources (wtedy pod nazwą rozliczenie).
- 2026-08-04 — naliczanie objęło /chapter-sources i /corpus; za ten sam dokument w tym samym temacie płacisz raz.
Co można bezpiecznie ponowić
- /corpus/status
- Bez ograniczeń — nie dotykają korpusu i nie są rozliczane.
- /chapter-sources
- Bezpiecznie. Dokumenty, za które już zapłaciłeś w tym temacie, nie naliczą się ponownie — zobaczysz je w polu billing.already_paid.
- /corpus (async: true)
- Bezpiecznie w oknie 30 minut: powtórzone zlecenie dla tego samego tematu zwróci TO SAMO jobId i nie naliczy drugiego tematu.
- /find-source
- Ponowienie po timeoucie może naliczyć rozstrzygnięte cytowania drugi raz — jeśli ponawiasz automatycznie, zapisuj u siebie, co już wróciło.
Zacznij od klucza testowego
100 cytowań miesięcznie, bez rozliczenia i bez karty. Klucz wydasz sobie sam w panelu, zaraz po założeniu konta.
Załóż konto