開発者ドキュメント

クイックスタート

API キーの作成から最初のリクエスト、Webhook の安全な受信までを 4 ステップで説明します。

1. API キーを作成する

アカウントコンソールで連携を作成し、API キーを発行します。キーの全文は一度しか表示されないため、シークレットマネージャーに安全に保管してください。

  • アカウント → API & Webhooks で、個人ワークスペース用、または自分が所有するチームスペース用の連携を作成します。
  • 必要なスコープを選択します: notes:readtranscripts:readsummaries:readwebhooks:manage
  • キーは alt_live_{key_id}.{secret} の形式で、一度だけ表示されます。シークレットマネージャーに保管してください。

以下のコマンドをそのまま実行できるよう、API キーをシェルの環境変数に設定します:

shell
export ALT_API_KEY="alt_live_...paste-your-key-here..."

2. 既存のノートを取得する

API が返す cursor を次のリクエストに渡すと、ノート一覧を最後まで続けて取得できます。その後、各ノートの文字起こしと要約を取得します。

curl
curl 'https://public-api.altalt.io/v1/notes?limit=100' \
  -H "Authorization: Bearer $ALT_API_KEY"

# Follow next_cursor until has_more is false
curl 'https://public-api.altalt.io/v1/notes?limit=100&cursor=NEXT_CURSOR' \
  -H "Authorization: Bearer $ALT_API_KEY"

# Fetch content per note (scopes: transcripts:read / summaries:read)
curl 'https://public-api.altalt.io/v1/notes/NOTE_ID/transcript' \
  -H "Authorization: Bearer $ALT_API_KEY"
curl 'https://public-api.altalt.io/v1/notes/NOTE_ID/summary' \
  -H "Authorization: Bearer $ALT_API_KEY"

初回同期後は、すべてのノートを毎回取得する必要はありません。?updated_after=<last sync time> で前回の同期後に変更されたノートだけを取得するか、Webhook を利用してください。

3. Webhook エンドポイントを登録する

新規・変更されたノートを繰り返し問い合わせる代わりに、通知を受け取る公開 HTTPS エンドポイントを登録します。

curl
curl -X POST 'https://public-api.altalt.io/v1/webhook-endpoints' \
  -H "Authorization: Bearer $ALT_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://example.com/webhooks/alt",
    "events": ["note.ended", "note.summary.generated", "note.updated", "note.deleted"]
  }'

レスポンスには Webhook の署名検証に使う signing_secretwhsec_...)が含まれ、全文は一度だけ表示されます。エンドポイントは pending_verification で作成され、受信側が verification イベントに 2xx を返すと有効になります。コンソール からコードを書かずに登録することもできます。

4. Webhook の署名を検証する

すべての Webhook リクエストで Standard Webhooks の署名を検証し、Alt から送信されたことを確認します。event_id で重複を除外し、先に応答してから REST API で最新の内容を取得します。

Node.js

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
import http from "node:http";

// whsec_... secret from endpoint creation (shown once). Keep it server-side.
const SECRET = process.env.ALT_WEBHOOK_SECRET;
const secretBytes = Buffer.from(SECRET.slice("whsec_".length), "base64url");

const TOLERANCE_SECONDS = 300;

function isValidSignature(headers, rawBody) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const signatureHeader = headers["webhook-signature"];
  if (!id || !timestamp || !signatureHeader) return false;

  // Reject stale timestamps (replay protection)
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;

  const expected = createHmac("sha256", secretBytes)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest("base64");

  // Header may contain multiple space-delimited signatures: "v1,abc v1,def"
  return String(signatureHeader)
    .split(" ")
    .some((part) => {
      const [version, signature] = part.split(",");
      if (version !== "v1" || !signature) return false;
      const a = Buffer.from(signature);
      const b = Buffer.from(expected);
      return a.length === b.length && timingSafeEqual(a, b);
    });
}

http
  .createServer((req, res) => {
    if (req.method !== "POST" || req.url !== "/webhooks/alt") {
      res.writeHead(404).end();
      return;
    }
    let rawBody = "";
    req.on("data", (chunk) => (rawBody += chunk));
    req.on("end", () => {
      if (!isValidSignature(req.headers, rawBody)) {
        res.writeHead(401).end();
        return;
      }
      const event = JSON.parse(rawBody);
      // 1. Dedupe on event.event_id (deliveries are at-least-once).
      // 2. Enqueue for async processing, then ack fast.
      // 3. Fetch the note from the REST API; apply only if revision is newer.
      console.log(event.event_type, event.data.note_id, event.data.revision);
      res.writeHead(204).end();
    });
  })
  .listen(3000);

Python

Python
import base64, hashlib, hmac, json, os, time
from http.server import BaseHTTPRequestHandler, HTTPServer

# whsec_... secret from endpoint creation (shown once). Keep it server-side.
raw_secret = os.environ["ALT_WEBHOOK_SECRET"].removeprefix("whsec_")
SECRET = base64.urlsafe_b64decode(raw_secret + "=" * (-len(raw_secret) % 4))

TOLERANCE_SECONDS = 300


def is_valid_signature(headers, raw_body: bytes) -> bool:
    msg_id = headers.get("webhook-id", "")
    timestamp = headers.get("webhook-timestamp", "")
    signature_header = headers.get("webhook-signature", "")
    if not msg_id or not timestamp or not signature_header:
        return False

    # Reject stale timestamps (replay protection)
    if abs(time.time() - float(timestamp)) > TOLERANCE_SECONDS:
        return False

    signed_content = f"{msg_id}.{timestamp}.".encode() + raw_body
    digest = hmac.new(SECRET, signed_content, hashlib.sha256).digest()
    expected = base64.b64encode(digest).decode()

    # Header may contain multiple space-delimited signatures: "v1,abc v1,def"
    for part in signature_header.split(" "):
        version, _, signature = part.partition(",")
        if version == "v1" and signature and hmac.compare_digest(signature, expected):
            return True
    return False


class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        if self.path != "/webhooks/alt":
            self.send_response(404); self.end_headers(); return
        raw_body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
        if not is_valid_signature(self.headers, raw_body):
            self.send_response(401); self.end_headers(); return
        event = json.loads(raw_body)
        # 1. Dedupe on event["event_id"] (deliveries are at-least-once).
        # 2. Enqueue for async processing, then ack fast.
        # 3. Fetch the note from the REST API; apply only if revision is newer.
        print(event["event_type"], event["data"]["note_id"], event["data"]["revision"])
        self.send_response(204); self.end_headers()


HTTPServer(("", 3000), Handler).serve_forever()

署名は Standard Webhooks の仕様に準拠しているため、npm / PyPI 向けの公式 standardwebhooks ライブラリを利用できます。重複イベント、到着順、通知漏れへの対処は Webhooks を参照してください。