REST API リファレンス

Manabu2 の学習コンテンツと学習進捗を、外部システムから読み書きするための REST API です。コースやレッスンを自社サービスに取り込む用途と、学習アプリから進捗を記録する用途の両方に対応します。

ベース URL: https://api.manabu2.com/api/v1 | 対話型リファレンス (Scalar)OpenAPI JSON

バージョニング

公開 API はすべて /api/v1 配下にあります。互換性を壊す変更が必要になった場合は /api/v2 を新設し、v1 は移行期間を設けたうえで維持します。フィールドの追加は破壊的変更とはみなしませんので、未知のフィールドは無視できるようにクライアントを実装してください。

バージョンなしの旧 URL(例: /api/courses)も当面は動作し、内部で /api/v1 に転送されます。ただし応答に Deprecation: true ヘッダーが付き、将来的に廃止されます。新規実装では必ず /api/v1 を直接指定してください。

スキーマ(OpenAPI / Swagger)

全エンドポイントの定義は機械可読な OpenAPI 3 ドキュメントとして公開しています。型付きクライアントの自動生成、Postman などへの取り込み、スキーマの差分確認にそのまま使えます。

OpenAPI ドキュメント https://api.manabu2.com/openapi/v1.json
ブラウザで試す https://api.manabu2.com/scalar/v1

各オペレーションには x-required-scope 拡張が付いており、必要なスコープをドキュメントから直接読み取れます。この値は実際に認可を行っている属性から生成しているため、実装とずれることはありません。

クライアント生成の例(openapi-generator の場合):

npx @openapitools/openapi-generator-cli generate   -i https://api.manabu2.com/openapi/v1.json   -g typescript-fetch   -o ./src/manabu2

1. 認証

すべてのエンドポイントに認証が必要です。資格情報は 2 種類あり、用途によって使い分けます。

受講者トークン (JWT)

学習アプリなど、受講者本人として操作する場合に使用します。本人の進捗の読み書きができます。

Authorization: Bearer <jwt>
組織 API キー

サーバー間連携で使用します。組織単位で発行し、付与されたスコープの範囲内でのみ動作します。特定の個人には紐づきません。

X-Api-Key: mnb_live_…
Authorization: Bearer mnb_live_…
受講者トークンの取得
POST https://api.manabu2.com/api/v1/auth/login
Content-Type: application/json

{
  "email": "learner@example.com",
  "password": "…",
  "clientId": "manabu2-learner-app"
}
{
  "accessToken": "eyJhbGciOi…",
  "expiresAt": "2026-07-30T15:00:00Z",
  "refreshToken": "3pQ7…",
  "scopes": ["read:catalog", "read:content", "read:progress", "write:progress"],
  "user": { "id": "…", "email": "learner@example.com", "displayName": "…" }
}

ログインすると 2 つのトークンが返ります。accessToken は各リクエストに付与する短命なトークンで、有効期間は 60 分です。refreshToken は有効期間 30 日で、accessToken が切れたときに新しい組を取得するために使います。accessToken は失効させることができない(署名を検証するだけ)ため、あえて短くしてあります。セッションを止める手段は refreshToken 側にあります。

clientId(アプリケーションの識別)

clientId は「どのアプリがサインインしているか」を示します。アプリごとに要求できるスコープの上限が登録されており、発行されるトークンはその上限を超えません。

トークンに付与されるスコープ = 「利用者のロールが許すスコープ」∩「そのアプリが要求できるスコープ」。たとえば組織管理者が学習者向けアプリにサインインしても、そのトークンに write:members は含まれません。アプリが必要としない権限を持ち歩かせないための仕組みです。
  • clientId を省略した場合は、学習者向けの 4 スコープ(read:catalog / read:content / read:progress / write:progress)のみが付与されます。省略することで管理系スコープを得ることはできません。
  • 未登録または無効化された clientId を指定した場合、スコープは一切付与されません(アプリの停止が実際に機能するためです)。ログイン時には 400 を返します。
  • リフレッシュ時も同じアプリの上限が再適用されます。トークンを更新することで権限が広がることはありません。
  • 現時点で clientId を発行できるのは自社アプリのみです(設定で登録します)。第三者向けのアプリ登録は、OAuth 認可フローの提供時に対応予定です。
パスワードを預からないサインイン(OAuth)

PKCE 付きの認可コードフローです。アプリはユーザーを manabu2.com のサインイン画面へ送り、短時間だけ有効な認可コードを受け取って、それをトークンと交換します。パスワードは当社の画面で入力され、アプリを経由することはありません。

自社で管理していない端末上で動くもの(モバイルアプリ、デスクトップアプリ、SPA など)はこの方式を使ってください。シングルサインオンもこの仕組みによるものです。すでに manabu2.com にサインイン済みのユーザーは、再入力を求められることなくアプリに戻ります。

1. PKCE のペアを生成します。verifier はアプリ内に保持し、送信するのはその SHA-256 ハッシュだけです。そのため、リダイレクトを傍受しただけではコードを引き換えられません。

code_verifier  = base64url(random 32–64 bytes)      // keep this
code_challenge = base64url(sha256(code_verifier))   // send this

2. この URL を OS 標準のブラウザーで開きます(アプリ内 WebView は使わないでください。WebView はパスワードを読み取れてしまい、この方式の意味がなくなります)。

GET https://manabu2.com/connect/authorize
  ?client_id=manabu2-mobile
  &redirect_uri=manabu2%3A%2F%2Fauth%2Fcallback
  &response_type=code
  &scope=read%3Acatalog%20read%3Acontent%20read%3Aprogress%20write%3Aprogress
  &state=<random, checked on the way back>
  &code_challenge=<from step 1>
  &code_challenge_method=S256

3. ユーザーがサインインすると、アプリにリダイレクトで戻ります。まず state が送信した値と一致することを確認してください。

manabu2://auth/callback?code=f5kIhYnGah1I…&state=<the value you sent>

4. コードをトークンと交換します。HTTP クライアントの都合に合わせて JSON ボディでも送信できます。

POST https://api.manabu2.com/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=f5kIhYnGah1I…
&client_id=manabu2-mobile
&redirect_uri=manabu2://auth/callback
&code_verifier=<from step 1>

レスポンスは /auth/login と同じ形式です(accessToken、expiresAt、refreshToken、scopes、user)。更新方法も同じで、POST /api/v1/auth/refresh を使います。

実装前に押さえておきたい仕様
  • redirect_uri は登録済みの値と完全に一致する必要があります(スキーム・ホスト・ポート・パス・末尾のスラッシュまで)。前方一致やワイルドカードは使えません。緩い一致は認可コードが盗まれる典型的な原因だからです。
  • PKCE はすべてのクライアントで必須で、code_challenge_method は S256 のみです。plain は拒否されます。
  • コードは 1 回だけ使えます。2 回目の提示は盗用とみなし、そのコードで作られたセッションを失効させ、ユーザーは再サインインが必要になります。このエラーが出た場合は、まず自アプリ側の再送処理を確認してください。
  • コードの有効期限は 60 秒です。受け取ったらすぐに交換してください。
  • 必要最小限のスコープを要求してください。実際に付与されるのは「要求したスコープ」「クライアントに許可されたスコープ」「ユーザーの権限」の三つの積集合です。余分に要求してもエラーにはならず、超過分は単に無視されます。
  • トークンエンドポイントのエラーは、この API の他の部分で使っている problem+json ではなく、OAuth 標準のエラー形式({ error, error_description })を返します。OAuth クライアントが期待する形式に合わせるためです。
サインアップ不要のアカウント作成(デバイス登録)

会員登録画面が最初の体験としてふさわしくないアプリ向けの方式です。アプリがインストールを登録すると受講者アカウントが作られ、利用者はメールアドレスもパスワードも入力せずにすぐ使い始められます。

クライアントごとの許可制です。アプリを登録しただけでは使えません。組織内にユーザーアカウントを作成できることは別の判断が必要なため、対象のクライアント ID について当社にご依頼ください。

1. 初回起動時に 1 度だけ、インストールを登録します。

POST https://api.manabu2.com/api/v1/devices/register
Content-Type: application/json

{
  "clientId": "wakaroute",
  "deviceId": "<stable per-install identifier, 16+ chars>",
  "platform": "ios"
}
{
  "deviceSecret": "mnbd_…",
  "isNewAccount": true,
  "auth": {
    "accessToken": "eyJhbGciOi…",
    "refreshToken": "3pQ7…",
    "scopes": ["read:catalog", "read:content", "read:progress", "write:progress"],
    "user": { "id": "…", "email": "", "displayName": "" }
  }
}
deviceSecret は何よりも先に Keychain / Keystore へ保存してください。表示されるのはこのレスポンスの中だけで、このインストールがアカウントの持ち主であることを証明できる唯一の値です。失うと利用者は学習履歴を取り戻せません。

2. リフレッシュトークンを失い、シークレットだけが残っている場合に使います。リフレッシュトークンが手元にあるうちは POST /api/v1/auth/refresh を使ってください。

POST https://api.manabu2.com/api/v1/devices/token

{ "clientId": "wakaroute", "deviceId": "…", "deviceSecret": "mnbd_…" }

3. 任意です。学習履歴が貯まってきた頃に案内するのが適しています。メールアドレスを紐づけると機種変更後もアカウントを引き継げます。ユーザー ID は変わらないため、それまでの記録はすべてそのまま残ります。

POST https://api.manabu2.com/api/v1/me/link
Authorization: Bearer <accessToken>

{ "email": "student@example.com", "password": "…", "displayName": "…" }
  • デバイス ID は識別のためのラベルであり、認証情報ではありません。認証するのはシークレットのほうです。デバイス ID だけを提示しても 401 になります。デバイス ID は端末から読み取れ、再インストールで変わるためです。
  • 同じデバイス ID で再登録すると、新しいシークレットとともに同じアカウントが返ります。空のアカウントが増えることはないため、登録済みか不明なときも安全に呼べます。
  • デバイス登録で作られた受講者は、組織の有料シートを消費しません。
  • 登録は IP あたり 1 分 5 回までです。実際の端末は生涯に 1 度しか登録しないため、影響を受けるのはテストと不正利用だけです。
  • 連携は 1 回だけ行えます。すでにメールアドレスがあるアカウントでは 409 already_linked が返ります。アドレスの変更は確認手順を伴う別の操作です。
アクセストークンの更新

refreshToken を送ると、新しい accessToken と新しい refreshToken が返ります。保存している refreshToken は必ず新しいものに置き換えてください。

POST https://api.manabu2.com/api/v1/auth/refresh
Content-Type: application/json

{ "refreshToken": "3pQ7…" }
refreshToken は 1 回しか使えません。更新のたびに新しい refreshToken が返るので、必ず保存して次回はそちらを使ってください。使用済みのものを再送した場合、そのリクエストは 401 refresh_token_reused で拒否されますが、セッション自体は有効なままです。最新の refreshToken で再試行してください。
ログアウト

そのセッションを終了します。accessToken の期限が切れた後でも実行できるよう認証は不要で、常に 204 を返します(トークンの存在確認に使われないようにするためです)。すでに発行済みの accessToken は失効せず、期限切れ(最大 60 分)まで有効なままである点にご注意ください。

POST https://api.manabu2.com/api/v1/auth/logout
Content-Type: application/json

{ "refreshToken": "3pQ7…" }
API キーの発行

API キーの発行・失効は、組織管理者または開発者のトークンでのみ行えます。API キー自身が別のキーを発行することはできません(キーが漏洩した場合に、後継キーを作られて失効が意味を失うのを防ぐためです)。

curl を使わずに、管理画面の「開発者 → API キー」からも発行・失効できます。組織管理者と開発者のどちらでも発行できますが、組織全体を対象とするスコープを付けられるのは組織管理者だけです。
POST https://api.manabu2.com/api/v1/organizations/{organizationId}/api-keys
Authorization: Bearer <admin-jwt>
Content-Type: application/json

{ "name": "Zapier integration", "scopes": ["read:catalog", "read:content"] }
{
  "key": "mnb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "warning": "Store this key now. It is hashed on the server and cannot be shown again.",
  "apiKey": { "id": "…", "maskedKey": "mnb_live_…a1b2", "scopes": ["read:catalog","read:content"] }
}
キー本体が返るのは発行時の 1 回だけです。サーバー側にはハッシュのみを保存するため、後から再表示することはできません。紛失した場合は失効させて再発行してください。

2. スコープ

スコープは、その資格情報が「何を試せるか」の上限です。スコープを満たしていても、参照できるのは自組織のコンテンツ(および公開コンテンツ)に限られます。

スコープ 許可される操作 受講者トークン
read:catalogコース・パス・修了証の一覧と詳細の取得
read:contentレッスン本文・教材・小テスト問題の取得
read:progress本人の進捗・アサイン・修了証の取得
write:progressレッスンの閲覧・完了の記録、小テストの提出
read:members組織メンバーの取得
write:members組織メンバーの作成・更新・無効化
write:contentコースやレッスン、テストなどの教材を作成・編集する。カリキュラム管理者のロールから付与される。
read:reports集計レポート・インサイトの取得
webhooks:manageWebhook 購読の作成・管理

webhooks:manage は定義済みですが、対応するエンドポイントは未提供です(付与しても現時点では効果はありません)。read:members / write:members / read:reports は上記の組織管理エンドポイントで利用できます。

スコープには二つの上限があります。ひとつは利用者本人のロール、もうひとつはクライアントに許可されたスコープです。実際に発行されるのはその積集合で、どちらかが欠けていれば付与されません。MCP のように利用者側で自動登録されるクライアントは受講者スコープが上限で、それ以上は組織管理者が「開発者 → アプリケーション → 接続済みのクライアント」から組織ごとに許可します。

3. 参照できる範囲

コンテンツの可視性は次のルールで決まります。マルチテナントの境界そのものなので、一読をおすすめします。

対象 参照できる条件
コース公開済みであり、かつ「一般公開」または「自組織が所有」であること
学習パス「一般公開」または「自組織が所有」であること
レッスン・教材公開済みであり、かつ所属コースを参照できること
修了証自組織が所有していること(修了証に一般公開の概念はありません)
  • 下書き(未公開)のコースやレッスンは、所有組織のメンバーであっても API からは一切返りません。オーサリングは管理画面で行ってください。
  • 404 は「存在しない」と「参照権限がない」の両方を意味します。存在の有無自体が情報になるため、意図的に区別していません。
  • コンテンツは言語ごとに作成され、言語間のフォールバックはありません。ja-JP のコースは en-US では返りません。

4. エンドポイント

カタログ・コンテンツ(参照)
メソッド URL 必要スコープ 概要
GET/api/v1/coursesread:catalogコース一覧(ページング・言語・全文検索に対応)
GET/api/v1/courses/{id}read:catalogコース詳細(セクションと公開済みレッスンを含む)
GET/api/v1/lessons/{id}read:contentレッスン詳細(本文・動画・スライド・教材・小テスト)
GET/api/v1/materials/{id}read:content教材の取得
GET/api/v1/pathsread:catalog学習パス一覧
GET/api/v1/paths/{id}read:catalog学習パス詳細(受講順のコース一覧)
GET/api/v1/certificatesread:catalog自組織の修了証一覧
受講者の進捗(参照・記録)

これらは「本人」を前提とするため、受講者トークンが必要です。API キーで呼び出すと 403 を返します。

メソッド URL 必要スコープ 概要
GET/api/v1/meread:progressサインイン中の受講者と所属組織
GET/api/v1/me/progressread:progress受講中コースごとの進捗
GET/api/v1/me/progress/{courseId}read:progress指定コースの進捗(レッスン単位の内訳)
GET/api/v1/me/quiz-attemptsread:progress自分の過去のクイズ受験結果(得点・合否・日時)。新しい順。from / to で日付範囲を指定できます(日本時間の暦日、両端を含む)。
GET/api/v1/me/test-attemptsread:progress自分の過去の確認テスト受験結果。新しい順。制限時間つきのテストでは、時間内に終わったかどうかも返します(判定できない場合は null)。
GET/api/v1/me/pathsread:progressアサインされた学習パス
GET/api/v1/me/certificatesread:progress取得済みの修了証
GET/api/v1/me/assignmentsread:progressアサインされたコース(直接またはパス経由)
GET/api/v1/me/goalsread:progress学習者の目標(志望校・志望大学など)を志望順に取得
PUT/api/v1/me/goalswrite:progress目標リストを丸ごと置き換える(並び順=志望順)
GET/api/v1/me/target-schoolsread:progress志望校を志望順に取得(ワカルート用の別名)
PUT/api/v1/me/target-schoolswrite:progress志望校リストを丸ごと置き換える
POST/api/v1/me/study-sessions/startwrite:progress学習タイマーを開始(サーバーが時刻を打刻)
POST/api/v1/me/study-sessions/{sessionId}/stopwrite:progress学習タイマーを停止。上限超過分は abandoned として計上しない
POST/api/v1/me/study-sessionswrite:progress終了済みセッションを記録(オフライン再送用。clientSessionId で重複防止)
GET/api/v1/me/study-sessionsread:progress学習履歴を新しい順に取得(何を・いつ)
GET/api/v1/me/study-summaryread:progressカレンダー用の日次・月次集計(0 の日も含む密な系列)
GET/api/v1/me/study-streakread:progress連続学習日数(端末を変えても保持)
DELETE/api/v1/mewrite:progressアカウントを削除(App Store 5.1.1(v) 対応。端末登録も消え、次回起動で新規アカウントになる)
POST/api/v1/lessons/{id}/viewwrite:progressレッスンを閲覧したことを記録(冪等)
POST/api/v1/lessons/{id}/completewrite:progressレッスンを完了として記録し、更新後の進捗を返す
POST/api/v1/lessons/{id}/quiz/submitwrite:progress小テストの解答を提出。採点はサーバー側で行い、合格するとレッスンも完了になります
認証・セッション

受講者トークンのライフサイクル。API キーには適用されません(API キーに期限切れやセッションの概念はなく、失効は組織管理者が行います)。

メソッド URL 必要な資格情報 概要
POST/api/v1/auth/loginメールアドレスとパスワードで認証し、accessToken と refreshToken を返す
POST/api/v1/auth/refreshrefreshTokenrefreshToken を新しい accessToken + refreshToken に交換(1 回限り・再利用検知あり)
POST/api/v1/auth/logoutrefreshTokenそのセッションを終了(冪等、常に 204)
POST/api/v1/auth/logout-allaccessTokenすべての端末のセッションを終了
GET/api/v1/auth/sessionsaccessToken有効なセッションの一覧(作成日時・端末情報)
組織管理(メンバー・部署・アサイン・レポート)

人事システムとの連携用です。組織管理者または HR の受講者トークン、あるいは該当スコープを付与した組織 API キーが必要です。いずれの場合も、ルートで指定した組織に対する権限が別途確認されます。

メソッド URL 必要スコープ 概要
GET/api/v1/organizations/{orgId}/membersread:membersメンバー一覧(招待中を含む)
GET/api/v1/organizations/{orgId}/members/{userId}read:membersメンバー 1 件の取得
POST/api/v1/organizations/{orgId}/memberswrite:membersメールアドレスで招待(シートを 1 つ消費)
PUT/api/v1/organizations/{orgId}/members/{userId}/roleswrite:membersロールを一括更新(送信しなかったロールは解除されます)
DELETE/api/v1/organizations/{orgId}/members/{userId}write:membersメンバーを削除し、シートを解放
GET/api/v1/organizations/{orgId}/seatsread:membersシートの利用状況(used / total / available)
GET/api/v1/organizations/{orgId}/departmentsread:members部署一覧
POST/api/v1/organizations/{orgId}/departmentswrite:members部署の作成
DELETE/api/v1/organizations/{orgId}/departments/{deptId}write:members部署の削除(メンバーは組織に残ります)
PUT / DELETE…/departments/{deptId}/members/{userId}write:members部署へのメンバー追加・削除
PUT / DELETE…/members/{userId}/paths/{pathId}write:members学習パスを個人にアサイン・解除
PUT / DELETE…/departments/{deptId}/paths/{pathId}write:members学習パスを部署にアサイン・解除(所属者全員に適用)
GET/api/v1/organizations/{orgId}/progressread:reports組織全体の進捗(メンバー別の明細と集計)
連携時に把握しておくべき挙動: 既存メンバーへの再招待はエラーではなく何もしません(同期を繰り返し実行しても安全です)。シートが不足している場合は 402 seat_limit_reached を返します。招待中のメンバーもシートを消費するため、事前に /seats で残数を確認できます。最後の組織管理者を降格・削除しようとすると 409 last_admin を返します。ロール更新は全項目を送信してください(省略したロールは解除されます)。
API キー管理

組織管理者の受講者トークンが必要です。

メソッド URL 概要
GET/api/v1/organizations/{orgId}/api-keys組織の API キー一覧(失効済みも含む。キー本体は返りません)
POST/api/v1/organizations/{orgId}/api-keysAPI キーを発行(キー本体はこの応答のみ)
DELETE/api/v1/organizations/{orgId}/api-keys/{keyId}API キーを即時失効
教材の作成・編集

いずれも write:content と、対象組織におけるカリキュラム管理者(または管理者)のロールが必要です。トークンにスコープがあるだけでは足りません。

メソッド URL 必要スコープ 概要
POST/api/v1/organizations/{organizationId}/courseswrite:contentコースを作成する
PUT/api/v1/organizations/{organizationId}/courses/{courseId}write:contentコースを更新する
POST/api/v1/organizations/{organizationId}/courses/{courseId}/publishwrite:contentコースの公開状態を切り替える
DELETE/api/v1/organizations/{organizationId}/courses/{courseId}write:contentコースを削除する。配下がある場合は cascade=true が必要
GET/api/v1/organizations/{organizationId}/courses/{courseId}/sectionsread:catalogセクション一覧(下書きを含む)
POST/api/v1/organizations/{organizationId}/courses/{courseId}/sectionswrite:contentセクションを追加する
PUT/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}write:contentセクションを更新する
DELETE/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}write:contentセクションを削除する。レッスンがある場合は cascade=true が必要
GET/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}/lessonsread:contentレッスン一覧(本文は含まない)
POST/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}/lessonswrite:contentレッスンを作成する
PUT/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}/lessons/{lessonId}write:contentレッスン本文を更新する
POST/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}/lessons/{lessonId}/publishwrite:contentレッスンの公開状態を切り替える
DELETE/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}/lessons/{lessonId}write:contentレッスンを削除する
GET/api/v1/organizations/{organizationId}/materialsread:content教材一覧
POST/api/v1/organizations/{organizationId}/materialswrite:content教材を作成する
PUT/api/v1/organizations/{organizationId}/lessons/{lessonId}/materialswrite:content教材をレッスンに紐づける
テスト

受験用の取得では正解は返しません。正解を含むのは組織配下の編集用エンドポイントのみです。

メソッド URL 必要スコープ 概要
GET/api/v1/tests/{testId}read:content受験用にテストを取得する(正解は含まない)
POST/api/v1/tests/{testId}/submitwrite:progress解答を送信し採点結果を受け取る
GET/api/v1/me/testsread:progress自分が受験できるテストと最新結果
GET/api/v1/organizations/{organizationId}/testsread:content組織のテスト一覧
GET/api/v1/organizations/{organizationId}/tests/{testId}write:content正解を含むテストを取得する(編集用)
POST/api/v1/organizations/{organizationId}/testswrite:contentテストを作成する
POST/api/v1/organizations/{organizationId}/tests/{testId}/questionswrite:content設問と選択肢をまとめて追加する
レポート・契約状況

すべて集計値です。個人単位の進捗は read:members が必要な既存のエンドポイントで提供します。契約情報は参照のみで、購入や変更の API はありません。

メソッド URL 必要スコープ 概要
GET/api/v1/organizations/{organizationId}/overviewread:reports組織の概況(席数・受講状況)
GET/api/v1/organizations/{organizationId}/reports/completionread:reportsコース別の修了状況
GET/api/v1/organizations/{organizationId}/reports/departmentsread:reports部署別の修了状況
GET/api/v1/organizations/{organizationId}/reports/trendread:reports月次の修了推移
GET/api/v1/organizations/{organizationId}/subscriptionread:reports契約プランと席数(参照のみ)
検索
メソッド URL 必要スコープ 概要
GET/api/v1/search?q=read:catalogコースとパスを全文検索する
AI 生成

生成結果を返すだけで、保存はしません。内容を確認したうえで作成用エンドポイントに渡してください。呼び出しごとに課金され、読み取り系とは別のレート制限が適用されます。

メソッド URL 必要スコープ 概要
POST/api/v1/organizations/{organizationId}/ai/course-outlinewrite:contentコース構成案を生成する
POST/api/v1/organizations/{organizationId}/ai/lesson-bodywrite:contentレッスン本文を生成する
POST/api/v1/organizations/{organizationId}/ai/quiz-questionswrite:content設問案を生成する(正解を含む)

5. リクエスト例

API キーでコース一覧を取得する例です。

curl -H "X-Api-Key: mnb_live_…" \
     "https://api.manabu2.com/api/v1/courses?culture=ja-JP&pageSize=2"
{
  "items": [
    {
      "id": "0c1f…",
      "title": "AI リテラシー入門",
      "culture": "ja-JP",
      "level": "Beginner",
      "estimatedMinutes": 90,
      "isPublic": true,
      "sectionCount": 4,
      "lessonCount": 12
    }
  ],
  "page": 1,
  "pageSize": 2,
  "totalItems": 137,
  "totalPages": 69
}

6. ページングとフィルタ

一覧系エンドポイントは共通の形式で items / page / pageSize / totalItems / totalPages を返します。

  • page — ページ番号(1 始まり、既定 1)
  • pageSize — 1 ページあたりの件数(既定 20、最大 100。超過分は拒否ではなく 100 に丸められます)
  • culture — コンテンツの言語(ja-JP / en-US)
  • organizationId — 参照できる組織のうち 1 つに絞り込み
  • q — コース名・説明の全文検索

7. エラー

エラーは JSON で返り、error / message / status を含みます。スコープ不足の場合は必要なスコープ名も返します。

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "type": "https://manabu2.com/ja-JP/docs/api#errors",
  "title": "Forbidden",
  "status": 403,
  "detail": "This endpoint requires the 'write:progress' scope.",
  "code": "insufficient_scope",
  "requiredScope": "write:progress"
}
分岐には必ず code を使ってください。detail は人間向けの説明文で、予告なく文言が変わることがあります。エラー応答の Content-Type は application/problem+json(RFC 7807)です。
エラーコード一覧
code ステータス 意味
invalid_request400リクエストの形式が不正(必須項目の欠落など)
invalid_credentials401メールアドレスまたはパスワードが違います
invalid_grant401refreshToken が無効、失効済み、または期限切れ
refresh_token_reused401使用済みの refreshToken が再送されました。セッション全体を失効させています
unauthorized401資格情報がない、または無効です
insufficient_scope403スコープが不足しています(requiredScope に必要なスコープが入ります)
user_credential_required403受講者トークンが必要な操作を API キーで呼び出しました
admin_required403組織管理者のみ実行できます
not_found404存在しない、または参照権限がありません
idempotency_key_reused409この Idempotency-Key は別のリクエストで既に使われています。新しいキーを使ってください
idempotency_key_in_progress409このキーの同一リクエストがまだ処理中です。しばらくしてから再試行してください
rate_limit_exceeded429レート制限を超過しました
HTTP ステータス
ステータス 意味
400リクエストが不正(必須項目の欠落、スコープ指定が空など)
401資格情報がない、無効、失効済み、または期限切れ
403認証は成功したが権限が不足(スコープ不足、または受講者トークンが必要な操作)
404存在しない、または参照権限がない
409競合 — 既に記録されている内容と矛盾しています。多くは Idempotency-Key を別のリクエストで再利用した場合です
429レート制限を超過(Retry-After ヘッダーの秒数だけ待ってから再試行してください)
500サーバー内部エラー

8. レート制限

レート制限は呼び出し元ごとに適用されます。API キー、次に利用者、いずれもない場合は IP アドレス単位です。同じ IP を共有していても、認証済みの呼び出しが匿名アクセスの枠に巻き込まれることはありません。

呼び出し元 上限
API キー / 受講者トークン(認証済み)300 / min
未認証(IP アドレス単位)60 / min
AI 生成 (/api/ai/*)10 / min

上限を超えると 429 を返します。応答には Retry-After と X-RateLimit-* ヘッダーが含まれるため、待つべき秒数を推測する必要はありません。

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1785418481

{
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Retry in 60 seconds.",
  "retryAfterSeconds": 60,
  "status": 429
}

X-RateLimit-* ヘッダーは 429 応答にのみ付与されます。通常の応答に残り回数は含まれません。上限は 1 分単位の固定ウィンドウで、超過分はキューイングされず即座に拒否されます(待たせるより早く失敗させる方が、呼び出し側で扱いやすいためです)。

9. Webhook(イベント通知)

ポーリングの代わりに、イベント発生時に指定の URL へ POST します。組織単位で登録し、webhooks:manage スコープが必要です。

エンドポイントの登録
POST https://api.manabu2.com/api/v1/organizations/{organizationId}/webhooks
X-Api-Key: mnb_live_…
Content-Type: application/json

{
  "url": "https://example.com/hooks/manabu2",
  "eventTypes": ["lesson.completed", "certificate.issued"],
  "description": "HR system sync"
}
{
  "secret": "whsec_…",
  "warning": "Store this signing secret now. It is not shown again.",
  "webhook": { "id": "…", "url": "https://example.com/hooks/manabu2", "isActive": true }
}
署名用シークレットが返るのは登録時の 1 回だけです。再表示はできません。紛失した場合は削除して再登録してください。なお URL は https のみ受け付けます(学習データを含むため)。
イベントの種類
イベント 送信されるタイミング
lesson.completed受講者がレッスンを初めて完了したとき(再完了では送信されません)
certificate.issued修了証が発行されたとき(自動発行・手動発行の両方)
pingテスト送信。/test エンドポイントからのみ発生します

course.started / member.added / subscription.activated は種類としては定義済みですが、まだ発生元が実装されていないため送信されません。eventTypes を省略すると「すべて」の購読となり、今後追加されるイベントも自動的に届きます。

配信されるリクエスト
POST https://example.com/hooks/manabu2
X-Manabu-Event: lesson.completed
X-Manabu-Delivery: 9f2c…
X-Manabu-Timestamp: 1785424800
X-Manabu-Signature: sha256=4a7f…

{
  "id": "96ea88a005cb461798e4659e3d9cb9a8",
  "type": "lesson.completed",
  "createdAt": "2026-07-31T09:20:00Z",
  "organizationId": "…",
  "data": { "userId": "…", "lessonId": "…", "courseId": "…" }
}
署名の検証

署名は HMAC-SHA256 で、対象は「タイムスタンプ + ピリオド + ボディ」です(Stripe や GitHub と同じ形式)。URL を知っている人なら誰でも POST できてしまうため、必ず検証してから処理してください。

// Node.js
const crypto = require("crypto");

function verify(req, secret) {
  const timestamp = req.headers["x-manabu-timestamp"];
  const signature = req.headers["x-manabu-signature"];
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${req.rawBody}`)   // the raw body, not the parsed object
    .digest("hex");

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
検証には JSON をパースする前の生のボディを使ってください。パース後に再シリアライズすると空白やキー順が変わり、署名が一致しません。比較は timingSafeEqual など定数時間の関数で行ってください。タイムスタンプも署名対象に含まれているため、古いリクエストの再送を検知できます。
リトライ

2xx 以外を返した場合、または応答がない場合は再送します。

  • 最大 6 回、30 秒 → 2 分 → 10 分 → 30 分 → 1 時間と間隔を広げながら再送し、約 2 時間で打ち切ります。
  • 成功と見なすのは 2xx のみです。処理に時間がかかる場合は、まず 200 を返してから非同期で処理してください(応答は 10 秒でタイムアウトします)。
  • 同じイベントが複数回届く可能性があります。ペイロードの id は再送しても同一なので、これを使って重複処理を避けてください。
  • 配信結果は /deliveries で確認でき、失敗したものは /replay で再送できます。
管理エンドポイント
メソッド URL 概要
GET/api/v1/webhook-events購読可能なイベント種類の一覧
GET…/organizations/{orgId}/webhooks登録済みエンドポイントの一覧(シークレットは含まれません)
POST…/organizations/{orgId}/webhooksエンドポイントを登録し、署名シークレットを返す
DELETE…/webhooks/{id}エンドポイントと配信履歴を削除
POST…/webhooks/{id}/testping を送信して疎通確認(そのエンドポイントにのみ送信されます)
GET…/webhooks/{id}/deliveries直近の配信履歴(試行回数・HTTP ステータス・次回再送予定)
POST…/webhooks/deliveries/{id}/replay失敗した配信を再送

すべて webhooks:manage スコープが必要です。登録・削除・テスト・再送は組織管理者または HR の権限も必要です。

MCP(AI クライアントからの接続)

同じデータを、AI クライアントからツールとして扱えます。認証は OAuth のみで、API キーは使えません。

エンドポイント https://api.manabu2.com/mcp
1. 認証 Authorization: Bearer <jwt>

Streamable HTTP(Mcp-Session-Id ヘッダー)と、従来の HTTP+SSE の両方に対応しています。クライアントが対応している方が使われます。

見えるツールはトークンのスコープで決まります。使えないツールは tools/list に出てきません。広告しておいて拒否するより、見せない方が確実です。

組織ごとの権限

スコープは「どこかで編集してよい」ことしか表しません。ある組織のカリキュラム管理者は他の組織でも write:content を持つため、変更を伴うツールは実行のたびに対象組織での役割を確認し、記録します。

自分で登録したクライアントは、はじめは受講者向けのスコープしか持ちません。教材の編集や割り当てを任せる場合は、組織管理者が「開発者 → アプリ → 接続済みのクライアント」で明示的に許可します。
エラーコード

ツールのエラーは JSON-RPC の error に data.code として安定した文字列を返します。

data.code
invalid_request引数が足りない、または値が不正です。
not_found存在しないか、権限がないため見えません。両者を区別しません。
forbiddenその組織での役割が足りません。
insufficient_scopeトークンにそのスコープがありません。
unknown_toolそのツールはありません。
user_credential_requiredAPI キーでは使えません。サインインして接続してください。

MCP(AI クライアントからの接続) →

10. 冪等性(Idempotency)

リクエストがタイムアウトしても、サーバー側で処理が実行されたかどうかは分かりません。書き込み系のリクエストに Idempotency-Key ヘッダーを付けると、安全に再送できます。最初の呼び出しだけが実際に処理を行い、同じリクエストの再送には最初の応答がそのまま返されます。特に重要なのはクイズの提出で、これがないと再送のたびに採点対象の受験履歴が新たに記録されてしまいます。

POST https://api.manabu2.com/api/v1/lessons/{lessonId}/quiz/submit
Authorization: Bearer <jwt>
Idempotency-Key: 9f1c2d4e-7a3b-4c11-9e55-2b8d0f6a1c37
Content-Type: application/json

{ "answers": [ { "questionId": "q1", "choiceId": "c3" } ] }

再送時には、ステータスコードも本文も最初の応答とまったく同じものが返り、次のヘッダーが追加されます。

HTTP/1.1 200 OK
Idempotency-Replayed: true
送信内容 結果
同じキー・同じリクエスト最初の応答(Idempotency-Replayed: true 付き)
同じキーで本文やパスが異なる409 idempotency_key_reused
最初の呼び出しの処理中に同じキーで再送409 idempotency_key_in_progress
最初の呼び出しが 5xx で失敗した結果は保存されないため、再送は実際に処理されます(一時的な障害が恒久的なエラーとして固定されることはありません)
  • 任意です。ヘッダーを付けない場合の動作はこれまでと変わりません。POST・PUT・PATCH で有効です。GET と DELETE はもともと再実行しても安全なため、指定しても無視されます。
  • キーは 24 時間保持されます。それ以降は同じ値を別のリクエストに再利用できます。
  • キーは認証情報ごとに管理されるため、他のお客様のキーと衝突することはありません。操作ごとに新しい値(UUID を推奨)を使ってください。
  • /auth 系のエンドポイントでは利用できません。発行したトークンを保存しないためです。リフレッシュについては、トークンファミリーによる再利用検知ですでに保護されています。

11. 現時点で未提供の機能

探しても見つからないものを明記しておきます。

  • プラン別のクォータ(無料 / 有料)と利用量の永続的な集計・課金連携は未実装です。上記のレート制限は適用されますが、月間の使用量に応じた制限はありません。
  • Webhook を管理する画面はまだありません(API からのみ登録・削除できます)。また配信ワーカーは単一インスタンス前提のため、API をスケールアウトすると重複配信の可能性があります。
  • 組織・メンバー管理エンドポイントは未提供です(スコープのみ予約済み)。
  • URL にバージョンは含まれていません。バージョニング方針は検討中で、後から変更する場合は破壊的変更になります。
  • 各言語向けの SDK と Postman コレクションは未提供です。OpenAPI JSON からの生成をご利用ください。
連携についてご相談がある場合は お問い合わせください。

次: Webhook ドキュメントへ進む →