# 発送依頼の作成方法

> 発送依頼を作成し、PDF をアップロードして確定するまでの手順。

発送依頼の作成を行うには以下の3つのAPIを呼び出す必要があります。

```
1. 作成      POST /v1/mails                    宛先と印刷設定を登録し、アップロード先を受け取る
2. アップロード POST <upload.url>                受け取った URL へ PDF を送る
3. 確定      POST /v1/mails/{mailId}/confirm   依頼を確定する
```

確定するまで郵便物は発送されず、料金も発生しません。確定した依頼は差し出し予定日に発送され、発送後に請求されます。

## はじめる前に

- `mails:write` スコープのアクセストークンを用意してください。取得方法は[認証](/guides/authentication/)を参照してください。
- 利用者の yubin.app アカウントにクレジットカードが登録されている必要があります。カードの登録は API では行えず、未登録のままでは手順 1 の作成に失敗します（`402` [`card_not_registered`](/problems/card_not_registered/)）。

## 1. 発送依頼を作成する

`POST /v1/mails` に、発送方法・差出人・宛先・印刷する PDF の情報を送ります。

```http
POST https://api.yubin.app/v1/mails
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "shipping_method": "normal",
  "shipping_options": ["simple-registered"],
  "sender_address": {
    "postal_code": "1000001",
    "state": "東京都",
    "city": "千代田区",
    "address1": "千代田1-1",
    "name": "差出 太郎"
  },
  "receiver_address": {
    "postal_code": "5300001",
    "state": "大阪府",
    "city": "大阪市北区",
    "address1": "梅田1-1-1",
    "name": "宛先 花子",
    "honorific": "様"
  },
  "files": [
    {
      "file_name": "invoice.pdf",
      "page_count": 3,
      "color": "grayscale",
      "duplex": "double",
      "staple": "top-left"
    }
  ]
}
```

主なフィールドは次のとおりです。全フィールドは [API リファレンス](/api-reference/)を参照してください。

| フィールド                | 説明                                                                                                                                                                             |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `shipping_method`         | 発送方法。`normal`（普通郵便）、`letterpack-lite`（レターパックライト）、`letterpack-plus`（レターパックプラス）                                                                 |
| `shipping_options`        | `express`（速達）、`trackable`（特定記録）、`simple-registered`（簡易書留）、`normal-registered`（一般書留）から指定します。`shipping_method` が `normal` のときのみ指定できます |
| `reply_letterpack`        | 指定すると返信用レターパックを同封します。省略すると同封しません。`reply_letterpack.address` を指定すると返信先が印字されます                                                    |
| `files[].page_count`      | その PDF のページ数。確定時に実際の PDF と突き合わせるため、正確な値を送ってください                                                                                             |
| `files[].staple` / `bind` | ホチキス留めと製本。ファイル単位で処理されます。省略するとどちらも行いません                                                                                                     |

レスポンスは `201` です。`contents` はリクエストの `files` と同じ並びで、ファイルごとのアップロード先が入っています。

```json
{
  "id": "mail_ehjP5eZ1qHQzXK3rN8dvW",
  "scheduled_to_ship_at": "2026-08-20T00:00:00.000Z",
  "confirm_by": "2026-08-19T10:30:00.000Z",
  "contents": [
    {
      "id": "mail_content_7bTqLm2xVc9NpRs4YfZgD",
      "file_name": "invoice.pdf",
      "upload": {
        "url": "https://...",
        "fields": {
          "Content-Type": "application/pdf",
          "...": "..."
        },
        "expires_at": "2026-08-19T10:05:00.000Z"
      }
    }
  ]
}
```

`id` は確定に使う発送依頼の ID、`scheduled_to_ship_at` は差し出し予定日です。`confirm_by` は確定の期限、`upload.expires_at` はアップロード URL の期限で、どちらも過ぎると作成からやり直しになります。

この時点では依頼は確定していません。発送も課金も行われません。

### 制約

| 項目           | 制約                                         |
| -------------- | -------------------------------------------- |
| ファイル数     | 1〜10 件                                     |
| ファイルサイズ | 1 件あたり 10MB 以下                         |
| ファイル形式   | PDF                                          |
| `paper_size`   | 現状は `a4` のみ。省略すると `a4`            |
| `state`        | 都道府県。4 文字以内                         |
| `page_count`   | 1〜2000                                      |
| 用紙枚数の合計 | 995 枚まで（両面印刷ならおよそ 1990 ページ） |

用紙枚数は 1 件の依頼に含まれるすべてのファイルの合計で判定されます。上限を超える場合は、依頼自体を分けるか、両面印刷（`duplex: "double"`）にして枚数を減らしてください。同じ依頼の `files` を分割しても合計は変わりません。

## 2. PDF をアップロードする

`contents[].upload` の URL へ、PDF を `multipart/form-data` で POST します。`fields` のすべてのキーと値をそのままフォームフィールドとして送り、`file` フィールドで PDF 本体を送ります。

```bash
curl -X POST "$UPLOAD_URL" \
  -F "Content-Type=application/pdf" \
  -F "<fields のキー>=<値>" \
  -F "file=@invoice.pdf"
```

成功すると `204` が返ります。

- `fields` の値を書き換えたり、`file` より後ろにフィールドを置いたりするとアップロードは拒否されます。受け取ったものをそのままの順序で送ってください。
- アップロード先はストレージサービスであり公開 API ではないため、失敗時のレスポンスは problem+json ではなく XML です。
- `files` を複数指定した場合は、すべてのファイルをアップロードしてください。1 つでも残っていると確定できません。

:::caution
アップロード URL の有効期限は **発行から 5 分** で、レスポンスの `upload.expires_at` がその時刻です。期限切れになった場合、URL を再発行する手段はありません。発送依頼の作成からやり直してください。
:::

## 3. 発送依頼を確定する

すべてのファイルをアップロードしたら、作成時に受け取った `id` を使って確定します。

```http
POST https://api.yubin.app/v1/mails/mail_ehjP5eZ1qHQzXK3rN8dvW/confirm
Authorization: Bearer ACCESS_TOKEN
```

成功すると `204` が返ります。確定すると PDF の内容が検証され、見積額でクレジットカードの与信が確保されます。詳しくは[決済](/guides/payment/)を参照してください。

確定後は PDF や宛先を変更できません。内容を変えたい場合は新しい発送依頼を作成してください。

:::caution
確定の期限はレスポンスの `confirm_by`（作成から 30 分）です。過ぎた依頼は自動的に削除され、確定できなくなります。アップロード済みの PDF も保管されず破棄されます。作成から確定までは続けて行ってください。
:::

## 4. 発送状況を確認する

`GET /v1/mails/{mailId}` で発送依頼の現在の状態を取得できます。作成・確定とは別に `mails:read` スコープが必要です。

```http
GET https://api.yubin.app/v1/mails/mail_ehjP5eZ1qHQzXK3rN8dvW
Authorization: Bearer ACCESS_TOKEN
```

```json
{
  "id": "mail_ehjP5eZ1qHQzXK3rN8dvW",
  "status": "shipped",
  "cancellable": false,
  "created_at": "2026-08-19T10:00:00.000Z",
  "scheduled_to_ship_at": "2026-08-20T00:00:00.000Z",
  "confirm_by": "2026-08-19T10:30:00.000Z",
  "shipped_at": "2026-08-20T02:13:00.000Z",
  "shipping_method": "normal",
  "shipping_options": ["simple-registered"],
  "tracking_number": "123456789012",
  "sender_address": { "...": "..." },
  "receiver_address": { "...": "..." },
  "reply_letterpack": null,
  "contents": [
    {
      "id": "mail_content_7bTqLm2xVc9NpRs4YfZgD",
      "file_name": "invoice.pdf",
      "page_count": 3,
      "paper_size": "a4",
      "color": "grayscale",
      "duplex": "double",
      "staple": "top-left",
      "bind": false
    }
  ]
}
```

`status` は次の 4 つです。

| `status`    | 状態                                                              |
| ----------- | ----------------------------------------------------------------- |
| `pending`   | 未確定。`confirm_by` を過ぎると削除されます                       |
| `confirmed` | 確定済み。差し出し予定日に発送されます                            |
| `shipped`   | 発送済み。追跡できる発送方法であれば `tracking_number` が入ります |
| `cancelled` | キャンセル済み                                                    |

`cancellable` は、その時点で[キャンセル](#5-発送依頼をキャンセルする)を受け付けられるかどうかです。発送状況・依頼からの経過時間などによってキャンセル可否状態は変化します。

存在しない依頼や他のアカウントの依頼を指した場合は `404` [`mail_not_found`](/problems/mail_not_found/) が返ります。

## 5. 発送依頼をキャンセルする

確定した依頼は `POST /v1/mails/{mailId}/cancel` でキャンセルできます。確保した与信（オーソリ）は解放され、料金は発生しません。キャンセルを受け付けられる状態かどうかは `GET /v1/mails/{mailId}` の `cancellable` で確認できます。

```http
POST https://api.yubin.app/v1/mails/mail_ehjP5eZ1qHQzXK3rN8dvW/cancel
Authorization: Bearer ACCESS_TOKEN
```

成功すると `204` が返ります。キャンセルした依頼の `status` は `cancelled` になり、元に戻すことはできません。同じ内容で送りたい場合は依頼の作成からやり直してください。

:::caution
現在キャンセルできるのは **作成から 30 分以内**（`confirm_by` と同じ時刻）に限られます。これを過ぎた依頼は印刷・梱包の準備に入るため取り消せません（`409` [`cancellation_window_expired`](/problems/cancellation_window_expired/)）。この期間は変わることがあるので、クライアント側で 30 分を持たずに `cancellable` を見てください。
:::

まだ確定していない依頼はキャンセルできません（`409` [`mail_not_cancellable`](/problems/mail_not_cancellable/)）。

## 6. 入稿した PDF を取得する

確定した依頼の PDF は `GET /v1/mails/{mailId}/contents/{contentId}/pdf` で取得できます。`mails:read` スコープが必要です。`contentId` は `GET /v1/mails/{mailId}` の `contents[].id` です。

```http
GET https://api.yubin.app/v1/mails/mail_ehjP5eZ1qHQzXK3rN8dvW/contents/mail_content_7bTqLm2xVc9NpRs4YfZgD/pdf
Authorization: Bearer ACCESS_TOKEN
```

PDF 本体ではなく、ダウンロード先の URL が返ります。

```json
{
  "url": "https://...",
  "expires_at": "2026-08-20T03:13:00.000Z"
}
```

`url` へ GET すると PDF が取得できます。期限が切れたら同じエンドポイントを呼び直してください。

```bash
curl -o invoice.pdf "$URL"
```

:::caution
`url` はアクセストークンなしで読めます。有効期限（**発行から 1 時間**、`expires_at` がその時刻）内は URL を知っている誰もが PDF を取得できるため、ログや第三者に渡さないでください。
:::

PDF は依頼の確定時に書き換え不能な保管先へ封印されます。未確定の依頼では取得できません（`409` [`mail_not_confirmed`](/problems/mail_not_confirmed/)）。指定した `contentId` がその依頼のファイルでない場合は `404` [`mail_content_not_found`](/problems/mail_content_not_found/) が返ります。

## エラー

エラーは problem+json で返ります。`code` で分岐するか、`type` の URI を参照してください。`type` を開くとその problem の解説が読めます。

```json
{
  "type": "https://docs.yubin.app/problems/over_weight",
  "title": "The mail exceeds the maximum weight",
  "status": 422,
  "code": "over_weight"
}
```

アクセストークンが無い・不正・期限切れの場合や、スコープが足りない場合は problem+json ではなく `{ "message": "Unauthorized" }` が返ります。[認証](/guides/authentication/)を参照してください。

### 作成時

| ステータス | `code`                                                  | 意味と対処                                                                                           |
| ---------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| 400        | [`invalid_request`](/problems/invalid_request/)         | リクエストボディが不正。`errors` の `path` と `message` を見て修正する。未知のキーもこのエラーになる |
| 402        | [`card_not_registered`](/problems/card_not_registered/) | アカウントにクレジットカードが登録されていない                                                       |
| 404        | [`account_not_found`](/problems/account_not_found/)     | トークンは有効だが、対応するアカウントが存在しない                                                   |
| 422        | [`over_weight`](/problems/over_weight/)                 | 用紙枚数の合計が上限超過。依頼を分けるか両面印刷にする                                               |

### 確定時

| ステータス | `code`                                                        | 意味と対処                                                           |
| ---------- | ------------------------------------------------------------- | -------------------------------------------------------------------- |
| 402        | [`card_declined`](/problems/card_declined/)                   | カードが拒否された。**依頼は削除済み**なので作成からやり直す         |
| 404        | [`account_not_found`](/problems/account_not_found/)           | トークンは有効だが、対応するアカウントが存在しない                   |
| 404        | [`mail_not_found`](/problems/mail_not_found/)                 | 依頼が存在しない、または自分の依頼ではない                           |
| 409        | [`mail_already_confirmed`](/problems/mail_already_confirmed/) | 確定済み。再確定はできない                                           |
| 409        | [`payment_in_progress`](/problems/payment_in_progress/)       | 同じ依頼の決済処理が進行中。依頼は残っているので少し待って再試行する |
| 422        | [`file_not_uploaded`](/problems/file_not_uploaded/)           | アップロードされていないファイルがある                               |
| 422        | [`invalid_file`](/problems/invalid_file/)                     | PDF として読めない、またはサイズが不正                               |
| 422        | [`page_count_mismatch`](/problems/page_count_mismatch/)       | 申告した `page_count` が実際の PDF と一致しない                      |

この 3 つは原因が特定の PDF にあるため、`errors` でどのファイルかを返します。`path` は作成時のレスポンスの `contents` を指し、`content_id` はその `contents[].id` です。`page_count_mismatch` は食い違っているファイルをすべて返すので、依頼を作り直す前にまとめて直せます。

```json
{
  "type": "https://docs.yubin.app/problems/page_count_mismatch",
  "title": "The page count does not match the uploaded PDF",
  "status": 422,
  "code": "page_count_mismatch",
  "errors": [
    {
      "path": "contents.2",
      "content_id": "mail_content_7bTqLm2xVc9NpRs4YfZgD",
      "message": "The declared page_count does not match the uploaded PDF"
    }
  ]
}
```

### キャンセル時

| ステータス | `code`                                                                  | 意味と対処                                                                        |
| ---------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| 404        | [`mail_not_found`](/problems/mail_not_found/)                           | 依頼が存在しない、または自分の依頼ではない                                        |
| 409        | [`mail_already_cancelled`](/problems/mail_already_cancelled/)           | キャンセル済み。再試行は不要                                                      |
| 409        | [`mail_not_cancellable`](/problems/mail_not_cancellable/)               | 未確定、または梱包・発送が始まっている。`GET /v1/mails/{mailId}` で状態を確認する |
| 409        | [`cancellation_window_expired`](/problems/cancellation_window_expired/) | 作成から 30 分を過ぎた。この依頼は予定どおり発送される                            |

いずれの API も、サーバ側で予期しない問題が起きた場合は `500` [`internal_server_error`](/problems/internal_server_error/) を返します。

すべての problem の解説は[エラー一覧](/problems/)にあります。