К содержимому

API для школ

Заводите классы и учеников из своей системы, забирайте логины, пароли и статистику занятий к себе — в журнал, в отчёт, в родительский чат. Всё то же, что преподаватель делает руками в кабинете, только из программы.

Обновлено 13.09.2026 Сервис бесплатный codewar.ru

Что это и зачем

CodeWar — бесплатный тренажёр слепой печати. У преподавателя есть кабинет: классы, ученики с логинами и паролями, задания и журнал. API даёт то же самое программе — чтобы не переписывать тридцать логинов руками и не открывать сайт каждый раз, когда завуч просит отчёт.

Завести класс за секунду

Отправляете список имён — получаете логины и пароли. Почта ученикам не нужна, регистрация тоже.

Пароли под рукой на уроке

Пароль ученика виден до тех пор, пока он не сменит его сам. Забрать список можно в любой момент.

Статистика в свой журнал

Скорость, точность, пройденные уроки, дни занятий — по ученику, по классу, за любой период.

Главное про безопасность в одной строке: ключ работает только с классами и учениками того преподавателя, которому он выдан. Чужие данные по нему не «скрыты» — их просто нет, на запрос приходит «не найдено».

Три шага до первого запроса

  1. Получите роль преподавателя Если её ещё нет — заявка в кабинете, раздел «Работа с классом». Обычно отвечаем в тот же день.
  2. Попросите доступ к API Кабинет → КлассAPI для школы → опишите, что хотите автоматизировать. Доступ открывается вручную: так мы понимаем, какая нагрузка нас ждёт.
  3. Создайте ключ и проверьте его Ключ показывается один раз — сразу сохраните его туда, откуда его возьмёт ваша программа. Первый запрос:

В ответ придёт вот это:

Ответ
{
  "data": {
    "teacher": { "id": 1204, "name": "Анна Сергеевна" },
    "key": {
      "name": "Школьный сервер",
      "prefix": "cw_live_7fa2c1d3",
      "scope": "write",
      "can_write": true,
      "created_at": "2026-09-13T10:04:11+03:00"
    },
    "limits": {
      "requests_per_minute": 120,
      "heavy_requests_per_minute": 10,
      "max_classes": 10,
      "max_students_per_class": 50,
      "classes_used": 2,
      "students_total": 47
    }
  }
}
Если пришло 401 unauthorized — ключ не тот или отозван. Если 403 api_disabled — доступ ещё не открыт или его выключили. Разбор всех ошибок — ниже.

Ключ и права

  1. Ключ передаётся заголовком Authorization: Bearer cw_live_…. В адресной строке его передавать нельзя, и мы его там не читаем: в адресе он осел бы в логах вашего сервера, наших и любого прокси между ними.
  2. Ключ показывается один раз, при создании. У нас хранится только его хеш, поэтому «покажите ещё раз» невозможно технически. Потеряли — отзовите и создайте новый.
  3. Действующих ключей может быть два. Второй нужен, чтобы поменять ключ без простоя: создали новый, переключили программу, отозвали старый.
  4. Права бывают двух уровней. Чтение — только забирать данные. Чтение и запись — ещё и заводить учеников, создавать классы, выдавать задания, сбрасывать пароли.
  5. Отзыв ключа действует сразу. Выключение доступа администратором отзывает все ключи разом.
  6. Каждый запрос попадает в журнал, который преподаватель видит у себя в разделе API: метод, адрес, ответ, время. По нему удобно отлаживать интеграцию, не заглядывая в свои логи.
Ключ — это доступ к персональным данным детей. Не кладите его в репозиторий, в JavaScript на странице и в мобильное приложение: оттуда его достанет любой. Место ключа — на вашем сервере, в переменной окружения.

Границы и пределы

Что видит ключ

  1. Классы, где преподаватель — владелец. Чужие классы не видны и по прямой ссылке: ответ 404, тот же самый, что на несуществующий класс.
  2. Учеников этих классов. Прогресс, попытки набора, дни занятий — только их.
  3. Временный пароль — только у своих. Ученик, которого завёл этот преподаватель и который ещё не менял пароль, — пароль виден. Ученик, пришедший по ссылке-приглашению со своим аккаунтом, — пароля нет никогда, даже если он в базе есть. Такой ученик помечен "managed": false.
  4. Пароль пропадает, как только ученик задаст свой в личном кабинете: у нас остаётся только хеш, и показывать больше нечего. В ответе поле тогда равно null.
  5. Ничего, кроме этого. Ни других преподавателей, ни чужих учеников, ни общих рейтингов, ни почты — даже своих учеников: если ученик указал почту сам, она его, а не класса.

Сколько чего можно

50

учеников в классе. Место преподавателя в классе тоже занято — оно одно из этих 50.

10

действующих классов у преподавателя. Архивные не считаются, но вернуть класс из архива сверх потолка нельзя.

120

запросов в минуту на ключ. Отчёт и выгрузка считаются тяжёлыми — их 10 в минуту.

Свободные места в классе видны в поле free_seats. Когда мест не хватает, лишние ученики не заводятся, а попадают в meta.skipped с причиной — молча их никто не потеряет.

Превысили частоту — придёт 429 и заголовок Retry-After с числом секунд. Подождите столько и повторите; готовый PHP-класс делает это сам.

Потолки общие с кабинетом: API ничего не может сверх того, что преподаватель делает руками. Если вам нужно больше — напишите в поддержку, это решается разговором, а не обходом.

Формат ответа и ошибки

Адрес API — https://codewar.ru/api/v1. Версия в пути не меняется: внутри v1 мы можем добавлять поля и ручки, но не убирать и не переименовывать. Ваш разбор ответа не сломается от нашей правки.

Успех — всегда data, при необходимости meta:

Успех
{
  "data": [ { "id": 31, "name": "7Б информатика" } ],
  "meta": { "page": 1, "per_page": 50, "total": 24, "pages": 1 }
}

Ошибка — всегда error с машинным кодом:

Ошибка
{
  "error": {
    "code": "validation_failed",
    "message": "Проверьте параметры запроса.",
    "fields": { "names": ["Поле names обязательно."] }
  }
}

Разбирайте ошибки по code, а не по тексту: текст мы можем переписать, код — нет.

КодHTTPЧто случилось и что делать
unauthorized401 Ключа нет, он неверный или отозван. Проверьте заголовок Authorization.
api_disabled403 Доступ к API у этого преподавателя закрыт или роль снята. Напишите в поддержку.
forbidden_scope403 Ключу не хватает прав на запись. Попросите права write.
not_found404 Не ваш класс, не ваш ученик или такого id вообще нет. Разницы между этими случаями нет намеренно.
conflict409 Действие невозможно: кончились места, класс в архиве, ученик уже занимался и его нельзя удалить. Текст объясняет, что именно.
validation_failed422 Не те параметры. Разбор по полям — в fields.
rate_limited429 Слишком часто. Подождите столько секунд, сколько сказано в Retry-After.
server_error500 Сломалось у нас. В ответе будет request_id — назовите его в поддержке, по нему мы найдём, что произошло.

В каждом ответе есть заголовки X-RateLimit-Limit и X-RateLimit-Remaining — сколько запросов осталось в текущей минуте.

Классы

GET /classes
Все классы преподавателя, включая архивные: архив — это состояние, а не удаление.
Ответ
{
  "data": [
    {
      "id": 31,
      "name": "7Б информатика",
      "students": 24,
      "free_seats": 25,
      "max_students": 50,
      "archived": false,
      "invite_enabled": true,
      "invite_hash": "a1b2c3d4e5",
      "target_cpm": 150,
      "target_accuracy": 95,
      "created_at": "2026-09-01T09:12:00+03:00"
    }
  ],
  "meta": { "total": 1 }
}

students — детей в классе; преподаватель туда не входит. free_seats — сколько ещё влезет, и считается с учётом места преподавателя. Не пугайтесь, что students + free_seats на единицу меньше max_students: это и есть его место.

GET /classes/{id}
Один класс.
POST /classes нужна запись
Создать класс.
ПараметрЧто это
name обяз. Название, 2–100 знаков.
target_cpm Цель по скорости, знаков в минуту: 20–1000.
target_accuracy Цель по точности, проценты: 50–100.
PATCH /classes/{id} нужна запись
Изменить класс. Любое подмножество полей: name, target_cpm, target_accuracy, invite_enabled (ссылка-приглашение), archived (убрать в архив или вернуть).

Ученики

GET /classes/{id}/students
Ученики класса с логинами и — у своих — паролями. Преподавателя в списке нет. Постранично: page, per_page (до 100).
Ответ
{
  "data": [
    {
      "id": 8801,
      "name": "Иванов Пётр",
      "login": "petr-ivanov-7b",
      "managed": true,
      "temp_password": "kesu-9142",
      "level": 3,
      "must_change_password": false,
      "created_at": "2026-09-01T09:14:02+03:00",
      "last_action_at": "2026-09-12T14:31:55+03:00"
    },
    {
      "id": 8814,
      "name": "Смирнова Лена",
      "login": "lena_smirnova",
      "managed": false,
      "level": 7,
      "must_change_password": false,
      "created_at": "2025-11-04T18:02:10+03:00",
      "last_action_at": "2026-09-12T19:40:00+03:00"
    }
  ],
  "meta": { "page": 1, "per_page": 50, "total": 24, "pages": 1 }
}

У второго ученика поля temp_password нет вовсе: он пришёл по ссылке со своим аккаунтом ("managed": false). Такого ученика нельзя переименовать, удалить или сбросить ему пароль — только исключить из класса.

GET /students/{id}
Один ученик.
POST /classes/{id}/students нужна запись
Главная ручка. Отправляете имена — получаете логины и пароли. Почта ученикам не нужна.
ПараметрЧто это
names обяз. Массив имён. Имя — от 2 до 80 знаков, хотя бы одна буква. Годится и «7Б-03», если фамилии в чужую систему отдавать не хотите.
Ответ 201
{
  "data": [
    { "id": 8801, "name": "Иванов Пётр",  "login": "petr-ivanov-7b",  "password": "kesu-9142" },
    { "id": 8802, "name": "Петрова Анна", "login": "anna-petrova-7b", "password": "voro-3318" }
  ],
  "meta": {
    "created": 2,
    "skipped": [
      { "name": "7Б-03", "reason": "В классе кончились свободные места" }
    ],
    "free_seats": 0
  }
}
Всегда проверяйте meta.skipped. «Создано 28 из 30» без разбора причин — это двое детей, которые придут на урок без пароля, а вы об этом узнаете от них.
POST /students/{id}/reset-password нужна запись
Новый пароль ученику — то же самое, что кнопка «Сбросить пароль» в кабинете. Возвращает login и password. Только для своих (managed: true).
PATCH /students/{id} нужна запись
Переименовать (name) или перевести в другой свой класс (class_id). Перевод не сработает, если в новом классе нет мест.
DELETE /students/{id} нужна запись
Удалить аккаунт. Получится, только пока ученик ничего не наработал — иначе 409: одним запросом стирать историю занятий живого человека нельзя. Дальше его можно лишь исключить из класса.
DELETE /classes/{id}/students/{id} нужна запись
Исключить из класса, не трогая аккаунт. Работает и с пришедшими по ссылке: класс ваш, и кто в нём состоит — решаете вы.

Статистика

Всё считается тем же кодом, что рисует страницы кабинета. Значит, цифры в вашем журнале и в нашем совпадают всегда — и спорить о том, чей отчёт правильный, не придётся.

GET /students/{id}/progress
Сводка по ученику: пройденные уроки, средняя скорость и точность, дни занятий, минуты, лучший результат теста скорости и разбивка по курсам.
Ответ
{
  "data": {
    "student": { "id": 8801, "name": "Иванов Пётр", "login": "petr-ivanov-7b", "managed": true, "level": 3 },
    "lessons_done": 14,
    "cpm": 148,
    "accuracy": 96.4,
    "active_days": 9,
    "minutes": 212,
    "best_speed_wpm": 34,
    "courses": [
      { "name": "Русская раскладка", "lessons_done": 14, "lessons_total": 60, "percent": 23, "cpm": 148 }
    ],
    "last_day": "2026-09-12"
  }
}
GET /students/{id}/sessions
Попытки набора — все, а не только зачтённые: урок, который начинали пять раз и не закончили, говорит об ученике больше, чем пять пройденных.
ПараметрЧто это
from, toДаты ГГГГ-ММ-ДД включительно. Без них — за всё время.
page, per_pageСтраница и размер, до 100.
Одна попытка
{
  "id": 918233,
  "lesson_id": 412,
  "lesson": "Буквы О и Л",
  "course": "Русская раскладка",
  "cpm": 152,
  "wpm": 30,
  "accuracy": 97.2,
  "errors": 4,
  "seconds": 96,
  "completed": true,
  "at": "2026-09-12T14:31:55+03:00"
}
GET /students/{id}/activity
Дни занятий — для табеля. Строка на каждый день, когда ученик что-то делал: minutes, lessons, trainings, games, speed_tests, arena. Параметры from и to те же.
GET /classes/{id}/report тяжёлый: 10/мин
Отчёт по классу за последние days дней (по умолчанию 30): средние по классу, разбивка по неделям и строка на каждого ученика с пометкой, растёт он или стоит.
Ответ, сокращённо
{
  "data": {
    "class": { "id": 31, "name": "7Б информатика" },
    "period_days": 30,
    "from": "14.08.2026",
    "summary": {
      "students": 24, "active": 21, "grown": 15, "stalled": 4, "idle": 2, "never": 3,
      "passed": 11, "cpm": 141, "delta": 23, "accuracy": 95.8, "lessons": 218, "minutes": 3140
    },
    "weeks": [ { "week": "14.08", "active": 18, "cpm": 118 } ],
    "target": { "cpm": 150, "accuracy": 95 },
    "students": [
      {
        "id": 8801, "name": "Иванов Пётр", "login": "petr-ivanov-7b",
        "lessons": 14, "attempts": 41, "cpm": 148, "accuracy": 96.4,
        "delta": 31, "days": 9, "minutes": 212,
        "verdict": "ok", "state": "grown"
      }
    ]
  }
}

state: grown — скорость выросла, stalled — занимается, но стоит на месте, idle — перестал заниматься, never — не начинал. verdict сравнивает с целью класса: above, ok, below.

GET /classes/{id}/export тяжёлый: 10/мин
Та же таблица, что скачивается кнопкой в кабинете, но разобранная: header — заголовки колонок, rows — строки. Разбирать CSV не нужно.
ПараметрЧто это
what students (по умолчанию) — строка на ученика. tests — тесты скорости, по строке на попытку.
columns Через запятую: who, course, typing, tests, class, visits. Без параметра — все.
from, to Даты ГГГГ-ММ-ДД.

Задания

Задание — это ссылка на урок или курс плюс срок и пороги. Отдельной механики у него нет: ученик проходит обычный урок, ничего не зная про задание, а «сдано» выводится из его результатов. Поэтому ручки «отметить сдано» нет и быть не может — отметка не хранится, она считается.

GET /classes/{id}/assignments
Задания класса и по каждому — строка на ученика: state, cpm, accuracy, at.
POST /classes/{id}/assignments нужна запись
Выдать задание.
ПараметрЧто это
kind обяз.lesson или course.
target_id обяз.Идентификатор урока или курса. Его видно в адресе урока в кабинете.
due_atСрок сдачи, дата или дата со временем.
min_cpmПорог скорости для зачёта.
max_errorsСколько ошибок допустимо.
student_idsКому именно. Пусто — всему классу, и тогда задание получат и те, кого заведут позже.
DELETE /classes/{id}/assignments/{id} нужна запись
Снять задание. История занятий при этом не трогается.
Словари и тесты скорости через API не задаются: у них есть режимы и настройки, которые в запрос не укладываются, а выбирать их вслепую бессмысленно. Эти задания выдаются из кабинета.

Готовый класс для PHP

Один файл, ни одной зависимости, PHP 7.4 и выше. Скачайте, положите к себе, подключите — и не думайте про заголовки, разбор ошибок и постраничные ответы.

В архиве CodeWarClient.php, рабочий example.php и короткий README.txt.

Как пользоваться
<?php

require __DIR__ . '/CodeWarClient.php';

// Ключ — из переменной окружения, а не из кода: код попадёт в репозиторий
$cw = new CodeWarClient(getenv('CODEWAR_KEY'));

try {
    $who = $cw->me();
    echo "Ключ работает: ", $who['teacher']['name'], PHP_EOL;

    $class = $cw->createClass('7Б информатика', 150, 95);
    $result = $cw->addStudents($class['id'], ['Иванов Пётр', 'Петрова Анна']);

    foreach ($result['data'] as $student) {
        echo $student['login'], "\t", $student['password'], PHP_EOL;
    }
} catch (CodeWarApiException $e) {
    // Разбирать по коду, а не по тексту
    if ($e->errorCode === 'forbidden_scope') {
        exit("Ключу не хватает прав на запись.\n");
    }

    throw $e;
}

Что он делает за вас:

  • подставляет заголовок Authorization — ключ никогда не уходит в адрес;
  • бросает CodeWarApiException с полями errorCode, status, fields, requestId;
  • сам ждёт и повторяет запрос, упёршийся в 429;
  • листает страницы: allStudents() и allSessions() возвращают всё сразу.

Рецепты

Первое сентября: завести всех разом

PHP
<?php

$cw = new CodeWarClient(getenv('CODEWAR_KEY'));

// Имена берутся из вашей системы. Фамилии отдавать не обязательно: сгодятся
// инициалы или номера по журналу — тренажёру фамилии ни к чему
$byClass = [
    '7А информатика' => ['Иванов Пётр', 'Петрова Анна'],
    '7Б информатика' => ['Сидоров Илья'],
];

$cards = fopen('passwords.csv', 'w');
fprintf($cards, "\xEF\xBB\xBF");            // BOM, иначе Excel испортит кириллицу
fputcsv($cards, ['Класс', 'Имя', 'Логин', 'Пароль'], ';');

foreach ($byClass as $name => $names) {
    $class = $cw->createClass($name);
    $result = $cw->addStudents($class['id'], $names);

    foreach ($result['data'] as $student) {
        fputcsv($cards, [$name, $student['name'], $student['login'], $student['password']], ';');
    }

    foreach ($result['meta']['skipped'] as $skipped) {
        fwrite(STDERR, 'ПРОПУЩЕН ' . $skipped['name'] . ': ' . $skipped['reason'] . PHP_EOL);
    }
}

fclose($cards);

Пароли к уроку: список для распечатки

Пароль лежит у нас открытым ровно до тех пор, пока ученик не задаст свой. Поэтому забрать список можно в любой момент — не только сразу после создания.

PHP
<?php

foreach ($cw->allStudents($classId) as $student) {
    printf(
        "%-24s %-18s %s\n",
        $student['name'],
        $student['login'],
        $student['temp_password'] ?? '— сменил сам'
    );
}

Еженедельная выгрузка в журнал

PHP
<?php

$from = date('Y-m-d', strtotime('-7 days'));
$to = date('Y-m-d');

foreach ($cw->classes() as $class) {
    if ($class['archived']) {
        continue;
    }

    $table = $cw->export($class['id'], 'students', $from, $to);

    // $table['header'] — названия колонок, $table['rows'] — строки
    foreach ($table['rows'] as $row) {
        $journal->save($class['name'], array_combine($table['header'], $row));
    }
}

Табель посещаемости

PHP
<?php

$days = $cw->activity($studentId, '2026-09-01', '2026-09-30');

foreach ($days as $day) {
    echo $day['date'], ': ', $day['minutes'], ' мин, уроков ', $day['lessons'], PHP_EOL;
}
Выгрузку лучше ставить на ночь и не чаще раза в сутки: отчёты — тяжёлые запросы, и десяти в минуту хватает только при спокойном темпе.

Частые вопросы

Можно ли добавить наш сервер в исключения по IP?
Не нужно: с ключом вы и так делаете всё, что вам нужно, и никакие проверки вам не мешают. Привязка к IP только усложнила бы вам жизнь — адрес школьного провайдера меняется чаще, чем кажется.
Почему пароль ученика хранится открытым?
Потому что иначе его нельзя отдать на уроке. Это временный пароль, который сгенерировали мы, и живёт он ровно до того момента, когда ученик задаст свой — тогда остаётся только хеш, и пароля не видит уже никто, включая нас. Пароли, которые люди придумали сами, не хранятся открытыми никогда.
Можно ли получить пароли учеников, которых завёл не я?
Нет, и это не настройка. Ученик, пришедший в класс по ссылке, владеет своим аккаунтом сам: в ответе у него "managed": false и поля с паролем нет вовсе.
А если школе нужно больше 50 учеников в классе?
Заведите второй класс: их можно до 10. Если и этого мало — напишите в поддержку, потолки настраиваются, но мы хотим знать, кому и зачем.
Ключ утёк. Что делать?
Отозвать его в кабинете: раздел API, кнопка «Отозвать». Запросы с ним перестанут проходить сразу. Потом создайте новый и посмотрите журнал запросов — там видно, что с ним успели сделать.
Можно ли звать API прямо из браузера, со страницы?
Нет. Ключ в JavaScript виден любому, кто откроет страницу, — это то же самое, что раздать его. Зовите API со своего сервера.
Что будет, если наш скрипт сойдёт с ума?
Упрётся в предел частоты и получит 429. Данные при этом целы: заводить учеников сверх мест в классе, а классы сверх потолка API не даёт — это те же ограничения, что и в кабинете.
Вы поменяете формат ответа?
Внутри v1 — нет. Поля и ручки могут добавляться, но существующие не убираются и не переименовываются. Если понадобится ломающее изменение, появится v2, а v1 продолжит работать.
Как получить доступ?
Кабинет → КлассAPI для школы → заявка. Если аккаунта ещё нет — сначала регистрация, потом заявка на роль преподавателя.