---
name: Sonogo API Integration Guide
description: Sonogo (sono-5.com) の全機能を JSON で叩ける REST API のリファレンス。リード登録 (`POST /api/v1/lead-submissions`) から顧客・資料・閲覧セッション・メール・通知・チーム・Webhook の CRUD まで、`https://sono-5.com/api/v1/*` をすべてカバー。「Sonogo API 連携」「sono-5.com/api」「リードフォーム連携」「Sonogo Webhook 受信」「Sonogo データ連携」を扱う全プロジェクトで参照。
type: reference
---

# Sonogo API 統合ガイド

## このガイドの役割

Sonogo (sono-5.com) の **全機能を JSON で叩ける REST API** のリファレンス。外部サイト・LP からのリード送り込みだけでなく、顧客・資料・閲覧セッション・メール・通知・チーム・Webhook を読み書きできる。Sonogo を SOR (system of record) として扱う BI / ETL / 自動化バックエンドのベースとして使う。

API キー発行は Sonogo の `/settings/api`、Webhook 登録は `/settings/notifications` または `/api/v1/webhooks` で行う前提。

---

## 概要

| 項目 | 値 |
|---|---|
| ベース URL | `https://sono-5.com` (本番) |
| 形式 | JSON (request/response) |
| 認証 | API キー (Bearer) のみ |
| HTTPS | 必須 |
| ページネーション | cursor-based (`next_cursor`) |

**注意:** API キーは絶対にブラウザ JavaScript に公開しないでください。自社サイトから利用する場合は **自社バックエンド経由** で叩く構成にしてください。

```
[ユーザー] → [自前フォーム] → [自社バックエンド] → [Sonogo API]
                                  ↑
                          API キーをここで保持
```

---

## 認証

API キー認証のみ。`/settings/api` で発行したキー (`sk_live_...` 形式) を `Authorization` ヘッダに Bearer token として付けてください。API キーはチーム単位で発行され、当該チームのリソースのみアクセスできます。

```
Authorization: Bearer sk_live_abcdef1234567890...
```

---

## レート制限

- **GET (読み取り系): API キー単位で 120 req/min**
- **POST/PATCH/PUT/DELETE (書き込み系): API キー単位で 60 req/min**
- `/api/v1/lead-submissions`: API キー単位で 60 req/min (個別エンドポイント上書きなし)
- `/api/v1/webhooks/:id/test`: API キー単位で 10 req/min (誤発火防止のため低め)
- ウィンドウは sliding window (60 秒)。超過時は `429 Too Many Requests` + `Retry-After` ヘッダを返します

---

## 共通 fetch ヘルパー

どのエンドポイントも同じ認証 / エラーハンドリングなので、薄いラッパを 1 つ書けば全エンドポイントで使い回せる。

```ts
const BASE_URL = "https://sono-5.com"
const API_KEY = process.env.SONOGO_API_KEY!

type SonogoOptions = {
  method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE"
  query?: Record<string, string | number | boolean | undefined>
  body?: unknown
}

export async function sonogo<T = unknown>(path: string, opts: SonogoOptions = {}): Promise<T> {
  const { method = "GET", query, body } = opts

  const url = new URL(`${BASE_URL}${path}`)
  if (query) {
    for (const [k, v] of Object.entries(query)) {
      if (v !== undefined) url.searchParams.set(k, String(v))
    }
  }

  const headers: Record<string, string> = {
    "Authorization": `Bearer ${API_KEY}`,
  }
  if (body !== undefined) headers["Content-Type"] = "application/json"

  const res = await fetch(url.toString(), {
    method,
    headers,
    body: body !== undefined ? JSON.stringify(body) : undefined,
  })

  const data = await res.json().catch(() => ({}))
  if (!res.ok) {
    const err = new Error(data?.error ?? `HTTP ${res.status}`)
    ;(err as any).status = res.status
    ;(err as any).details = data?.details
    throw err
  }
  return data as T
}
```

使用例:

```ts
// リスト
const { customers, next_cursor } = await sonogo<{ customers: any[]; next_cursor: string | null }>(
  "/api/v1/customers",
  { query: { limit: 100, search: "yamada" } },
)

// 作成
const created = await sonogo("/api/v1/customers", {
  method: "POST",
  body: { name: "山田太郎", email: "yamada@example.com" },
})

// 更新
await sonogo(`/api/v1/customers/${id}`, {
  method: "PATCH",
  body: { notes: "更新メモ" },
})

// リード登録 (最頻出)
await sonogo("/api/v1/lead-submissions", {
  method: "POST",
  body: {
    source: "inquiry",
    customer: { name: "山田太郎", email: "yamada@example.com" },
    payload: { subject: "料金について", message: "..." },
    tags: ["問合せ"],
  },
})
```

---

## エンドポイント一覧 (ドメイン別カタログ)

各エンドポイントの詳細スキーマは OpenAPI YAML (`/docs/sonogo-api.yaml`) を参照。ここではメソッド + パス + 概要のみを列挙する。

### Lead Submissions

リード (顧客 + 送信イベント) 登録。外部 LP / 問合せフォーム → Sonogo の最短経路。

- `POST   /api/v1/lead-submissions` — リードを登録

<details><summary>curl 例: POST /api/v1/lead-submissions</summary>

```bash
curl -X POST https://sono-5.com/api/v1/lead-submissions \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "source": "inquiry",
    "customer": {
      "name": "山田太郎",
      "email": "yamada@example.com",
      "company_name": "株式会社サンプル"
    },
    "payload": {
      "subject": "料金について",
      "message": "詳しい料金を知りたいです"
    },
    "context": {
      "utm_source": "google",
      "utm_campaign": "spring-2026"
    },
    "tags": ["問合せ"]
  }'
```

</details>

### Customers

顧客の CRUD と顧客タイムライン (送信履歴・閲覧・メール) 取得。

- `GET    /api/v1/customers` — 顧客一覧
- `POST   /api/v1/customers` — 顧客を作成
- `GET    /api/v1/customers/{id}` — 顧客を取得
- `PATCH  /api/v1/customers/{id}` — 顧客を更新
- `DELETE /api/v1/customers/{id}` — 顧客を削除
- `GET    /api/v1/customers/{id}/tags` — 顧客のタグ一覧
- `PUT    /api/v1/customers/{id}/tags` — 顧客のタグを置換
- `GET    /api/v1/customers/{id}/timeline` — 顧客タイムライン

<details><summary>curl 例: GET /api/v1/customers</summary>

```bash
curl https://sono-5.com/api/v1/customers?limit=50&search=yamada \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Customer Tags

顧客タグの定義 CRUD。タグの付与は `/customers/{id}/tags` で行う。

- `GET    /api/v1/customer-tags` — 顧客タグ一覧
- `POST   /api/v1/customer-tags` — 顧客タグを作成
- `PATCH  /api/v1/customer-tags/{id}` — 顧客タグを更新
- `DELETE /api/v1/customer-tags/{id}` — 顧客タグを削除

<details><summary>curl 例: GET /api/v1/customer-tags</summary>

```bash
curl https://sono-5.com/api/v1/customer-tags \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Documents

資料 (PDF) のメタ CRUD とファイル差し替え履歴 (versions)。

- `GET    /api/v1/documents` — 資料一覧
- `POST   /api/v1/documents` — 資料を作成
- `GET    /api/v1/documents/{id}` — 資料を取得
- `PATCH  /api/v1/documents/{id}` — 資料を更新
- `DELETE /api/v1/documents/{id}` — 資料を削除
- `GET    /api/v1/documents/{id}/versions` — 資料のバージョン一覧
- `GET    /api/v1/documents/{id}/tags` — 資料のタグ一覧
- `PUT    /api/v1/documents/{id}/tags` — 資料のタグを置換

<details><summary>curl 例: GET /api/v1/documents</summary>

```bash
curl https://sono-5.com/api/v1/documents?limit=50 \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Document Tags

資料タグの定義 CRUD。タグの付与は `/documents/{id}/tags` で行う。

- `GET    /api/v1/document-tags` — 資料タグ一覧
- `POST   /api/v1/document-tags` — 資料タグを作成
- `PATCH  /api/v1/document-tags/{id}` — 資料タグを更新
- `DELETE /api/v1/document-tags/{id}` — 資料タグを削除

<details><summary>curl 例: GET /api/v1/document-tags</summary>

```bash
curl https://sono-5.com/api/v1/document-tags \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Sessions

viewer での資料閲覧セッションとページ別滞在時間。

- `GET    /api/v1/sessions` — 資料閲覧セッション一覧
- `GET    /api/v1/sessions/{id}` — 閲覧セッションを取得

<details><summary>curl 例: GET /api/v1/sessions</summary>

```bash
curl https://sono-5.com/api/v1/sessions?limit=50&document_id=4f8c9b2a-... \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Email Accounts

個別メール (IMAP/SMTP) アカウントの読み取り (パスワード等は含まない)。

- `GET    /api/v1/email-accounts` — メールアカウント一覧

<details><summary>curl 例: GET /api/v1/email-accounts</summary>

```bash
curl https://sono-5.com/api/v1/email-accounts \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Email Threads

個別メールのスレッド一覧 / 詳細 (メッセージ含む)。

- `GET    /api/v1/email-threads` — メールスレッド一覧
- `GET    /api/v1/email-threads/{id}` — メールスレッドを取得 (本文含む)

<details><summary>curl 例: GET /api/v1/email-threads</summary>

```bash
curl https://sono-5.com/api/v1/email-threads?limit=50&customer_id=9f6c1b58-... \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Email Messages

個別メールのメッセージ単位の取得 / 既読化。

- `GET    /api/v1/email-messages/{id}` — メールメッセージを取得
- `PATCH  /api/v1/email-messages/{id}` — メールメッセージを更新 (既読化)

<details><summary>curl 例: GET /api/v1/email-messages/{id}</summary>

```bash
curl https://sono-5.com/api/v1/email-messages/bbbb-... \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Notifications

ログインユーザー宛の通知 (アプリ内通知)。

- `GET    /api/v1/notifications` — 通知一覧
- `GET    /api/v1/notifications/unread-count` — 未読通知数を取得
- `PATCH  /api/v1/notifications/{id}` — 通知を既読/未読化
- `POST   /api/v1/notifications/mark-all-read` — 通知を全件既読化

<details><summary>curl 例: GET /api/v1/notifications</summary>

```bash
curl https://sono-5.com/api/v1/notifications?is_read=false&limit=50 \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Team

現在のチーム情報・メンバー・招待。

- `GET    /api/v1/team` — 現在のチーム情報
- `GET    /api/v1/team/members` — チームメンバー一覧
- `GET    /api/v1/team/invites` — 招待中の一覧
- `POST   /api/v1/team/invites` — チーム招待を発行
- `DELETE /api/v1/team/invites/{token}` — 招待を取り消し

<details><summary>curl 例: GET /api/v1/team</summary>

```bash
curl https://sono-5.com/api/v1/team \
  -H "Authorization: Bearer sk_live_..."
```

</details>

### Webhooks

Sonogo → 外部 URL の Webhook 通知設定の CRUD と動作確認。

- `GET    /api/v1/webhooks` — Webhook 一覧
- `POST   /api/v1/webhooks` — Webhook を登録
- `GET    /api/v1/webhooks/{id}` — Webhook を取得
- `PATCH  /api/v1/webhooks/{id}` — Webhook を更新
- `DELETE /api/v1/webhooks/{id}` — Webhook を削除
- `POST   /api/v1/webhooks/{id}/test` — Webhook 動作確認

<details><summary>curl 例: GET /api/v1/webhooks</summary>

```bash
curl https://sono-5.com/api/v1/webhooks \
  -H "Authorization: Bearer sk_live_..."
```

</details>

---

## 最頻出ユースケース: リード登録

`POST /api/v1/lead-submissions` は外部 LP / 問合せフォーム → Sonogo の最短経路。同一 team 内で email が一致する顧客があれば再利用、無ければ新規作成する (顧客タイムラインに「問い合わせ」「資料 DL」として表示)。

### リクエスト型

```ts
type LeadSubmissionRequest = {
  /** 流入種別 (必須)。複数フォームを区別したいときは tags で分ける */
  source: "document_download" | "inquiry" | "webinar" | "newsletter" | "trial" | "custom" | "manual"

  customer: {
    name: string                  // 必須、最大 200
    email?: string                // 推奨 (重複検出キー)
    phone?: string
    company_name?: string
    fields?: Record<string, unknown>  // 拡張フィールド (恒久的な顧客属性)
  }

  payload?: {
    subject?: string              // 件名 (問合せ等)
    message?: string              // 本文 (最大 10000)
    document_id?: string          // 資料 ID (指定時はトラッキングリンクも自動発行)
    [key: string]: unknown        // チャネル固有データ自由 (送信単位で残る)
  }

  context?: {
    utm_source?: string
    utm_medium?: string
    utm_campaign?: string
    utm_term?: string
    utm_content?: string
    referrer?: string
    landing_url?: string
  }

  tags?: string[]                 // 最大 20 個。未存在は自動作成
}
```

### customer.fields vs payload の使い分け (重要)

| 種類 | 入れ場所 | 例 | 永続性 |
|---|---|---|---|
| 顧客の固定属性 | `customer.fields` | 部署・業種・従業員数・予算 | 顧客プロフィールに恒久保存。UI 編集可。`department` 等の既知 key は日本語ラベル化 |
| その送信特有の情報 | `payload` | 件名・本文・希望日時・参照元 | `lead_submissions.payload` に audit 保存。タイムラインに出るのは `subject`/`message` のみ |

「次回も同じ顧客で活きる情報か?」で判断する。**部署・業種は次回も同じだから fields。問い合わせ内容は毎回違うから payload。** どちらに入れても、Slack/メール/Webhook の通知本文には自動的に含まれます。

### Next.js Route Handler 例

```ts
// app/api/contact/route.ts (自社サイト側バックエンド)
export async function POST(request: Request) {
  const body = await request.json()

  await sonogo("/api/v1/lead-submissions", {
    method: "POST",
    body: {
      source: "inquiry",
      customer: {
        name: body.name,
        email: body.email,
        company_name: body.company,
        fields: { department: body.department, industry: body.industry },
      },
      payload: {
        subject: body.subject,
        message: body.message,
      },
      context: {
        utm_source: body.utm_source,
        referrer: request.headers.get("referer") ?? undefined,
      },
      tags: ["お問い合わせ"],
    },
  })

  return Response.json({ ok: true })
}
```

---

## Webhook 受信実装

Sonogo は管理画面または `POST /api/v1/webhooks` で登録した URL に対してイベント発生時に POST する。

### サポートイベント

- `lead_created` — リード作成 (API・フォーム経由含む)
- `viewing` — 資料閲覧開始
- `completed` — 資料完読
- `email_opened` — メルマガ開封
- `email_clicked` — メルマガ内リンククリック
- `email_bounced` — メルマガ配信失敗
- `email_unsubscribed` — メルマガ配信停止
- `email_complained` — メルマガ迷惑メール報告
- `email_replied` — 個別メール返信
- `imap_opened` — 個別メール開封
- `imap_clicked` — 個別メール内リンククリック
- `imap_watched_sender` — 監視リストに登録した送信者から個別メール受信
- `imap_scheduled_sent` — 予約していた個別メールの送信が完了
- `imap_scheduled_failed` — 予約していた個別メールの送信が失敗

### ペイロード形式

```
POST <your-webhook-url>
Content-Type: application/json
User-Agent: Sonogo-Webhook/1.0
X-Sonogo-Signature: sha256=<hex>   (汎用 webhook で secret 設定時のみ)

{
  "event": "lead_created",
  "occurred_at": "2026-05-02T10:30:00Z",
  "team_id": "uuid",
  "organization_id": "uuid",
  "data": {
    "title": "山田太郎 さんからお問い合わせがありました",
    "body": "株式会社サンプル / yamada@example.com",
    "action_url": "https://sono-5.com/customers?customerId=<id>&drawer=timeline",
    "customer": {
      "id": "uuid",
      "name": "山田太郎",
      "email": "yamada@example.com",
      "phone": "090-0000-0000",
      "company_name": "株式会社サンプル"
    },
    "assignee": "営業 花子",
    "document_id": null,
    "email_id": null,
    "email_message_id": null
  }
}
```

### 署名検証 (Node.js)

シークレット設定時のみ `X-Sonogo-Signature` ヘッダが付く。受信側で同じシークレットで HMAC SHA256 を再計算し timing-safe 比較する。

```ts
import crypto from "crypto"

function verifySignature(rawBody: string, header: string | null, secret: string): boolean {
  if (!header) return false
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex")
  if (header.length !== expected.length) return false
  return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected))
}

// Next.js Route Handler
export async function POST(request: Request) {
  const rawBody = await request.text()
  const sig = request.headers.get("x-sonogo-signature")
  if (!verifySignature(rawBody, sig, process.env.SONOGO_WEBHOOK_SECRET!)) {
    return new Response("invalid signature", { status: 401 })
  }
  const payload = JSON.parse(rawBody)
  // 業務処理...
  return Response.json({ ok: true })
}
```

### 配信ポリシー (重要)

- **Discord / Microsoft Teams / Google Chat の URL は自動判別**: それぞれのリッチメッセージ形式に整形して送信される (汎用 JSON ではない)
- **汎用 JSON**: 上記以外の URL (Zapier / Make / n8n / 自前サーバー) には上記の `event` / `data` 構造で POST
- **fire-and-forget**: Sonogo はレスポンス本文を読まない。5 秒タイムアウト
- **リトライなし**: 失敗時は `last_status` / `last_error` に記録されるだけ (Sonogo 側の運用観測のみ)
- **順序保証なし**: 同一 customer の複数イベントが順不同で届きうる
- **冪等性**: 受信側で `event` + `occurred_at` + `customer.id` 等の組合せでデデュプ推奨
- **3xx リダイレクト禁止**: Sonogo 側で reject される
- **localhost / プライベート IP 禁止**: SSRF 対策で reject される。Public な URL を登録すること
- **Slack URL は登録不可**: `hooks.slack.com/...` は弾かれる。Slack 連携は OAuth (ワンクリック接続) を使うこと

---

## エラー仕様

エラー時は HTTP ステータス + JSON ボディを返す:

```json
{
  "error": "Validation error",
  "details": {
    "customer.email": ["Invalid email"]
  }
}
```

| Status | 意味 |
|---|---|
| 400 | リクエスト不正 (JSON パース失敗 / バリデーションエラー) |
| 401 | API キー無効 / 失効 |
| 403 | クォータ超過 (link quota など) |
| 404 | フォーム / 資料 / 対象リソース未発見 |
| 410 | フォームが無効化済み (アーカイブ等) |
| 429 | レート制限超過 (`Retry-After` ヘッダ参照) |
| 500 | サーバ内部エラー (重複時はサポートにご連絡ください) |

---

## 環境変数の置き方

```env
# サーバ側 (絶対にブラウザに公開しない)
SONOGO_API_KEY=sk_live_xxxxxxxxxxxxx

# Webhook 受信側
SONOGO_WEBHOOK_SECRET=任意の長いランダム文字列
```

---

## トラブルシューティング

### 「リードが Sonogo に届かない」

1. **API キーが間違っている**: Sonogo の `/settings/api` で「キーを表示」して比較
2. **`source` 未指定**: 必須
3. **API キーをブラウザから直接送ってる**: バックエンド経由にする
4. **429 レート制限**: 送信ペースを落とす + `Retry-After` を尊重

### 「Webhook が届かない」

1. **`X-Sonogo-Signature` 検証で 401 を返してる**: 受信側で先に rawBody を保持し、parse する前に検証
2. **5 秒以上かかってる**: タイムアウト → fire-and-forget なので Sonogo 側に通知が残らないが配信失敗扱い
3. **HTTPS でない**: 本番環境は https 必須
4. **プライベート IP**: SSRF 対策で reject されている

### 「同じ顧客が複数登録される」

email を必ず指定する。Sonogo は email で team 内デデュプを行う (大文字小文字区別なし)。email 無しだと毎回新規作成になる。

### 「リスト系で `next_cursor` を使った続き取得が要らない場合」

`limit=200` (最大) で 1 回叩けば十分。それ以上必要なら cursor で順次取得する。`next_cursor` が null になるまで while ループ。

---

## 関連スキル

- `meddy-api`: Meddy CMS API 連携 (記事系)
- `sonogo-style-lp`: Sonogo 風 LP デザインガイド (LP 作成側)
