{"openapi":"3.1.0","info":{"title":"sprdnja.lol API","version":"1.0.0","description":"Read-only access to every Informer article sprdnja.lol collected, with AI labels about the protest movement in Serbia and personal data.\n\nNo key needed. Limit: 120 requests per minute per IP. For everything at once, download the ZIP: https://sprdnja.lol/api/public/dataset\n\nCaveats:\n- Only one outlet: Informer. Not a sample of Serbian media.\n- Every label comes from an AI model and can be wrong. Check the original article before making a public claim; ai_note explains each decision.\n- A personal-data flag means the detail appears in the article text. The model cannot tell whether it was already public, so it does not mean Informer revealed it first.\n- Collection is automatic from 2026-03-14. Earlier articles were added by hand, so that period is incomplete.\n- Days when our collector was down have no articles (see collection_gaps). Treat them as missing, not zero.\n- Analysis reads the headline and the first 8,000 characters of the body.\n- Informer sometimes publishes one article under two URLs with the same number in the path; deduplicate on that number.\n\nThe data contains names and photos of people Informer targeted. Use it to show patterns, not to expose them again. Do not republish private people's personal data from this dataset.","contact":{"url":"https://sprdnja.lol/podaci"},"license":{"name":"Labels and docs: reuse with attribution to sprdnja.lol. Article texts belong to Informer.","url":"https://sprdnja.lol/podaci#koriscenje"}},"servers":[{"url":"https://sprdnja.lol"}],"security":[],"externalDocs":{"url":"https://sprdnja.lol/podaci","description":"Data documentation (Serbian)"},"paths":{"/api/v1":{"get":{"summary":"About the dataset","description":"Counts, method, caveats, collection gaps, field dictionary and links.","operationId":"about","responses":{"200":{"description":"Dataset description","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/articles":{"get":{"summary":"Search and filter articles","description":"Newest first by default. Follow next_cursor to page through results; it is null on the last page.","operationId":"listArticles","parameters":[{"name":"q","in":"query","required":false,"description":"Full-text search in Serbian. Latin or Cyrillic, with or without diacritics; word forms match (blokader finds blokaderi, blokaderima).","schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"from","in":"query","required":false,"description":"First publication day, inclusive (UTC), YYYY-MM-DD.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"to","in":"query","required":false,"description":"Last publication day, inclusive (UTC), YYYY-MM-DD.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"about_protests","in":"query","required":false,"description":"Only articles the AI labelled as about (true) or not about (false) the protest movement in Serbia.","schema":{"type":"boolean"}},{"name":"has_personal_data","in":"query","required":false,"description":"Only protest articles where the AI found (true) or did not find (false) personal data.","schema":{"type":"boolean"}},{"name":"found","in":"query","required":false,"description":"Require every listed flag: name, address, photo_or_video, phone, student (person_is_student), journalist (person_is_journalist). Comma-separated.","schema":{"maxItems":6,"type":"array","items":{"type":"string","enum":["name","address","photo_or_video","phone","student","journalist"]}},"style":"form","explode":false},{"name":"status","in":"query","required":false,"description":"Analysis status.","schema":{"type":"string","enum":["done","failed","pending"]}},{"name":"sort","in":"query","required":false,"description":"Default: relevance when q is set, otherwise newest.","schema":{"type":"string","enum":["newest","oldest","relevance"]}},{"name":"limit","in":"query","required":false,"description":"Results per page, 1-100. Default 20.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"next_cursor from the previous page.","schema":{"type":"string","maxLength":200}},{"name":"include_text","in":"query","required":false,"description":"Include the full article body in `text`. Default false.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"A page of articles","content":{"application/json":{"schema":{"type":"object","required":["data","total","next_cursor","sort"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Article"}},"total":{"type":"integer","description":"Matches across all pages."},"next_cursor":{"type":["string","null"]},"sort":{"type":"string","enum":["newest","oldest","relevance"]}}}}}},"400":{"description":"Invalid parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/articles/{id}":{"get":{"summary":"One article","operationId":"getArticle","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"include_text","in":"query","required":false,"description":"Include the full body. Default true.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The article","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Article"}}}},"404":{"description":"No such article","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/stats":{"get":{"summary":"Counts over time","description":"Every bucket in the range is listed, including empty ones. Check collection_gaps and complete before reading a low count as a real drop.","operationId":"getStats","parameters":[{"name":"from","in":"query","required":false,"description":"First day, inclusive (UTC). Default: 30 days before `to`.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"to","in":"query","required":false,"description":"Last day, inclusive (UTC). Default: today.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"interval","in":"query","required":false,"description":"Bucket size. Default: day up to 92 days, week beyond.","schema":{"type":"string","enum":["day","week","month"]}}],"responses":{"200":{"description":"Series and totals","content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"interval":{"type":"string","enum":["day","week","month"]},"totals":{"type":"object","properties":{"articles":{"type":"integer"},"about_protests":{"type":"integer"},"has_personal_data":{"type":"integer"},"found_name":{"type":"integer"},"found_address":{"type":"integer"},"found_photo_or_video":{"type":"integer"},"found_phone":{"type":"integer"},"person_is_student":{"type":"integer"},"person_is_journalist":{"type":"integer"},"analysis_failed":{"type":"integer"},"analysis_pending":{"type":"integer"}}},"series":{"type":"array","items":{"type":"object","properties":{"period":{"type":"string","format":"date","description":"First day of the bucket."},"complete":{"type":"boolean","description":"False when the bucket covers only part of its period."},"articles":{"type":"integer"},"about_protests":{"type":"integer"},"has_personal_data":{"type":"integer"},"found_name":{"type":"integer"},"found_address":{"type":"integer"},"found_photo_or_video":{"type":"integer"},"found_phone":{"type":"integer"},"person_is_student":{"type":"integer"},"person_is_journalist":{"type":"integer"},"analysis_failed":{"type":"integer"},"analysis_pending":{"type":"integer"}}}},"collection_gaps":{"type":"array","description":"Days the collector was down. Counts there are missing, not zero.","items":{"type":"object","properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"}}}},"notes":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Invalid parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/methodology":{"get":{"summary":"Model, prompt and output schema","operationId":"getMethodology","responses":{"200":{"description":"Exact model instructions","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Article":{"type":"object","description":"One Informer article. Analysis fields are null unless analysis_status is done.","properties":{"id":{"type":["string","null"],"description":"Our article ID."},"url":{"type":["string","null"],"description":"Link to the original article."},"media":{"type":["string","null"],"description":"Outlet. Always Informer."},"title":{"type":["string","null"],"description":"Headline."},"lead":{"type":["string","null"],"description":"Short RSS description (usually the first sentence)."},"author":{"type":["string","null"],"description":"Author, if the RSS lists one (rare)."},"categories":{"type":"array","items":{"type":"string"},"description":"RSS sections. Joined with \"; \" in the CSV, an array in the JSONL."},"image_url":{"type":["string","null"],"description":"Main article image."},"published_at":{"type":["string","null"],"format":"date-time","description":"When the article was published (UTC)."},"collected_at":{"type":["string","null"],"format":"date-time","description":"When we collected it (UTC)."},"text_chars":{"type":["integer","null"],"description":"Body length in characters. Empty if we could not fetch the body."},"analysis_status":{"type":["string","null"],"description":"done = analysed, failed = the AI call failed, pending = not analysed yet."},"analyzed_at":{"type":["string","null"],"format":"date-time","description":"When the AI analysis ran (UTC)."},"about_protests":{"type":["boolean","null"],"description":"Whether the article is about the protest movement in Serbia."},"has_personal_data":{"type":["boolean","null"],"description":"about_protests and at least one found_* or person_is_* flag. Same as \"Sa ličnim podacima\" on the site."},"found_name":{"type":["boolean","null"],"description":"Names a private person from the movement (not public officials)."},"found_address":{"type":["boolean","null"],"description":"Gives an exact address (street and number, flat, house). A city or faculty does not count."},"found_photo_or_video":{"type":["boolean","null"],"description":"Contains or describes a photo, video, screenshot or document that identifies the person."},"found_phone":{"type":["boolean","null"],"description":"Gives a phone number."},"person_is_student":{"type":["boolean","null"],"description":"At least one person whose data was found is explicitly identified as a student."},"person_is_journalist":{"type":["boolean","null"],"description":"At least one person whose data was found is a journalist at an independent or critical outlet."},"ai_note":{"type":["string","null"],"description":"The AI's one or two sentence reasoning. Often names people."},"text":{"type":["string","null"],"description":"Full article body as scraped. Only when requested (include_text) and in articles.jsonl."}}},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"status":{"type":"integer"},"code":{"type":"string","enum":["invalid_parameter","not_found","rate_limited","server_error"]},"message":{"type":"string"},"docs":{"type":"string"}}}}}}}}