# Sonogo API リファレンス

最終更新: 2026-06-03 / バージョン: 1.0

Sonogo の API は **Sonogo の全機能 (顧客・資料・閲覧セッション・メール・通知・チーム・Webhook) を JSON でアクセスできる REST API** です。外部サイトの問合せフォーム / LP / 自社バックエンド / Zapier 等のいずれからも、同じ API キーで叩けます。

- **API ベース URL:** `https://sono-5.com`
- **形式:** JSON (request/response)
- **認証:** API キー (Bearer token)
- **CORS:** 全公開エンドポイントで `Access-Control-Allow-Origin: *`
- **HTTPS 必須** (本番環境)
- **ページネーション:** リスト系は `next_cursor` (cursor-based)。`?cursor=...&limit=...` で次ページ取得

---

## 目次

- [認証](#認証)
- [レート制限](#レート制限)
- [ページネーション](#ページネーション)
- **エンドポイント**
  - [Lead Submissions](#lead-submissions)
    - [POST /api/v1/lead-submissions](#post-apiv1leadsubmissions)
  - [Customers](#customers)
    - [GET /api/v1/customers](#get-apiv1customers)
    - [POST /api/v1/customers](#post-apiv1customers)
    - [GET /api/v1/customers/{id}](#get-apiv1customersid)
    - [PATCH /api/v1/customers/{id}](#patch-apiv1customersid)
    - [DELETE /api/v1/customers/{id}](#delete-apiv1customersid)
    - [GET /api/v1/customers/{id}/tags](#get-apiv1customersidtags)
    - [PUT /api/v1/customers/{id}/tags](#put-apiv1customersidtags)
    - [GET /api/v1/customers/{id}/timeline](#get-apiv1customersidtimeline)
  - [Customer Tags](#customer-tags)
    - [GET /api/v1/customer-tags](#get-apiv1customertags)
    - [POST /api/v1/customer-tags](#post-apiv1customertags)
    - [PATCH /api/v1/customer-tags/{id}](#patch-apiv1customertagsid)
    - [DELETE /api/v1/customer-tags/{id}](#delete-apiv1customertagsid)
  - [Documents](#documents)
    - [GET /api/v1/documents](#get-apiv1documents)
    - [POST /api/v1/documents](#post-apiv1documents)
    - [GET /api/v1/documents/{id}](#get-apiv1documentsid)
    - [PATCH /api/v1/documents/{id}](#patch-apiv1documentsid)
    - [DELETE /api/v1/documents/{id}](#delete-apiv1documentsid)
    - [GET /api/v1/documents/{id}/versions](#get-apiv1documentsidversions)
    - [GET /api/v1/documents/{id}/tags](#get-apiv1documentsidtags)
    - [PUT /api/v1/documents/{id}/tags](#put-apiv1documentsidtags)
  - [Document Tags](#document-tags)
    - [GET /api/v1/document-tags](#get-apiv1documenttags)
    - [POST /api/v1/document-tags](#post-apiv1documenttags)
    - [PATCH /api/v1/document-tags/{id}](#patch-apiv1documenttagsid)
    - [DELETE /api/v1/document-tags/{id}](#delete-apiv1documenttagsid)
  - [Sessions](#sessions)
    - [GET /api/v1/sessions](#get-apiv1sessions)
    - [GET /api/v1/sessions/{id}](#get-apiv1sessionsid)
  - [Email Accounts](#email-accounts)
    - [GET /api/v1/email-accounts](#get-apiv1emailaccounts)
  - [Email Threads](#email-threads)
    - [GET /api/v1/email-threads](#get-apiv1emailthreads)
    - [GET /api/v1/email-threads/{id}](#get-apiv1emailthreadsid)
  - [Email Messages](#email-messages)
    - [GET /api/v1/email-messages/{id}](#get-apiv1emailmessagesid)
    - [PATCH /api/v1/email-messages/{id}](#patch-apiv1emailmessagesid)
  - [Notifications](#notifications)
    - [GET /api/v1/notifications](#get-apiv1notifications)
    - [GET /api/v1/notifications/unread-count](#get-apiv1notificationsunreadcount)
    - [PATCH /api/v1/notifications/{id}](#patch-apiv1notificationsid)
    - [POST /api/v1/notifications/mark-all-read](#post-apiv1notificationsmarkallread)
  - [Team](#team)
    - [GET /api/v1/team](#get-apiv1team)
    - [GET /api/v1/team/members](#get-apiv1teammembers)
    - [GET /api/v1/team/invites](#get-apiv1teaminvites)
    - [POST /api/v1/team/invites](#post-apiv1teaminvites)
    - [DELETE /api/v1/team/invites/{token}](#delete-apiv1teaminvitestoken)
  - [Webhooks](#webhooks)
    - [GET /api/v1/webhooks](#get-apiv1webhooks)
    - [POST /api/v1/webhooks](#post-apiv1webhooks)
    - [GET /api/v1/webhooks/{id}](#get-apiv1webhooksid)
    - [PATCH /api/v1/webhooks/{id}](#patch-apiv1webhooksid)
    - [DELETE /api/v1/webhooks/{id}](#delete-apiv1webhooksid)
    - [POST /api/v1/webhooks/{id}/test](#post-apiv1webhooksidtest)
- [Webhook 通知 (outbound)](#webhook-通知-outbound)
- [エラー仕様](#エラー仕様)
- [Claude Code 用 Skill ファイル](#claude-code-用-skill-ファイル)

---

## 認証

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

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

> ⚠️ **注意**: API キーはサーバ側にのみ保存し、ブラウザ JavaScript には絶対に公開しないでください。

---

## レート制限

- **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` ヘッダを返します

---

## ページネーション

リスト系エンドポイントは cursor-based ページネーションを採用しています。

- レスポンスに `next_cursor` (string | null) が入る
- `next_cursor` を次のリクエストの `?cursor=...` に渡すと続きを取得できる
- `null` なら末尾
- `limit` は 1〜200、デフォルト 50

---

## Lead Submissions
<a id="lead-submissions"></a>

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

<a id="post-apiv1leadsubmissions"></a>
### POST /api/v1/lead-submissions

**リードを登録**

リード (顧客 + 送信イベント) を登録します。**同一 team 内で email が一致する顧客があれば再利用**、無ければ新規作成します。送信のたびに `lead_submissions` に 1 行ずつ append され、顧客タイムラインに表示されます。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `source` | string | 必須 | 'document_download' | 'inquiry' | 'webinar' | 'newsletter' | 'trial' | 'custom' | 'manual' |
| `customer.name` | string | 必須 | 顧客名 (最大 200 文字) |
| `customer.email` | string | 任意 | メアド (重複検出キー) |
| `customer.phone` | string | 任意 | 電話番号 |
| `customer.company_name` | string | 任意 | 会社名 |
| `customer.fields` | object | 任意 | 顧客の拡張フィールド (任意 key-value)。`department` `position` `address` `notes` 等の既知 key は自動的に日本語ラベル化されます。値は顧客プロフィールに恒久保存され、Slack/メール通知の本文にも含まれます。 |
| `payload.subject` | string | 任意 | 件名 (問合せ等) |
| `payload.message` | string | 任意 | 本文 (問合せ等、最大 10000 文字) |
| `payload.document_id` | uuid | 任意 | 資料 ID (指定時はトラッキングリンクも自動発行) |
| `payload.<custom_key>` | any | 任意 | 送信単位で残したい任意 key を追加できます (例: `preferred_schedule`)。`lead_submissions.payload` に保存され、Slack/メール通知の本文にも含まれます。 |
| `context.utm_source / utm_medium / utm_campaign / utm_term / utm_content` | string | 任意 | UTM パラメータ (流入計測用) |
| `context.referrer` | string | 任意 | リファラ |
| `context.landing_url` | string | 任意 | ランディング URL |
| `tags` | string[] | 任意 | タグ名 (最大 20 個)。未存在は自動作成 |

#### レスポンス (201)

```json
{
  "customer": {
    "id": "uuid",
    "name": "山田太郎",
    "email": "yamada@example.com",
    "company_name": "株式会社サンプル",
    "reused": false
  },
  "submission": {
    "id": "uuid",
    "source": "inquiry",
    "created_at": "2026-05-02T10:30:00Z"
  },
  "tracking_link": null
}
```

`tracking_link` は `payload.document_id` 指定時のみ `{ id, url, document_id, unique_id }` が入ります。

#### curl

```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": ["問合せ"]
  }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/lead-submissions", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    source: "inquiry",
    customer: { name, email, company_name },
    payload: { subject, message },
    context: { utm_source: req.query.utm_source },
  }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

#### Python

```python
import os, requests

res = requests.post(
    "https://sono-5.com/api/v1/lead-submissions",
    headers={
        "Authorization": f"Bearer {os.environ['SONOGO_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "source": "inquiry",
        "customer": {"name": "山田太郎", "email": "yamada@example.com"},
        "payload": {"subject": "料金について", "message": "..."},
        "tags": ["問合せ"],
    },
    timeout=10,
)
res.raise_for_status()
data = res.json()
```

---

## Customers
<a id="customers"></a>

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

<a id="get-apiv1customers"></a>
### GET /api/v1/customers

**顧客一覧**

チームに紐付く顧客を一覧取得します。`search` / `tag_id` でフィルタ、`cursor` でページング。

#### クエリパラメータ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `limit` | integer | 任意 | 1〜200。デフォルト 50 |
| `cursor` | string | 任意 | 前回レスポンスの `next_cursor` を渡す |
| `search` | string | 任意 | name / email / company_name の部分一致 |
| `tag_id` | uuid | 任意 | タグでフィルタ |

#### レスポンス (200)

```json
{
  "customers": [{
      "id": "9f6c1b58-1234-4abc-9def-000000000001",
      "name": "山田太郎",
      "email": "yamada@example.com",
      "phone": "090-1234-5678",
      "company_name": "株式会社サンプル",
      "notes": null,
      "fields": { "department": "営業" },
      "created_at": "2026-05-02T10:30:00Z",
      "updated_at": "2026-05-02T10:30:00Z",
      "tags": [{ "id": "...", "name": "VIP", "color": "#ef4444" }],
      "assignees": [],
      "stats": {
        "link_count": 3,
        "document_count": 2,
        "total_views": 8,
        "last_viewed_at": "2026-05-20T08:15:00Z",
        "email_open_count": 5
      }
    }],
  "next_cursor": "2026-05-02T10:30:00Z|9f6c1b58-1234-4abc-9def-000000000001"
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customers?limit=50", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="post-apiv1customers"></a>
### POST /api/v1/customers

**顧客を作成**

新規顧客を作成します。email が指定されると team 内で重複チェックされ、既存があれば 409 を返します。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `name` | string | 必須 | 顧客名 (最大 200) |
| `email` | string | 任意 | メアド |
| `phone` | string | 任意 | 電話番号 |
| `company_name` | string | 任意 | 会社名 |
| `notes` | string | 任意 | メモ (最大 10000) |
| `fields` | object | 任意 | 拡張フィールド (任意 key-value) |
| `tag_ids` | uuid[] | 任意 | タグ ID 配列 (最大 50) |

#### レスポンス (201)

```json
{
  "id": "9f6c1b58-1234-4abc-9def-000000000001",
  "name": "山田太郎",
  "email": "yamada@example.com",
  "phone": "090-1234-5678",
  "company_name": "株式会社サンプル",
  "notes": null,
  "fields": { "department": "営業" },
  "created_at": "2026-05-02T10:30:00Z",
  "updated_at": "2026-05-02T10:30:00Z",
  "tags": [{ "id": "...", "name": "VIP", "color": "#ef4444" }],
  "assignees": [],
  "stats": {
    "link_count": 3,
    "document_count": 2,
    "total_views": 8,
    "last_viewed_at": "2026-05-20T08:15:00Z",
    "email_open_count": 5
  }
}
```

#### curl

```bash
curl -X POST https://sono-5.com/api/v1/customers \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "山田太郎",
    "email": "yamada@example.com",
    "company_name": "株式会社サンプル"
  }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customers", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "山田太郎",
    email: "yamada@example.com",
    company_name: "株式会社サンプル",
  }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1customersid"></a>
### GET /api/v1/customers/{id}

**顧客を取得**

顧客 ID で 1 件取得します。タグ・担当者・閲覧統計を含む。

#### レスポンス (200)

```json
{
  "id": "9f6c1b58-1234-4abc-9def-000000000001",
  "name": "山田太郎",
  "email": "yamada@example.com",
  "phone": "090-1234-5678",
  "company_name": "株式会社サンプル",
  "notes": null,
  "fields": { "department": "営業" },
  "created_at": "2026-05-02T10:30:00Z",
  "updated_at": "2026-05-02T10:30:00Z",
  "tags": [{ "id": "...", "name": "VIP", "color": "#ef4444" }],
  "assignees": [],
  "stats": {
    "link_count": 3,
    "document_count": 2,
    "total_views": 8,
    "last_viewed_at": "2026-05-20T08:15:00Z",
    "email_open_count": 5
  }
}
```

#### curl

```bash
curl https://sono-5.com/api/v1/customers/9f6c1b58-1234-4abc-9def-000000000001 \
  -H "Authorization: Bearer sk_live_..."
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customers/9f6c1b58-1234-4abc-9def-000000000001", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="patch-apiv1customersid"></a>
### PATCH /api/v1/customers/{id}

**顧客を更新**

指定したフィールドのみ更新します。omit したフィールドは変更されません。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `name` | string | 任意 | 顧客名 (最大 200) |
| `email` | string | 任意 | メアド |
| `phone` | string | 任意 | 電話番号 |
| `company_name` | string | 任意 | 会社名 |
| `notes` | string | 任意 | メモ (最大 10000) |
| `fields` | object | 任意 | 拡張フィールド (任意 key-value) |

#### レスポンス (200)

```json
{
  "id": "9f6c1b58-1234-4abc-9def-000000000001",
  "name": "山田太郎",
  "email": "yamada@example.com",
  "phone": "090-1234-5678",
  "company_name": "株式会社サンプル",
  "notes": null,
  "fields": { "department": "営業" },
  "created_at": "2026-05-02T10:30:00Z",
  "updated_at": "2026-05-02T10:30:00Z",
  "tags": [{ "id": "...", "name": "VIP", "color": "#ef4444" }],
  "assignees": [],
  "stats": {
    "link_count": 3,
    "document_count": 2,
    "total_views": 8,
    "last_viewed_at": "2026-05-20T08:15:00Z",
    "email_open_count": 5
  }
}
```

#### curl

```bash
curl -X PATCH https://sono-5.com/api/v1/customers/9f6c1b58-... \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "notes": "更新後のメモ" }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customers/9f6c1b58-...", {
  method: "PATCH",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ notes: "更新後のメモ" }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="delete-apiv1customersid"></a>
### DELETE /api/v1/customers/{id}

**顧客を削除**

顧客を削除します。関連する送信履歴・トラッキングリンクも cascade 削除されます。

#### レスポンス (200)

```json
{ "id": "9f6c1b58-...", "deleted": true }
```

#### curl

```bash
curl -X DELETE https://sono-5.com/api/v1/customers/9f6c1b58-... \
  -H "Authorization: Bearer sk_live_..."
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customers/9f6c1b58-...", {
  method: "DELETE",
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1customersidtags"></a>
### GET /api/v1/customers/{id}/tags

**顧客のタグ一覧**

顧客に付与されているタグを取得します。

#### レスポンス (200)

```json
{ "tags": [{ "id": "...", "name": "VIP", "color": "#ef4444" }] }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customers/9f6c1b58-.../tags", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="put-apiv1customersidtags"></a>
### PUT /api/v1/customers/{id}/tags

**顧客のタグを置換**

顧客のタグを `tag_ids` の内容で全置換します。指定されないタグは外れます。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `tag_ids` | uuid[] | 必須 | タグ ID 配列 (最大 50)。指定したタグだけが残り、それ以外は外れる (全置換) |

#### レスポンス (200)

```json
{ "tags": [{ "id": "...", "name": "VIP", "color": "#ef4444" }] }
```

#### curl

```bash
curl -X PUT https://sono-5.com/api/v1/customers/9f6c1b58-.../tags \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "tag_ids": ["aaaa-...", "bbbb-..."] }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customers/9f6c1b58-.../tags", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ tag_ids: ["aaaa-...", "bbbb-..."] }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1customersidtimeline"></a>
### GET /api/v1/customers/{id}/timeline

**顧客タイムライン**

顧客のリード送信・閲覧セッション・メールスレッドを時系列で取得します。

#### レスポンス (200)

```json
{
  "events": [
    {
      "type": "viewing_session",
      "occurred_at": "2026-05-20T08:00:00Z",
      "id": "11111111-...",
      "title": "サービス紹介資料 v3 を閲覧",
      "summary": "12 / 24 ページ閲覧",
      "metadata": { "document_id": "4f8c9b2a-..." }
    },
    {
      "type": "lead_submission",
      "occurred_at": "2026-05-02T10:30:00Z",
      "id": "...",
      "title": "問合せ",
      "summary": "料金について",
      "metadata": null
    }
  ]
}
```

#### curl

```bash
curl https://sono-5.com/api/v1/customers/9f6c1b58-.../timeline \
  -H "Authorization: Bearer sk_live_..."
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customers/9f6c1b58-.../timeline", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Customer Tags
<a id="customer-tags"></a>

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

<a id="get-apiv1customertags"></a>
### GET /api/v1/customer-tags

**顧客タグ一覧**

チームで定義されている顧客タグを取得します。

#### レスポンス (200)

```json
{
  "tags": [
    { "id": "...", "name": "VIP", "color": "#ef4444", "sort_order": 0 },
    { "id": "...", "name": "見込み高", "color": "#10b981", "sort_order": 1 }
  ]
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customer-tags", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="post-apiv1customertags"></a>
### POST /api/v1/customer-tags

**顧客タグを作成**

新しい顧客タグを作成します。同一チーム内で `name` が既存だと 409 を返します。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `name` | string | 必須 | タグ名 (最大 100) |
| `color` | string | 必須 | `#rrggbb` 形式の HEX カラー |
| `sort_order` | integer | 任意 | 表示順 |

#### レスポンス (201)

```json
{ "id": "...", "name": "VIP", "color": "#ef4444", "sort_order": 0 }
```

#### curl

```bash
curl -X POST https://sono-5.com/api/v1/customer-tags \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "VIP", "color": "#ef4444" }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customer-tags", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "VIP", color: "#ef4444" }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="patch-apiv1customertagsid"></a>
### PATCH /api/v1/customer-tags/{id}

**顧客タグを更新**

顧客タグの名前・色・表示順を更新します。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `name` | string | 任意 | タグ名 (最大 100) |
| `color` | string | 任意 | `#rrggbb` 形式の HEX カラー |
| `sort_order` | integer | 任意 | 表示順 |

#### レスポンス (200)

```json
{ "id": "...", "name": "VIP", "color": "#dc2626", "sort_order": 0 }
```

#### curl

```bash
curl -X PATCH https://sono-5.com/api/v1/customer-tags/aaaa-... \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "color": "#dc2626" }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customer-tags/aaaa-...", {
  method: "PATCH",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ color: "#dc2626" }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="delete-apiv1customertagsid"></a>
### DELETE /api/v1/customer-tags/{id}

**顧客タグを削除**

顧客タグを削除します。タグの付与関係 (assignments) も cascade 削除されます。

#### レスポンス (200)

```json
{ "id": "aaaa-...", "deleted": true }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/customer-tags/aaaa-...", {
  method: "DELETE",
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Documents
<a id="documents"></a>

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

<a id="get-apiv1documents"></a>
### GET /api/v1/documents

**資料一覧**

チームの資料を一覧取得します。`search` (title 部分一致) / `tag_id` でフィルタ可。

#### クエリパラメータ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `limit` | integer | 任意 | 1〜200。デフォルト 50 |
| `cursor` | string | 任意 | 前回レスポンスの `next_cursor` を渡す |
| `search` | string | 任意 | title の部分一致 |
| `tag_id` | uuid | 任意 | タグでフィルタ |

#### レスポンス (200)

```json
{
  "documents": [{
      "id": "4f8c9b2a-1234-5678-9abc-def012345678",
      "title": "サービス紹介資料 v3",
      "description": "営業向け 16:9 スライド",
      "original_file_url": "https://.../sonogo-deck.pdf",
      "original_file_name": "sonogo-deck.pdf",
      "thumbnail_url": "https://.../thumb.png",
      "page_count": 24,
      "created_at": "2026-05-01T10:00:00Z",
      "updated_at": "2026-05-10T09:00:00Z",
      "tags": [{ "id": "...", "name": "営業", "color": "#3b82f6" }]
    }],
  "next_cursor": null
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/documents?limit=50", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="post-apiv1documents"></a>
### POST /api/v1/documents

**資料を作成**

資料 (PDF) のメタを作成します。ファイル本体は別途 Storage にアップロードして URL を渡してください。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `title` | string | 必須 | 資料タイトル (最大 200) |
| `description` | string | 任意 | 説明 (最大 10000) |
| `original_file_url` | string | 任意 | 元ファイル URL |
| `original_file_name` | string | 任意 | 元ファイル名 |
| `thumbnail_url` | string | 任意 | サムネイル URL |
| `page_count` | integer | 任意 | ページ数 |
| `tag_ids` | uuid[] | 任意 | タグ ID 配列 (最大 50) |

#### レスポンス (201)

```json
{
  "id": "4f8c9b2a-1234-5678-9abc-def012345678",
  "title": "サービス紹介資料 v3",
  "description": "営業向け 16:9 スライド",
  "original_file_url": "https://.../sonogo-deck.pdf",
  "original_file_name": "sonogo-deck.pdf",
  "thumbnail_url": "https://.../thumb.png",
  "page_count": 24,
  "created_at": "2026-05-01T10:00:00Z",
  "updated_at": "2026-05-10T09:00:00Z",
  "tags": [{ "id": "...", "name": "営業", "color": "#3b82f6" }]
}
```

#### curl

```bash
curl -X POST https://sono-5.com/api/v1/documents \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "サービス紹介資料 v3",
    "original_file_url": "https://.../sonogo-deck.pdf",
    "page_count": 24
  }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/documents", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "サービス紹介資料 v3",
    original_file_url: "https://.../sonogo-deck.pdf",
    page_count: 24,
  }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1documentsid"></a>
### GET /api/v1/documents/{id}

**資料を取得**

資料を 1 件取得します。`versions` (ファイル差し替え履歴) を含みます。

#### レスポンス (200)

```json
{
  "id": "4f8c9b2a-1234-5678-9abc-def012345678",
  "title": "サービス紹介資料 v3",
  "description": "営業向け 16:9 スライド",
  "original_file_url": "https://.../sonogo-deck.pdf",
  "original_file_name": "sonogo-deck.pdf",
  "thumbnail_url": "https://.../thumb.png",
  "page_count": 24,
  "created_at": "2026-05-01T10:00:00Z",
  "updated_at": "2026-05-10T09:00:00Z",
  "tags": [{ "id": "...", "name": "営業", "color": "#3b82f6" }]
,
  "versions": [
    {
      "id": "33333333-...",
      "document_id": "4f8c9b2a-...",
      "version_number": 1,
      "original_file_url": "https://.../v1.pdf",
      "original_file_name": "sonogo-deck.pdf",
      "thumbnail_url": null,
      "page_count": 24,
      "created_at": "2026-05-01T10:00:00Z"
    }
  ]
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/documents/4f8c9b2a-...", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="patch-apiv1documentsid"></a>
### PATCH /api/v1/documents/{id}

**資料を更新**

資料のメタ情報を更新します。omit したフィールドは変更されません。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `title` | string | 任意 | 資料タイトル (最大 200) |
| `description` | string | 任意 | 説明 (最大 10000) |
| `original_file_url` | string | 任意 | 元ファイル URL |
| `original_file_name` | string | 任意 | 元ファイル名 |
| `thumbnail_url` | string | 任意 | サムネイル URL |
| `page_count` | integer | 任意 | ページ数 |

#### レスポンス (200)

```json
{
  "id": "4f8c9b2a-1234-5678-9abc-def012345678",
  "title": "サービス紹介資料 v3",
  "description": "営業向け 16:9 スライド",
  "original_file_url": "https://.../sonogo-deck.pdf",
  "original_file_name": "sonogo-deck.pdf",
  "thumbnail_url": "https://.../thumb.png",
  "page_count": 24,
  "created_at": "2026-05-01T10:00:00Z",
  "updated_at": "2026-05-10T09:00:00Z",
  "tags": [{ "id": "...", "name": "営業", "color": "#3b82f6" }]
}
```

#### curl

```bash
curl -X PATCH https://sono-5.com/api/v1/documents/4f8c9b2a-... \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "title": "サービス紹介資料 v4" }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/documents/4f8c9b2a-...", {
  method: "PATCH",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ title: "サービス紹介資料 v4" }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="delete-apiv1documentsid"></a>
### DELETE /api/v1/documents/{id}

**資料を削除**

資料を削除します。トラッキングリンク・閲覧履歴も cascade 削除されます。

#### レスポンス (200)

```json
{ "id": "4f8c9b2a-...", "deleted": true }
```

#### curl

```bash
curl -X DELETE https://sono-5.com/api/v1/documents/4f8c9b2a-... \
  -H "Authorization: Bearer sk_live_..."
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/documents/4f8c9b2a-...", {
  method: "DELETE",
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1documentsidversions"></a>
### GET /api/v1/documents/{id}/versions

**資料のバージョン一覧**

資料のファイル差し替え履歴を取得します (`version_number` 降順)。

#### レスポンス (200)

```json
{
  "versions": [
    {
      "id": "33333333-...",
      "document_id": "4f8c9b2a-...",
      "version_number": 2,
      "original_file_url": "https://.../v2.pdf",
      "original_file_name": "sonogo-deck-v2.pdf",
      "thumbnail_url": null,
      "page_count": 26,
      "created_at": "2026-05-10T09:00:00Z"
    }
  ]
}
```

#### curl

```bash
curl https://sono-5.com/api/v1/documents/4f8c9b2a-.../versions \
  -H "Authorization: Bearer sk_live_..."
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/documents/4f8c9b2a-.../versions", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1documentsidtags"></a>
### GET /api/v1/documents/{id}/tags

**資料のタグ一覧**

資料に付与されているタグを取得します。

#### レスポンス (200)

```json
{ "tags": [{ "id": "...", "name": "営業", "color": "#3b82f6" }] }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/documents/4f8c9b2a-.../tags", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="put-apiv1documentsidtags"></a>
### PUT /api/v1/documents/{id}/tags

**資料のタグを置換**

資料のタグを `tag_ids` で全置換します。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `tag_ids` | uuid[] | 必須 | タグ ID 配列 (最大 50)。指定したタグだけが残り、それ以外は外れる (全置換) |

#### レスポンス (200)

```json
{ "tags": [{ "id": "...", "name": "営業", "color": "#3b82f6" }] }
```

#### curl

```bash
curl -X PUT https://sono-5.com/api/v1/documents/4f8c9b2a-.../tags \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "tag_ids": ["aaaa-..."] }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/documents/4f8c9b2a-.../tags", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ tag_ids: ["aaaa-..."] }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Document Tags
<a id="document-tags"></a>

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

<a id="get-apiv1documenttags"></a>
### GET /api/v1/document-tags

**資料タグ一覧**

チームで定義されている資料タグを取得します。

#### レスポンス (200)

```json
{ "tags": [{ "id": "...", "name": "営業", "color": "#3b82f6", "sort_order": 0 }] }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/document-tags", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="post-apiv1documenttags"></a>
### POST /api/v1/document-tags

**資料タグを作成**

新しい資料タグを作成します。同一チーム内で `name` が既存だと 409 を返します。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `name` | string | 必須 | タグ名 (最大 100) |
| `color` | string | 必須 | `#rrggbb` 形式の HEX カラー |
| `sort_order` | integer | 任意 | 表示順 |

#### レスポンス (201)

```json
{ "id": "...", "name": "営業", "color": "#3b82f6", "sort_order": 0 }
```

#### curl

```bash
curl -X POST https://sono-5.com/api/v1/document-tags \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "営業", "color": "#3b82f6" }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/document-tags", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "営業", color: "#3b82f6" }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="patch-apiv1documenttagsid"></a>
### PATCH /api/v1/document-tags/{id}

**資料タグを更新**

資料タグの名前・色・表示順を更新します。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `name` | string | 任意 | タグ名 (最大 100) |
| `color` | string | 任意 | `#rrggbb` 形式の HEX カラー |
| `sort_order` | integer | 任意 | 表示順 |

#### レスポンス (200)

```json
{ "id": "...", "name": "営業", "color": "#2563eb", "sort_order": 0 }
```

#### curl

```bash
curl -X PATCH https://sono-5.com/api/v1/document-tags/aaaa-... \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "color": "#2563eb" }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/document-tags/aaaa-...", {
  method: "PATCH",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ color: "#2563eb" }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="delete-apiv1documenttagsid"></a>
### DELETE /api/v1/document-tags/{id}

**資料タグを削除**

資料タグを削除します。タグの付与関係 (assignments) も cascade 削除されます。

#### レスポンス (200)

```json
{ "id": "aaaa-...", "deleted": true }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/document-tags/aaaa-...", {
  method: "DELETE",
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Sessions
<a id="sessions"></a>

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

<a id="get-apiv1sessions"></a>
### GET /api/v1/sessions

**資料閲覧セッション一覧**

viewer での資料閲覧セッションを一覧取得します。デフォルトでメンバー閲覧 (`is_member_access = true`) は除外。

#### クエリパラメータ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `limit` | integer | 任意 | 1〜200。デフォルト 50 |
| `cursor` | string | 任意 | 前回レスポンスの `next_cursor` を渡す |
| `customer_id` | uuid | 任意 | 顧客でフィルタ |
| `document_id` | uuid | 任意 | 資料でフィルタ |
| `since` | datetime | 任意 | ISO 8601。この日時以降の started_at |
| `until` | datetime | 任意 | ISO 8601。この日時以前の started_at |
| `include_member_views` | boolean | 任意 | メンバー閲覧 (内部 IP) を含めるか。デフォルト false |

#### レスポンス (200)

```json
{
  "sessions": [{
      "id": "11111111-1111-4111-8111-111111111111",
      "tracking_link_id": "22222222-...",
      "document_id": "4f8c9b2a-...",
      "file_version_id": "33333333-...",
      "customer_id": "9f6c1b58-...",
      "started_at": "2026-05-20T08:00:00Z",
      "ended_at": "2026-05-20T08:09:30Z",
      "duration_seconds": 570,
      "inactive_seconds": 0,
      "is_active": false,
      "is_member_access": false,
      "pages_viewed": 12,
      "total_pages": 24,
      "completion_rate": 0.5,
      "end_reason": "tab_closed",
      "ip_address": "203.0.113.10",
      "user_agent": "Mozilla/5.0 ...",
      "device_type": "desktop",
      "browser_name": "Chrome",
      "os_name": "macOS",
      "geo_country": "JP",
      "geo_region": "Tokyo",
      "geo_city": "Shibuya",
      "access_quality": "normal",
      "referrer": null
    }],
  "next_cursor": null
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/sessions?limit=50", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1sessionsid"></a>
### GET /api/v1/sessions/{id}

**閲覧セッションを取得**

セッション 1 件を取得します。`page_dwell_times` (ページ別滞在時間) を含みます。

#### レスポンス (200)

```json
{
  "id": "11111111-1111-4111-8111-111111111111",
  "tracking_link_id": "22222222-...",
  "document_id": "4f8c9b2a-...",
  "file_version_id": "33333333-...",
  "customer_id": "9f6c1b58-...",
  "started_at": "2026-05-20T08:00:00Z",
  "ended_at": "2026-05-20T08:09:30Z",
  "duration_seconds": 570,
  "inactive_seconds": 0,
  "is_active": false,
  "is_member_access": false,
  "pages_viewed": 12,
  "total_pages": 24,
  "completion_rate": 0.5,
  "end_reason": "tab_closed",
  "ip_address": "203.0.113.10",
  "user_agent": "Mozilla/5.0 ...",
  "device_type": "desktop",
  "browser_name": "Chrome",
  "os_name": "macOS",
  "geo_country": "JP",
  "geo_region": "Tokyo",
  "geo_city": "Shibuya",
  "access_quality": "normal",
  "referrer": null
,
  "page_dwell_times": [
    { "page_number": 1, "dwell_time_ms": 8200, "enter_count": 1, "file_version_id": "33333333-..." },
    { "page_number": 2, "dwell_time_ms": 14500, "enter_count": 1, "file_version_id": "33333333-..." }
  ]
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/sessions/11111111-...", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Email Accounts
<a id="email-accounts"></a>

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

<a id="get-apiv1emailaccounts"></a>
### GET /api/v1/email-accounts

**メールアカウント一覧**

チームで接続されている IMAP/SMTP メールアカウントを取得します (パスワード等の暗号化フィールドは含まれません)。

#### レスポンス (200)

```json
{
  "accounts": [
    {
      "id": "...",
      "user_id": "...",
      "team_id": "...",
      "organization_id": "...",
      "label": "営業 1 課",
      "email_address": "sales@example.com",
      "imap_host": "imap.example.com",
      "imap_port": 993,
      "imap_user": "sales@example.com",
      "imap_secure": true,
      "smtp_host": "smtp.example.com",
      "smtp_port": 587,
      "smtp_user": "sales@example.com",
      "smtp_secure": true,
      "sync_folders": ["INBOX", "Sent"],
      "visibility": "team",
      "is_active": true,
      "last_sync_at": "2026-05-20T08:00:00Z",
      "last_sync_error": null,
      "created_at": "2026-05-01T00:00:00Z",
      "updated_at": "2026-05-20T08:00:00Z"
    }
  ]
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/email-accounts", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Email Threads
<a id="email-threads"></a>

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

<a id="get-apiv1emailthreads"></a>
### GET /api/v1/email-threads

**メールスレッド一覧**

個別メール (IMAP/SMTP) のスレッドを一覧取得します。

#### クエリパラメータ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `limit` | integer | 任意 | 1〜200。デフォルト 50 |
| `cursor` | string | 任意 | 前回レスポンスの `next_cursor` を渡す |
| `account_id` | uuid | 任意 | メールアカウントでフィルタ |
| `customer_id` | uuid | 任意 | 顧客でフィルタ |
| `is_read` | boolean | 任意 | 未読/既読フィルタ |

#### レスポンス (200)

```json
{
  "threads": [
    {
      "id": "...",
      "account_id": "...",
      "customer_id": "9f6c1b58-...",
      "subject": "Re: 料金について",
      "snippet": "ご返信ありがとうございます...",
      "message_count": 4,
      "is_read": false,
      "is_starred": false,
      "has_sonogo_message": true,
      "last_message_at": "2026-05-20T09:00:00Z",
      "created_at": "2026-05-02T10:30:00Z",
      "updated_at": "2026-05-20T09:00:00Z"
    }
  ],
  "next_cursor": null
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/email-threads?limit=50", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1emailthreadsid"></a>
### GET /api/v1/email-threads/{id}

**メールスレッドを取得 (本文含む)**

スレッド 1 件と、そのスレッドに含まれる全メッセージを取得します。

#### レスポンス (200)

```json
{
  "id": "...",
  "subject": "Re: 料金について",
  "snippet": "...",
  "message_count": 4,
  "is_read": true,
  "messages": [
    {
      "id": "...",
      "thread_id": "...",
      "direction": "inbound",
      "from_address": "yamada@example.com",
      "subject": "料金について",
      "body_text": "詳しい料金を知りたいです",
      "is_read": true,
      "received_at": "2026-05-02T10:30:00Z"
    }
  ]
}
```

省略されているフィールドあり。実際のレスポンスは `EmailThread` + `messages: EmailMessage[]` の全フィールドを含みます。

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/email-threads/aaaa-...", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Email Messages
<a id="email-messages"></a>

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

<a id="get-apiv1emailmessagesid"></a>
### GET /api/v1/email-messages/{id}

**メールメッセージを取得**

メール 1 件を取得します (`body_text` / `body_html` を含む)。

#### レスポンス (200)

```json
{
  "id": "...",
  "thread_id": "...",
  "account_id": "...",
  "direction": "inbound",
  "folder": "INBOX",
  "from_address": "yamada@example.com",
  "from_name": "山田太郎",
  "to_addresses": ["sales@example.com"],
  "cc_addresses": [],
  "bcc_addresses": [],
  "subject": "料金について",
  "body_text": "詳しい料金を知りたいです",
  "body_html": "<p>詳しい料金を知りたいです</p>",
  "is_read": true,
  "has_tracking_pixel": false,
  "send_status": "received",
  "received_at": "2026-05-02T10:30:00Z",
  "created_at": "2026-05-02T10:30:00Z",
  "updated_at": "2026-05-02T10:30:00Z"
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/email-messages/bbbb-...", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="patch-apiv1emailmessagesid"></a>
### PATCH /api/v1/email-messages/{id}

**メールメッセージを更新 (既読化)**

メールの既読フラグを切り替えます。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `is_read` | boolean | 任意 | 既読/未読を切り替え |

#### レスポンス (200)

```json
{ "id": "bbbb-...", "is_read": true }
```

#### curl

```bash
curl -X PATCH https://sono-5.com/api/v1/email-messages/bbbb-... \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "is_read": true }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/email-messages/bbbb-...", {
  method: "PATCH",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ is_read: true }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Notifications
<a id="notifications"></a>

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

<a id="get-apiv1notifications"></a>
### GET /api/v1/notifications

**通知一覧**

ログインユーザー宛の通知を取得します (API キー作成者のユーザー宛)。

#### クエリパラメータ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `limit` | integer | 任意 | 1〜200。デフォルト 50 |
| `cursor` | string | 任意 | 前回レスポンスの `next_cursor` を渡す |
| `is_read` | boolean | 任意 | 未読/既読フィルタ |
| `type` | string | 任意 | 通知種別 (opened / viewing / completed / email_opened / lead_created など) |

#### レスポンス (200)

```json
{
  "notifications": [{
      "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
      "type": "viewing",
      "title": "山田太郎さんが「サービス紹介資料 v3」を閲覧中",
      "body": "12 / 24 ページまで閲覧",
      "is_read": false,
      "customer_id": "9f6c1b58-...",
      "document_id": "4f8c9b2a-...",
      "email_id": null,
      "email_message_id": null,
      "session_id": "11111111-...",
      "tracking_link_id": "22222222-...",
      "created_at": "2026-05-20T08:02:00Z"
    }],
  "next_cursor": null
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/notifications?is_read=false", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1notificationsunreadcount"></a>
### GET /api/v1/notifications/unread-count

**未読通知数を取得**

未読通知の件数を返します (バッジ用)。

#### レスポンス (200)

```json
{ "count": 5 }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/notifications/unread-count", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="patch-apiv1notificationsid"></a>
### PATCH /api/v1/notifications/{id}

**通知を既読/未読化**

通知の既読フラグを切り替えます。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `is_read` | boolean | 必須 | true で既読化、false で未読化 |

#### レスポンス (200)

```json
{ "id": "aaaaaaaa-...", "is_read": true }
```

#### curl

```bash
curl -X PATCH https://sono-5.com/api/v1/notifications/aaaa-... \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "is_read": true }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/notifications/aaaa-...", {
  method: "PATCH",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ is_read: true }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="post-apiv1notificationsmarkallread"></a>
### POST /api/v1/notifications/mark-all-read

**通知を全件既読化**

ログインユーザー宛のすべての未読通知を既読化します。

#### レスポンス (201)

```json
{ "updated": 5 }
```

#### curl

```bash
curl -X POST https://sono-5.com/api/v1/notifications/mark-all-read \
  -H "Authorization: Bearer sk_live_..."
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/notifications/mark-all-read", {
  method: "POST",
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Team
<a id="team"></a>

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

<a id="get-apiv1team"></a>
### GET /api/v1/team

**現在のチーム情報**

API キーに紐付くチーム (`teams` テーブル 1 行) を返します。

#### レスポンス (200)

```json
{
  "id": "...",
  "organization_id": "...",
  "name": "営業部",
  "icon_url": null,
  "created_at": "2026-04-01T00:00:00Z",
  "updated_at": "2026-05-01T00:00:00Z"
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/team", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1teammembers"></a>
### GET /api/v1/team/members

**チームメンバー一覧**

チームに所属するメンバーを取得します。`permissions` に `org_admin` / `billing_admin` enum 配列。

#### レスポンス (200)

```json
{
  "members": [
    {
      "id": "...",
      "user_id": "...",
      "team_id": "...",
      "joined_at": "2026-04-01T00:00:00Z",
      "role": "member",
      "permissions": ["org_admin"],
      "profile": {
        "id": "...",
        "email": "admin@example.com",
        "full_name": "管理者太郎",
        "avatar_url": null
      }
    }
  ]
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/team/members", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1teaminvites"></a>
### GET /api/v1/team/invites

**招待中の一覧**

発行済みのチーム招待 (招待リンク + 期限) を取得します。

#### レスポンス (200)

```json
{
  "invites": [
    {
      "id": "...",
      "team_id": "...",
      "email": "new@example.com",
      "role": "member",
      "grant_permissions": [],
      "invited_by": "...",
      "token": "...",
      "expires_at": "2026-06-05T00:00:00Z",
      "accepted_at": null,
      "created_at": "2026-05-05T00:00:00Z"
    }
  ]
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/team/invites", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="post-apiv1teaminvites"></a>
### POST /api/v1/team/invites

**チーム招待を発行**

新しい招待を発行します (招待メール自動送信は別系統)。同じ email で有効な招待が既存だと 409 を返します。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `email` | string | 必須 | 招待先メアド |
| `grant_permissions` | string[] | 任意 | `org_admin` / `billing_admin` の配列 (最大 2)。省略時は通常メンバー |

#### レスポンス (201)

```json
{
  "id": "...",
  "team_id": "...",
  "email": "new@example.com",
  "role": "member",
  "grant_permissions": ["billing_admin"],
  "invited_by": "...",
  "token": "uuid-token-...",
  "expires_at": "2026-06-05T00:00:00Z",
  "accepted_at": null,
  "created_at": "2026-05-05T00:00:00Z"
}
```

#### curl

```bash
curl -X POST https://sono-5.com/api/v1/team/invites \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "new@example.com",
    "grant_permissions": ["billing_admin"]
  }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/team/invites", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    email: "new@example.com",
    grant_permissions: ["billing_admin"],
  }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="delete-apiv1teaminvitestoken"></a>
### DELETE /api/v1/team/invites/{token}

**招待を取り消し**

未受諾の招待を取り消します。`token` は招待発行時のレスポンスに含まれます。

#### レスポンス (200)

```json
{ "token": "uuid-token-...", "deleted": true }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/team/invites/uuid-token-...", {
  method: "DELETE",
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Webhooks
<a id="webhooks"></a>

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

<a id="get-apiv1webhooks"></a>
### GET /api/v1/webhooks

**Webhook 一覧**

チームに登録されている Webhook を取得します (`secret` は返しません)。

#### レスポンス (200)

```json
{ "webhooks": [{
  "id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
  "team_id": "...",
  "organization_id": "...",
  "name": "Slack 通知",
  "url": "https://hooks.slack.com/services/T.../B.../...",
  "event_types": ["lead_created", "viewing"],
  "is_active": true,
  "last_status": 200,
  "last_error": null,
  "last_fired_at": "2026-05-20T08:00:00Z",
  "created_at": "2026-05-01T00:00:00Z",
  "updated_at": "2026-05-20T08:00:00Z"
}] }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/webhooks", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="post-apiv1webhooks"></a>
### POST /api/v1/webhooks

**Webhook を登録**

新しい Webhook を登録します。URL は SSRF 対策のためプライベート IP / 内部ホストは拒否されます。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `name` | string | 必須 | Webhook 名 (最大 200) |
| `url` | string | 必須 | 通知先 URL (https://...)。Discord / Slack / Teams / Google Chat / 汎用エンドポイントを自動判別 |
| `event_types` | string[] | 必須 | 購読するイベント名の配列 (例: `lead_created`, `viewing`, `email_opened`) |
| `secret` | string | 任意 | 署名検証用シークレット (最大 500)。指定時は `X-Sonogo-Signature` ヘッダを付与 |
| `is_active` | boolean | 任意 | デフォルト true |

#### レスポンス (201)

```json
{
  "id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
  "team_id": "...",
  "organization_id": "...",
  "name": "Slack 通知",
  "url": "https://hooks.slack.com/services/T.../B.../...",
  "event_types": ["lead_created", "viewing"],
  "is_active": true,
  "last_status": 200,
  "last_error": null,
  "last_fired_at": "2026-05-20T08:00:00Z",
  "created_at": "2026-05-01T00:00:00Z",
  "updated_at": "2026-05-20T08:00:00Z"
}
```

#### curl

```bash
curl -X POST https://sono-5.com/api/v1/webhooks \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Slack 通知",
    "url": "https://hooks.slack.com/services/T.../B.../...",
    "event_types": ["lead_created", "viewing"]
  }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/webhooks", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Slack 通知",
    url: "https://hooks.slack.com/services/T.../B.../...",
    event_types: ["lead_created", "viewing"],
  }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="get-apiv1webhooksid"></a>
### GET /api/v1/webhooks/{id}

**Webhook を取得**

Webhook 1 件を取得します (`secret` は返しません)。

#### レスポンス (200)

```json
{
  "id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
  "team_id": "...",
  "organization_id": "...",
  "name": "Slack 通知",
  "url": "https://hooks.slack.com/services/T.../B.../...",
  "event_types": ["lead_created", "viewing"],
  "is_active": true,
  "last_status": 200,
  "last_error": null,
  "last_fired_at": "2026-05-20T08:00:00Z",
  "created_at": "2026-05-01T00:00:00Z",
  "updated_at": "2026-05-20T08:00:00Z"
}
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/webhooks/bbbb-...", {
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="patch-apiv1webhooksid"></a>
### PATCH /api/v1/webhooks/{id}

**Webhook を更新**

Webhook の設定を部分更新します。`secret` を null にすると署名検証なしに戻ります。

#### リクエストボディ

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| `name` | string | 任意 | Webhook 名 (最大 200) |
| `url` | string | 任意 | 通知先 URL (https://...)。Discord / Slack / Teams / Google Chat / 汎用エンドポイントを自動判別 |
| `event_types` | string[] | 任意 | 購読するイベント名の配列 (例: `lead_created`, `viewing`, `email_opened`) |
| `secret` | string | 任意 | 署名検証用シークレット (最大 500)。指定時は `X-Sonogo-Signature` ヘッダを付与 |
| `is_active` | boolean | 任意 | デフォルト true |

#### レスポンス (200)

```json
{
  "id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
  "team_id": "...",
  "organization_id": "...",
  "name": "Slack 通知",
  "url": "https://hooks.slack.com/services/T.../B.../...",
  "event_types": ["lead_created", "viewing"],
  "is_active": true,
  "last_status": 200,
  "last_error": null,
  "last_fired_at": "2026-05-20T08:00:00Z",
  "created_at": "2026-05-01T00:00:00Z",
  "updated_at": "2026-05-20T08:00:00Z"
}
```

#### curl

```bash
curl -X PATCH https://sono-5.com/api/v1/webhooks/bbbb-... \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/webhooks/bbbb-...", {
  method: "PATCH",
  headers: {
    "Authorization": `Bearer ${process.env.SONOGO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ is_active: false }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="delete-apiv1webhooksid"></a>
### DELETE /api/v1/webhooks/{id}

**Webhook を削除**

Webhook を削除します。

#### レスポンス (200)

```json
{ "id": "bbbb-...", "deleted": true }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/webhooks/bbbb-...", {
  method: "DELETE",
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

<a id="post-apiv1webhooksidtest"></a>
### POST /api/v1/webhooks/{id}/test

**Webhook 動作確認**

テストイベント (`[TEST] テスト通知`) を Webhook に送信します。誤発火防止のため **10 req/min** に制限されています。

#### レスポンス (201)

```json
{ "ok": true, "status": 200, "error": null, "kind": "slack" }
```

#### curl

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

#### JavaScript / Node

```ts
const res = await fetch("https://sono-5.com/api/v1/webhooks/bbbb-.../test", {
  method: "POST",
  headers: { "Authorization": `Bearer ${process.env.SONOGO_API_KEY}` },
})
const data = await res.json()
if (!res.ok) throw new Error(data.error)
```

---

## Webhook 通知 (outbound)

イベント発生時に Sonogo から外部 URL へ POST します。`/api/v1/webhooks` の管理 API、または `/settings/notifications` の UI で URL とイベント種別を登録してください。Discord・Microsoft Teams・Google Chat の URL は自動でリッチメッセージ形式に整形されます。

### サポート対象イベント

| event | 発火タイミング |
|---|---|
| `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` | 予約していた個別メールの送信が失敗 |

### ペイロード形式

```http
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
  }
}
```

### 署名検証

シークレットを設定した場合、ペイロード本文の HMAC SHA256 が `X-Sonogo-Signature` に入ります。受信側で同じシークレットで再計算して一致するか確認してください。

```ts
// Node.js 検証例
import crypto from "crypto"

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

### 配信ポリシー

- **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` | サーバ内部エラー (重複時はサポートにご連絡ください) |

---

## Claude Code 用 Skill ファイル

このリファレンスを Anthropic Claude Code (CLI / Desktop) の Skill として配置すると、AI が文脈を自動で読み込み、API クライアントや Webhook 受信実装を一発で生成できます。

```bash
mkdir -p ~/.claude/skills/sonogo-api && \
curl -o ~/.claude/skills/sonogo-api/SKILL.md \
  https://sono-5.com/skills/sonogo-api.md
```

または OpenAPI YAML を直接読ませることもできます: <https://sono-5.com/docs/sonogo-api.yaml>

---

## サポート / お問い合わせ

- API キー発行・管理: <https://sono-5.com/settings/api>
- 質問・障害連絡: <https://sono-5.com/contact>
- 本ドキュメント (Web 版): <https://sono-5.com/docs/api>
- OpenAPI YAML: <https://sono-5.com/docs/sonogo-api.yaml>
- Markdown 版 (本ファイル): <https://sono-5.com/docs/sonogo-api.md>
