API Documentation

Sample Code

各プログラミング言語でのPII-Fi APIの使用例を示します。

Python

Client Class

再利用可能なクライアントクラスの実装例です。

python
import requests

class PiiFiDetectorClient:
    # base_url: デモ環境(本番環境では専用エンドポイントを提供)
    def __init__(self, api_key, base_url="https://api.pii-fi.com/api"):
        self.api_key = api_key
        self.base_url = base_url
        self.headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }

    def detect(self, text, deidentification_type="replace", enabled_entities=None):
        """PII検出・マスキング"""
        payload = {
            "text": text,
            "deidentification_type": deidentification_type
        }
        if enabled_entities:
            payload["enabled_entities"] = enabled_entities

        response = requests.post(
            f"{self.base_url}/detect",
            headers=self.headers,
            json=payload
        )
        response.raise_for_status()
        return response.json()

    def fake_with_mapping(self, text):
        """ダミーデータに置換(復元用マッピング付き)"""
        payload = {
            "text": text,
            "deidentification_type": "fake",
            "include_mapping_info": True
        }
        response = requests.post(
            f"{self.base_url}/detect",
            headers=self.headers,
            json=payload
        )
        response.raise_for_status()
        return response.json()

    def restore(self, text, mapping_info):
        """マスキング済みテキストを復元"""
        payload = {
            "text": text,
            "mapping_info": mapping_info,
            "mode": "simple"
        }
        response = requests.post(
            f"{self.base_url}/restore",
            headers=self.headers,
            json=payload
        )
        response.raise_for_status()
        return response.json()


# 使用例
client = PiiFiDetectorClient("YOUR_API_KEY")

# 基本的な検出
result = client.detect("田中太郎(090-1234-5678)のメールはtanaka@example.com")
print(f"マスキング済み: {result['deidentified_text']}")

# 特定エンティティのみ検出
result = client.detect(
    "田中太郎(090-1234-5678)",
    enabled_entities=["JPII_PHONE_NUMBER"]
)
print(f"電話番号のみ: {result['deidentified_text']}")

# fakeマスキングと復元
fake_result = client.fake_with_mapping("田中太郎様、ご連絡ありがとうございます。")
print(f"マスキング済み: {fake_result['deidentified_text']}")

# LLMで処理したテキストを復元
llm_response = fake_result['deidentified_text']  # 実際にはLLMの応答
restored = client.restore(llm_response, fake_result['mapping_info'])
print(f"復元: {restored['restored_text']}")

Batch Processing

python
def detect_batch(client, texts, deidentification_type="replace"):
    """複数テキストを一括処理"""
    payload = [
        {"text": text, "deidentification_type": deidentification_type}
        for text in texts
    ]
    response = requests.post(
        f"{client.base_url}/detect/batch",
        headers=client.headers,
        json=payload
    )
    response.raise_for_status()
    return response.json()

# 使用例
texts = [
    "田中太郎です",
    "電話: 090-1234-5678",
    "メール: test@example.com"
]
results = detect_batch(client, texts)
for i, result in enumerate(results):
    print(f"{i+1}: {result['deidentified_text']}")

Node.js

Client Class

javascript
class PiiFiDetectorClient {
    // baseUrl: デモ環境(本番環境では専用エンドポイントを提供)
    constructor(apiKey, baseUrl = "https://api.pii-fi.com/api") {
        this.apiKey = apiKey;
        this.baseUrl = baseUrl;
    }

    async detect(text, options = {}) {
        const {
            deidentificationType = "replace",
            enabledEntities = null
        } = options;

        const payload = {
            text,
            deidentification_type: deidentificationType
        };
        if (enabledEntities) {
            payload.enabled_entities = enabledEntities;
        }

        const response = await fetch(`${this.baseUrl}/detect`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${this.apiKey}`
            },
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            throw new Error(`HTTP Error: ${response.status}`);
        }
        return response.json();
    }

    async fakeWithMapping(text) {
        const response = await fetch(`${this.baseUrl}/detect`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${this.apiKey}`
            },
            body: JSON.stringify({
                text,
                deidentification_type: "fake",
                include_mapping_info: true
            })
        });

        if (!response.ok) {
            throw new Error(`HTTP Error: ${response.status}`);
        }
        return response.json();
    }

    async restore(text, mappingInfo) {
        const response = await fetch(`${this.baseUrl}/restore`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${this.apiKey}`
            },
            body: JSON.stringify({
                text,
                mapping_info: mappingInfo,
                mode: "simple"
            })
        });

        if (!response.ok) {
            throw new Error(`HTTP Error: ${response.status}`);
        }
        return response.json();
    }
}

// 使用例
const client = new PiiFiDetectorClient("YOUR_API_KEY");

// 基本的な検出
const result = await client.detect("田中太郎(090-1234-5678)のメールはtanaka@example.com");
console.log(`マスキング済み: ${result.deidentified_text}`);

// 特定エンティティのみ検出
const phoneOnly = await client.detect("田中太郎(090-1234-5678)", {
    enabledEntities: ["JPII_PHONE_NUMBER"]
});
console.log(`電話番号のみ: ${phoneOnly.deidentified_text}`);

// fakeマスキングと復元
const fakeResult = await client.fakeWithMapping("田中太郎様、ご連絡ありがとうございます。");
console.log(`マスキング済み: ${fakeResult.deidentified_text}`);

const restored = await client.restore(fakeResult.deidentified_text, fakeResult.mapping_info);
console.log(`復元: ${restored.restored_text}`);

ES Modules形式

javascript
// pii-client.mjs
export default class PiiFiClient {
    constructor(apiKey) {
        this.apiKey = apiKey;
        // デモ環境(本番環境では専用エンドポイントを提供)
        this.baseUrl = "https://api.pii-fi.com/api";
    }
    // ... 上記と同様のメソッド
}

// main.mjs
import PiiFiClient from './pii-client.mjs';
const client = new PiiFiClient("YOUR_API_KEY");

const result = await client.detect("田中太郎のメールはtanaka@example.com");
console.log(result.deidentified_text);

curl

Basic Detection

bash
# replace方式(タグ形式: <人名>、<電話番号>等)
curl -X POST https://api.pii-fi.com/api/detect \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"text": "田中太郎です", "deidentification_type": "replace"}'
# 出力: <人名>です

# mask方式
curl -X POST https://api.pii-fi.com/api/detect \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"text": "090-1234-5678", "deidentification_type": "mask"}'
# 出力: 090-****-5678

# full_mask方式
curl -X POST https://api.pii-fi.com/api/detect \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"text": "田中太郎です", "deidentification_type": "full_mask"}'
# 出力: ****です

# hash方式
curl -X POST https://api.pii-fi.com/api/detect \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"text": "田中太郎です", "deidentification_type": "hash"}'
# 出力: JPII_PERSON_NAME_a1b2c3d4です

# fake方式(復元用マッピング付き)
curl -X POST https://api.pii-fi.com/api/detect \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"text": "田中太郎です", "deidentification_type": "fake", "include_mapping_info": true}'
# 出力: 山田花子です

enabled_entities

bash
# 電話番号のみ検出(人名は無視)
curl -X POST https://api.pii-fi.com/api/detect \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "text": "田中太郎(090-1234-5678)のメールはtanaka@example.com",
    "enabled_entities": ["JPII_PHONE_NUMBER"]
  }'
# 出力: 田中太郎(<電話番号>)のメールはtanaka@example.com

Batch Processing

bash
curl -X POST https://api.pii-fi.com/api/detect/batch \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '[
    {"text": "田中太郎です", "deidentification_type": "replace"},
    {"text": "090-1234-5678", "deidentification_type": "mask"}
  ]'

Restore

bash
curl -X POST https://api.pii-fi.com/api/restore \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "text": "山田花子様、ご連絡ありがとうございます。",
    "mapping_info": {
      "mappings": [
        {"original": "田中太郎", "fake": "山田花子", "entity_type": "JPII_PERSON_NAME"}
      ]
    },
    "mode": "simple"
  }'

ポリシー管理

⚠ 楽観的ロック(ETag/If-Match)が必要です

ポリシー変更APIには楽観的ロックが必要です。まずGETでETagを取得し、変更時にIf-Matchヘッダーで送信します。

bash
# ポリシー一覧
curl https://api.pii-fi.com/api/policy/list \
  -H "Authorization: Bearer YOUR_API_KEY"

# アクティブポリシー確認
curl https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY"

# ポリシー作成(※If-Matchヘッダー必須)
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

curl -X PUT https://api.pii-fi.com/api/policy/named/my_policy \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG" \
  -d '{
    "policy": {
      "version": "2.0",
      "entities": {
        "person_name": {"enabled": true},
        "email": {"enabled": false}
      }
    }
  }'

# ポリシーアクティブ化(※If-Matchヘッダー必須)
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

curl -X POST https://api.pii-fi.com/api/policy/activate/my_policy \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG"

Custom Recognizer

bash
# カスタム認識器一覧
curl https://api.pii-fi.com/api/policy/recognizers \
  -H "Authorization: Bearer YOUR_API_KEY"

# カスタム認識器追加(※If-Matchヘッダー必須)
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

curl -X POST https://api.pii-fi.com/api/policy/recognizers \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG" \
  -d '{
    "id": "custom.employee_id",
    "name": "社員番号認識器",
    "entity_type": "JPII_EMPLOYEE_ID",
    "priority": 100,
    "patterns": [
      {"name": "emp_pattern", "regex": "EMP-\\d{6}", "score": 0.9}
    ]
  }'

# カスタム認識器削除(※If-Matchヘッダー必須)
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

curl -X DELETE https://api.pii-fi.com/api/policy/recognizers/custom.employee_id \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG"

Dictionary Management

bash
# 辞書一覧
curl https://api.pii-fi.com/api/policy/dictionaries \
  -H "Authorization: Bearer YOUR_API_KEY"

# 辞書作成(※If-Matchヘッダー必須)
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

curl -X POST https://api.pii-fi.com/api/policy/dictionaries \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG" \
  -d '{
    "id": "dict.companies",
    "name": "会社名辞書",
    "entity_type": "JPII_COMPANY_NAME",
    "entries": [
      {"value": "トヨタ自動車"},
      {"value": "ソニー"}
    ]
  }'

# ファジーマッチング有効の辞書作成(※If-Matchヘッダー必須)
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

curl -X POST https://api.pii-fi.com/api/policy/dictionaries \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG" \
  -d '{
    "id": "dict.products",
    "name": "製品名辞書",
    "entity_type": "JPII_PRODUCT_NAME",
    "priority": 90,
    "entries": [
      {"value": "Bestllam"},
      {"value": "ChatStream"},
      {"value": "MotionVox"}
    ],
    "fuzzy": true,
    "fuzzy_threshold": 0.8
  }'

# ファジーマッチングの動作確認(タイプミスを含むテキスト)
curl -X POST https://api.pii-fi.com/api/detect \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "text": "弊社のBestlamとChatSteamの新機能をご紹介します",
    "deidentification_type": "replace"
  }'
# → {"deidentified_text": "弊社の<製品名>と<製品名>の新機能をご紹介します", ...}

# エントリ追加(※If-Matchヘッダー必須)
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

curl -X POST https://api.pii-fi.com/api/policy/dictionaries/dict.companies/entries \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG" \
  -d '{"entries": [{"value": "任天堂"}]}'

# エントリ削除(※If-Matchヘッダー必須)
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

curl -X DELETE https://api.pii-fi.com/api/policy/dictionaries/dict.companies/entries \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG" \
  -d '{"values": ["任天堂"]}'

# 辞書削除(※If-Matchヘッダー必須)
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

curl -X DELETE https://api.pii-fi.com/api/policy/dictionaries/dict.companies \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG"

辞書管理クラス(Python/Node.js)

会社名や製品名など、固有名詞を辞書ベースで検出するためのクライアントクラスです。

Python DictionaryManager

ETag/If-Matchによる楽観的ロックとファジーマッチングに対応した辞書管理クラスです。

python
import requests

class DictionaryManager:
    # base_url: デモ環境(本番環境では専用エンドポイントを提供)
    def __init__(self, api_key, base_url="https://api.pii-fi.com/api"):
        self.api_key = api_key
        self.base_url = base_url
        self.headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }

    def _get_etag(self):
        """アクティブポリシーからETagを取得"""
        response = requests.get(
            f"{self.base_url}/policy/active",
            headers=self.headers
        )
        response.raise_for_status()
        return response.headers.get("ETag")

    def list_dictionaries(self):
        """辞書一覧を取得"""
        response = requests.get(
            f"{self.base_url}/policy/dictionaries",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def create_dictionary(self, config):
        """辞書を作成(ETag必須)"""
        etag = self._get_etag()
        headers = {**self.headers, "If-Match": etag}
        response = requests.post(
            f"{self.base_url}/policy/dictionaries",
            headers=headers,
            json=config
        )
        response.raise_for_status()
        return response.json()

    def add_entries(self, dictionary_id, entries):
        """エントリを追加(ETag必須)"""
        etag = self._get_etag()
        headers = {**self.headers, "If-Match": etag}
        response = requests.post(
            f"{self.base_url}/policy/dictionaries/{dictionary_id}/entries",
            headers=headers,
            json={"entries": entries}
        )
        response.raise_for_status()
        return response.json()

    def delete_entries(self, dictionary_id, values):
        """エントリを削除(ETag必須)"""
        etag = self._get_etag()
        headers = {**self.headers, "If-Match": etag}
        response = requests.delete(
            f"{self.base_url}/policy/dictionaries/{dictionary_id}/entries",
            headers=headers,
            json={"values": values}
        )
        response.raise_for_status()
        return response.json()

    def delete_dictionary(self, dictionary_id):
        """辞書を削除(ETag必須)"""
        etag = self._get_etag()
        headers = {**self.headers, "If-Match": etag}
        response = requests.delete(
            f"{self.base_url}/policy/dictionaries/{dictionary_id}",
            headers=headers
        )
        response.raise_for_status()
        return response.json()


# 使用例
manager = DictionaryManager("YOUR_API_KEY")

# === 会社名辞書(完全一致) ===
manager.create_dictionary({
    "id": "dict.companies",
    "name": "取引先会社名",
    "entity_type": "JPII_COMPANY_NAME",
    "priority": 90,
    "entries": [
        {"value": "トヨタ自動車", "metadata": {"industry": "自動車"}},
        {"value": "ソニー", "metadata": {"industry": "電機"}}
    ]
})

# エントリを追加
manager.add_entries("dict.companies", [
    {"value": "任天堂", "metadata": {"industry": "ゲーム"}}
])

# === 製品名辞書(ファジーマッチング有効) ===
manager.create_dictionary({
    "id": "dict.products",
    "name": "製品名辞書",
    "entity_type": "JPII_PRODUCT_NAME",
    "priority": 90,
    "entries": [
        {"value": "Bestllam", "metadata": {"type": "LLM"}},
        {"value": "ChatStream", "metadata": {"type": "Chat"}},
        {"value": "MotionVox", "metadata": {"type": "Voice"}}
    ],
    "fuzzy": True,
    "fuzzy_threshold": 0.8  # 類似度80%以上でマッチ
})

# === ファジーマッチングの動作確認 ===
detect_result = requests.post(
    f"{manager.base_url}/detect",
    headers=manager.headers,
    json={
        "text": "弊社のBestlamとChatSteamの新機能をご紹介します",
        "deidentification_type": "replace"
    }
).json()

print(detect_result["deidentified_text"])
# → "弊社の<製品名>と<製品名>の新機能をご紹介します"

# 検出方法の詳細を確認
for entity in detect_result.get("entities", []):
    method = entity.get("detection_method", "")
    if method == "dictionary_fuzzy":
        print(f"  タイプミス検出: {entity['original_text']}"
              f" → 辞書: {entity.get('masking_details', {}).get('original_entry', '')}"
              f" (類似度: {entity['confidence_score']})")
    elif method == "dictionary_exact":
        print(f"  完全一致: {entity['original_text']}")
    elif method == "dictionary_normalized":
        print(f"  正規化一致: {entity['original_text']}")

Node.js DictionaryManager

javascript
class DictionaryManager {
    // baseUrl: デモ環境(本番環境では専用エンドポイントを提供)
    constructor(apiKey, baseUrl = "https://api.pii-fi.com/api") {
        this.apiKey = apiKey;
        this.baseUrl = baseUrl;
    }

    async #getEtag() {
        const response = await fetch(`${this.baseUrl}/policy/active`, {
            headers: { "Authorization": `Bearer ${this.apiKey}` }
        });
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.headers.get("ETag");
    }

    async listDictionaries() {
        const response = await fetch(`${this.baseUrl}/policy/dictionaries`, {
            headers: { "Authorization": `Bearer ${this.apiKey}` }
        });
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.json();
    }

    async createDictionary(config) {
        const etag = await this.#getEtag();
        const response = await fetch(`${this.baseUrl}/policy/dictionaries`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${this.apiKey}`,
                "If-Match": etag
            },
            body: JSON.stringify(config)
        });
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.json();
    }

    async addEntries(dictionaryId, entries) {
        const etag = await this.#getEtag();
        const response = await fetch(
            `${this.baseUrl}/policy/dictionaries/${dictionaryId}/entries`,
            {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${this.apiKey}`,
                    "If-Match": etag
                },
                body: JSON.stringify({ entries })
            }
        );
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.json();
    }

    async deleteEntries(dictionaryId, values) {
        const etag = await this.#getEtag();
        const response = await fetch(
            `${this.baseUrl}/policy/dictionaries/${dictionaryId}/entries`,
            {
                method: "DELETE",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${this.apiKey}`,
                    "If-Match": etag
                },
                body: JSON.stringify({ values })
            }
        );
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.json();
    }

    async deleteDictionary(dictionaryId) {
        const etag = await this.#getEtag();
        const response = await fetch(
            `${this.baseUrl}/policy/dictionaries/${dictionaryId}`,
            {
                method: "DELETE",
                headers: {
                    "Authorization": `Bearer ${this.apiKey}`,
                    "If-Match": etag
                }
            }
        );
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.json();
    }
}

// 使用例
const manager = new DictionaryManager("YOUR_API_KEY");

// === 会社名辞書(完全一致) ===
await manager.createDictionary({
    id: "dict.companies",
    name: "取引先会社名",
    entity_type: "JPII_COMPANY_NAME",
    priority: 90,
    entries: [
        { value: "トヨタ自動車", metadata: { industry: "自動車" } },
        { value: "ソニー", metadata: { industry: "電機" } }
    ]
});

// エントリを追加
await manager.addEntries("dict.companies", [
    { value: "任天堂", metadata: { industry: "ゲーム" } }
]);

// === 製品名辞書(ファジーマッチング有効) ===
await manager.createDictionary({
    id: "dict.products",
    name: "製品名辞書",
    entity_type: "JPII_PRODUCT_NAME",
    priority: 90,
    entries: [
        { value: "Bestllam", metadata: { type: "LLM" } },
        { value: "ChatStream", metadata: { type: "Chat" } },
        { value: "MotionVox", metadata: { type: "Voice" } }
    ],
    fuzzy: true,
    fuzzy_threshold: 0.8  // 類似度80%以上でマッチ
});

// === ファジーマッチングの動作確認 ===
const detectResponse = await fetch(`${manager.baseUrl}/detect`, {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${manager.apiKey}`
    },
    body: JSON.stringify({
        text: "弊社のBestlamとChatSteamの新機能をご紹介します",
        deidentification_type: "replace"
    })
});
const detectResult = await detectResponse.json();

console.log(detectResult.deidentified_text);
// → "弊社の<製品名>と<製品名>の新機能をご紹介します"

// 検出方法の詳細を確認
for (const entity of detectResult.entities) {
    const method = entity.detection_method;
    if (method === "dictionary_fuzzy") {
        console.log(`  タイプミス検出: ${entity.original_text}`
            + ` → 辞書: ${entity.masking_details?.original_entry}`
            + ` (類似度: ${entity.confidence_score})`);
    } else if (method === "dictionary_exact") {
        console.log(`  完全一致: ${entity.original_text}`);
    } else if (method === "dictionary_normalized") {
        console.log(`  正規化一致: ${entity.original_text}`);
    }
}

文脈解析パイプライン

文脈解析パイプラインは、同じ数値や金額でも周辺の文脈から異なる機密カテゴリに分類できる高度な認識器です。 「年収500万円」→個人収入、「売上高500万円」→企業収益、「M&A金額500万円」→戦略情報と自動分類します。

パイプライン作成(curl)

bash
# ETag取得
ETAG=$(curl -s -I https://api.pii-fi.com/api/policy/active \
  -H "Authorization: Bearer YOUR_API_KEY" | grep -i etag | awk '{print $2}' | tr -d '\r')

# 金融情報分類パイプラインを作成
curl -X POST https://api.pii-fi.com/api/policy/pipelines \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "If-Match: $ETAG" \
  -d '{
    "id": "pipeline.financial",
    "name": "金融情報分類パイプライン",
    "description": "金額の文脈を解析し、機密カテゴリを自動分類",
    "categories": [
      {
        "id": "PERSONAL_INCOME",
        "label": "個人収入",
        "context_keywords": ["年収", "月収", "給与", "給料", "手取り", "所得"],
        "entity_type": "JPII_PERSONAL_INCOME"
      },
      {
        "id": "CORPORATE_EARNINGS",
        "label": "企業収益",
        "context_keywords": ["売上", "売上高", "収益", "利益", "営業利益", "経常利益"],
        "entity_type": "JPII_CORPORATE_EARNINGS"
      },
      {
        "id": "CORPORATE_STRATEGIC",
        "label": "戦略情報",
        "context_keywords": ["M&A", "買収", "合併", "出資", "投資額", "資金調達"],
        "entity_type": "JPII_CORPORATE_STRATEGIC"
      }
    ],
    "priority": 85
  }'

文脈分類の動作確認

bash
# 同じ「500万円」が文脈によって異なるカテゴリに分類される
curl -X POST https://api.pii-fi.com/api/detect \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "text": "田中氏の年収は500万円です。A社の売上高は500万円でした。B社のM&A金額は500万円と報道されています。",
    "deidentification_type": "replace"
  }'

# 結果:
# {
#   "deidentified_text": "<人名>の<個人収入>です。A社の<企業収益>でした。B社の<戦略情報>と報道されています。",
#   "entities": [
#     {"entity_type": "JPII_PERSON_NAME", "original_text": "田中氏", ...},
#     {"entity_type": "JPII_PERSONAL_INCOME", "original_text": "年収は500万円", "context_classification": {"category": "PERSONAL_INCOME", ...}},
#     {"entity_type": "JPII_CORPORATE_EARNINGS", "original_text": "売上高は500万円", "context_classification": {"category": "CORPORATE_EARNINGS", ...}},
#     {"entity_type": "JPII_CORPORATE_STRATEGIC", "original_text": "M&A金額は500万円", "context_classification": {"category": "CORPORATE_STRATEGIC", ...}}
#   ]
# }

Python ContextualPipelineManager

python
import requests

class ContextualPipelineManager:
    # base_url: デモ環境(本番環境では専用エンドポイントを提供)
    def __init__(self, api_key, base_url="https://api.pii-fi.com/api"):
        self.api_key = api_key
        self.base_url = base_url
        self.headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }

    def _get_etag(self):
        """アクティブポリシーからETagを取得"""
        response = requests.get(
            f"{self.base_url}/policy/active",
            headers=self.headers
        )
        response.raise_for_status()
        return response.headers.get("ETag")

    def list_pipelines(self):
        """パイプライン一覧を取得"""
        response = requests.get(
            f"{self.base_url}/policy/pipelines",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def create_pipeline(self, config):
        """パイプラインを作成(ETag必須)"""
        etag = self._get_etag()
        headers = {**self.headers, "If-Match": etag}
        response = requests.post(
            f"{self.base_url}/policy/pipelines",
            headers=headers,
            json=config
        )
        response.raise_for_status()
        return response.json()

    def delete_pipeline(self, pipeline_id):
        """パイプラインを削除(ETag必須)"""
        etag = self._get_etag()
        headers = {**self.headers, "If-Match": etag}
        response = requests.delete(
            f"{self.base_url}/policy/pipelines/{pipeline_id}",
            headers=headers
        )
        response.raise_for_status()
        return response.json()

    def detect(self, text, deidentification_type="replace"):
        """テキストを検出(文脈分類情報付き)"""
        response = requests.post(
            f"{self.base_url}/detect",
            headers=self.headers,
            json={
                "text": text,
                "deidentification_type": deidentification_type
            }
        )
        response.raise_for_status()
        return response.json()


# === 使用例 ===
manager = ContextualPipelineManager("YOUR_API_KEY")

# 金融情報分類パイプラインを作成
manager.create_pipeline({
    "id": "pipeline.financial",
    "name": "金融情報分類パイプライン",
    "categories": [
        {
            "id": "PERSONAL_INCOME",
            "label": "個人収入",
            "context_keywords": ["年収", "月収", "給与", "給料", "手取り"],
            "entity_type": "JPII_PERSONAL_INCOME"
        },
        {
            "id": "CORPORATE_EARNINGS",
            "label": "企業収益",
            "context_keywords": ["売上", "売上高", "収益", "利益"],
            "entity_type": "JPII_CORPORATE_EARNINGS"
        },
        {
            "id": "CORPORATE_STRATEGIC",
            "label": "戦略情報",
            "context_keywords": ["M&A", "買収", "合併", "出資"],
            "entity_type": "JPII_CORPORATE_STRATEGIC"
        }
    ]
})

# 文脈分類の動作確認
result = manager.detect(
    "田中氏の年収は500万円です。A社の売上高は500万円でした。"
)
print(result["deidentified_text"])
# → "<人名>の<個人収入>です。A社の<企業収益>でした。"

# 各エンティティの分類詳細を確認
for entity in result.get("entities", []):
    ctx = entity.get("context_classification")
    if ctx:
        print(f"  {entity['original_text']} → {ctx['category_label']}"
              f" (キーワード: {ctx['context_keywords_matched']})")

# ※ 大量の自社ドキュメントからの学習による高精度なパイプライン構築は
#   「認識器学習構築サービス」(有償)にて承ります。
#   詳しくは /docs/training-service をご参照ください。

Node.js ContextualPipelineManager

javascript
class ContextualPipelineManager {
    // baseUrl: デモ環境(本番環境では専用エンドポイントを提供)
    constructor(apiKey, baseUrl = "https://api.pii-fi.com/api") {
        this.apiKey = apiKey;
        this.baseUrl = baseUrl;
    }

    async #getEtag() {
        const response = await fetch(`${this.baseUrl}/policy/active`, {
            headers: { "Authorization": `Bearer ${this.apiKey}` }
        });
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.headers.get("ETag");
    }

    async listPipelines() {
        const response = await fetch(`${this.baseUrl}/policy/pipelines`, {
            headers: { "Authorization": `Bearer ${this.apiKey}` }
        });
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.json();
    }

    async createPipeline(config) {
        const etag = await this.#getEtag();
        const response = await fetch(`${this.baseUrl}/policy/pipelines`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${this.apiKey}`,
                "If-Match": etag
            },
            body: JSON.stringify(config)
        });
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.json();
    }

    async deletePipeline(pipelineId) {
        const etag = await this.#getEtag();
        const response = await fetch(
            `${this.baseUrl}/policy/pipelines/${pipelineId}`,
            {
                method: "DELETE",
                headers: {
                    "Authorization": `Bearer ${this.apiKey}`,
                    "If-Match": etag
                }
            }
        );
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.json();
    }

    async detect(text, deidentificationType = "replace") {
        const response = await fetch(`${this.baseUrl}/detect`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${this.apiKey}`
            },
            body: JSON.stringify({
                text,
                deidentification_type: deidentificationType
            })
        });
        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
        return response.json();
    }
}

// === 使用例 ===
const manager = new ContextualPipelineManager("YOUR_API_KEY");

// 金融情報分類パイプラインを作成
await manager.createPipeline({
    id: "pipeline.financial",
    name: "金融情報分類パイプライン",
    categories: [
        {
            id: "PERSONAL_INCOME",
            label: "個人収入",
            context_keywords: ["年収", "月収", "給与", "給料", "手取り"],
            entity_type: "JPII_PERSONAL_INCOME"
        },
        {
            id: "CORPORATE_EARNINGS",
            label: "企業収益",
            context_keywords: ["売上", "売上高", "収益", "利益"],
            entity_type: "JPII_CORPORATE_EARNINGS"
        },
        {
            id: "CORPORATE_STRATEGIC",
            label: "戦略情報",
            context_keywords: ["M&A", "買収", "合併", "出資"],
            entity_type: "JPII_CORPORATE_STRATEGIC"
        }
    ]
});

// 文脈分類の動作確認
const result = await manager.detect(
    "田中氏の年収は500万円です。A社の売上高は500万円でした。B社のM&A金額は500万円です。"
);
console.log(result.deidentified_text);
// → "<人名>の<個人収入>です。A社の<企業収益>でした。B社の<戦略情報>です。"

// 各エンティティの分類詳細を確認
for (const entity of result.entities || []) {
    const ctx = entity.context_classification;
    if (ctx) {
        console.log(`  ${entity.original_text} → ${ctx.category_label}`
            + ` (キーワード: ${ctx.context_keywords_matched})`);
    }
}

// ※ 大量の自社ドキュメントからの学習による高精度なパイプライン構築は
//   「認識器学習構築サービス」(有償)にて承ります。
//   詳しくは /docs/training-service をご参照ください。

Next Steps