API documentation
Four endpoints, one auth header, billing per resolved citation. Everything you need to integrate — every example here was run against production.
Authentication
Bearer token in the header. An unknown key, a disabled key and a missing header all return the same 401 — we do not hint which one failed.
Authorization: Bearer cyt_live_…Two kinds of keys
A cyt_test_ key uses the same corpus and returns the same answers but is never billed — 300 citations a month for integration and error handling. A cyt_live_ key serves production traffic and counts towards your plan. The key is shown exactly once when issued; we store only its hash.
What you pay for
The billing unit is a RESOLVED CITATION — one sentence matched to a source with a page number. A miss (found: false) is free. Formatting never touches the corpus and is free on every plan. A NEW TOPIC is metered separately: building the corpus for a subject we do not have yet takes minutes and costs two orders of magnitude more, which is why you trigger it deliberately with harvestOnMiss.
| Plan | Price / mo | Citations | New topics | Overage |
|---|---|---|---|---|
| Test | free | 100 | 3 | — |
| Start | $49 | 1,500 | 20 | $0.04 / $1.00 |
| Pro | $199 | 7,000 | 100 | $0.04 / $1.00 |
| Scale | $799 | 30,000 | 500 | $0.04 / $1.00 |
Endpoints
GET /usage
Plan state and usage for the current month. Costs nothing and consumes nothing — the cheapest way to check that a key works.
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
A sentence in, a source with a page number out. The only billable call — and only when it finds something. Up to 60 items per request; the excess is dropped EXPLICITLY. The hint field is the topic of the work: without it we ask “does this source support this sentence”, with it “could someone writing THIS paper cite it”.
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": "Prokrastynacji towarzyszy poczucie winy i dyskomfort psychiczny.",
"hint": "prokrastynacja u studentow"
}
],
"langs": [
"pl"
],
"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": "Prokrastynacji towarzyszy poczucie winy i dyskomfort psychiczny.",
"hint": "prokrastynacja u studentow"
}
],
"langs": [
"pl"
],
"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": "Prokrastynacji towarzyszy poczucie winy i dyskomfort psychiczny.",
"hint": "prokrastynacja u studentow"
}
],
"langs": [
"pl"
],
"harvestOnMiss": false
}),
});
if (!r.ok) throw new Error(`cytado ${r.status}: ${await r.text()}`);
console.log(await r.json());POST /format
Metadata in, a formatted footnote and bibliography entry out. Styles: apa7 (alias apa), mla, chicago, pl-footnote. Use include to pick the forms: footnote, entry, bibtex, ris. Never touches the corpus, so it is free on every plan.
curl -s -X POST https://cytado.com/api/ext/format \
-H "Authorization: Bearer $CYTADO_KEY" \
-H "Content-Type: application/json" \
-d '{
"style": "apa",
"include": [
"footnote",
"entry",
"bibtex",
"ris"
],
"sources": [
{
"title": "Regulacja nastroju a prokrastynacja",
"authors": "Pisarska, A.",
"year": 2020,
"page": 208
}
]
}'import os, requests
r = requests.post(
"https://cytado.com/api/ext/format",
headers={"Authorization": f"Bearer {os.environ['CYTADO_KEY']}"},
json={
"style": "apa",
"include": [
"footnote",
"entry",
"bibtex",
"ris"
],
"sources": [
{
"title": "Regulacja nastroju a prokrastynacja",
"authors": "Pisarska, A.",
"year": 2020,
"page": 208
}
]
},
timeout=600,
)
r.raise_for_status()
print(r.json())const r = await fetch("https://cytado.com/api/ext/format", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CYTADO_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"style": "apa",
"include": [
"footnote",
"entry",
"bibtex",
"ris"
],
"sources": [
{
"title": "Regulacja nastroju a prokrastynacja",
"authors": "Pisarska, A.",
"year": 2020,
"page": 208
}
]
}),
});
if (!r.ok) throw new Error(`cytado ${r.status}: ${await r.text()}`);
console.log(await r.json());POST /chapter-sources
A chapter topic in, a set of citable sources out, ranked by topic coverage. The exclude field lists sources already used — coverage is then computed over what remains, so consecutive chapters do not keep receiving the same material.
curl -s -X POST https://cytado.com/api/ext/chapter-sources \
-H "Authorization: Bearer $CYTADO_KEY" \
-H "Content-Type: application/json" \
-d '{
"topic": "czas ekranowy dzieci w wieku przedszkolnym",
"chapter": "Wprowadzenie",
"exclude": []
}'import os, requests
r = requests.post(
"https://cytado.com/api/ext/chapter-sources",
headers={"Authorization": f"Bearer {os.environ['CYTADO_KEY']}"},
json={
"topic": "czas ekranowy dzieci w wieku przedszkolnym",
"chapter": "Wprowadzenie",
"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": "czas ekranowy dzieci w wieku przedszkolnym",
"chapter": "Wprowadzenie",
"exclude": []
}),
});
if (!r.ok) throw new Error(`cytado ${r.status}: ${await r.text()}`);
console.log(await r.json());Response fields worth understanding
- found
- false means the corpus has nothing to support this claim. We do not return the nearest match instead, and we do not charge.
- page_from / page_to
- The PRINTED page number — the one a supervisor checking your footnote will land on, not the sheet number inside the PDF.
- page_from_label
- Filled only for unusual numbering (e.g. “E136”). An empty label with a filled page_from means “the printed number is simply 152”, not “no pagination”.
- relevance
- 9–10: directly about this claim and this group. 7–8: the same phenomenon in a related setting — different profession, age or country; citable, but mark the context.
- url
- A link to the original — a DOI or a direct address. EVERY returned source has one; for more than half the corpus it opens the full text, because those are open-access publications.
- snippet
- A short excerpt under fair use. We never return the full text of sources we may not republish — we return the link under which the reader can read them at the publisher.
- rozliczenie
- Exactly what this request was charged for: number of citations and number of new topics.
Limits and errors
- 401 unauthorized
- Unknown, disabled or missing key.
- 400 invalid-json
- The request body is not valid JSON.
- 400 missing-items
- The required `items` array is missing.
- 422 invalid_request
- Syntactically valid but not acceptable — e.g. an unknown citation style.
- 429 rate_limited
- Limit exceeded. Retry-After says how many seconds to wait. The per-minute guard means “slow down”, the daily limit means “done for today” — the message distinguishes them.
- 402
- Plan exhausted on an operation that requires paid access.
Start with a test key
300 citations a month, never billed, no card. You issue the key yourself in the panel, right after signing up.
Create an account