Ачкыч алуу
API ачкычын мекеменин негизги аккаунту (админи) гана түзө алат. Мугалимдерге ачкыч көрсөтүлбөйт.
- 1Мекемеңиздин бетин ачыңыз
Synak+ кабинетинде Менин мекемем → API бөлүмүнө өтүңүз.
- 2«Ачкыч түзүү» баскычын басыңыз
Толук ачкыч (
sk_inst_…) бир гана жолу көрсөтүлөт. Аны көчүрүп, IT адисиңизге коопсуз жол менен бериңиз. Synak+ анын хэшин гана сактайт — кайра көрсөтө албайт. - 3Ачкычты серверде сактаңыз
Ачкыч — сыр сөз. Аны браузердин кодуна, мобилдик тиркемеге же git репозиторийине жазбаңыз; тышкы системаңыздын серверинде гана (мисалы, чөйрө өзгөрмөсүндө) кармаңыз.
Жаңы ачкыч түзүлгөндө эскиси ошол замат иштебей калат. Тышкы система жаңы ачкычка өткөнгө чейин 401 алат.
Токтотуу ачкычты өзгөртпөй интеграцияны убактылуу өчүрөт; Улантуу аны кайра күйгүзөт. Жокко чыгаруу — биротоло.
Авторизация
Ар бир сурам ачкычты Authorization header'инде алып келет. Мекеме ачкычтан аныкталат — сурамга institution_id жазуунун кереги жок (жазылса да эске алынбайт).
Authorization: Bearer sk_inst_3f9a1c7e…
Негизги дарек
Бардык эндпоинттер ушул даректен башталат. Сурамдар HTTPS аркылуу гана кабыл алынат.
https://synak.app/api/v1
?key=…, ?api_key=… же ?token=… менен келген сурам 400 KEY_IN_URL алат: URL серверлердин журналдарында жана браузердин тарыхында калат.
Абалды текшерүү
Ачкыч иштейби, мекемеңиздин тарифи төлөнгөнбү — туташууну текшерүүнүн эң жөнөкөй жолу.
curl https://synak.app/api/v1/status \ -H "Authorization: Bearer sk_inst_3f9a1c7e…"
{
"ok": true,
"institution": { "id": "198d4ee3-6258-4488-b5bc-ea9bbe2d1af3", "name": "ОшТУ" },
"key_prefix": "sk_inst_3f9a",
"limits": { "per_minute": 60, "per_day": 10000 },
"server_time": "2026-10-10T10:00:00.000Z"
}Бул жооп жыйынтыктарды же окуучулардын маалыматын камтыбайт. Мекеменин тарифи төлөнбөсө — 402 INSTITUTION_INACTIVE.
Тесттер
Мекемеңиздин тесттеринин тизмеси. test_idни ушул жерден алып, жыйынтыктарды чыпкалоого колдонсоңуз болот. Суроолор жана жооптор берилбейт.
| Параметр | Түрү | Сүрөттөмө |
|---|---|---|
limit | integer | 1–500, демейки 100каалоо боюнча |
cursor | string | Мурунку жооптогу next_cursorкаалоо боюнча |
{
"tests": [
{
"test_id": "5b2e7a90-1c4d-4e8f-a6b3-0d9c2f1e7a55",
"title": "Информатика, 1-модуль",
"question_count": 20,
"created_at": "2026-10-01T08:12:40.000Z"
}
],
"next_cursor": "WyIyMDI2LTEwLTAxVDA4OjEyOjQwLjAwMFoiLCI1YjJlN2E5MCJd",
"has_more": false
}Сессиялар
Сессия — тесттин бир же бир нече топко, белгилүү убакытка өзүнчө шилтеме менен ачылышы. groups — сессиянын топтору; бир нечеси болсо, окуучу шилтемени ачканда өз тобун тандайт.
| Параметр | Түрү | Сүрөттөмө |
|---|---|---|
state | string | active — азыр жүрүп жаткан; ended — аяктаганкаалоо боюнча |
test_id | uuid | Бир тесттин сессиялары ганакаалоо боюнча |
limit | integer | 1–500, демейки 100каалоо боюнча |
cursor | string | Мурунку жооптогу next_cursorкаалоо боюнча |
{
"sessions": [
{
"session_id": "c4a7e1d2-3f5b-4a6c-8e9d-2b1a0f3c5d7e",
"test_id": "5b2e7a90-1c4d-4e8f-a6b3-0d9c2f1e7a55",
"test_title": "Информатика, 1-модуль",
"groups": ["ИСТ-21", "ИСТ-22"],
"started_at": "2026-10-10T09:00:00.000Z",
"ends_at": "2026-10-10T09:45:00.000Z",
"ended_at": "2026-10-10T09:45:00.000Z",
"closed_early": false,
"state": "ended",
"results_count": 27
}
],
"next_cursor": "WyIyMDI2LTEwLTAxVDA4OjEyOjQwLjAwMFoiLCI1YjJlN2E5MCJd",
"has_more": false
}Сессия убактысы бүткөндө же мугалим аны мөөнөтүнөн мурда жапканда ended болот (closed_early: true). Аяктаган сессиянын жыйынтыктары өчпөйт.
Сессия аяктагандан кийин да, ага чейин баштаган окуучу тестти бүтүрүп жөнөтө алат — ошондуктан аяктаган сессияга бир аз убакыт жаңы жыйынтык келиши мүмкүн.
Жыйынтыктар
Мекемеңиздин тесттеринин жыйынтыктары — сессия аркылуу да, тесттин кадимки шилтемеси аркылуу да тапшырылгандар. Тартиби: тапшырылган убакыт боюнча, эскиси биринчи.
| Параметр | Түрү | Сүрөттөмө |
|---|---|---|
test_id | uuid | Бир тесттин жыйынтыктарыкаалоо боюнча |
session_id | uuid | Бир сессиянын жыйынтыктарыкаалоо боюнча |
group | string | Окуучунун тобу (мисалы 10-А), так дал келүүкаалоо боюнча |
from, to | ISO 8601 | Тапшырылган убакыттын аралыгы (экөө тең кошо)каалоо боюнча |
since | ISO 8601 | Ушул убакыттан КИЙИН тапшырылгандаркаалоо боюнча |
limit | integer | 1–500, демейки 100каалоо боюнча |
cursor | string | Мурунку жооптогу next_cursorкаалоо боюнча |
Сурам
curl "https://synak.app/api/v1/results?session_id=c4a7e1d2-3f5b-4a6c-8e9d-2b1a0f3c5d7e&limit=100" \ -H "Authorization: Bearer sk_inst_3f9a1c7e…"
Жооп
{
"results": [
{
"attempt_id": "8d1f0c52-6a3e-4c1b-9f2a-1e7d5b3c9a40",
"institution_id": "198d4ee3-6258-4488-b5bc-ea9bbe2d1af3",
"test_id": "5b2e7a90-1c4d-4e8f-a6b3-0d9c2f1e7a55",
"test_title": "Информатика, 1-модуль",
"session_id": "c4a7e1d2-3f5b-4a6c-8e9d-2b1a0f3c5d7e",
"group_name": "ИСТ-21",
"student_name": "Айпери Асанова",
"student_id": null,
"student_fields": [{ "label": "№ зачетки", "value": "2021-0417" }],
"score": 34,
"max_score": 40,
"correct_answers": 17,
"total_questions": 20,
"percentage": 85,
"submitted_at": "2026-10-10T09:41:07.512Z",
"duration_seconds": 1284,
"status": "submitted"
}
],
"next_cursor": "WyIyMDI2LTEwLTEwVDA5OjQxOjA3LjUxMloiLCI4ZDFmMGM1MiJd",
"has_more": true
}Бир сессиянын жыйынтыктары
Сессиянын маалыматы (топтору, убактысы, абалы) жана анын жыйынтыктары бир жоопто.
{
"session": {
"session_id": "c4a7e1d2-3f5b-4a6c-8e9d-2b1a0f3c5d7e",
"test_id": "5b2e7a90-1c4d-4e8f-a6b3-0d9c2f1e7a55",
"test_title": "Информатика, 1-модуль",
"groups": ["ИСТ-21", "ИСТ-22"],
"started_at": "2026-10-10T09:00:00.000Z",
"ends_at": "2026-10-10T09:45:00.000Z",
"ended_at": "2026-10-10T09:45:00.000Z",
"closed_early": false,
"state": "ended",
"results_count": 27
},
"results": [ … ],
"next_cursor": "WyIyMDI2LTEwLTEwVDA5OjQ0OjU5LjEwMFoiLCI3YTJjOWUxMCJd",
"has_more": false
}limit жана cursor бул жерде да иштейт. Сессия мекемеңизге таандык болбосо — 404 (бар экени да айтылбайт).
Webhook
Жыйынтыкты сурап алуунун ордуна аны кабыл алыңыз: окуучу тест тапшырары менен Synak+ жыйынтыкты сиздин системаңызга POST сурамы менен өзү жөнөтөт. Жыйынтык адегенде Synak+ ичинде сакталат — системаңыз иштебей турса да эч нерсе жоголбойт.
- 1Дарек даярдаңыз
Сервериңизде JSON body менен
POSTкабыл алган HTTPS дарек, мисалыhttps://school.example/synak/webhook. Ички тармактын даректери кабыл алынбайт. - 2Synak+ ичинде сактаңыз
Менин мекемем → API → Webhook: даректи жазып, Сактоо баскычын басыңыз. Кол тамганын сыры (
whsec_…) бир гана жолу көрсөтүлөт — аны сервериңизде сактаңыз. - 3Сыноо билдирүүсүн жөнөтүңүз
Сыноо билдирүүсү баскычы
webhook.testокуясын жөнөтөт. Дарегиңиз 200 менен жооп берсе, байланыш иштейт.
Окуялар
| type | Качан |
|---|---|
test.result.created | Окуучу мекемеңиздин тестин тапшырды (сессия же кадимки шилтеме аркылуу). data.result — API'деги жыйынтык менен бирдей талаалар |
webhook.test | Админ Сыноо билдирүүсү баскычын басты. Анда жыйынтык жок |
Сурамдын body'си
{
"id": "e2b7c1a4-9d3f-4a8e-b6c5-7f1d0a2e3b94",
"type": "test.result.created",
"created_at": "2026-10-10T09:41:07.733Z",
"institution_id": "198d4ee3-6258-4488-b5bc-ea9bbe2d1af3",
"data": {
"result": {
"attempt_id": "8d1f0c52-6a3e-4c1b-9f2a-1e7d5b3c9a40",
"institution_id": "198d4ee3-6258-4488-b5bc-ea9bbe2d1af3",
"test_id": "5b2e7a90-1c4d-4e8f-a6b3-0d9c2f1e7a55",
"test_title": "Информатика, 1-модуль",
"session_id": "c4a7e1d2-3f5b-4a6c-8e9d-2b1a0f3c5d7e",
"group_name": "ИСТ-21",
"student_name": "Айпери Асанова",
"student_id": null,
"student_fields": [{ "label": "№ зачетки", "value": "2021-0417" }],
"score": 34,
"max_score": 40,
"correct_answers": 17,
"total_questions": 20,
"percentage": 85,
"submitted_at": "2026-10-10T09:41:07.512Z",
"duration_seconds": 1284,
"status": "submitted"
}
}
}Header'лер
POST /synak/webhook HTTP/1.1 Content-Type: application/json User-Agent: Synak-Webhooks/1 Synak-Event-Id: e2b7c1a4-9d3f-4a8e-b6c5-7f1d0a2e3b94 Synak-Event-Type: test.result.created Synak-Signature: t=1791625268,v1=5f2c8e1a9b…
| Header | Мааниси |
|---|---|
Synak-Event-Id | Окуянын ID'си, body'деги id менен бирдей. Кайра жөнөткөндө өзгөрбөйт — кайталанган билдирүүнү ушул менен тааныңыз |
Synak-Event-Type | Body'деги type менен бирдей |
Synak-Signature | t=<unix убакыт>,v1=<кол тамга> — t.body сабынын сырыңыз менен эсептелген HMAC-SHA256 |
Кол тамганы текшерүү
- Кол тамганы чийки body боюнча текшериңиз — JSON'ду талдаганга чейин. Бир боштук өзгөрсө да кол тамга дал келбей калат.
tубактысы сиздин саатыңыздан 5 мүнөттөн ашык айырмаланса, окуяны четке кагыңыз — кармалып калган эски сурамды кайра жөнөтүү ушинтип бөгөттөлөт.- Кол тамгаларды туруктуу убакыттагы функция менен салыштырыңыз (
timingSafeEqual,compare_digest). - Окуяда API ачкычыңыз да, сыр да эч качан болбойт.
import crypto from "node:crypto"
import express from "express"
const SECRET = process.env.SYNAK_WEBHOOK_SECRET // whsec_…
const app = express()
// express.raw — кол тамга чийки body'ге коюлат.
app.post("/synak/webhook", express.raw({ type: "application/json" }), (req, res) => {
const parts = Object.fromEntries(
(req.get("Synak-Signature") ?? "").split(",").map(p => p.split("="))
)
const expected = crypto.createHmac("sha256", SECRET)
.update(`${parts.t}.${req.body}`).digest("hex")
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
const valid = typeof parts.v1 === "string" && parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))
if (!fresh || !valid) return res.sendStatus(400)
const event = JSON.parse(req.body)
if (event.type === "test.result.created") saveOnce(event.id, event.data.result)
res.sendStatus(200)
})import hashlib, hmac, json, os, time
from flask import Flask, abort, request
SECRET = os.environ["SYNAK_WEBHOOK_SECRET"].encode() # whsec_…
app = Flask(__name__)
@app.post("/synak/webhook")
def synak_webhook():
body = request.get_data() # чийки body
header = request.headers.get("Synak-Signature", "")
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
t, v1 = parts.get("t", ""), parts.get("v1", "")
expected = hmac.new(SECRET, t.encode() + b"." + body, hashlib.sha256).hexdigest()
if not t.isdigit() or abs(time.time() - int(t)) > 300 or not hmac.compare_digest(v1, expected):
abort(400)
event = json.loads(body)
if event["type"] == "test.result.created":
save_once(event["id"], event["data"]["result"])
return "", 200Жеткирүү жана кайра аракеттер
- Дарегиңиз 8 секунддун ичинде каалаган 2xx статусу менен жооп берсе, окуя жеткирилди деп эсептелет. Redirect (3xx) аткарылбайт.
- Болбосо Synak+ кайра жөнөтөт: ошол замат, 5 секунддан кийин, 20 секунддан кийин, андан кийин кийинки 2 күн бою күнүнө бир жолу (жалпы 5 аракет).
- Дарек 2 күн бою бир да окуяны кабыл албаса, webhook автоматтык өчөт; админ эскертүүнү көрүп, аны кайра күйгүзөт.
- Админ каалаган окуяны Акыркы жеткирүүлөр тизмесинен кол менен кайра жөнөтө алат. Окуянын ID'си өзгөрбөйт.
Тез жооп бериңиз: окуяны сактап, 200 кайтарыңыз, иштетүүнү андан кийин жасаңыз. Жыйынтыктарды attempt_id (же окуянын id) боюнча сактаңыз — кайталанган окуя эки жолу жазылбайт.
Webhook жана API бирге иштейт: системаңыз узак убакыт иштебей калса, өткөрүп жиберилген жыйынтыктарды курсор менен алыңыз (Барактоо жана жаңы жыйынтыктар бөлүмү).
Жыйынтыктын талаалары
Базада чын эле бар маалымат гана берилет. Жок маалымат ойлоп табылбайт.
| Талаа | Түрү | Мааниси |
|---|---|---|
attempt_id | uuid | Бир тапшыруунун туруктуу ID'си. Кайталанган жазууну ушул боюнча тааныңыз |
institution_id | uuid | Мекеменин ID'си |
test_id, test_title | uuid, string | Тест |
session_id | uuid | null | Сессия. Кадимки шилтеме аркылуу тапшырылса — null |
group_name | string | null | Окуучу сессияда тандаган топ (сессияда бир топ болсо — ошол), болбосо окуучу жазган «Класс» талаасы |
student_name | string | null | Окуучу жазган аты-жөнү |
student_id | string | null | Кадимки шилтемеде окуучу жазган «ID» талаасы (мугалим ал талааны күйгүзгөн болсо). Сессияда — null |
student_fields | array | Окуучу сессияга киргенде толтурган ведомосттун мамычалары, мис. [{ "label": "№ зачетки", "value": "2021-0417" }]. label — мамычанын шаблондогу аталышы. Толтурулбаса — бош массив |
score, max_score | integer | Алынган жана мүмкүн болгон упай. Упай коюлбаган тестте — туура жооптордун жана суроолордун саны |
correct_answers, total_questions | integer | Туура жооптор жана суроолор |
percentage | integer | 0–100 |
submitted_at | ISO 8601 | Тапшырылган убакыт (UTC) |
duration_seconds | integer | null | Тапшырууга кеткен убакыт. Белгисиз болсо null |
status | string | Азыр ар дайым submitted |
grade (баа) жок: Synak+ бааны эсептебейт, ошондуктан упай менен пайызды өзүңүздүн баалоо эрежеңиз боюнча айландырыңыз.
Окуучунун почтасы, телефону жана жооптору API аркылуу берилбейт.
Барактоо жана жаңы жыйынтыктар
Бир жолу эң көп 500 жазуу берилет. Калганы курсор менен алынат.
- Ар бир жоопто
next_cursor(акыркы берилген жазуудан кийинки орун) жанаhas_more(дагы барак барбы) болот. has_more: trueболсо — ошол эле сурамдыcursor=<next_cursor>менен кайталаңыз.- Жаңыларын гана алуу: акыркы
next_cursorду сактап коюңуз. Кийинки жолу (мисалы, бир сааттан кийин) аны берсеңиз — ошондон кийин тапшырылгандар гана келет. Ал акыркы баракта да берилет. - Курсордун ордуна
since=<submitted_at>да колдонсо болот, бирок курсор так: бир секундда тапшырылган эки жыйынтыкты да өткөрүп жибербейт. - Бир жыйынтык бир жолу жазылат жана өзгөрбөйт. Тармак үзүлүп, бир баракты кайра алсаңыз — жазууларды
attempt_idбоюнча жаңыртыңыз (upsert), ошондо эч нерсе кайталанбайт.
// Жаңы жыйынтыктарды гана алуу (мисалы, ар бир саатта).
const H = { Authorization: `Bearer ${process.env.SYNAK_KEY}` }
let cursor = loadSavedCursor() // мурунку жолу сакталган next_cursor
while (true) {
const url = new URL("https://synak.app/api/v1/results")
url.searchParams.set("limit", "500")
if (cursor) url.searchParams.set("cursor", cursor)
const res = await fetch(url, { headers: H })
if (res.status === 429) { await sleep(60_000); continue }
if (!res.ok) throw new Error((await res.json()).error.type)
const { results, next_cursor, has_more } = await res.json()
for (const r of results) upsertByAttemptId(r) // attempt_id боюнча — кайталанбайт
if (next_cursor) saveCursor((cursor = next_cursor))
if (!has_more) break
}Мекеменин системасында attempt_idге уникалдуу индекс коюңуз — кайталап алынган жазуу ошондо өзү эле четке кагылат.
Чектер
Чектер мекеменин бир ачкычына эсептелет.
| Чек | Мааниси | Ашып кетсе |
|---|---|---|
| Мүнөтүнө сурам | 60 | 429 |
| Сутка ичинде сурам | 10 000 | 429 |
| Бир барактагы жазуу | 500 | 500гө чейин кыскарат |
API мекеменин тарифи төлөнүп турганда иштейт. Мөөнөтү бүтсө — 402; төлөм ырасталары менен ачкыч кайра иштейт, жыйынтыктар жоголбойт.
Каталар
Ката бирдей форматта келет. type — туруктуу код, программаңыз ошону текшерсин; message — англисче түшүндүрмө.
{
"error": {
"code": 401,
"type": "INVALID_API_KEY",
"message": "The API key is missing, invalid or revoked."
}
}| Код | type | Качан |
|---|---|---|
| 400 | INVALID_PARAMETER | Параметр туура эмес (мисалы test_id UUID эмес, limit сан эмес) |
| 400 | KEY_IN_URL | Ачкыч URL'де келди — header'ге көчүрүңүз |
| 400 | HTTPS_REQUIRED | Сурам HTTP аркылуу келди |
| 401 | INVALID_API_KEY | Ачкыч жок, туура эмес, токтотулган же жокко чыгарылган |
| 402 | INSTITUTION_INACTIVE | Мекеменин тарифи төлөнгөн эмес же мөөнөтү бүткөн |
| 404 | NOT_FOUND | Сессия табылган жок же мекемеңизге таандык эмес |
| 429 | RATE_LIMIT_EXCEEDED | Чек ашты. Retry-After header'индеги секунддан кийин кайталаңыз |
| 500 | SERVER_ERROR | Synak+ тараптагы убактылуу ката |
Качан кайталоо керек
- 429 — `Retry-After` секунд күтүп, ошол эле сурамды кайталаңыз.
- 500 — бир нече секунддан кийин, аралыкты көбөйтүп (1, 2, 4… сек) кайталаңыз.
- 400, 401, 402, 404 — кайталоо жардам бербейт: параметрди, ачкычты же тарифти оңдоңуз.
Туташтыруу жана текшерүү
Тышкы системаны туташтыруунун кыска тартиби.
- 1Ачкычты текшериңиз
GET /api/v1/status200 жана мекемеңиздин атын кайтарышы керек. - 2Сынак тест өткөрүңүз
Мекемеде тестке сессия ачып, 1–2 жолу өзүңүз тапшырыңыз.
- 3Жыйынтыкты алыңыз
GET /api/v1/results?session_id=…— тапшырылгандар ошол жерде болушу керек. - 4Шайкештирүүнү иштетиңиз
Жыйынтыктарды курсор менен мезгил-мезгили менен алып,
attempt_idбоюнча сактаңыз (жогорудагы мисал).
Жыйынтыктарды сурабастан, бир нече секундда алуу үчүн webhook'ту да орнотуңуз (Webhook бөлүмү). Курсор менен шайкештирүүнү камдык катары калтырыңыз — системаңыз иштебей турганда өткөрүп жиберилгендерди ал кармайт.