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

# REST API

位置引数を指定せずにコンテナーを起動すると、FineParser はポート 8080 で HTTP サーバーとして動作し、明示的に停止するまで稼働し続けます。アプリケーションやその他のサービスからネットワーク経由で FineParser にDocumentを送信する必要がある場合は、このモードを使用します。

```bash theme={null}
docker run -d --name fineparser -p 8080:8080 --stop-timeout 60 \
  -e FINEPARSER_LICENSE_DATA="$(cat acme.fineparserlicense)" \
  -v fineparser-output:/app/output \
  abbyyteam/fineparser
```

Recognition mode、languages、License はいずれもコンテナーの起動時に設定されるため、requests に認証情報や recognition settings を含める必要はありません。結果 は `/app/output` に書き込まれるので、その場所にボリュームをマウントしてください。マウントしないと、コンテナーの終了とともに消えてしまいます。[Configuration](/ja/fine-parser/basics/configuration) を参照してください。

<h2 id="how-a-parse-works">
  解析の仕組み
</h2>

解析は非同期で行われます。Documentを送信するとジョブ ID がすぐに返されるので、ジョブが完了状態になるまでポーリングし、その後に結果をダウンロードします。認識処理はコンテナー内の単一の worker で 1 Documentずつ実行されるため、認識処理が終わるまで HTTP 接続が開いたままになることはありません。

<Steps>
  <Step title="送信">
    Documentと出力タイプを指定して `POST /parse` を実行します。レスポンスは `202 Accepted` で、ジョブ ID が返されます。
  </Step>

  <Step title="ポーリング">
    `status` が `successful`、`failed`、`cancelled` のいずれかになるまで `GET /jobs/{id}` を実行します。
  </Step>

  <Step title="ダウンロード">
    `GET /jobs/{file}` を実行します。`file` にはステータスのレスポンスで返されたパスを指定します。結果は添付ファイルとしてストリーミングで返されます。
  </Step>
</Steps>

<CodeGroup>
  ```bash cURL theme={null}
  # 送信
  curl -s -X POST http://localhost:8080/parse \
    -F "file=@document.pdf" \
    -F "outputType=doclang"
  # {"jobId":"6c4f1f0e-...","file":"document.pdf"}

  # ポーリング
  curl -s http://localhost:8080/jobs/6c4f1f0e-...
  # {"status":"successful","file":"6c4f1f0e-.../document.pdf.doclang"}

  # ダウンロード
  curl -OJ http://localhost:8080/jobs/6c4f1f0e-.../document.pdf.doclang
  ```

  ```python Python theme={null}
  import time
  import requests

  BASE = "http://localhost:8080"

  with open("document.pdf", "rb") as f:
      submitted = requests.post(
          f"{BASE}/parse",
          files={"file": f},
          data={"outputType": "doclang"},
      )
  submitted.raise_for_status()
  job_id = submitted.json()["jobId"]

  while True:
      job = requests.get(f"{BASE}/jobs/{job_id}").json()
      if job["status"] not in ("pending", "in-progress"):
          break
      time.sleep(1)

  if job["status"] != "successful":
      raise RuntimeError(job.get("error", job["status"]))

  result = requests.get(f"{BASE}/jobs/{job['file']}")
  result.raise_for_status()
  with open("document.doclang", "wb") as out:
      out.write(result.content)
  ```

  ```javascript Node.js theme={null}
  import { openAsBlob } from "node:fs";
  import { writeFile } from "node:fs/promises";
  import { setTimeout as sleep } from "node:timers/promises";

  const BASE = "http://localhost:8080";

  const form = new FormData();
  form.append("file", await openAsBlob("document.pdf"), "document.pdf");
  form.append("outputType", "doclang");

  const submitted = await fetch(`${BASE}/parse`, { method: "POST", body: form });
  if (!submitted.ok) throw new Error(await submitted.text());
  const { jobId } = await submitted.json();

  let job;
  do {
    await sleep(1000);
    job = await (await fetch(`${BASE}/jobs/${jobId}`)).json();
  } while (job.status === "pending" || job.status === "in-progress");

  if (job.status !== "successful") throw new Error(job.error ?? job.status);

  const result = await fetch(`${BASE}/jobs/${job.file}`);
  await writeFile("document.doclang", Buffer.from(await result.arrayBuffer()));
  ```
</CodeGroup>

<h2 id="endpoints">
  エンドポイント
</h2>

すべてのエラーレスポンスは JSON 形式で、問題の内容を示す `error` field を含みます。一部のレスポンスには `code` または `status` field も含まれます (以下を参照) 。

<h3 id="post-parse">
  POST /parse
</h3>

Document を 1 件送信します。`multipart/form-data` 形式で送信し、document を格納した `file` field と、`doclang`、`json`、`txt` のいずれかを指定した `outputType` field を含めてください。[出力形式](/ja/fine-parser/basics/output-formats)を参照してください。

送信に成功すると、`202 Accepted`、ジョブ を指す `Location` header、および ジョブ の ID と FineParser が記録したファイル名を含む body が返されます。

```json theme={null}
{"jobId":"6c4f1f0e-...","file":"document.pdf"}
```

| ステータス | 原因                                                                                                                                         |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `202` | 受理されました。`/jobs/{id}` をポーリングしてください。                                                                                                         |
| `400` | `outputType` が指定されていない、または `doclang`、`json`、`txt` のいずれでもありません。あるいは、request に `file` という名前の part がちょうど 1 つ含まれていません。                         |
| `402` | プランの残りページ数がありません。body には `code: out_of_credits` が含まれます。[上限に達した場合](/ja/fine-parser/basics/licenses-and-plans#reaching-your-limit)を参照してください。 |
| `403` | コンテナーに License がない、または License がプロビジョニングされていません。body には `code: no_instance` または `code: not_provisioned` が含まれます。                            |
| `413` | request body がアップロード上限 (既定では 1 GiB) を超えています。[Configuration](/ja/fine-parser/basics/configuration) を参照してください。                               |
| `503` | ライセンスサーバーに到達できないか、再試行を求められました。body には `code: server_unreachable`、`rate_limited`、または `unknown` が含まれます。時間をおいて再度お試しください。                      |

licensing のチェックはアップロードの読み取りが始まる前に行われるため、License のないコンテナーは帯域や disk を消費することなく request を拒否します。

<h3 id="get-jobsid">
  GET /jobs/{id}
</h3>

ジョブの状態を返します。

```json theme={null}
{"status":"successful","file":"6c4f1f0e-.../document.pdf.doclang"}
```

| `status`      | 意味                                                          |
| ------------- | ----------------------------------------------------------- |
| `pending`     | キューに入り、worker を待機中。                                         |
| `in-progress` | 認識処理中。                                                      |
| `successful`  | 完了。結果がディスク上にある間は `file` が存在します。                             |
| `failed`      | 完了しませんでした。失敗の理由は `error` に格納されます。                           |
| `cancelled`   | この Document の認識中にコンテナーがシャットダウンしました。最終状態です。必要であれば再度送信してください。 |

`file` は `{jobId}/{name}` という形式の結果のパスです。先頭に `/jobs/` を付けるとダウンロード URL になります。name はアップロードしたファイル名に出力の型を付加したもので、`document.pdf` を DocLang に解析した場合は `document.pdf.doclang` となります。この field はジョブが成功するまで存在せず、結果がディスクから削除されると再び消えます。

page count は Document がロードされて初めて判明するため、ジョブが受理された後で licensing 上の理由により失敗することがあります。その場合、`status` は `failed` となり、`error` にその旨が記載されます。失敗したジョブに対して課金は発生しません。

該当するジョブが存在しない場合は `404` を返します。

<h3 id="get-jobsidname">
  GET /jobs/{id}/{name}
</h3>

結果をダウンロードします。パスにはステータスレスポンスの `file` の値を使用します。

結果は、出力形式に応じた `Content-Type` と、ファイル名を示す `Content-Disposition` の attachment ヘッダーを付けてストリーミング返却されるため、`curl -OJ` を使えば適切な名前で保存されます。範囲指定のリクエストには対応していません。

| ステータス | 原因                                                           |
| ----- | ------------------------------------------------------------ |
| `200` | 結果。                                                          |
| `404` | 該当するジョブが存在しないか、指定した名前がこのジョブの結果ではありません。                       |
| `409` | ジョブが成功していません。ボディには `status` が含まれ、ジョブが失敗した場合は `error` も含まれます。 |
| `410` | ジョブは成功しましたが、その結果はすでにディスク上に存在しません。                            |

<h3 id="delete-jobsid">
  DELETE /jobs/{id}
</h3>

ジョブとその結果をディスクから削除します。

| ステータス | 原因                                      |
| ----- | --------------------------------------- |
| `204` | 削除しました。                                 |
| `404` | 該当するジョブが存在しません。                         |
| `409` | ジョブが現在認識処理中のため削除できません。処理が完了するまでお待ちください。 |

<h3 id="get-healthz">
  GET /healthz
</h3>

コンテナーがライセンス認証済みで、Documentを受け付ける準備ができているかどうかを返します。Kubernetes やお使いのオーケストレーターの Readiness プローブとして使用してください。コンテナーイメージ側でも、これを Docker の `HEALTHCHECK` として実行します。

```bash theme={null}
curl -s localhost:8080/healthz
# {"code":"ok","ready":true,"telemetry":true}
```

準備完了のコンテナーは `200` を返します。準備が完了していないコンテナーは `503` を返し、そのレスポンスには `ready: false`、理由を示す `code`、通常は `detail` メッセージが含まれます。準備状態に反映されるのは licensing のみです。テレメトリーは併せて報告されますが、準備状態に影響することはありません。[ライセンスの確認](/ja/fine-parser/basics/configuration#confirming-the-license)を参照してください。

<h2 id="results-on-disk">
  ディスク上の結果
</h2>

すべての結果は `/app/output` 配下に `{jobId}/{name}` として書き込まれ、同じ場所に小さなジョブデータベースが配置されます。このレイアウトは変わらないため、HTTP 経由でダウンロードする代わりに、マウントしたボリュームから直接結果を取得することもできます。

既定では、ジョブを `DELETE` するか、ボリュームから手動で削除するまで結果は保持されます。経過時間による期限切れはありません。FineParser 側でクリーンアップさせたい場合は、`DELETE_ON_DOWNLOAD=true` を指定してコンテナーを起動してください。各結果は、完全にダウンロードされた時点でジョブレコードとともに削除されます。それ以降は、ステータスもダウンロードも `404` を返します。ダウンロードが中断された場合は、再試行できるように結果がそのまま残ります。

アップロードされたファイルは別の一時ディレクトリに保持され、認識が終了した時点で (成否にかかわらず) 削除されます。

<h2 id="restarts-and-shutdown">
  再起動とシャットダウン
</h2>

ジョブは出力ボリューム上のジョブデータベースに記録されるため、ボリュームが残っている限り再起動後も保持されます。保留中のジョブは再度取得されて処理されます。プロセスが停止した時点で処理中だったジョブは `failed` としてマークされます。これは、結果がどこまで書き込まれたかをプロセス側で判断できないためです。

正常な停止時には、FineParser は新しい requests の受け付けを停止し、処理中の requests を完了させたうえで、認識処理中の Document を待たずに終了します。Recognition は中断できないため、その完了を待つとシャットダウンが数分間止まってしまうおそれがあります。該当のジョブは `cancelled` としてマークされ、部分的な結果は削除されます。引き続き処理が必要な場合は、改めて送信してください。

コンテナーには 60 秒の停止タイムアウトを設定してください。Docker では `--stop-timeout 60`、Kubernetes では `terminationGracePeriodSeconds: 60` を指定します。Docker の既定値である 10 秒ではドレイン処理が途中で打ち切られ、前回のエクスポート以降の使用状況テレメトリがすべて失われます。

1 つの出力ディレクトリを同時に使用できるコンテナーは 1 つだけです。ジョブデータベースは開いている間ロックされるため、同じボリューム上で 2 つ目のコンテナーを起動しようとしても失敗します。2 つのコンテナーを動かすには、ボリュームも 2 つ必要です。
