> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apiyi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ログクエリ API

> 呼び出しログをプログラムで取得し、各リクエストのモデル、実際の課金額、レイテンシー、エラーコードを把握して、自動照合とセルフサービスでのトラブルシューティングを可能にします

## API 概要

Log Query API は、アカウントで行われた **すべての API 呼び出し** の詳細な記録を返します。
使用したモデル、実際の請求額、レイテンシー、呼び出しがストリーミングされたかどうか、
および呼び出しが失敗したときのエラーコードを含みます。

これは [Balance Query API](/ja/api-capabilities/balance-query) を補完するものです。残高照会では
残っているクレジット量がわかり、ログ照会ではそれがどこに使われたかがわかります。

代表的な 3 つのユースケースは次のとおりです。

<CardGroup cols={3}>
  <Card title="自動照合" icon="calculator">
    時間範囲別またはモデル別に実際の支出を集計し、自社の課金と突き合わせます
  </Card>

  <Card title="セルフサービスのトラブルシューティング" icon="bug">
    失敗したリクエストのエラーコードを確認し、パラメータの問題と上流側の問題を切り分けます
  </Card>

  <Card title="サポートチケット" icon="life-buoy">
    サポートに `request_id` を共有すると、正確な呼び出しを特定してもらえます
  </Card>
</CardGroup>

<Info>
  ログは、コンソールの Logs ページでも確認できます。この API は同じデータへのプログラムによるエントリーポイントであり、自動照合、定期エクスポート、または独自のモニタリングへの取り込みを目的としています。手動で確認する場合は、コンソールを使用してください — [呼び出し記録の確認方法](/ja/faq/call-logs) をご覧ください。
</Info>

## System Token の取得方法

Log Query API は **System Token** で認証します。これは API key とは別物です（このページ末尾の重要な注意事項を参照してください）。

<Steps>
  <Step title="コンソールにアクセス">
    `api.apiyi.com/account/profile` にアクセスしてプロフィールページを開いてください
  </Step>

  <Step title="System Token を見つける">
    ページ下部の「Account Options - System Token」セクションを見つけてください
  </Step>

  <Step title="AccessToken を生成">
    アカウントのパスワードを入力すると、後続の API クエリに使用できる AccessToken を取得できます
  </Step>
</Steps>

<img src="https://mintcdn.com/apiyillc/PXVoab-l7wSQlQVE/images/apiyi-system-accesstoken.png?fit=max&auto=format&n=PXVoab-l7wSQlQVE&q=85&s=eb4f48476a795dfa5bfd7cb053081bdc" alt="System Token を取得する" width="1020" height="460" data-path="images/apiyi-system-accesstoken.png" />

## API情報

| 項目          | 説明                                                      |
| ----------- | ------------------------------------------------------- |
| **API URL** | `https://api.apiyi.com/api/log/self`                    |
| **メソッド**    | `GET`                                                   |
| **認証**      | Authorization ヘッダー（生の token 文字列、**`Bearer` プレフィックスなし**） |
| **レスポンス形式** | JSON（gzip 圧縮）                                           |
| **データ範囲**   | ご自身のアカウントのログのみ                                          |

## リクエスト詳細

### リクエストヘッダー

| Header Name     | Required | Description                       |
| --------------- | -------- | --------------------------------- |
| `Authorization` | Yes      | システム token。生の token 文字列として渡してください |
| `Accept`        | No       | 推奨: `application/json`            |

### クエリパラメータ

| Parameter         | Type    | Required | Description                       |
| ----------------- | ------- | -------- | --------------------------------- |
| `p`               | Integer | No       | ページ番号、**ゼロ起点**（1ではありません）          |
| `page_size`       | Integer | No       | 1ページあたりのレコード数、**10件が上限**（下の警告を参照） |
| `type`            | Integer | No       | ログタイプ。照合には `2` を渡してください。下の表を参照    |
| `model_name`      | String  | No       | モデルによる完全一致フィルター。例: `gpt-5.6`      |
| `token_name`      | String  | No       | token 名でフィルター                     |
| `start_timestamp` | Integer | No       | 開始時刻、Unix 秒                       |
| `end_timestamp`   | Integer | No       | 終了時刻、Unix 秒                       |
| `group`           | String  | No       | グループでフィルター                        |

<Warning>
  **`page_size` の実効上限は 10 です。** `page_size=100` を渡しても10件のみ返ります。
  これはエラーではなく、この期間に「10回しか呼び出していない」と誤解しやすいので注意してください。
  **意味のある期間を取得するにはページネーションが必須です。** レスポンスが空配列を返すまでループしてください。
  下の Python と Node.js の例はすでにこれに対応しています。
</Warning>

### ログタイプ

| Value | Meaning | Notes                                   |
| ----- | ------- | --------------------------------------- |
| `1`   | チャージ    | チャージ前後の残高を記録します。`quota` は 0 です          |
| `2`   | **消費**  | **照合に必要なのはこのタイプだけです**。`quota` が実際の請求額です |
| `3`   | 管理      | アカウント変更などの操作。`quota` は 0 です             |
| `4`   | システム    | システム付与クレジットなど。`quota` は 0 です            |

<Warning>
  **支出を計算する際は、必ず `type=2` を渡してください。** これを指定しないと、チャージやシステム付与のレコードも返されます。
  それらの `quota` は 0 ですが、`model_name` と `token_name` も空なので、単純に合計したりモデルでグループ化したりすると誤った結果になります。
</Warning>

## レスポンス詳細

### 成功レスポンスの例

```json theme={null}
{
  "success": true,
  "message": "",
  "data": [
    {
      "request_id": "2026080114481936471351696e93ae3FTV8WKfk",
      "created_at": 1785595715,
      "type": 2,
      "content": "Fixed model price 0.015, group ratio 1",
      "username": "your-account",
      "token_name": "production-primary",
      "token_group": "default",
      "model_name": "gpt-5.6",
      "quota": 7500,
      "prompt_tokens": 1000,
      "completion_tokens": 0,
      "duration_for_view": 16,
      "is_stream": false,
      "error_code": "",
      "other": "{\"billing_type\":\"by_count\",\"request_path\":\"/v1/images/generations\",\"group_ratio\":1,\"model_ratio\":1,\"usage\":{}}"
    }
  ]
}
```

### 主なレスポンスフィールド

| フィールド名                                | 型       | 説明                                             |
| ------------------------------------- | ------- | ---------------------------------------------- |
| `quota`                               | Integer | **この呼び出しで実際に課金された金額**。クレジット単位; ÷ 500,000 = USD |
| `content`                             | String  | 人間が読める課金メモ。たとえば固定のモデル価格やグループ比率                 |
| `model_name`                          | String  | 実際に課金されたモデル                                    |
| `token_name`                          | String  | この呼び出しを行った API キー                              |
| `token_group`                         | String  | token のグループ。これはグループ識別子です。FAQ を参照してください         |
| `prompt_tokens` / `completion_tokens` | Integer | 入力 / 出力 token 数                                |
| `duration_for_view`                   | Integer | 呼び出し時間（秒）                                      |
| `is_stream`                           | Boolean | 呼び出しがストリーミングだったかどうか                            |
| `error_code`                          | String  | 失敗理由コード。成功時は空文字列                               |
| `created_at`                          | Integer | 呼び出し時刻、Unix 秒                                  |
| `request_id`                          | String  | **リクエスト ID — サポートチケットを開く際にこれを提供してください**        |
| `other`                               | String  | 追加の課金およびリクエスト詳細。**再度パースする必要がある JSON 文字列**      |

<Info>
  `other` フィールドには入れ子のオブジェクトではなく JSON の**文字列**が入るため、2 回目のパースが必要です
  (`json.loads()` は Python、`JSON.parse()` は JavaScript で使用します)。そこには `billing_type`、
  `request_path`（実際に呼び出されたエンドポイント）、`group_ratio`、`model_ratio`、および `usage` が含まれます。
</Info>

### クォータ変換

<Card title="変換ルール" icon="calculator">
  500,000 クォータ = \$1.00 USD
</Card>

**式:** USD 金額 = `quota` ÷ 500,000

**例:**

* `quota: 7500` → \$0.015 USD
* `quota: 22500` → \$0.045 USD
* `quota: 18` → \$0.000036 USD

これは [残高照会 API](/ja/api-capabilities/balance-query) で使用されているのと同じ換算であり、
2 つは直接対応します。

## エラーレスポンス

### HTTP 401 - 認証に失敗しました

```json theme={null}
{
  "success": false,
  "message": "You are not authorized to perform this operation. The access token is invalid."
}
```

**理由:** システム token が無効または期限切れであるか、`sk-`で始まる APIキーが誤ってシステム token として使用されました。

**解決策:** コンソールでシステム token を再生成し、`Authorization` には **`Bearer` プレフィックスなしの** 生の値が入っていることを確認してください。

## コード例

### cURL 例（単一ページ、簡易確認）

```bash theme={null}
export APIYI_SYS_TOKEN='YOUR_SYSTEM_TOKEN'

curl --compressed -s 'https://api.apiyi.com/api/log/self?p=0&page_size=10&type=2' \
  -H "Authorization: $APIYI_SYS_TOKEN" \
  -H 'Accept: application/json' | jq '.data[] | {created_at, model_name, quota, request_id}'
```

<Warning>
  **`--compressed` オプションは必須です**。API が gzip 圧縮されたコンテンツを返すためです。
  これがないと、文字化けした出力になります。
</Warning>

<Info>
  このコマンドは最大 10 件のレコードを返します。実際の照合には、下のページネーション版を使用してください。
</Info>

### Python 例（ページネーション対応、すぐ実行可能）

```python theme={null}
import json
import os
import time
from collections import defaultdict

import requests

BASE = "https://api.apiyi.com"
TOKEN = os.environ["APIYI_SYS_TOKEN"]
QUOTA_PER_USD = 500_000

HEADERS = {"Authorization": TOKEN, "Accept": "application/json"}


def fetch_logs(hours=24, model_name=None):
    """Fetch consumption logs for the last N hours, paginating automatically."""
    now = int(time.time())
    params = {
        "page_size": 10,          # server-side cap is 10; larger values have no effect
        "type": 2,                # 2 = consumption, the only type used for reconciliation
        "start_timestamp": now - hours * 3600,
        "end_timestamp": now,
    }
    if model_name:
        params["model_name"] = model_name

    rows, seen = [], set()
    page = 0
    while True:
        resp = requests.get(f"{BASE}/api/log/self",
                            headers=HEADERS, params={**params, "p": page}, timeout=30)
        resp.raise_for_status()
        data = resp.json().get("data") or []
        if not data:
            break                 # an empty array means we reached the end

        fresh = 0
        for row in data:
            # some records always report id as 0, so deduplicate on request_id instead
            key = row.get("request_id")
            if key in seen:
                continue
            seen.add(key)
            rows.append(row)
            fresh += 1
        if fresh == 0:
            break                 # whole page was duplicates, defensive exit
        page += 1
    return rows


def summarize(rows):
    """Aggregate call count and spend per model."""
    stat = defaultdict(lambda: {"count": 0, "quota": 0})
    for row in rows:
        s = stat[row.get("model_name") or "(none)"]
        s["count"] += 1
        s["quota"] += row.get("quota") or 0

    total = sum(s["quota"] for s in stat.values())
    print(f"{'MODEL':32s} {'CALLS':>6s} {'SPEND(USD)':>12s}")
    for model, s in sorted(stat.items(), key=lambda kv: -kv[1]["quota"]):
        print(f"{model:32s} {s['count']:6d} {s['quota'] / QUOTA_PER_USD:12.4f}")
    print(f"\n{len(rows)} calls, {total:,} quota = ${total / QUOTA_PER_USD:.4f} USD")


if __name__ == "__main__":
    logs = fetch_logs(hours=24)
    summarize(logs)

    # other is a JSON string and needs a second parse
    if logs:
        extra = json.loads(logs[0].get("other") or "{}")
        print("\nEndpoint of the most recent call:", extra.get("request_path"))
```

**サンプル出力:**

```
MODEL                             CALLS   SPEND(USD)
gpt-5.6                             128       2.3850
gemini-3-pro-image                   30       1.3500
deepseek-chat                       412       0.0148

570 calls, 1,867,400 quota = $3.7348 USD
```

### Node.js 例（ページネーション対応）

```javascript theme={null}
const BASE = "https://api.apiyi.com";
const TOKEN = process.env.APIYI_SYS_TOKEN;
const QUOTA_PER_USD = 500_000;

async function fetchLogs({ hours = 24, modelName = null } = {}) {
  const now = Math.floor(Date.now() / 1000);
  const base = {
    page_size: "10",           // server-side cap is 10
    type: "2",                 // 2 = consumption
    start_timestamp: String(now - hours * 3600),
    end_timestamp: String(now),
  };
  if (modelName) base.model_name = modelName;

  const rows = [];
  const seen = new Set();
  for (let page = 0; ; page += 1) {
    const qs = new URLSearchParams({ ...base, p: String(page) });
    const resp = await fetch(`${BASE}/api/log/self?${qs}`, {
      headers: { Authorization: TOKEN, Accept: "application/json" },
    });
    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
    const { data } = await resp.json();
    if (!data || data.length === 0) break;

    let fresh = 0;
    for (const row of data) {
      // deduplicate on request_id, since some records always report id as 0
      if (seen.has(row.request_id)) continue;
      seen.add(row.request_id);
      rows.push(row);
      fresh += 1;
    }
    if (fresh === 0) break;
  }
  return rows;
}

const logs = await fetchLogs({ hours: 24 });
const total = logs.reduce((sum, r) => sum + (r.quota || 0), 0);
console.log(`${logs.length} calls, $${(total / QUOTA_PER_USD).toFixed(4)} USD`);
```

<Info>
  Python の requests ライブラリと Node.js の fetch API は、どちらも gzip を自動で展開するため、
  追加の設定は不要です。curl だけは明示的な `--compressed` フラグが必要です。
</Info>

## 一般的なシナリオ

### 期間内の支出を計算する

上の Python の例を使ってください。重要なのは、`type=2` を渡すことと、合計した `quota` を 500,000 で割ることです。単一のモデルに絞るには、`model_name` パラメータを追加します。

### 失敗した呼び出しを見つける

```python theme={null}
failed = [r for r in fetch_logs(hours=24) if r.get("error_code")]
for r in failed:
    print(r["created_at"], r["model_name"], r["error_code"], r["request_id"])
```

<Info>
  ゲートウェイによって拒否されたリクエスト（不正なパラメータなど）は `quota` が 0 で、
  **課金されません**。ログ内の `error_code` を使うと、「呼び出しが失敗した」のか
  「呼び出しは成功したが、結果が気に入らなかった」のかを切り分けられます。
</Info>

### サポートに渡すリクエスト ID を提供する

ログで問題のある呼び出しを見つけて、サポートに `request_id` を伝えてください。これにより、
正確なリクエストをエンドツーエンドで特定できるため、「あるモデルへの呼び出しが
ある時刻の近くで失敗した」と説明するよりはるかに効率的です。

## よくある質問

<AccordionGroup>
  <Accordion title="なぜ 10 件しか取得できないのですか？">
    `page_size` のサーバー側上限は 10 です。それより大きい値でもエラーにはなりませんが、反映もされません。
    それ以上取得するには、ページネーション（`p=0`、`p=1`、`p=2` など）を使い、レスポンスが空の配列を返すまで続ける必要があります。上の Python と Node.js の例には、すでにこの処理が含まれています。
  </Accordion>

  <Accordion title="レスポンスの一部のフィールドが空です。何か問題がありますか？">
    いいえ。一部のフィールドにはプラットフォーム内部の情報が含まれており、通常のアカウントから見ると空または 0 です。これは想定どおりで、照合やトラブルシューティングに必要なフィールドには影響しません — `quota`、`model_name`、`error_code`、`request_id`
    はいずれも完全に埋まっています。
  </Accordion>

  <Accordion title="token_group がコンソールに表示されるグループ名と異なるのはなぜですか？">
    API はグループの識別子を返しますが、コンソールはグループのラベルを表示します。
    これらは異なる場合があります — たとえば、API では `default` が返り、コンソールでは Default と表示されます。

    完全な対応表は、公開エンドポイント `https://api.apiyi.com/api/pricing` の `usable_group` フィールドで確認できます。このフィールドは識別子をラベルに対応付けます。レポートをコンソールと一致させたい場合は、この対応付けを自分で適用してください。
  </Accordion>

  <Accordion title="token のカウントとクォータが一致していないようです。どちらが正しいのですか？">
    `quota` を使用してください。これは、その呼び出しで実際に差し引かれた量であり、照合に適した唯一のフィールドです。画像生成や動画生成のような呼び出しごとの課金モデルでは、レスポンス内の token カウントが価格計算に関与しないプレースホルダー値になる場合があります — そうしたモデルは `other.billing_type` に `by_count` を報告します。
  </Accordion>

  <Accordion title="どこまで遡ってクエリできますか？">
    `start_timestamp` と `end_timestamp` を使って任意の範囲を指定できます。履歴データの正確な保持期間については、サポートにお問い合わせください。長期的な遡及参照を API に頼るよりも、照合データを定期的にエクスポートすることをおすすめします。
  </Accordion>

  <Accordion title="curl で文字化けする、または jq がエラーを出す">
    **理由:** API が gzip 圧縮されたコンテンツ（`Content-Encoding: gzip`）を返しており、curl がそれを展開していないためです。

    **対処法:** `--compressed` フラグを追加します：

    ```bash theme={null}
    curl --compressed 'https://api.apiyi.com/api/log/self?p=0' \
      -H "Authorization: $APIYI_SYS_TOKEN" | jq
    ```

    Python の requests ライブラリと Node.js の fetch API は自動的に展開します。
  </Accordion>

  <Accordion title="ログをクエリするとクォータを消費しますか？">
    いいえ。ログクエリエンドポイントはクォータを消費しません。
  </Accordion>
</AccordionGroup>

## 重要な注意事項

<Warning>
  **システム token は API key ではなく、両者は互換性がありません**

  * **API key**（`sk-` で始まる）は `/v1/*` 推論エンドポイント用です。`/api/log/self` に対して使用すると
    401 が返ります。
  * **システム token**（プレフィックスのないプレーンな文字列）は `/api/*` 管理エンドポイント用です。
    `/v1/chat/completions` に対して使用すると、無効な token エラーが返ります。

  システム token のスコープはアカウント全体に及ぶため、**アカウントのパスワードのように扱ってください**:
  コード内ではなくシークレットマネージャーに保存し、リポジトリにコミットせず、定期的にローテーションしてください。
</Warning>

<Warning>
  **ログレスポンスには自分の API key がプレーンテキストで含まれています**

  各ログレコードには、その呼び出しを行った token の情報が含まれています。**ログレスポンスの生データを公開の場に貼り付けたり、スクリーンショットを共有したり、第三者に渡したりしないでください** —
  エクスポート前に機密フィールドを削除してください。

  特に、このプレーンテキストには `sk-` プレフィックスが付かないため、**一般的なシークレットスキャナーでは検出されない場合があります**。自動チェックに検出を頼らないでください。
</Warning>

<Info>
  **リクエスト制限**

  * レート制限を避けるため、クエリの間隔は少なくとも 1 秒空けてください
  * 適切なリクエストタイムアウトを設定してください（30 秒を推奨します）
  * 広い期間範囲では、ページネーションと再試行処理を実装してください
</Info>

<Card title="関連ドキュメント" icon="link">
  * [残高照会 API](/ja/api-capabilities/balance-query) — アカウントの残り残高を確認します
  * [token 管理 API](/ja/api-capabilities/token-management) — API key をプログラムから作成・管理します
  * [呼び出し履歴の確認方法](/ja/faq/call-logs) — コンソールで手動確認します
  * [ログと課金の理解](/ja/faq/log-billing-explained) — 課金フィールドの読み方
</Card>
