Uzun Qulaq Partner API

API Reference

A read-mostly REST API for syndicating Central Asia's news into your surfaces — OEM home feeds, news widgets and companion apps. A personalized, multi-signal-ranked feed plus article detail, search, and the catalog endpoints that drive language, topic and category pickers — filterable by language, country and topic.

Base URL
https://api.uzunqulaq.com
Open Swagger / OpenAPI
REST · JSON OpenAPI 3 Bearer auth Page pagination Consent-gated content

All endpoints sit under the /api/v2 prefix on the base URL above. The OpenAPI schema is generated live by the backend and always reflects production — the interactive Swagger explorer (Redoc) above is the source of truth for request/response shapes.


Authentication

Every endpoint requires a per-partner API key sent as a bearer token in the Authorization header on every request. Keys are opaque random tokens (not JWTs) — the server stores only a SHA-256 hash. Rotate from your partner account at any time; the previous key stops working immediately on rotation.

Authorization header
Authorization: Bearer 8f2c4e7a93b1…a91d
Keep keys secret. Never embed a partner key in client-side code or a mobile app — it identifies your whole platform. Proxy requests through your own backend, and pass each end-user's stable id in the user parameter so ranking and engagement stay per-reader. Need a key? Request one →

Quickstart

Fetch a personalized feed for one reader, in Kazakh and Russian. The user is any stable id you assign per device. Pick your language below.

# Personalized feed — Kazakh + Russian editions
curl "https://api.uzunqulaq.com/api/v2/feed?user=u_8f3c&editions=kk-kz,ru-kz&page_size=20" \
  -H "Authorization: Bearer $UQ_API_KEY"
const res = await fetch(
  "https://api.uzunqulaq.com/api/v2/feed?user=u_8f3c&editions=kk-kz,ru-kz&page_size=20",
  { headers: { Authorization: `Bearer ${process.env.UQ_API_KEY}` } }
);
const { results } = await res.json();
console.log(results[0].title);
import requests, os

r = requests.get(
    "https://api.uzunqulaq.com/api/v2/feed",
    params={"user": "u_8f3c", "editions": "kk-kz,ru-kz", "page_size": 20},
    headers={"Authorization": f"Bearer {os.environ['UQ_API_KEY']}"},
)
print(r.json()["results"][0]["title"])

Pagination

List endpoints (feed, search) are page-paginated. Pass a 1-based page and a page_size (clamped to 1–100 and your platform's max_page_size). The response echoes page, page_size and count; a page shorter than page_size means you've reached the end.

Response envelope
{
  "page": 1,
  "page_size": 20,
  "count": 20,
  "results": [ /* … */ ]
}

Personalized feed

A personalized, dedup-aware, multi-signal–ranked news feed for one reader. Each card carries a display_rules consent block and a signed reader_url. Use editions for an ordered multi-language feed, or a single lang + country.

GET/api/v2/feed
Query parameters
userstringrequired · Opaque reader id you assign (a per-device UUID is fine). Personalizes ranking and anchors engagement events.
editionsstringoptional · Ordered CSV of edition codes (e.g. kk-kz,ru-kz, max 10). Replaces lang+country; index 0 is the primary edition.
langstringoptional · BCP-47 language code, e.g. kk.
countrystringoptional · ISO 3166-1 alpha-2 country code, e.g. kz.
categorystringoptional · Restrict the feed to a single category code.
include_postsbooleanoptional · Interleave followed-person social posts (companion-app feature; off by default).
pageintegeroptional · 1-based page number (default 1).
page_sizeintegeroptional · Items per page (your platform default; clamped 1–100 and your max_page_size).
Example request
cURL
curl "https://api.uzunqulaq.com/api/v2/feed?user=u_8f3c&editions=kk-kz,ru-kz&page=1&page_size=20" \
  -H "Authorization: Bearer $UQ_API_KEY"
Response 200 OK
application/json
{
  "page": 1,
  "page_size": 20,
  "count": 20,
  "results": [
    {
      "id": 20413,
      "title": "A new rail corridor promises to redraw the map",
      "short_content": "The line would cut freight times between…",
      "category": { "code": "world", "name": "World", "name_kk": "Әлем", "name_ru": "Мир" },
      "source": { "domain": "kazinform.kz", "code": "kazinform" },
      "reading_time": 6,
      "image_url": "https://images.uzunqulaq.com/20413/desktop.jpg",
      "reader_url": "https://api.uzunqulaq.com/read/20413?sig=…",
      "published_at": "2026-06-14T09:30:00Z",
      "display_rules": { "title_ok": true, "excerpt_ok": true, "image_ok": true, "full_text_ok": false, "link_required": true, "attribution_label": "Kazinform" }
    }
  ],
  "scoring": { "engine": "multi-signal-v2", "signals": [ "recency", "affinity", "popularity" ] }
}
Honor display_rules. Each card states what you may show (title, excerpt, image, full text) and whether a backlink and attribution are required. Content you aren't licensed to show is already blanked server-side.

Article detail

Full detail for one article — body, images and up to 5 similar articles. The display_rules block is included and any content you aren't licensed to show is already blanked server-side.

GET/api/v2/article/{article_id}
Path parameters
article_idintegerrequired · Article id (from a feed or search id).
Query parameters
userstringoptional · Opaque reader id. When supplied, the call also records the relevant engagement event for this reader.
Response 200 OK
application/json
{
  "id": 20413,
  "title": "A new rail corridor promises to redraw the map",
  "full_content": "<p>The line would cut freight times…</p>",
  "short_content": "The line would cut freight times between…",
  "reading_time": 6,
  "published_at": "2026-06-14T09:30:00Z",
  "source": { "domain": "kazinform.kz", "code": "kazinform" },
  "category": { "code": "world", "name": "World", "name_kk": "Әлем", "name_ru": "Мир" },
  "images": { "desktop": "…/desktop.jpg", "mobile": "…/mobile.jpg", "thumbnail": "…/thumb.jpg" },
  "similar_articles": [ { "id": 20390, "title": "Freight volumes hit a record", "similarity": 0.82 } ],
  "display_rules": { "title_ok": true, "excerpt_ok": true, "image_ok": true, "full_text_ok": false, "link_required": true, "attribution_required": true, "attribution_label": "Kazinform" }
}

Search autocomplete

Fast prefix suggestions for search-as-you-type (up to 5). Optionally scope to an edition with lang + country.

GET/api/v2/search/autocomplete
Query parameters
qstringrequired · Prefix string the reader has typed so far.
langstringoptional · BCP-47 language code, e.g. kk.
countrystringoptional · ISO 3166-1 alpha-2 country code, e.g. kz.
Response 200 OK
application/json
{
  "query": "aral",
  "suggestions": [ "aral sea", "aral sea restoration", "aralsk" ]
}

Editions

Every edition (language + country) in the catalog — the vocabulary for language pickers and the editions / lang+country parameters elsewhere. Add ?platform=true to scope to the editions your platform may serve.

GET/api/v2/editions
Query parameters
platformbooleanoptional · When true, return only the editions your platform may serve (falls back to all if none are set).
Response 200 OK
application/json
{
  "count": 8,
  "results": [
    { "code": "kk-kz", "name": "Kazakh · Kazakhstan", "language_code": "kk", "language_name": "Қазақша", "country_code": "kz", "country_name": "Kazakhstan" },
    { "code": "ru-kz", "name": "Russian · Kazakhstan", "language_code": "ru", "language_name": "Русский", "country_code": "kz", "country_name": "Kazakhstan" }
  ]
}

Topics

Topic catalog for a given edition, ordered by recent article volume (most active first) so it doubles as an importance signal. Drives topic pickers and section navigation. lang and country are required.

GET/api/v2/topics
Query parameters
countrystringrequired · ISO 3166-1 alpha-2 country code, e.g. kz.
langstringrequired · BCP-47 language code, e.g. kk.
limitintegeroptional · Max topics to return (default 100; clamped 1–200).
qstringoptional · Case-insensitive filter on topic name + code.
Response 200 OK
application/json
{
  "count": 12,
  "edition": "kk-kz",
  "results": [
    { "id": 7, "name": "Business", "code": "business", "name_kk": "Бизнес", "name_ru": "Бизнес", "recent_article_count": 214 },
    { "id": 3, "name": "Technology", "code": "tech", "name_kk": "Технология", "name_ru": "Технологии", "recent_article_count": 156 }
  ]
}

Category weighting

Your platform's per-category ranking config — weights and how many top items to surface — ordered by weight. Use it to build sections and navigation. Optionally scope to one edition with lang + country.

GET/api/v2/platform/categories
Query parameters
langstringoptional · BCP-47 language code, e.g. kk.
countrystringoptional · ISO 3166-1 alpha-2 country code, e.g. kz.
Response 200 OK
application/json
{
  "platform": "uzunqulaq",
  "count": 6,
  "categories": [
    { "category_code": "world", "category_name": "World", "category_name_kk": "Әлем", "category_name_ru": "Мир", "edition_code": "kk-kz", "weight": 100, "show_ntop_news": 5 },
    { "category_code": "business", "category_name": "Business", "category_name_kk": "Бизнес", "category_name_ru": "Бизнес", "edition_code": "kk-kz", "weight": 80, "show_ntop_news": 3 }
  ]
}

Platform info

The configuration for the platform identified by your bearer token: display name, type, page-size limits, hourly rate limit, and the editions you may serve (["all"] if unrestricted).

GET/api/v2/platform
Response 200 OK
application/json
{
  "name": "Acme Browser",
  "code": "acme",
  "type": "oem",
  "website": "https://acme.example",
  "default_page_size": 20,
  "max_page_size": 50,
  "rate_limit_per_hour": 10000,
  "editions": [ "kk-kz", "ru-kz" ]
}

Track an engagement event

Report a reader engagement event (impression, click, or read progress) so ranking improves for that reader. Optional but recommended — the event is keyed to the user you pass.

POST/api/v2/track
Request body application/json
userstringrequired · The opaque reader id the event belongs to.
article_idintegerrequired · The article the event is about.
eventstringrequired · One of impression, click, read_start, read_progress, read_complete.
time_spentintegeroptional · Seconds spent reading.
scroll_depthintegeroptional · Percent of the article scrolled (0–100).
read_completedbooleanoptional · Whether the reader finished the article.
Example request
cURL
curl -X POST "https://api.uzunqulaq.com/api/v2/track" \
  -H "Authorization: Bearer $UQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user":"u_8f3c","article_id":20413,"event":"read_complete","time_spent":92,"scroll_depth":100}'
Response 200 OK
application/json
{ "status": "ok" }

Errors

The API uses conventional HTTP status codes and returns a consistent error envelope. 2xx means success, 4xx a problem with the request, and 5xx an error on our side.

200 OK 400 Bad Request401 Unauthorized403 Forbidden404 Not Found429 Too Many Requests 500 Server Error

Errors return a single error string describing what went wrong. 401 means a missing/invalid key, 403 that your platform isn't licensed for that content or edition, and 400 a bad parameter.

Error envelope
{
  "error": "Unknown edition code(s): 'xx-zz'."
}

Rate limits

Limits are applied per platform (your bearer token), not per IP. Your ceiling is the rate_limit_per_hour value returned by GET /platform. When you exceed it you'll receive a 429 with a Retry-After header (seconds to wait before retrying). Honor it with exponential backoff.

Building something bigger? OEM, browser and enterprise integrations get dedicated quotas, larger page sizes and support. Talk to our partnerships team →
API v2 · © 2026 Uzun Qulaq · Tethreon News Ltd