{
  "components": {
    "parameters": {
      "LetterSearchTerm": {
        "description": "Case-insensitive search across the recipient's name, company, address lines, city, state and zip, and the letter's USPS tracking `code`.",
        "in": "query",
        "name": "searchTerm",
        "schema": {
          "example": "Tyler",
          "type": "string"
        }
      },
      "LetterServiceType": {
        "description": "Filter by the `service_type` you submitted in `mailing_options`, matching every envelope it resolves to \u2014 `cm-rre` matches `cm-10-rre`, `cm-6x9-rre` and `cm-9x12-rre`. Use this when you do not care which envelope was used. An unrecognized value matches nothing.",
        "in": "query",
        "name": "serviceType",
        "schema": {
          "enum": [
            "fc",
            "cm",
            "cm-rre",
            "pm",
            "pm-box",
            "intl-fc",
            "rim"
          ],
          "example": "cm-rre",
          "type": "string"
        }
      },
      "LetterSortBy": {
        "description": "Column to sort by. An unrecognized value falls back to `id`.",
        "in": "query",
        "name": "sortBy",
        "schema": {
          "default": "id",
          "enum": [
            "id",
            "created_at",
            "updated_at",
            "serial_number",
            "status",
            "type"
          ],
          "example": "serial_number",
          "type": "string"
        }
      },
      "LetterStatus": {
        "description": "Filter by letter status.\n\n| Value | Status |\n|---|---|\n| `0` | draft |\n| `1` | processing |\n| `2` | printed |\n| `3` | mailed |\n| `4` | in_transit |\n| `5` | delivered |\n",
        "in": "query",
        "name": "status",
        "schema": {
          "enum": [
            0,
            1,
            2,
            3,
            4,
            5
          ],
          "example": 3,
          "type": "integer"
        }
      },
      "LetterType": {
        "description": "Filter by the rendered letter type. This is the resolved service-and-envelope combination, not the `service_type` you submitted \u2014 for example a `cm-rre` recipient in a `9x12` envelope becomes `cm-9x12-rre`. To filter by what you submitted, use `serviceType`.",
        "in": "query",
        "name": "type",
        "schema": {
          "enum": [
            "fc-ps",
            "fc-10",
            "fc-6x9",
            "fc-9x12",
            "cm-10",
            "cm-6x9",
            "cm-9x12",
            "cm-10-rre",
            "cm-6x9-rre",
            "cm-9x12-rre",
            "pm",
            "pm-box",
            "intl-fc-10",
            "intl-fc-9x12",
            "rim-10",
            "rim-9x12"
          ],
          "example": "cm-9x12-rre",
          "type": "string"
        }
      },
      "Page": {
        "description": "1-based page number.",
        "in": "query",
        "name": "page",
        "schema": {
          "default": 1,
          "example": 1,
          "minimum": 1,
          "type": "integer"
        }
      },
      "PerPage": {
        "description": "Records per page. Capped at 100 \u2014 a larger value is clamped, and the `pagination` object reports the clamped value actually used.",
        "in": "query",
        "name": "perPage",
        "schema": {
          "default": 100,
          "example": 25,
          "maximum": 100,
          "minimum": 1,
          "type": "integer"
        }
      },
      "ProcessId": {
        "description": "The `batch_id` returned by `POST /batches`.",
        "in": "path",
        "name": "processId",
        "required": true,
        "schema": {
          "example": "36e7708b-5ef0-4baf-9ede-0cc9d8b9b312",
          "format": "uuid",
          "type": "string"
        }
      },
      "SortType": {
        "description": "Sort direction. Applies to whichever column `sortBy` selects.",
        "in": "query",
        "name": "sortType",
        "schema": {
          "enum": [
            "asc",
            "desc"
          ],
          "example": "desc",
          "type": "string"
        }
      }
    },
    "responses": {
      "BatchNotFound": {
        "content": {
          "application/json": {
            "example": {
              "is_successful": false,
              "message": "Batch was not found"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "No batch with this id belongs to the API key's account."
      },
      "Unauthorized": {
        "content": {
          "application/json": {
            "examples": {
              "invalidKey": {
                "summary": "Key present but rejected",
                "value": {
                  "is_successful": false,
                  "message": "invalid api key"
                }
              },
              "missingHeader": {
                "summary": "No x-api-key header",
                "value": 401
              }
            },
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "description": "Returned verbatim when the `x-api-key` header is absent.",
                  "example": 401,
                  "type": "integer"
                }
              ]
            }
          }
        },
        "description": "The `x-api-key` header is missing, or the key is invalid, expired or\ninactive.\n\nThe body differs by cause. A **missing** header returns the bare JSON\nnumber `401`. A header that is **present but rejected** returns the\nstandard error envelope.\n"
      }
    },
    "schemas": {
      "Address": {
        "description": "A postal address.\n\n`first_name` and `last_name` are not individually required, but supply at\nleast one of them or `company` \u2014 otherwise the envelope has nobody to address.\n\nFor international mail (`service_type` of `intl-fc` or `rim`), `country` is\nrequired and `state` is not validated.\n",
        "properties": {
          "address_line_1": {
            "example": "3032 Arjun Views",
            "maxLength": 128,
            "type": "string"
          },
          "address_line_2": {
            "example": "Apt. 470",
            "maxLength": 128,
            "type": "string"
          },
          "city": {
            "example": "Reillyton",
            "type": "string"
          },
          "company": {
            "description": "Company name, printed above the street address.",
            "example": "Sterling Law Group, P.A.",
            "type": "string"
          },
          "country": {
            "description": "Required for international mail only.",
            "example": "CA",
            "type": "string"
          },
          "first_name": {
            "example": "Spencer",
            "type": "string"
          },
          "full_name": {
            "description": "Read-only; populated on responses. Ignored on input.",
            "example": "Spencer Tyler",
            "type": "string"
          },
          "id": {
            "description": "Read-only; populated on responses. Ignored on input.",
            "example": 254961,
            "type": "integer"
          },
          "last_name": {
            "example": "Tyler",
            "type": "string"
          },
          "phone": {
            "description": "API recipient phone on address and sender phone on return_address; required for FedEx, with a configured recipient default allowed. SFTP headers are Recipient Phone Number and Return Phone. FedEx SFTP accepts formatting characters and optional leading 1/+1, then requires exactly 10 normalized U.S. digits; plain 10-digit input is recommended.",
            "title": "Phone Number",
            "type": "string"
          },
          "state": {
            "description": "Domestic state required: two-letter USPS code. International state is not validated as a U.S. state.",
            "example": "PA",
            "type": "string"
          },
          "zip": {
            "description": "Domestic ZIP/ZIP+4; international postal data must not be constrained to the U.S. ZIP pattern.",
            "example": "96504-0522",
            "type": "string"
          }
        },
        "required": [
          "address_line_1",
          "city"
        ],
        "type": "object"
      },
      "Batch": {
        "description": "A batch as returned by the API.",
        "properties": {
          "async_fullfill_requested": {
            "description": "Fulfilment has been requested. Once `true`, both `PATCH` endpoints reject further changes. Note the spelling.",
            "example": false,
            "type": "boolean"
          },
          "created_at": {
            "example": "2026-07-10T09:36:07Z",
            "format": "date-time",
            "type": "string"
          },
          "firm_mailing_book_url": {
            "description": "Present only when the account has firm mailing books enabled.",
            "type": "string"
          },
          "id": {
            "description": "The batch id \u2014 the same value returned as `batch_id` from `POST /batches`, and the `{processId}` in every other path.",
            "example": "6f6708b0-8fd4-4e3f-80bf-28401a657c9d",
            "format": "uuid",
            "type": "string"
          },
          "in_loading": {
            "description": "The batch is still being prepared in the background. `PUT .../fulfill-v1` rejects the batch while this is `true`; `PUT .../fulfill` waits instead.",
            "example": false,
            "type": "boolean"
          },
          "order_id": {
            "description": "Human-facing sequential order number. Not usable as a path parameter.",
            "example": 15511,
            "type": "integer"
          },
          "sftp_filename": {
            "description": "Source filename, for batches created over SFTP. Empty otherwise.",
            "example": "",
            "type": "string"
          },
          "status": {
            "description": "Status name. May be an empty string for a batch whose numeric status has no name assigned. Observed values below.",
            "enum": [
              "",
              "draft",
              "processing",
              "printed",
              "mailed",
              "in_transit",
              "delivered",
              "canceled",
              "error"
            ],
            "example": "processing",
            "type": "string"
          },
          "title": {
            "example": "batch-07-10-2026",
            "type": "string"
          },
          "type": {
            "description": "How the batch was created.",
            "enum": [
              "manual",
              "import",
              "ocr",
              "json_api",
              "sftp",
              "import_simple"
            ],
            "example": "json_api",
            "type": "string"
          },
          "updated_at": {
            "example": "2026-07-10T09:36:08Z",
            "format": "date-time",
            "type": "string"
          }
        },
        "type": "object"
      },
      "BatchWriteBody": {
        "description": "The body accepted by **both** `POST /batches` and\n`PATCH /batches/{processId}/recipients` \u2014 the two share one handler, so they\ntake an identical body.\n\nFields other than `recipients` are batch-level settings, and most are honoured\n**only on `POST`**. On `PATCH`, `same_day_mailing`, `certificate_of_mailing`,\n`restricted_delivery` and `batch_type` are inherited from the existing batch \u2014\nsending them has no effect. Set them when you create the batch.\n\nUnknown fields are ignored without error.\n",
        "properties": {
          "address_verification_type": {
            "description": "Set `address_verification_type` explicitly on POST /batches and every PATCH recipient-addition request. Omitting it uses the company default, including on PATCH, rather than preserving the batch's original selection. Explicit NONE overrides that default. Invalid or incorrectly cased enum values return 422 without creating the batch. An explicit API address_verification_type works independently of company-level CASS/NCOA enablement. The company setting is used only when the API request omits the field. SFTP verification is controlled entirely by the company setting; there is no file field. Verification cannot be combined with international mail.\n\nRetrieve available LOOKUP results through GET /batches/{processId}/recipients and its `ncoa_lookup` information.\nModes and charges: NONE original; LOOKUP standardized and report forwarding; BEST forwarding/best available; BOTH standardized plus forwarding copy. Failure falls back to original. Configured verification price defaults to $0.05, including failed lookups and shadows; extra BOTH copy has mailing charge without another verification fee. An explicit API address_verification_type works independently of company-level CASS/NCOA enablement. The company setting is used only when the API request omits the field. SFTP verification is controlled entirely by the company setting; there is no file field. Verification cannot be combined with international mail.",
            "enum": [
              "NONE",
              "CASS_NCOA_LOOKUP",
              "CASS_NCOA_BEST",
              "CASS_NCOA_BOTH"
            ],
            "example": "NONE",
            "type": "string"
          },
          "batch_type": {
            "default": "letter",
            "description": "What the batch carries. Optional; defaults to `letter`.\n\n| Value | Contents |\n|---|---|\n| `letter` | Letters only. |\n| `letter_and_check` | Letters and checks submitted together. |\n| `check` | Checks only. |\n\nIgnored on `PATCH`, where it is inherited from the existing batch.\nCheck batches require check mailing to be enabled on the account.\n",
            "enum": [
              "letter",
              "letter_and_check",
              "check"
            ],
            "example": "letter",
            "type": "string"
          },
          "certificate_of_mailing": {
            "default": false,
            "description": "Attach a USPS Certificate of Mailing. Optional; defaults to `false`. Requires the feature to be enabled on the account. **Every recipient in the batch must use `service_type: fc`**, and it cannot be combined with `restricted_delivery`. USPS only. `POST` only \u2014 inherited and ignored on `PATCH`.",
            "example": false,
            "type": "boolean"
          },
          "recipients": {
            "description": "Start with about 100 recipients per POST or recipient PATCH as an operational recommendation. The API enforces no fixed recipient-count limit on either request or on the batch. Append additional groups to the same draft before fulfillment.",
            "items": {
              "$ref": "#/components/schemas/Recipient"
            },
            "type": "array"
          },
          "restricted_delivery": {
            "default": false,
            "description": "USPS Restricted Delivery. Optional; defaults to `false`. Requires the feature to be enabled on the account. **Every recipient in the batch must use `service_type` of `cm` or `cm-rre`**, and it cannot be combined with `certificate_of_mailing`. USPS only. `POST` only \u2014 inherited and ignored on `PATCH`.",
            "example": false,
            "type": "boolean"
          },
          "same_day_mailing": {
            "default": false,
            "description": "Enabled accounts only; default cutoff 11 AM Central, account configurable. Evaluated at charging/fulfillment processing or manual approval. After cutoff or non-mailing days, dropped without surcharge and mailed next business mailing day. When applied, $1 per piece including shadow copies. Not international. Set at POST.",
            "example": false,
            "type": "boolean"
          }
        },
        "required": [
          "recipients"
        ],
        "type": "object"
      },
      "CheckOptions": {
        "description": "Check fields for check/letter_and_check batches. Verified company bank account required. Bank UID, amount and MM-DD-YYYY date required; memo and notes optional. Printing is black and white and single-sided; shadow ignored.",
        "properties": {
          "amount": {
            "description": "The check amount, as a decimal string.",
            "example": "125.00",
            "type": "string"
          },
          "bank_account_uid": {
            "description": "UID of a bank account registered on the account. The account must be verified and belong to your company, otherwise the recipient is rejected with `Bank Account UID is invalid`.",
            "example": "01H8XGJWBWBAQ4H1SVX0JT2M7Z",
            "type": "string"
          },
          "date": {
            "description": "Check date in MM-DD-YYYY; not in the past.",
            "example": "12-15-2026",
            "type": "string"
          },
          "memo": {
            "description": "Optional check memo.",
            "example": "Invoice 4471",
            "type": "string"
          },
          "note_1": {
            "description": "Free-text note printed on the check stub. Optional.",
            "example": "Thank you for your business",
            "type": "string"
          },
          "note_2": {
            "description": "Second free-text note printed on the check stub. Optional.",
            "example": "",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Error": {
        "description": "Standard error envelope. Every non-2xx response uses this shape. Two\noperations add a field to it: `createBatch` (400) adds `invalid_letters`,\nand `fulfillBatch` (422) adds `missed_pdf_files`.\n",
        "properties": {
          "is_successful": {
            "enum": [
              false
            ],
            "example": false,
            "type": "boolean"
          },
          "message": {
            "description": "Human-readable explanation. Not a stable machine-readable code.",
            "example": "Batch was not found",
            "type": "string"
          }
        },
        "required": [
          "is_successful",
          "message"
        ],
        "type": "object"
      },
      "Letter": {
        "description": "A rendered letter, as produced by the print pipeline.\n\nOnly present once the batch has been printed \u2014 until then `letters` is `null`.\nFields marked optional are omitted while empty, so an unprinted or in-flight\nletter carries far fewer keys than a delivered one.\n",
        "properties": {
          "affidavit": {
            "description": "The affidavit slug applied to this letter, if any.",
            "enum": [
              "sb",
              "sl",
              "nb",
              "nl"
            ],
            "type": "string"
          },
          "check": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CheckOptions"
              }
            ],
            "description": "Check details. Always present \u2014 an empty object on a standard letter mailing."
          },
          "code": {
            "description": "USPS tracking number. Present once the letter has been posted.",
            "example": "9214890321486300010108",
            "type": "string"
          },
          "created_at": {
            "example": "2026-07-10T09:36:07Z",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "example": 217947,
            "type": "integer"
          },
          "is_colored": {
            "example": true,
            "type": "boolean"
          },
          "is_double_sided": {
            "example": false,
            "type": "boolean"
          },
          "is_rre": {
            "description": "This letter carries Return Receipt Electronic.",
            "example": true,
            "type": "boolean"
          },
          "letter_image": {
            "description": "Time-limited signed URL to the rendered letter PDF. Fetch it promptly; do not store it.",
            "example": "https://example-bucket.s3.amazonaws.com/letter.pdf?X-Amz-Signature=REDACTED",
            "format": "uri",
            "type": "string"
          },
          "price": {
            "description": "Amount charged for this letter, as a decimal string.",
            "example": "8.64",
            "type": "string"
          },
          "recipient": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "The resolved delivery address."
          },
          "recipient_id": {
            "example": 254961,
            "type": "integer"
          },
          "return_envelope": {
            "description": "The return envelope applied to this letter, if any. Always `re-`-prefixed.",
            "example": "re-9npna",
            "type": "string"
          },
          "return_mail_image": {
            "description": "Signed URLs to scanned return-mail images, if the letter came back.",
            "items": {
              "format": "uri",
              "type": "string"
            },
            "type": "array"
          },
          "rre_enabled": {
            "example": false,
            "type": "boolean"
          },
          "rre_image": {
            "description": "Time-limited signed URL to the Return Receipt Electronic PDF.",
            "format": "uri",
            "type": "string"
          },
          "sender": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "The resolved return address. Always present \u2014 an empty object until the letter has been produced."
          },
          "serial_number": {
            "example": 1010,
            "type": "integer"
          },
          "status": {
            "description": "Letter status.\n\n| Value | Status |\n|---|---|\n| `0` | draft |\n| `1` | processing |\n| `2` | printed |\n| `3` | mailed |\n| `4` | in_transit |\n| `5` | delivered |\n\nThe developer-confirmed status list includes 11 (pre-processing). Production status is not carrier-confirmed delivery.\nThe developer-confirmed status list includes 11 (pre-processing). Production status is not carrier-confirmed delivery.",
            "enum": [
              0,
              1,
              2,
              3,
              4,
              5,
              11
            ],
            "example": 1,
            "type": "integer"
          },
          "tracking_info": {
            "additionalProperties": true,
            "description": "The carrier's **raw** tracking payload, passed through unmodified. Its\nshape is set by USPS, not by this API, and it varies by mail class \u2014 treat\nit as opaque.\n\nWhen no raw payload has been stored for a letter, a normalized form is\nreturned instead:\n\n```json\n{\n  \"error\": null,\n  \"done\": true,\n  \"logs\": [\n    { \"date\": \"Dec 27, 2024\", \"description\": \"Delivered\", \"location\": \"Reillyton, PA\" },\n    { \"date\": \"Dec 23, 2024\", \"description\": \"Accepted\", \"location\": \"Frisco, TX\" }\n  ]\n}\n```\n\n`error` carries a human-readable explanation when USPS has nothing to\nreport yet. Code defensively for both shapes.\n\nOmitted entirely until the letter has been posted and a first scan\narrives. The earliest log entry is the acceptance scan \u2014 this is where the\nmailing date is read from, as letters carry no separate mailing-date\nfield.\n",
            "type": "object"
          },
          "type": {
            "description": "Resolved service-and-envelope slug \u2014 the combination of the recipient's `service_type` and `envelope_type`, not either one alone.",
            "example": "cm-9x12-rre",
            "type": "string"
          },
          "updated_at": {
            "example": "2026-07-10T09:36:40Z",
            "format": "date-time",
            "type": "string"
          }
        },
        "type": "object"
      },
      "LetterMappingResult": {
        "description": "Reports how uploaded PDFs line up with the batch's recipients. Returned by `PATCH .../letters`, and by `PUT .../fulfill` when the batch is not ready.",
        "properties": {
          "extra_pdf_files": {
            "description": "Uploaded filenames that no recipient references.",
            "example": [
              "nobody_wants_me.pdf"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "is_successful": {
            "type": "boolean"
          },
          "missed_pdf_files": {
            "description": "Filenames recipients reference via `pdf_name` but which were never uploaded.",
            "example": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "not_mapped_recipients": {
            "description": "Recipients still without a matching PDF, each with an `error_message`.",
            "example": [],
            "items": {
              "$ref": "#/components/schemas/Recipient"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "MailingOptions": {
        "description": "How this letter is printed and mailed.",
        "properties": {
          "affidavit": {
            "description": "Affidavit of Mailing (AOM). Omit for none.\n\n| Value | Meaning | Billing |\n|---|---|---|\n| `sb` | Signature Only, **Batch Level** \u2014 one signed affidavit covers the entire batch | flat, once per batch |\n| `sl` | Signature Only, **Letter Level** \u2014 a separate signed affidavit per recipient | **per letter** |\n| `nb` | Notarized, **Batch Level** \u2014 one notarized affidavit covers the entire batch | flat, once per batch |\n| `nl` | Notarized, **Letter Level** \u2014 a separate notarized affidavit per recipient | **per letter** |\n\nThe first letter is the kind of affidavit \u2014 `s` signed, `n` notarized. The\nsecond letter is the scope: **`b` is batch level**, **`l` is letter level**.\n\nNotarized affidavits are reviewed manually and are not published\nautomatically.\n\n**Every recipient in a batch must carry the same value.** If one recipient\nrequests an affidavit, they all must request the same one; a batch with\nmixed values is rejected with 422.\n\nOnly available if the account has affidavit mailing enabled; otherwise\nthe value is ignored. An unrecognized value returns 422.\n",
            "enum": [
              "sb",
              "sl",
              "nb",
              "nl"
            ],
            "example": "sb",
            "type": "string"
          },
          "envelope_type": {
            "description": "Domestic fc/cm/cm-rre: omitted selects by page count; explicit 10, 6x9 or 9x12 accepted. Physical-sheet capacities including cover: 5, 16 and 70. International intl-fc/rim: explicit 10 or 9x12 required. FedEx: explicit overnight or 2day required. pm/pm-box: ignored; leave blank. ps is checks-only First-Class packaging. Inserts reduce document allowance.",
            "enum": [
              "10",
              "6x9",
              "9x12",
              "ps",
              "overnight",
              "2day"
            ],
            "example": "10",
            "type": "string"
          },
          "is_colored": {
            "default": false,
            "description": "Print in color. Defaults to `false`. Forced to `false` for check batches.",
            "example": true,
            "type": "boolean"
          },
          "is_double_sided": {
            "default": false,
            "description": "Print on both sides. Defaults to `false`. Forced to `false` for check batches.",
            "example": false,
            "type": "boolean"
          },
          "return_envelope": {
            "description": "Include a return envelope. Optional; omit it, or send `re-empty`, for\nnone. The selection may vary from one recipient to the next.\n\n| Value | Envelope |\n|---|---|\n| `re-9npna` | #9, no postage, no pre-addressed return destination |\n| `re-9pna` | #9, prepaid postage, no pre-addressed return destination |\n| `re-9pa` | #9, prepaid postage and a pre-addressed return destination |\n| `re-9pam` | #9 prepaid, pre-addressed, metered |\n| `re-empty` | No return envelope. Same as omitting the field. |\n\nThe `re-` prefix is optional on input \u2014 `9npna` is accepted and stored as\n`re-9npna` \u2014 but responses always return the prefixed form, so prefer it.\nAny other value returns a validation error.\n\nConditions:\n\n- Cannot be used with international mail (`intl-fc`, `rim`) \u2014 doing so\n  returns a validation error.\n- `re-9pam` is accepted only with a `9x12` envelope or a Priority Mail\n  service (`pm`, `pm-box`).\n- Silently cleared if return envelopes are not enabled on the account.\n",
            "enum": [
              "re-empty",
              "re-9npna",
              "re-9pna",
              "re-9pa",
              "re-9pam"
            ],
            "example": "re-9npna",
            "type": "string"
          },
          "service_type": {
            "description": "Supported service codes: fc, cm, cm-rre, pm, pm-box, intl-fc, rim and case-sensitive FedEx. For API-key requests on FedEx-enabled accounts, FedEx requires envelope_type overnight or 2day. Separate international/domestic and USPS/FedEx batches. International requires country and excludes checks, return envelopes, same-day and verification.",
            "enum": [
              "fc",
              "cm",
              "cm-rre",
              "pm",
              "pm-box",
              "intl-fc",
              "rim",
              "FedEx"
            ],
            "example": "fc",
            "type": "string"
          }
        },
        "required": [
          "service_type"
        ],
        "type": "object"
      },
      "Pagination": {
        "description": "Present only when the request asked for a paged response.",
        "properties": {
          "current_page": {
            "example": 1,
            "type": "integer"
          },
          "per_page": {
            "example": 25,
            "type": "integer"
          },
          "total": {
            "description": "Total matching records across all pages.",
            "example": 137,
            "type": "integer"
          },
          "total_pages": {
            "example": 6,
            "type": "integer"
          }
        },
        "required": [
          "current_page",
          "per_page",
          "total",
          "total_pages"
        ],
        "type": "object"
      },
      "PdfFileConfig": {
        "description": "Binds this recipient's letter to a PDF you upload via `PATCH /batches/{processId}/letters`.",
        "properties": {
          "pdf_name": {
            "description": "The exact filename of the PDF uploaded through `PATCH /batches/{processId}/letters`, matched character for character. Required whenever the batch carries letters. **Filenames must be unique within a batch** \u2014 this is the only key linking a recipient to its letter image.",
            "example": "Sample Letter.pdf",
            "type": "string"
          },
          "pdf_page_numbers": {
            "description": "The pages within `pdf_name` assigned to this recipient. Either a single\npage (`\"3\"`) or an inclusive range (`\"1-3\"`). The start must be less than\nthe end. Spaces are ignored. `\"0\"` is rejected.\n\n**Optional \u2014 omit it when this recipient has a PDF to itself**, and the\nwhole uploaded file becomes that recipient's letter. Supply it when one\nuploaded PDF carries letters for several recipients, each taking its own\npage range.\n",
            "example": "1-3",
            "pattern": "^\\s*\\d+\\s*(-\\s*\\d+\\s*)?$",
            "type": "string"
          },
          "pdf_process_type": {
            "description": "Internal. Leave it unset \u2014 page selection is driven entirely by `pdf_page_numbers`. It is echoed back on responses, usually as an empty string.",
            "example": "",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Recipient": {
        "description": "One recipient \u2014 which is to say, **one letter**. A recipient carries its own\nmailing options and its own binding to a PDF, so adding a recipient adds a\nletter to be mailed.\n",
        "properties": {
          "address": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RecipientAddress"
              }
            ],
            "description": "The recipient's mailing address. Required."
          },
          "check_options": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CheckOptions"
              }
            ],
            "description": "Check details. Present only on `check` and `letter_and_check` batches."
          },
          "error_message": {
            "description": "Read-only; populated on responses when this recipient failed validation.",
            "example": "",
            "type": "string"
          },
          "letter_id": {
            "description": "A customer-provided identifier for the individual letter. Optional; echoed back on responses.",
            "example": "letter_1",
            "type": "string"
          },
          "mailing_options": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MailingOptions"
              }
            ],
            "description": "How this letter is printed and mailed. Required."
          },
          "pdf_file_config": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PdfFileConfig"
              }
            ],
            "description": "Binds this recipient's letter to an uploaded PDF. Required whenever the batch carries letters."
          },
          "return_address": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "The return address printed on the mailing. Required. If you leave the phone off, the phone on your account's default return address is used."
          },
          "shadow": {
            "default": false,
            "description": "Send a duplicate First-Class copy of this letter.\n\nWhen enabled for an eligible Certified Mail (`cm`) or Certified Mail with\nRRE (`cm-rre`) piece, UploadLetters automatically sends an identical\nadditional copy by First-Class Mail. You submit the recipient one time and\nUploadLetters automatically generates the additional First-Class piece,\nwhich is printed and billed like any other letter.\n\nOptional; defaults to `false`. Conditions:\n\n- Has no effect unless `mailing_options.service_type` is `cm` or `cm-rre`.\n- Ignored on check batches.\n",
            "example": false,
            "type": "boolean"
          },
          "unique_id": {
            "description": "A customer-provided unique identifier used to match the mailing record to your own system. Optional; echoed back on responses so you can reconcile. Send it as a string.",
            "example": "1",
            "type": "string"
          }
        },
        "required": [
          "address",
          "return_address",
          "mailing_options"
        ],
        "type": "object"
      },
      "RecipientAddress": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Address"
          },
          {
            "properties": {
              "phone": {
                "title": "Recipient Phone Number"
              }
            },
            "required": [
              "zip"
            ],
            "type": "object"
          }
        ],
        "description": "The delivery address. Same as `Address`, but `zip` is also required and format-checked."
      },
      "Transaction": {
        "description": "A single wallet movement.",
        "properties": {
          "amount": {
            "description": "Signed integer, in cents. Negative for a `withdraw`. Note this differs from `balance`, which is a decimal number of dollars.",
            "example": -864,
            "type": "integer"
          },
          "confirmed": {
            "example": true,
            "type": "boolean"
          },
          "created_at": {
            "example": "2026-07-10T04:32:57Z",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "example": 7753,
            "type": "integer"
          },
          "meta": {
            "additionalProperties": true,
            "description": "Free-form context. For a batch purchase, the batch title and a description.",
            "example": {
              "description": "Purchase of Batch",
              "title": "batch-07-10-2026"
            },
            "type": "object"
          },
          "type": {
            "description": "`withdraw` debits the wallet, `deposit` credits it.",
            "enum": [
              "withdraw",
              "deposit"
            ],
            "example": "withdraw",
            "type": "string"
          },
          "updated_at": {
            "example": "2026-07-10T04:32:57Z",
            "format": "date-time",
            "type": "string"
          },
          "user_id": {
            "example": "74a54d21-e451-4f09-9090-4462ce7edf02",
            "format": "uuid",
            "type": "string"
          },
          "uuid": {
            "example": "8d631ca1-024e-47db-af13-f55c7def1e2b",
            "format": "uuid",
            "type": "string"
          },
          "wallet_id": {
            "example": 247,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "ValidationErrors": {
        "additionalProperties": {
          "additionalProperties": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "type": "object"
        },
        "description": "Per-recipient validation failures, keyed by the recipient's **index in the\n`recipients` array you submitted** (as a string). The inner object is keyed by\ndotted field path, each mapping to a list of messages.\n",
        "example": {
          "0": {
            "address.city": [
              "The city field is required."
            ],
            "address.zip": [
              "The zip code field has an invalid value."
            ],
            "mailing_options.service_type": [
              "The service type field is required."
            ]
          }
        },
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "description": "API key issued from your account settings.",
        "in": "header",
        "name": "x-api-key",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "description": "API documentation reconciled with developer answers and owner confirmation on November 9, 2026. All functionality-review questions are resolved. Fulfillment is asynchronous; all fulfilled batches are real and charged. No sandbox or webhooks. This documentation revision is not a live integration test.",
    "title": "UploadLetters Public Print & Mail API",
    "version": "2026-11-09"
  },
  "openapi": "3.0.0",
  "paths": {
    "/batches": {
      "get": {
        "description": "Returns batches belonging to the API key's account, newest first.\n\nPass `paged=1` to receive a `pagination` object alongside the results;\nwithout it, all matching batches are returned in one array.\n\n**Request body: None required.**\n",
        "operationId": "listBatches",
        "parameters": [
          {
            "description": "Only batches created on or after this date, `YYYY-MM-DD`. Independent of `to` \u2014 either bound may be given on its own.",
            "in": "query",
            "name": "from",
            "schema": {
              "example": "2026-01-01",
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Only batches created on or before this date, `YYYY-MM-DD`. Independent of `from`.",
            "in": "query",
            "name": "to",
            "schema": {
              "example": "2026-12-31",
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Case-insensitive match on the batch `title` and `sftp_filename`. A digits-only term additionally matches the batch's `order_id` exactly.",
            "in": "query",
            "name": "search",
            "schema": {
              "example": "batch-07-10-2026",
              "type": "string"
            }
          },
          {
            "description": "Column to sort by. An unrecognized value falls back to `created_at`.",
            "in": "query",
            "name": "sortBy",
            "schema": {
              "default": "created_at",
              "enum": [
                "created_at",
                "updated_at",
                "id",
                "order_id",
                "title",
                "status"
              ],
              "example": "title",
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/SortType"
          },
          {
            "description": "Filter by batch status. This filter takes the integer, while the\nresponse reports the name.\n\n| Value | Status |\n|---|---|\n| `0` | draft |\n| `1` | processing |\n| `2` | printed |\n| `3` | mailed |\n| `4` | in_transit |\n| `5` | delivered |\n| `6` | canceled |\n| `10` | error |\n\n11 = pre-processing.",
            "in": "query",
            "name": "status",
            "schema": {
              "enum": [
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                10,
                11
              ],
              "example": 3,
              "type": "integer"
            }
          },
          {
            "description": "Filter by how the batch was created. Note this filter takes the integer,\nwhile the response reports the name.\n\n| Value | Type |\n|---|---|\n| `0` | manual |\n| `1` | import |\n| `2` | ocr |\n| `3` | json_api |\n| `4` | sftp |\n| `5` | import_simple |\n",
            "in": "query",
            "name": "type",
            "schema": {
              "enum": [
                0,
                1,
                2,
                3,
                4,
                5
              ],
              "example": 3,
              "type": "integer"
            }
          },
          {
            "description": "Set to `1` to paginate the response.",
            "in": "query",
            "name": "paged",
            "schema": {
              "enum": [
                1
              ],
              "example": 1,
              "type": "integer"
            }
          },
          {
            "description": "1-based page number. Only meaningful with `paged`.",
            "in": "query",
            "name": "page",
            "schema": {
              "default": 1,
              "example": 1,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Records per page, capped at 100. Only meaningful with `paged`.",
            "in": "query",
            "name": "perPage",
            "schema": {
              "default": 100,
              "example": 25,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "paged": {
                    "summary": "With paged=1",
                    "value": {
                      "batches": [
                        {
                          "async_fullfill_requested": false,
                          "created_at": "2026-07-10T09:36:07Z",
                          "id": "6f6708b0-8fd4-4e3f-80bf-28401a657c9d",
                          "in_loading": false,
                          "order_id": 15511,
                          "sftp_filename": "",
                          "status": "processing",
                          "title": "batch-07-10-2026",
                          "type": "json_api",
                          "updated_at": "2026-07-10T09:36:08Z"
                        }
                      ],
                      "is_successful": true,
                      "pagination": {
                        "current_page": 1,
                        "per_page": 2,
                        "total": 323,
                        "total_pages": 162
                      }
                    }
                  },
                  "unpaged": {
                    "summary": "Without paged \u2014 every matching batch, no pagination object",
                    "value": {
                      "batches": [
                        {
                          "async_fullfill_requested": false,
                          "created_at": "2026-07-10T09:36:07Z",
                          "id": "6f6708b0-8fd4-4e3f-80bf-28401a657c9d",
                          "in_loading": false,
                          "order_id": 15511,
                          "sftp_filename": "",
                          "status": "processing",
                          "title": "batch-07-10-2026",
                          "type": "json_api",
                          "updated_at": "2026-07-10T09:36:08Z"
                        }
                      ],
                      "is_successful": true
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "batches": {
                      "items": {
                        "$ref": "#/components/schemas/Batch"
                      },
                      "type": "array"
                    },
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "is_successful",
                    "batches"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Matching batches. The `pagination` object is present only when `paged` was set."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "A query parameter could not be parsed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "summary": "List batches",
        "tags": [
          "Batches"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.uploadletters.com/batches?paged=1&perPage=25\" \\\n  -H \"x-api-key: $API_KEY\"\n"
          }
        ]
      },
      "post": {
        "description": "Create a real draft batch. Start with about 100 recipients per POST or recipient PATCH as an operational recommendation. The API enforces no fixed recipient-count limit on either request or on the batch. Append additional groups to the same draft before fulfillment. Invalid recipients are returned in invalid_letters while valid recipients are saved. Repeating POST creates a new batch; no idempotency key. Fulfill separately after reconciling recipients and PDFs.",
        "operationId": "createBatch",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "address_verification_type": "NONE",
                "batch_type": "letter",
                "recipients": [
                  {
                    "unique_id": "example-001",
                    "letter_id": "example-letter-001",
                    "address": {
                      "first_name": "Example",
                      "last_name": "Recipient",
                      "address_line_1": "123 Example Street",
                      "city": "Frisco",
                      "state": "TX",
                      "zip": "75036"
                    },
                    "return_address": {
                      "company": "Example Business",
                      "address_line_1": "456 Example Avenue",
                      "city": "Frisco",
                      "state": "TX",
                      "zip": "75036"
                    },
                    "mailing_options": {
                      "service_type": "fc",
                      "envelope_type": "10",
                      "is_colored": false,
                      "is_double_sided": false
                    },
                    "pdf_file_config": {
                      "pdf_name": "sample-letter.pdf"
                    }
                  }
                ]
              },
              "schema": {
                "$ref": "#/components/schemas/BatchWriteBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "allAccepted": {
                    "summary": "Every recipient valid",
                    "value": {
                      "batch_id": "bb00423d-d830-41be-b60b-dde443312432",
                      "is_successful": true
                    }
                  },
                  "partiallyAccepted": {
                    "summary": "Third recipient rejected; the other two were accepted",
                    "value": {
                      "batch_id": "bb00423d-d830-41be-b60b-dde443312432",
                      "invalid_letters": {
                        "2": {
                          "address.zip": [
                            "The zip code field has an invalid value."
                          ]
                        }
                      },
                      "is_successful": true
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "batch_id": {
                      "example": "bb00423d-d830-41be-b60b-dde443312432",
                      "format": "uuid",
                      "type": "string"
                    },
                    "invalid_letters": {
                      "$ref": "#/components/schemas/ValidationErrors"
                    },
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "is_successful",
                    "batch_id"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Batch created and accepted for processing. `invalid_letters` appears only when some recipients were rejected; the valid ones were still accepted."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "invalid_letters": {
                    "0": {
                      "address.city": [
                        "The city field is required."
                      ],
                      "address.state": [
                        "The state field is required."
                      ]
                    }
                  },
                  "is_successful": false,
                  "message": "Invalid data in request body."
                },
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "invalid_letters": {
                          "$ref": "#/components/schemas/ValidationErrors"
                        }
                      },
                      "type": "object"
                    }
                  ]
                }
              }
            },
            "description": "Every recipient failed validation. No batch was created."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The account is not permitted to use a requested feature, such as check mailing or an affidavit."
          },
          "422": {
            "content": {
              "application/json": {
                "example": {
                  "is_successful": false,
                  "message": "address_verification_type must be one of (NONE, CASS_NCOA_LOOKUP, CASS_NCOA_BEST, CASS_NCOA_BOTH)."
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The request body was unusable \u2014 malformed JSON, an unrecognized `address_verification_type` or `affidavit`, or a recipient limit exceeded."
          }
        },
        "summary": "Create a batch",
        "tags": [
          "Batches"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl -X POST https://api.uploadletters.com/batches \\\n  -H \"x-api-key: $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"batch_type\": \"letter\",\n    \"address_verification_type\": \"NONE\",\n    \"recipients\": [\n      {\n        \"unique_id\": \"1\",\n        \"letter_id\": \"letter_1\",\n        \"address\": {\n          \"first_name\": \"Spencer\",\n          \"last_name\": \"Tyler\",\n          \"address_line_1\": \"3032 Arjun Views\",\n          \"address_line_2\": \"Apt. 470\",\n          \"city\": \"Reillyton\",\n          \"state\": \"PA\",\n          \"zip\": \"96504-0522\"\n        },\n        \"return_address\": {\n          \"first_name\": \"Sterling\",\n          \"last_name\": \"Law Group\",\n          \"company\": \"Sterling Law Group, P.A.\",\n          \"address_line_1\": \"456 Gulf Breeze Way\",\n          \"address_line_2\": \"Suite 200\",\n          \"city\": \"Naples\",\n          \"state\": \"FL\",\n          \"zip\": \"34102\"\n        },\n        \"pdf_file_config\": {\n          \"pdf_name\": \"Sample Letter.pdf\",\n          \"pdf_page_numbers\": \"1-3\"\n        },\n        \"mailing_options\": {\n          \"envelope_type\": \"10\",\n          \"service_type\": \"cm-rre\",\n          \"is_colored\": false,\n          \"is_double_sided\": false\n        }\n      }\n    ]\n  }'\n"
          }
        ]
      }
    },
    "/batches/transactions": {
      "get": {
        "description": "Returns the current wallet balance for the account owning the API key, together with its transaction history. Batches are charged against this balance when you call `PUT /batches/{processId}/fulfill`.",
        "operationId": "listTransactions",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "balance": 484168.83,
                  "is_successful": true,
                  "transactions": [
                    {
                      "amount": -864,
                      "confirmed": true,
                      "created_at": "2026-07-10T04:32:57Z",
                      "id": 7753,
                      "meta": {
                        "description": "Purchase of Batch",
                        "title": "batch-07-10-2026"
                      },
                      "type": "withdraw",
                      "updated_at": "2026-07-10T04:32:57Z",
                      "user_id": "74a54d21-e451-4f09-9090-4462ce7edf02",
                      "uuid": "8d631ca1-024e-47db-af13-f55c7def1e2b",
                      "wallet_id": 247
                    }
                  ]
                },
                "schema": {
                  "properties": {
                    "balance": {
                      "description": "Current wallet balance, in dollars.",
                      "example": 484168.83,
                      "format": "double",
                      "type": "number"
                    },
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    },
                    "transactions": {
                      "items": {
                        "$ref": "#/components/schemas/Transaction"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "is_successful",
                    "balance",
                    "transactions"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Current `balance` and the account's full transaction history, newest\nfirst.\n\nMind the units: `balance` is a decimal number of **dollars**, while each\ntransaction's `amount` is a signed integer in **cents**.\n\nThis endpoint returns every transaction on the account and is not\npaginated, so the response can be large.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The request could not be attributed to an account."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "summary": "Get wallet balance and transactions",
        "tags": [
          "Transactions"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl https://api.uploadletters.com/batches/transactions \\\n  -H \"x-api-key: $API_KEY\"\n"
          }
        ]
      }
    },
    "/batches/{processId}": {
      "get": {
        "description": "Returns a single batch with its current status, plus counters describing how much background processing is still outstanding.",
        "operationId": "getBatch",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProcessId"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "active_bg_tasks_count": 0,
                  "batch": {
                    "async_fullfill_requested": false,
                    "created_at": "2026-07-10T09:36:07Z",
                    "id": "6f6708b0-8fd4-4e3f-80bf-28401a657c9d",
                    "in_loading": true,
                    "order_id": 15511,
                    "sftp_filename": "",
                    "status": "",
                    "title": "batch-07-10-2026",
                    "type": "json_api",
                    "updated_at": "2026-07-10T09:36:07Z"
                  },
                  "current_letters_count": 0,
                  "is_successful": true,
                  "minimum_letters_count": 2
                },
                "schema": {
                  "properties": {
                    "active_bg_tasks_count": {
                      "description": "Background tasks still running for this batch. `0` means processing has settled.",
                      "example": 0,
                      "type": "integer"
                    },
                    "batch": {
                      "$ref": "#/components/schemas/Batch"
                    },
                    "current_letters_count": {
                      "description": "Letters produced so far.",
                      "example": 0,
                      "type": "integer"
                    },
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    },
                    "minimum_letters_count": {
                      "description": "Letters expected, i.e. the number of accepted recipients.",
                      "example": 2,
                      "type": "integer"
                    }
                  },
                  "required": [
                    "is_successful",
                    "batch"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "The batch, with counters describing outstanding background work."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The batch id is malformed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/BatchNotFound"
          }
        },
        "summary": "Get a batch",
        "tags": [
          "Batches"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl https://api.uploadletters.com/batches/$BATCH_ID \\\n  -H \"x-api-key: $API_KEY\"\n"
          }
        ]
      }
    },
    "/batches/{processId}/fulfill": {
      "put": {
        "description": "Returns 200 when queued; charging and fulfillment happen asynchronously. Insufficient funds is reported by notification and leaves the order in Pre-Processing, not a synchronous 422. With manual review enabled, returns 422 \"Batch should be reviewed at web portal in incomplete orders.\", moves the draft to Incomplete Orders and does not charge. Invalid/missing PDFs are checked before review. Same-batch fulfillment retries do not charge paid orders twice. Poll existing state; no webhooks.",
        "operationId": "fulfillBatch",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProcessId"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "is_successful": true,
                  "message": "Batch put to queue and will be charged and fulfilled."
                },
                "schema": {
                  "properties": {
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "is_successful",
                    "message"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Batch queued for charging and printing."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/BatchNotFound"
          },
          "422": {
            "content": {
              "application/json": {
                "examples": {
                  "alreadyFulfilled": {
                    "summary": "Fulfil already requested",
                    "value": {
                      "is_successful": false,
                      "message": "Batch fulfilling already requested."
                    }
                  },
                  "missingPdfs": {
                    "summary": "A recipient references a PDF that was never uploaded",
                    "value": {
                      "extra_pdf_files": [],
                      "is_successful": false,
                      "message": "Batch contains invalid letters. Some letters have processing errors, missing PDF files, or a PDF name mismatch between batch configuration and uploaded files.",
                      "missed_pdf_files": [
                        "Sample Letter.pdf"
                      ],
                      "not_mapped_recipients": [
                        {
                          "error_message": "",
                          "letter_id": "letter_1",
                          "pdf_file_config": {
                            "pdf_name": "Sample Letter.pdf",
                            "pdf_page_numbers": "1-3"
                          },
                          "unique_id": "1"
                        }
                      ]
                    }
                  },
                  "manualReview": {
                    "summary": "Manual review required",
                    "value": {
                      "is_successful": false,
                      "message": "Batch should be reviewed at web portal in incomplete orders."
                    }
                  }
                },
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "$ref": "#/components/schemas/LetterMappingResult"
                    }
                  ]
                }
              }
            },
            "description": "Validation/missing PDFs, already-requested/already-fulfilled, or company-enabled manual review. Insufficient funds is asynchronous on this endpoint."
          }
        },
        "summary": "Queue charging and fulfillment",
        "tags": [
          "Batches"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "# Charges the wallet and sends the batch to production. Irreversible.\n# No request body.\ncurl -X PUT https://api.uploadletters.com/batches/$BATCH_ID/fulfill \\\n  -H \"x-api-key: $API_KEY\"\n"
          }
        ]
      }
    },
    "/batches/{processId}/letters": {
      "get": {
        "description": "Returns the letters in a batch once it has been processed, including USPS\ntracking data, links to the rendered letter images, and Return Receipt\nElectronic (RRE) images where applicable. Image links are time-limited\nsigned URLs \u2014 fetch them promptly rather than storing them.\n\n**Request body: None required.**\n",
        "operationId": "listLetters",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProcessId"
          },
          {
            "$ref": "#/components/parameters/LetterType"
          },
          {
            "$ref": "#/components/parameters/LetterServiceType"
          },
          {
            "$ref": "#/components/parameters/LetterStatus"
          },
          {
            "$ref": "#/components/parameters/LetterSearchTerm"
          },
          {
            "$ref": "#/components/parameters/LetterSortBy"
          },
          {
            "$ref": "#/components/parameters/SortType"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "beforePrinting": {
                    "summary": "Batch not yet printed \u2014 both arrays are null",
                    "value": {
                      "affidavit_images": null,
                      "is_successful": true,
                      "letters": null
                    }
                  },
                  "delivered": {
                    "summary": "A delivered Certified Mail with RRE letter",
                    "value": {
                      "affidavit_images": [],
                      "is_successful": true,
                      "letters": [
                        {
                          "code": "9214890321486300120500",
                          "created_at": "2026-08-04T09:36:07Z",
                          "id": 72272,
                          "is_colored": false,
                          "is_double_sided": false,
                          "is_rre": true,
                          "letter_image": "https://example-bucket.s3.amazonaws.com/letter.pdf?X-Amz-Signature=REDACTED",
                          "price": "8.64",
                          "recipient": {
                            "address_line_1": "3032 Arjun Views",
                            "address_line_2": "Apt. 470",
                            "city": "Reillyton",
                            "first_name": "Spencer",
                            "last_name": "Tyler",
                            "state": "PA",
                            "zip": "96504-0522"
                          },
                          "return_mail_image": [],
                          "rre_image": "https://example-bucket.s3.amazonaws.com/rre.pdf?X-Amz-Signature=REDACTED",
                          "serial_number": 1010,
                          "status": 5,
                          "tracking_info": {
                            "done": true,
                            "error": null,
                            "logs": [
                              {
                                "date": "Dec 27, 2024",
                                "description": "Delivered",
                                "location": "Reillyton, PA"
                              },
                              {
                                "date": "Dec 23, 2024",
                                "description": "Accepted",
                                "location": "Frisco, TX"
                              }
                            ]
                          },
                          "type": "cm-10-rre",
                          "updated_at": "2026-08-04T09:36:40Z"
                        }
                      ]
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "affidavit_images": {
                      "description": "Signed URLs to affidavit PDFs. `null` when the batch has no affidavit.",
                      "items": {
                        "type": "string"
                      },
                      "nullable": true,
                      "type": "array"
                    },
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    },
                    "letters": {
                      "description": "`null` until the batch has been printed. Each letter carries its rendered image, USPS tracking, and the resolved sender/recipient.",
                      "items": {
                        "$ref": "#/components/schemas/Letter"
                      },
                      "nullable": true,
                      "type": "array"
                    }
                  },
                  "required": [
                    "is_successful"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Letters in the batch, plus `affidavit_images` when the batch carries an affidavit. Both are `null` \u2014 not `[]` \u2014 until the print pipeline has produced them.",
            "x-example-note": "Trimmed for readability. Signed-URL query strings are truncated, the tracking history is cut to two events, and the always-present `sender` and `check` keys are omitted \u2014 on a standard letter mailing both are empty objects. `tracking_info` is shown in its normalized form; most letters return the carrier's raw payload there instead."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/BatchNotFound"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The batch id is unusable, or the batch could not be read."
          }
        },
        "summary": "List letters in a batch",
        "tags": [
          "Letters"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl https://api.uploadletters.com/batches/$BATCH_ID/letters \\\n  -H \"x-api-key: $API_KEY\"\n\n# Filtered\ncurl \"https://api.uploadletters.com/batches/$BATCH_ID/letters?serviceType=cm-rre&status=5\" \\\n  -H \"x-api-key: $API_KEY\"\n"
          }
        ]
      },
      "patch": {
        "description": "Uploads the PDF files for letters already in the batch.\n\nThis does **not** create letters. A letter exists because a recipient\nexists. This call supplies the artwork for those letters, binding each PDF\nto the recipients whose `pdf_file_config.pdf_name` matches the uploaded\nfilename exactly.\n\n### One file\n\n```bash\ncurl --request PATCH \\\n  --url \"https://api.uploadletters.com/batches/{batchId}/letters\" \\\n  --header \"x-api-key: YOUR_API_KEY\" \\\n  --form \"letters[]=@Sample Letter.pdf\"\n```\n\n### Multiple files\n\n```bash\ncurl --request PATCH \\\n  --url \"https://api.uploadletters.com/batches/{batchId}/letters\" \\\n  --header \"x-api-key: YOUR_API_KEY\" \\\n  --form \"letters[]=@Spencer_Tyler.pdf\" \\\n  --form \"letters[]=@Amelia_Reyes.pdf\"\n```\n\n- The request uses `multipart/form-data`.\n- The file upload field is `letters[]`.\n- One or multiple PDF files can be uploaded.\n- The uploaded filenames need to exactly match the `pdf_name` values\n  included in the recipient data, and must be unique within the batch.\nBuild around your company’s Single PDF Max Size setting, which defaults to 15 MB per PDF. A setting of 0 means the company-level per-file limit is disabled. The API does not enforce the previously documented 20 MB total request limit. No fixed cumulative API upload or total-page cap is documented. Per-letter envelope capacities still apply. Coordinate unusually large uploads with UploadLetters.\n- PDF files are uploaded as files. They are not inserted into the JSON\n  request as Base64.\n\nCallable repeatedly, and freely interleaved with `PATCH .../recipients`,\nuntil the batch is fulfilled.\n\nThe response tells you what did and did not line up: `missed_pdf_files` are\nfilenames recipients expect but you have not uploaded; `extra_pdf_files` are\nfiles you uploaded that no recipient references.\n",
        "operationId": "uploadLetterFiles",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProcessId"
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "encoding": {
                "letters[]": {
                  "contentType": "application/pdf"
                }
              },
              "schema": {
                "properties": {
                  "letters[]": {
                    "description": "One or more PDF files. Repeat the field for each file.",
                    "items": {
                      "format": "binary",
                      "type": "string"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "letters[]"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "extra_pdf_files": [
                    "nobody_wants_me.pdf"
                  ],
                  "is_successful": true,
                  "missed_pdf_files": [],
                  "not_mapped_recipients": []
                },
                "schema": {
                  "$ref": "#/components/schemas/LetterMappingResult"
                }
              }
            },
            "description": "Files accepted. The three arrays describe what still does not line up \u2014 all empty means the batch is ready to fulfill."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The multipart body could not be read."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "content": {
              "application/json": {
                "example": {
                  "is_successful": false,
                  "message": "Batch fulfil was requested. You cannot add more letters after that."
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The files could not be accepted. Common causes:\n\n- `Batch fulfil was requested. You cannot add more letters after that.`\n- `Invalid batch id`\n- A file is not a PDF, or exceeds the account's per-file size limit.\n"
          }
        },
        "summary": "Upload letter PDFs",
        "tags": [
          "Letters"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "# One file\ncurl -X PATCH https://api.uploadletters.com/batches/$BATCH_ID/letters \\\n  -H \"x-api-key: $API_KEY\" \\\n  -F \"letters[]=@Sample Letter.pdf\"\n\n# Several files - repeat letters[] once per file\ncurl -X PATCH https://api.uploadletters.com/batches/$BATCH_ID/letters \\\n  -H \"x-api-key: $API_KEY\" \\\n  -F \"letters[]=@Spencer_Tyler.pdf\" \\\n  -F \"letters[]=@Amelia_Reyes.pdf\"\n"
          }
        ]
      }
    },
    "/batches/{processId}/letters/paged": {
      "get": {
        "description": "A paginated view of `GET /batches/{processId}/letters`. Prefer this for large batches \u2014 the unpaginated call returns every letter, with tracking history, in one response.\nTakes the same filters and sorting as the unpaginated call. `pagination` describes the filtered result set, not the whole batch.",
        "operationId": "listLettersPaged",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProcessId"
          },
          {
            "$ref": "#/components/parameters/LetterType"
          },
          {
            "$ref": "#/components/parameters/LetterServiceType"
          },
          {
            "$ref": "#/components/parameters/LetterStatus"
          },
          {
            "$ref": "#/components/parameters/LetterSearchTerm"
          },
          {
            "$ref": "#/components/parameters/LetterSortBy"
          },
          {
            "$ref": "#/components/parameters/SortType"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "affidavit_images": null,
                  "is_successful": true,
                  "letters": null,
                  "pagination": {
                    "current_page": 1,
                    "per_page": 2,
                    "total": 0,
                    "total_pages": 0
                  }
                },
                "schema": {
                  "properties": {
                    "affidavit_images": {
                      "items": {
                        "type": "string"
                      },
                      "nullable": true,
                      "type": "array"
                    },
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    },
                    "letters": {
                      "items": {
                        "$ref": "#/components/schemas/Letter"
                      },
                      "nullable": true,
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "is_successful",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "A page of letters, with a `pagination` object."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/BatchNotFound"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The batch id is unusable, or the batch could not be read."
          }
        },
        "summary": "List letters, paginated",
        "tags": [
          "Letters"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.uploadletters.com/batches/$BATCH_ID/letters/paged?page=1&perPage=25\" \\\n  -H \"x-api-key: $API_KEY\"\n"
          }
        ]
      }
    },
    "/batches/{processId}/recipients": {
      "get": {
        "description": "Returns the recipients in a batch and, crucially, **whether each one has\nbeen matched to an uploaded PDF**.\n\nCall this before `PUT .../fulfill`. The batch is ready when:\n\n- `total_not_mapped_recipients` is `0`, and\n- `missed_images` is empty.\n\n`mapped_recipients` are matched to a PDF. `not_mapped_recipients` are not,\neach carrying an `error_message` explaining why. `missed_images` lists\n`pdf_name` values that recipients referenced but which were never uploaded.\n\n`ncoa_lookup` reports address changes found during address verification.\n\n**Note:** this endpoint returns recipient data only for batches created\nthrough this API. Batches created by other means (e.g. manually) will not\nreturn recipient data here.\n",
        "operationId": "listRecipients",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProcessId"
          },
          {
            "description": "When set, `total_not_mapped_recipients` and `not_mapped_recipients` are omitted from the response entirely. Useful once the batch is known good; do not use it while verifying.",
            "in": "query",
            "name": "onlyValid",
            "schema": {
              "example": true,
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "beforeUpload": {
                    "summary": "PDFs not yet uploaded \u2014 the batch is not ready to fulfill",
                    "value": {
                      "is_successful": true,
                      "mapped_recipients": [],
                      "missed_images": [
                        "Sample Letter.pdf"
                      ],
                      "ncoa_lookup": null,
                      "not_mapped_recipients": [
                        {
                          "error_message": "",
                          "letter_id": "letter_1",
                          "pdf_file_config": {
                            "pdf_name": "Sample Letter.pdf",
                            "pdf_page_numbers": "1-3",
                            "pdf_process_type": ""
                          },
                          "shadow": false,
                          "unique_id": "1"
                        }
                      ],
                      "total_not_mapped_recipients": 2,
                      "total_recipients": 2
                    }
                  },
                  "onlyValid": {
                    "summary": "With onlyValid=1 \u2014 the not-mapped keys are absent",
                    "value": {
                      "is_successful": true,
                      "mapped_recipients": [
                        {
                          "error_message": "",
                          "letter_id": "letter_1",
                          "shadow": false,
                          "unique_id": "1"
                        }
                      ],
                      "missed_images": [],
                      "ncoa_lookup": null,
                      "total_recipients": 3
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    },
                    "mapped_recipients": {
                      "description": "Recipients matched to an uploaded PDF.",
                      "items": {
                        "$ref": "#/components/schemas/Recipient"
                      },
                      "type": "array"
                    },
                    "missed_images": {
                      "description": "`pdf_name` values referenced by recipients but never uploaded.",
                      "example": [],
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    },
                    "ncoa_lookup": {
                      "description": "Address changes found during NCOALink verification. `null` when `address_verification_type` was `NONE`.",
                      "items": {
                        "additionalProperties": true,
                        "type": "object"
                      },
                      "nullable": true,
                      "type": "array"
                    },
                    "not_mapped_recipients": {
                      "description": "Recipients with no matching PDF. Omitted entirely when `onlyValid` is set.",
                      "items": {
                        "$ref": "#/components/schemas/Recipient"
                      },
                      "type": "array"
                    },
                    "total_not_mapped_recipients": {
                      "description": "Omitted entirely when `onlyValid` is set.",
                      "example": 0,
                      "type": "integer"
                    },
                    "total_recipients": {
                      "example": 3,
                      "type": "integer"
                    }
                  },
                  "required": [
                    "is_successful",
                    "total_recipients",
                    "mapped_recipients",
                    "missed_images"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Recipients with mapping state. `not_mapped_recipients` and `total_not_mapped_recipients` are omitted when `onlyValid` is set."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/BatchNotFound"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The batch id is unusable, or the batch could not be read."
          }
        },
        "summary": "List recipients and check letter mapping",
        "tags": [
          "Recipients"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "# Verify the batch before fulfilling: ready when\n# total_not_mapped_recipients is 0 and missed_images is empty.\ncurl https://api.uploadletters.com/batches/$BATCH_ID/recipients \\\n  -H \"x-api-key: $API_KEY\"\n"
          }
        ]
      },
      "patch": {
        "description": "Append recipients before fulfillment. Start with about 100 recipients per POST or recipient PATCH as an operational recommendation. The API enforces no fixed recipient-count limit on either request or on the batch. Append additional groups to the same draft before fulfillment. Resend only corrected rejected rows. Repeating accepted rows duplicates them. Explicitly send address_verification_type on every call: omission uses the company default rather than preserving the original batch mode.",
        "operationId": "addRecipients",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProcessId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "recipients": [
                  {
                    "address": {
                      "address_line_1": "32704 Sven Spurs",
                      "city": "New Estaton",
                      "first_name": "June",
                      "last_name": "Kim",
                      "state": "IL",
                      "zip": "44989"
                    },
                    "letter_id": "letter_4",
                    "mailing_options": {
                      "envelope_type": "10",
                      "is_colored": false,
                      "is_double_sided": false,
                      "service_type": "cm-rre"
                    },
                    "pdf_file_config": {
                      "pdf_name": "Sample Letter.pdf",
                      "pdf_page_numbers": "10-12"
                    },
                    "return_address": {
                      "address_line_1": "456 Gulf Breeze Way",
                      "address_line_2": "Suite 200",
                      "city": "Naples",
                      "company": "Sterling Law Group, P.A.",
                      "first_name": "Sterling",
                      "last_name": "Law Group",
                      "state": "FL",
                      "zip": "34102"
                    },
                    "unique_id": "4"
                  }
                ]
              },
              "schema": {
                "$ref": "#/components/schemas/BatchWriteBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "batch_id": "6f6708b0-8fd4-4e3f-80bf-28401a657c9d",
                  "is_successful": true
                },
                "schema": {
                  "properties": {
                    "batch_id": {
                      "example": "6f6708b0-8fd4-4e3f-80bf-28401a657c9d",
                      "format": "uuid",
                      "type": "string"
                    },
                    "invalid_letters": {
                      "$ref": "#/components/schemas/ValidationErrors"
                    },
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "is_successful",
                    "batch_id"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Recipients accepted. Carries `batch_id`, plus `invalid_letters` if some were rejected."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "invalid_letters": {
                          "$ref": "#/components/schemas/ValidationErrors"
                        }
                      },
                      "type": "object"
                    }
                  ]
                }
              }
            },
            "description": "Every recipient failed validation, or the batch has already been fulfilled."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The account is not permitted to use a requested feature."
          },
          "422": {
            "content": {
              "application/json": {
                "example": {
                  "is_successful": false,
                  "message": "Batch fulfil was requested. You cannot add more letters after that."
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The recipients could not be added. Common causes:\n\n- `Batch fulfil was requested. You cannot add more letters after that.`\n- `Invalid batch id`\n- An unrecognized `address_verification_type` or `affidavit`.\n"
          }
        },
        "summary": "Add recipients to a batch",
        "tags": [
          "Recipients"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl -X PATCH https://api.uploadletters.com/batches/$BATCH_ID/recipients \\\n  -H \"x-api-key: $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"recipients\": [\n      {\n        \"unique_id\": \"4\",\n        \"letter_id\": \"letter_4\",\n        \"address\": {\n          \"first_name\": \"June\",\n          \"last_name\": \"Kim\",\n          \"address_line_1\": \"32704 Sven Spurs\",\n          \"city\": \"New Estaton\",\n          \"state\": \"IL\",\n          \"zip\": \"44989\"\n        },\n        \"return_address\": {\n          \"first_name\": \"Sterling\",\n          \"last_name\": \"Law Group\",\n          \"company\": \"Sterling Law Group, P.A.\",\n          \"address_line_1\": \"456 Gulf Breeze Way\",\n          \"address_line_2\": \"Suite 200\",\n          \"city\": \"Naples\",\n          \"state\": \"FL\",\n          \"zip\": \"34102\"\n        },\n        \"pdf_file_config\": {\n          \"pdf_name\": \"Sample Letter.pdf\",\n          \"pdf_page_numbers\": \"10-12\"\n        },\n        \"mailing_options\": {\n          \"envelope_type\": \"10\",\n          \"service_type\": \"cm-rre\",\n          \"is_colored\": false,\n          \"is_double_sided\": false\n        }\n      }\n    ]\n  }'\n"
          }
        ]
      }
    },
    "/batches/{processId}/recipients/paged": {
      "get": {
        "description": "A paginated view of the batch's recipients. Unlike `GET .../recipients`,\nthis returns a flat `recipients` array with a `pagination` object, and does\nnot report letter-mapping state. Use `GET .../recipients` to verify a batch\nbefore fulfilling it.\n\nSearching and filtering here read the recipient **as you submitted it**, so\nthey work from the moment a recipient is added \u2014 including on a draft batch\nwhose letters do not exist yet. If address verification later rewrites an\naddress, `search` still matches what you sent, not what was mailed.\n\n`pagination` describes the filtered result set, not the whole batch.\n\n**Note:** this endpoint returns recipient data only for batches created\nthrough this API. Batches created by other means (e.g. manually) will not\nreturn recipient data here.\n",
        "operationId": "listRecipientsPaged",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProcessId"
          },
          {
            "description": "Case-insensitive search across the recipient's first name, last name, full name, company, address lines, city, state and zip.",
            "in": "query",
            "name": "search",
            "schema": {
              "example": "Tyler",
              "type": "string"
            }
          },
          {
            "description": "Filter by the `service_type` submitted in `mailing_options`.",
            "in": "query",
            "name": "serviceType",
            "schema": {
              "enum": [
                "fc",
                "cm",
                "cm-rre",
                "pm",
                "pm-box",
                "intl-fc",
                "rim"
              ],
              "example": "cm-rre",
              "type": "string"
            }
          },
          {
            "description": "Exact match on the recipient's `unique_id`.",
            "in": "query",
            "name": "uniqueId",
            "schema": {
              "example": "1",
              "type": "string"
            }
          },
          {
            "description": "Column to sort by. An unrecognized value falls back to `id`, which is the order the recipients were added.",
            "in": "query",
            "name": "sortBy",
            "schema": {
              "default": "id",
              "enum": [
                "id",
                "import_order",
                "created_at"
              ],
              "example": "import_order",
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/SortType"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "is_successful": true,
                  "pagination": {
                    "current_page": 1,
                    "per_page": 2,
                    "total": 3,
                    "total_pages": 2
                  },
                  "recipients": [
                    {
                      "error_message": "",
                      "letter_id": "letter_1",
                      "pdf_file_config": {
                        "pdf_name": "Sample Letter.pdf",
                        "pdf_page_numbers": "1-3",
                        "pdf_process_type": ""
                      },
                      "shadow": false,
                      "unique_id": "1"
                    }
                  ]
                },
                "schema": {
                  "properties": {
                    "is_successful": {
                      "example": true,
                      "type": "boolean"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    "recipients": {
                      "items": {
                        "$ref": "#/components/schemas/Recipient"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "is_successful",
                    "recipients",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "A page of recipients, with a `pagination` object. Note the key is `recipients` here, not `mapped_recipients`."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/BatchNotFound"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The batch id is unusable, or the batch could not be read."
          }
        },
        "summary": "List recipients, paginated",
        "tags": [
          "Recipients"
        ],
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.uploadletters.com/batches/$BATCH_ID/recipients/paged?page=1&perPage=25\" \\\n  -H \"x-api-key: $API_KEY\"\n"
          }
        ]
      }
    }
  },
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "servers": [
    {
      "description": "Production",
      "url": "https://api.uploadletters.com"
    }
  ],
  "tags": [
    {
      "description": "UploadLetters turns a PDF and an address into a piece of physical mail. You\nupload recipients and letter artwork, we print, stamp and post them, and you\ntrack delivery through USPS.\n\n**A recipient is a letter.** Each recipient you add carries its own mailing\noptions and its own reference to a PDF, so adding a recipient adds a letter\nto be mailed. There is no separate letter object to create.\n\n### The six steps\n\n| | Call | What it does |\n|---|---|---|\n| 1 | `POST /batches` | Create the batch and seed it with recipients. Returns `batch_id`. |\n| 2 | `PATCH /batches/{batchId}/letters` | Upload the PDF files. |\n| 3 | `PATCH /batches/{batchId}/recipients` | Add more recipients, when you need them. |\n| 4 | `GET /batches/{batchId}/recipients` | Verify every recipient found its PDF. |\n| 5 | `PUT /batches/{batchId}/fulfill` | Charge the wallet and send the batch to production. |\n| 6 | `GET \u2026` | Read batch status, tracking information, letter images, RRE images, affidavits and return mail images. |\n\nSteps 2 and 3 are repeatable and may run in any order, as many times as you\nneed. Step 5 closes the batch permanently.\n\n### Nothing is mailed until you fulfill\n\nCreating a batch does not submit the mailing. Uploading the PDF files does\nnot submit the mailing. The wallet is charged and the batch moves into\nproduction only after `PUT /batches/{batchId}/fulfill` is successfully\ncalled.\n\n### Do not skip step 4\n\n`POST /batches` returns before your recipients have finished processing, so a\nbatch can look complete while it is still missing PDFs. Confirm\n`total_not_mapped_recipients` is `0` and `missed_images` is empty before you\nfulfill \u2014 **a batch fulfilled with unmapped recipients is still charged.**\n\n### PDF filenames are the link\n\nThe filenames you upload in step 2 must match the `pdf_name` values in your\nbatch or recipient request **exactly**. That filename is the only thing\nbinding a PDF to the recipients who should receive it.\n\nStart with about 100 recipients per POST or recipient PATCH as an operational recommendation. The API enforces no fixed recipient-count limit on either request or on the batch. Append additional groups to the same draft before fulfillment.\n",
      "name": "Start Here",
      "x-traitTag": true
    },
    {
      "description": "API access needs to be enabled for your UploadLetters account. Once API\naccess is enabled, you can generate an API key under **Settings**.\n\nEvery request needs to include that key in the `x-api-key` header:\n\n```\nx-api-key: YOUR_API_KEY\n```\n\nProduction API URL:\n\n```\nhttps://api.uploadletters.com\n```\n\n### Limits\n\n| | |\n|---|---|\n| Total request size | The API does not enforce the previously documented 20 MB limit. |\n| Per-PDF size | Company Single PDF Max Size, 15 MB by default. A setting of 0 disables the company-level per-file limit. |\n| Letter files | PDF only |\n| Recipients per request | About 100 recommended. The API enforces no fixed recipient-count cap per request or batch. |\n",
      "name": "API Access",
      "x-traitTag": true
    },
    {
      "description": "The same six steps, as commands you can run. A batch is **accumulated**,\nthen **committed** \u2014 it is not a linear pipeline.\n\n### 1. Create the batch\n\n```bash\ncurl -X POST https://api.uploadletters.com/batches \\\n  -H \"x-api-key: $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d @batch.json\n```\n\nThe response carries `batch_id`. Use it as `{batchId}` everywhere below.\n\n> **This call is asynchronous.** A `200` means your recipients were accepted\n> and are being processed in the background. It does not mean processing has\n> finished. Recipients that fail validation come back in `invalid_letters`\n> and are skipped \u2014 the rest still create the batch.\n\n### 2. Upload the letter PDFs \u2014 repeatable\n\n```bash\ncurl -X PATCH https://api.uploadletters.com/batches/$BATCH_ID/letters \\\n  -H \"x-api-key: $API_KEY\" \\\n  -F \"letters[]=@Sample Letter.pdf\"\n```\n\n`multipart/form-data`, not JSON. Repeat the `letters[]` field once per file.\nFilenames must be unique within the batch.\n\n### 3. Add more recipients \u2014 repeatable\n\n```bash\ncurl -X PATCH https://api.uploadletters.com/batches/$BATCH_ID/recipients \\\n  -H \"x-api-key: $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"recipients\": [ \u2026 ] }'\n```\n\nCall it as often as you like, in any order relative to step 2.\n\n### 4. Verify before you pay\n\n```bash\ncurl https://api.uploadletters.com/batches/$BATCH_ID/recipients \\\n  -H \"x-api-key: $API_KEY\"\n```\n\nThe batch is ready when `total_not_mapped_recipients` is `0` and\n`missed_images` is empty. Anything in `not_mapped_recipients` carries an\n`error_message` telling you why.\n\nBecause step 1 is asynchronous, poll this until the counts settle.\n\n### 5. Fulfill\n\n```bash\ncurl -X PUT https://api.uploadletters.com/batches/$BATCH_ID/fulfill \\\n  -H \"x-api-key: $API_KEY\"\n```\n\n```json\n{ \"is_successful\": true, \"message\": \"Batch put to queue and will be charged and fulfilled.\" }\n```\n\nLike step 1, this is **asynchronous**: the `200` means the batch was *queued*\nfor charging and printing, not that your wallet has been debited yet. Poll\n`GET /batches/transactions` if you need to observe the charge land.\n\nIt is **irreversible**, and it closes the batch: both `PATCH` endpoints\nreturn `422` from this point on.\n\n### 6. Read the results\n\n```bash\ncurl https://api.uploadletters.com/batches/$BATCH_ID/letters \\\n  -H \"x-api-key: $API_KEY\"\n```\n",
      "name": "Basic Workflow",
      "x-traitTag": true
    },
    {
      "description": "### Batch types\n\n`batch_type` is set on `POST /batches`. Optional; defaults to `letter`.\n\n| Value | Meaning |\n|---|---|\n| `letter` | Letters only. |\n| `letter_and_check` | Letters and checks submitted together. |\n| `check` | Checks only. |\n\n### USPS service types\n\n`service_type` is set per recipient inside `mailing_options`, and is\nrequired.\n\n| Value | Service |\n|---|---|\n| `fc` | USPS First-Class Mail |\n| `cm` | USPS Certified Mail |\n| `cm-rre` | USPS Certified Mail with Return Receipt Electronic |\n| `pm` | USPS Priority Mail |\n| `pm-box` | USPS Priority Mail Medium Flat Rate Box |\n\nTwo further service types cover international mail: `intl-fc`\n(International First-Class) and `rim` (International Registered Mail). Each\ncarries extra requirements \u2014 see `mailing_options` in the endpoint\nreference.\n\n### Envelope types\n\n`envelope_type` is set per recipient inside `mailing_options`.\n\n| Value | Envelope |\n|---|---|\n| `10` | #10 envelope |\n| `6x9` | 6 \u00d7 9 envelope |\n| `9x12` | 9 \u00d7 12 envelope |\n| `ps` | Pressure-seal check. Check batches only, and only with `service_type: fc`. |\n\n`envelope_type` is optional for USPS mail: leave it out and the envelope is\nchosen from the page count.\n\n**Priority Mail pieces do not need an envelope type.** The field is ignored\nfor both `pm` and `pm-box`, so leave it blank. To send a Priority Mail Medium\nFlat Rate Box, set `service_type` to `pm-box` \u2014 the box is a service, not an\nenvelope.\n\n\n### Additional USPS services\n\nThese are **batch-level** flags, set on `POST /batches` only. Each must be\nenabled on your account, and the two cannot be combined in one batch.\n\n| Field | Service | Condition |\n|---|---|---|\n| `certificate_of_mailing` | Certificate of Mailing | every recipient in the batch must be `fc` |\n| `restricted_delivery` | Restricted Delivery | every recipient in the batch must be `cm` or `cm-rre` |\n\n### Print color\n\n| | |\n|---|---|\n| `\"is_colored\": false` | Print the letter in black and white. The default. |\n| `\"is_colored\": true` | Print the letter in color. |\n\n### Printing sides\n\n| | |\n|---|---|\n| `\"is_double_sided\": false` | Print the letter one-sided. The default. |\n| `\"is_double_sided\": true` | Print the letter two-sided. |\n\nBoth are forced to `false` on check batches.\n\n### Affidavit of Mailing\n\n`affidavit` is set per recipient. If no affidavit is requested, none is\ngenerated.\n\n| Value | Meaning |\n|---|---|\n| `sb` | Signature Only, Batch Level. One signed Affidavit of Mailing covers the entire batch. |\n| `sl` | Signature Only, Letter Level. A separate signed Affidavit of Mailing is generated for each recipient. |\n| `nb` | Notarized, Batch Level. One notarized Affidavit of Mailing covers the entire batch. |\n| `nl` | Notarized, Letter Level. A separate notarized Affidavit of Mailing is generated for each recipient. |\n\nThe first letter is the kind of affidavit \u2014 `s` signed, `n` notarized. The\nsecond letter is the scope: **`b` is batch level** (one affidavit for the\nwhole batch, billed once) and **`l` is letter level** (one per recipient,\nbilled per letter).\n\nNotarized affidavits are reviewed manually.\n\n**If an affidavit option is selected, the same affidavit option needs to be\nused for all recipients within the batch.** A batch with mixed values is\nrejected. The feature must be enabled on your account.\n\n### Return envelopes\n\n`return_envelope` is set per recipient, and the selection can vary by\nrecipient.\n\n| Value | Envelope |\n|---|---|\n| `re-9npna` | Standard #9 return envelope with no postage and no pre-addressed return destination. |\n| `re-9pna` | #9 return envelope with prepaid postage and no pre-addressed return destination. |\n| `re-9pa` | #9 return envelope with prepaid postage and a pre-addressed return destination. |\n\nThere is also `re-9pam`, a #9 prepaid, pre-addressed and metered envelope.\nOmit the field, or send `re-empty`, for no return envelope.\n\nThe `re-` prefix is optional on input: `9npna` is accepted and stored as\n`re-9npna`. Responses always return the prefixed form, so prefer it in your\nrequests too. Return envelopes cannot be used with international mail.\n\n### Address verification\n\n`address_verification_type` is a **batch-level** setting on `POST /batches`.\nTo mail every address exactly as you supplied it, send `NONE`.\n\n| Value | Behavior |\n|---|---|\n| `NONE` | The recipient address is printed exactly as provided in the request, without modification. |\n| `CASS_NCOA_BEST` | First run CASS. If CASS passes, run NCOA to look for an updated address. If an updated address is found, mail to the NCOA address. If no NCOA update is found, mail to the CASS-certified address. If CASS fails, mail to the original address. |\n| `CASS_NCOA_LOOKUP` | Run CASS. If CASS fails, mail to the original address. If CASS passes and an NCOA update is found, mail to the CASS-certified address but return the NCOA address in the response so you can update your records. |\n| `CASS_NCOA_BOTH` | Run CASS and then NCOA. If an updated address is found, mail to both the NCOA address and the CASS-certified address. If no updated address is found, mail only to the CASS-certified address. If CASS fails, mail to the original address. |\n\n**Omitting the field is not the same as sending `NONE`.** When it is absent\nwe fall back to the default configured on your account, which may be one of\nthe CASS options. Send `NONE` explicitly if you need addresses untouched.\n\nAddress verification cannot be used with international service types.\n\n### Duplicate First-Class copy\n\n`shadow` is a per-recipient boolean.\n\n```json\n\"shadow\": true\n```\n\nWhen enabled for an eligible Certified Mail or Certified Mail with RRE piece,\nUploadLetters automatically sends an identical additional copy by First-Class\nMail. You submit the recipient one time, and UploadLetters automatically\ngenerates the additional First-Class piece.\n\nThe extra piece is printed and billed like any other letter. `shadow` has no\neffect on other service types, and is ignored on check batches.\n\n### Same-day mailing\n\n`same_day_mailing` is a **batch-level** boolean on `POST /batches`. Orders\nmeeting the applicable business-day cutoff are mailed the same business day.\nIt carries a per-letter surcharge, must be enabled on your account, and\ncannot be used with international mail.\n",
      "name": "Terminology and Supported Values",
      "x-traitTag": true
    },
    {
      "description": "One recipient. Certified Mail with Return Receipt Electronic, #10 envelope,\nblack-and-white, one-sided. No affidavit, no return envelope, no duplicate\nFirst-Class copy, no same-day mailing, no address verification.\n\nThis is the request to copy into Postman and modify.\n\n```bash\ncurl -X POST https://api.uploadletters.com/batches \\\n  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d @batch.json\n```\n\n```json\n{\n  \"batch_type\": \"letter\",\n  \"address_verification_type\": \"NONE\",\n  \"recipients\": [\n    {\n      \"unique_id\": \"1\",\n      \"letter_id\": \"letter_1\",\n      \"address\": {\n        \"first_name\": \"Spencer\",\n        \"last_name\": \"Tyler\",\n        \"address_line_1\": \"3032 Arjun Views\",\n        \"address_line_2\": \"Apt. 470\",\n        \"city\": \"Reillyton\",\n        \"state\": \"PA\",\n        \"zip\": \"96504-0522\"\n      },\n      \"return_address\": {\n        \"first_name\": \"Sterling\",\n        \"last_name\": \"Law Group\",\n        \"company\": \"Sterling Law Group, P.A.\",\n        \"address_line_1\": \"456 Gulf Breeze Way\",\n        \"address_line_2\": \"Suite 200\",\n        \"city\": \"Naples\",\n        \"state\": \"FL\",\n        \"zip\": \"34102\"\n      },\n      \"pdf_file_config\": {\n        \"pdf_name\": \"Sample Letter.pdf\",\n        \"pdf_page_numbers\": \"1-3\"\n      },\n      \"mailing_options\": {\n        \"envelope_type\": \"10\",\n        \"service_type\": \"cm-rre\",\n        \"is_colored\": false,\n        \"is_double_sided\": false\n      }\n    }\n  ]\n}\n```\n\n```json\n{ \"is_successful\": true, \"batch_id\": \"bb00423d-d830-41be-b60b-dde443312432\" }\n```\n\nOptional features you are not using are left out entirely rather than sent as\n`false` or `\"\"`. The one deliberate exception is\n`address_verification_type`, spelled out as `NONE` because leaving it off\nfalls back to your account's configured default.\n\n### The PDF fields\n\n**`pdf_name`** \u2014 the exact filename of the PDF that will be uploaded through\n`PATCH /batches/{batchId}/letters`. Required whenever the batch carries\nletters, and matched character for character.\n\n```json\n\"pdf_name\": \"Sample Letter.pdf\"\n```\n\n**`pdf_page_numbers`** \u2014 the page or page range within the uploaded PDF that\nbelongs to the recipient. A single page (`\"3\"`) or an inclusive range\n(`\"1-3\"`).\n\n```json\n\"pdf_page_numbers\": \"1-3\"\n```\n\n**Leave `pdf_page_numbers` out when a recipient has a PDF to itself** \u2014 the\nwhole file is used for that letter.\n\nBetween them these two fields express how your PDFs map to your recipients.\nThere is no separate merge setting:\n\n| What you upload | What your recipients say |\n|---|---|\n| One PDF containing every letter | the same `pdf_name`, each recipient with its own `pdf_page_numbers` |\n| One PDF per recipient | a different `pdf_name` each, `pdf_page_numbers` omitted |\n\nBoth are worked out in full under **Multiple Recipients and PDF Mapping**.\n",
      "name": "Basic Create Batch Example",
      "x-traitTag": true
    },
    {
      "description": "Upload the letter artwork for recipients that already exist in the batch.\n\n### One file\n\n```bash\ncurl --request PATCH \\\n  --url \"https://api.uploadletters.com/batches/{batchId}/letters\" \\\n  --header \"x-api-key: YOUR_API_KEY\" \\\n  --form \"letters[]=@Sample Letter.pdf\"\n```\n\n### Multiple files\n\n```bash\ncurl --request PATCH \\\n  --url \"https://api.uploadletters.com/batches/{batchId}/letters\" \\\n  --header \"x-api-key: YOUR_API_KEY\" \\\n  --form \"letters[]=@Spencer_Tyler.pdf\" \\\n  --form \"letters[]=@Amelia_Reyes.pdf\"\n```\n\n- The request uses `multipart/form-data`.\n- The file upload field is `letters[]`.\n- One or multiple PDF files can be uploaded.\n- The uploaded filenames need to exactly match the `pdf_name` values included\n  in the recipient data.\nBuild around your company\u2019s Single PDF Max Size setting, which defaults to 15 MB per PDF. A setting of 0 means the company-level per-file limit is disabled. The API does not enforce the previously documented 20 MB total request limit. No fixed cumulative API upload or total-page cap is documented. Per-letter envelope capacities still apply. Coordinate unusually large uploads with UploadLetters.\n- PDF files are uploaded as files. They are not inserted into the JSON\n  request as Base64.\n\nThe response reports how the files lined up:\n\n```json\n{\n  \"is_successful\": true,\n  \"missed_pdf_files\": [],\n  \"extra_pdf_files\": [],\n  \"not_mapped_recipients\": []\n}\n```\n\n`missed_pdf_files` are filenames your recipients expect but you have not\nuploaded. `extra_pdf_files` are files you uploaded that no recipient\nreferences \u2014 usually a filename typo.\n",
      "name": "PDF Upload Example",
      "x-traitTag": true
    },
    {
      "description": "```bash\ncurl --request PUT \\\n  --url \"https://api.uploadletters.com/batches/{batchId}/fulfill\" \\\n  --header \"x-api-key: YOUR_API_KEY\"\n```\n\n**Request body: None required.**\n\n```json\n{ \"is_successful\": true, \"message\": \"Batch put to queue and will be charged and fulfilled.\" }\n```\n\nFulfill is the final submission step. A successful Fulfill request deducts\nthe order amount from your UploadLetters wallet and moves the batch into\nproduction. Creating the batch and uploading the PDF files does not submit\nthe mailing.\n\nTwo things to know:\n\n- **It is asynchronous.** The `200` means the batch was queued for charging\n  and printing, not that your wallet has already been debited. Watch\n  `GET /batches/transactions` if you need to see the charge land.\n- **It is irreversible, and it closes the batch.** After a successful call,\n  `PATCH /batches/{batchId}/recipients` and\n  `PATCH /batches/{batchId}/letters` both return `422`.\n\nVerify the batch first \u2014 see step 4 of the **Basic Workflow**.\n",
      "name": "Fulfill Example",
      "x-traitTag": true
    },
    {
      "description": "### One combined PDF for several recipients\n\n`Sample Letter.pdf` holds six pages: pages 1-3 are Spencer Tyler's letter,\npages 4-6 are Amelia Reyes's. Both recipients reference the same\n`pdf_name` and carve out their own pages with `pdf_page_numbers`.\n\n```json\n{\n  \"batch_type\": \"letter\",\n  \"address_verification_type\": \"NONE\",\n  \"recipients\": [\n    {\n      \"unique_id\": \"1\",\n      \"letter_id\": \"letter_1\",\n      \"address\": {\n        \"first_name\": \"Spencer\",\n        \"last_name\": \"Tyler\",\n        \"address_line_1\": \"3032 Arjun Views\",\n        \"address_line_2\": \"Apt. 470\",\n        \"city\": \"Reillyton\",\n        \"state\": \"PA\",\n        \"zip\": \"96504-0522\"\n      },\n      \"return_address\": {\n        \"first_name\": \"Sterling\",\n        \"last_name\": \"Law Group\",\n        \"company\": \"Sterling Law Group, P.A.\",\n        \"address_line_1\": \"456 Gulf Breeze Way\",\n        \"address_line_2\": \"Suite 200\",\n        \"city\": \"Naples\",\n        \"state\": \"FL\",\n        \"zip\": \"34102\"\n      },\n      \"pdf_file_config\": {\n        \"pdf_name\": \"Sample Letter.pdf\",\n        \"pdf_page_numbers\": \"1-3\"\n      },\n      \"mailing_options\": {\n        \"envelope_type\": \"10\",\n        \"service_type\": \"cm-rre\",\n        \"is_colored\": false,\n        \"is_double_sided\": false\n      }\n    },\n    {\n      \"unique_id\": \"2\",\n      \"letter_id\": \"letter_2\",\n      \"address\": {\n        \"first_name\": \"Amelia\",\n        \"last_name\": \"Reyes\",\n        \"address_line_1\": \"6934 Larue Key\",\n        \"address_line_2\": \"Suite 895\",\n        \"city\": \"Marysebury\",\n        \"state\": \"MD\",\n        \"zip\": \"57158-3202\"\n      },\n      \"return_address\": {\n        \"first_name\": \"Sterling\",\n        \"last_name\": \"Law Group\",\n        \"company\": \"Sterling Law Group, P.A.\",\n        \"address_line_1\": \"456 Gulf Breeze Way\",\n        \"address_line_2\": \"Suite 200\",\n        \"city\": \"Naples\",\n        \"state\": \"FL\",\n        \"zip\": \"34102\"\n      },\n      \"pdf_file_config\": {\n        \"pdf_name\": \"Sample Letter.pdf\",\n        \"pdf_page_numbers\": \"4-6\"\n      },\n      \"mailing_options\": {\n        \"envelope_type\": \"10\",\n        \"service_type\": \"cm-rre\",\n        \"is_colored\": false,\n        \"is_double_sided\": false\n      }\n    }\n  ]\n}\n```\n\nThen upload the one file:\n\n```bash\ncurl --request PATCH \\\n  --url \"https://api.uploadletters.com/batches/{batchId}/letters\" \\\n  --header \"x-api-key: YOUR_API_KEY\" \\\n  --form \"letters[]=@Sample Letter.pdf\"\n```\n\n### A separate PDF per recipient\n\nSame two recipients, but each has its own file. **`pdf_page_numbers` is\nomitted** \u2014 when a recipient has a PDF to itself, the whole file is that\nrecipient's letter.\n\n```json\n{\n  \"batch_type\": \"letter\",\n  \"address_verification_type\": \"NONE\",\n  \"recipients\": [\n    {\n      \"unique_id\": \"1\",\n      \"letter_id\": \"letter_1\",\n      \"address\": {\n        \"first_name\": \"Spencer\",\n        \"last_name\": \"Tyler\",\n        \"address_line_1\": \"3032 Arjun Views\",\n        \"address_line_2\": \"Apt. 470\",\n        \"city\": \"Reillyton\",\n        \"state\": \"PA\",\n        \"zip\": \"96504-0522\"\n      },\n      \"return_address\": {\n        \"first_name\": \"Sterling\",\n        \"last_name\": \"Law Group\",\n        \"company\": \"Sterling Law Group, P.A.\",\n        \"address_line_1\": \"456 Gulf Breeze Way\",\n        \"address_line_2\": \"Suite 200\",\n        \"city\": \"Naples\",\n        \"state\": \"FL\",\n        \"zip\": \"34102\"\n      },\n      \"pdf_file_config\": {\n        \"pdf_name\": \"Spencer_Tyler.pdf\"\n      },\n      \"mailing_options\": {\n        \"envelope_type\": \"10\",\n        \"service_type\": \"cm-rre\",\n        \"is_colored\": false,\n        \"is_double_sided\": false\n      }\n    },\n    {\n      \"unique_id\": \"2\",\n      \"letter_id\": \"letter_2\",\n      \"address\": {\n        \"first_name\": \"Amelia\",\n        \"last_name\": \"Reyes\",\n        \"address_line_1\": \"6934 Larue Key\",\n        \"address_line_2\": \"Suite 895\",\n        \"city\": \"Marysebury\",\n        \"state\": \"MD\",\n        \"zip\": \"57158-3202\"\n      },\n      \"return_address\": {\n        \"first_name\": \"Sterling\",\n        \"last_name\": \"Law Group\",\n        \"company\": \"Sterling Law Group, P.A.\",\n        \"address_line_1\": \"456 Gulf Breeze Way\",\n        \"address_line_2\": \"Suite 200\",\n        \"city\": \"Naples\",\n        \"state\": \"FL\",\n        \"zip\": \"34102\"\n      },\n      \"pdf_file_config\": {\n        \"pdf_name\": \"Amelia_Reyes.pdf\"\n      },\n      \"mailing_options\": {\n        \"envelope_type\": \"10\",\n        \"service_type\": \"cm-rre\",\n        \"is_colored\": false,\n        \"is_double_sided\": false\n      }\n    }\n  ]\n}\n```\n\n```bash\ncurl --request PATCH \\\n  --url \"https://api.uploadletters.com/batches/{batchId}/letters\" \\\n  --header \"x-api-key: YOUR_API_KEY\" \\\n  --form \"letters[]=@Spencer_Tyler.pdf\" \\\n  --form \"letters[]=@Amelia_Reyes.pdf\"\n```\n",
      "name": "Multiple Recipients and PDF Mapping",
      "x-traitTag": true
    },
    {
      "description": "Small fragments to drop into the basic example. Everything here is optional \u2014\nthe basic request works without any of it.\n\n### First-Class Mail\n\n```json\n\"mailing_options\": {\n  \"envelope_type\": \"10\",\n  \"service_type\": \"fc\",\n  \"is_colored\": false,\n  \"is_double_sided\": false\n}\n```\n\n### Certified Mail\n\n```json\n\"mailing_options\": {\n  \"envelope_type\": \"10\",\n  \"service_type\": \"cm\",\n  \"is_colored\": false,\n  \"is_double_sided\": false\n}\n```\n\n### Certified Mail with Return Receipt Electronic\n\n```json\n\"mailing_options\": {\n  \"envelope_type\": \"10\",\n  \"service_type\": \"cm-rre\",\n  \"is_colored\": false,\n  \"is_double_sided\": false\n}\n```\n\n### Priority Mail\n\nNo `envelope_type` \u2014 it is ignored for Priority Mail.\n\n```json\n\"mailing_options\": {\n  \"service_type\": \"pm\",\n  \"is_colored\": false,\n  \"is_double_sided\": false\n}\n```\n\n### Priority Mail Medium Flat Rate Box\n\nThe box is a service type, not an envelope type.\n\n```json\n\"mailing_options\": {\n  \"service_type\": \"pm-box\",\n  \"is_colored\": false,\n  \"is_double_sided\": false\n}\n```\n\n### Affidavit of Mailing\n\nInside `mailing_options`, and the same value on every recipient in the batch.\n\n| | |\n|---|---|\n| Signature Only, Batch Level | `\"affidavit\": \"sb\"` |\n| Signature Only, Letter Level | `\"affidavit\": \"sl\"` |\n| Notarized, Batch Level | `\"affidavit\": \"nb\"` |\n| Notarized, Letter Level | `\"affidavit\": \"nl\"` |\n\n### Return envelope\n\nInside `mailing_options`. May differ from one recipient to the next.\n\n| | |\n|---|---|\n| Standard return envelope | `\"return_envelope\": \"re-9npna\"` |\n| Prepaid return envelope | `\"return_envelope\": \"re-9pna\"` |\n| Prepaid and pre-addressed return envelope | `\"return_envelope\": \"re-9pa\"` |\n\n### Duplicate First-Class copy\n\nAlongside `mailing_options`, on the recipient. Certified Mail and Certified\nMail with RRE only.\n\n```json\n\"shadow\": true\n```\n\n### Same-day mailing\n\nAt the top level of the `POST /batches` body, beside `recipients`. Orders\nmeeting the applicable business-day cutoff are mailed the same business day.\n\n```json\n\"same_day_mailing\": true\n```\n\n### Certificate of Mailing and Restricted Delivery\n\nAlso at the top level of the `POST /batches` body. Choose at most one.\n\n```json\n\"certificate_of_mailing\": true\n```\n\n```json\n\"restricted_delivery\": true\n```\n\nCertificate of Mailing requires every recipient in the batch to use `fc`.\nRestricted Delivery requires every recipient to use `cm` or `cm-rre`.\n",
      "name": "Optional Mailing Configurations",
      "x-traitTag": true
    },
    {
      "description": "Create, inspect, and fulfill batches.",
      "name": "Batches"
    },
    {
      "description": "Add recipients (letters) to a batch, and check that each one is matched to an uploaded PDF.",
      "name": "Recipients"
    },
    {
      "description": "Upload letter PDFs, and read back rendered letters with tracking.",
      "name": "Letters"
    },
    {
      "description": "Wallet balance and billing history.",
      "name": "Transactions"
    },
    {
      "description": "Build a batch from a spreadsheet: check the file, see what is wrong with it, and import it.",
      "name": "Import"
    },
    {
      "description": "Depending on the mailing service and the options you selected, the API\nresponse can include:\n\n- USPS tracking numbers and tracking events\n- The final letter image\n- Return Receipt Electronic signature images\n- Affidavit of Mailing images\n- Return mail images\n- Recipient information\n- Return address information\n- Mailing status\n- Pricing and order metadata\n\nRRE images and return mail images become available only when they apply, and\nonly after the corresponding information is received from USPS. Image links\nare time-limited signed URLs \u2014 fetch them promptly rather than storing them.\n\nA letter does not carry an explicit mailing date field. The date a piece\nentered the mail stream is read from its tracking events, where the earliest\nentry is the acceptance scan.\n\n### Batch status\n\n`GET /batches` and `GET /batches/{batchId}` report the status by name; the\n`status` query filter takes the number.\n\n| Value | Status | Meaning |\n|---|---|---|\n| `0` | draft | Created but not yet fulfilled. |\n| `1` | processing | The fulfilled batch is being prepared for production. |\n| `2` | printed | The batch has been printed. |\n| `3` | mailed | The batch has been mailed. |\n| `4` | in_transit | In the postal network. |\n| `5` | delivered | Delivered. |\n| `6` | canceled | Canceled. |\n| `10` | error | Processing failed. |\n\n### Letter status\n\n`GET /batches/{batchId}/letters` returns `status` as a number on each letter.\n\n| Value | Status |\n|---|---|\n| `0` | draft |\n| `1` | processing |\n| `2` | printed |\n| `3` | mailed |\n| `4` | in_transit |\n| `5` | delivered |\n",
      "name": "Returned Data and Mailing Artifacts",
      "x-traitTag": true
    },
    {
      "description": "Checks are a separate product from standard letter mail. Everything in this\nchapter applies only to batches whose `batch_type` is `check` or\n`letter_and_check`; for `letter` batches, omit `check_options` entirely.\n\nCheck mailing must be enabled on your account.\n\n### Batch types\n\n| `batch_type` | Contents |\n|---|---|\n| `check` | Checks only. No letter PDF is required. |\n| `letter_and_check` | A check plus an accompanying letter. Each recipient still needs a `pdf_file_config` for the letter. |\n\n### Check options\n\n`check_options` sits on the recipient, beside `mailing_options`. Once it is\npresent, `bank_account_uid`, `amount`, `date` and `memo` are all required.\n\n```json\n\"check_options\": {\n  \"bank_account_uid\": \"01H8XGJWBWBAQ4H1SVX0JT2M7Z\",\n  \"amount\": \"125.00\",\n  \"date\": \"2026-08-04\",\n  \"memo\": \"Invoice 4471\",\n  \"note_1\": \"Thank you for your business\",\n  \"note_2\": \"\"\n}\n```\n\n| Field | Meaning |\n|---|---|\n| `bank_account_uid` | UID of a bank account registered on your account. It must be verified and belong to your company, otherwise the recipient is rejected. |\n| `amount` | The check amount, as a decimal string. |\n| `date` | The date printed on the check. |\n| `memo` | The check memo line. |\n| `note_1`, `note_2` | Free-text notes printed on the check stub. |\n\n**The payee is not a field.** It is taken from the recipient's `first_name`\nand `last_name`, so the recipient address doubles as the payee.\n\n### Pressure-seal checks\n\nSet `envelope_type` to `ps` for a pressure-seal check. It is valid only on a\ncheck batch, and only with `service_type: fc`.\n\n```json\n\"mailing_options\": {\n  \"envelope_type\": \"ps\",\n  \"service_type\": \"fc\"\n}\n```\n\n### What check batches ignore\n\n- `is_colored` and `is_double_sided` are forced to `false`.\n- `shadow` is ignored \u2014 there is no duplicate First-Class copy for a check.\n",
      "name": "Checks",
      "x-traitTag": true
    },
    {
      "description": "Every error uses the same envelope:\n\n```json\n{ \"is_successful\": false, \"message\": \"Batch was not found\" }\n```\n\n| Status | Meaning |\n|---|---|\n| `400` | The request was unusable, or every recipient failed validation. `POST /batches` adds `invalid_letters` keyed by the recipient's index in your array. |\n| `401` | Missing, invalid, expired or inactive `x-api-key`. |\n| `403` | Your account is not permitted to use a requested feature, such as check mailing or affidavits. |\n| `404` | No such batch. |\n| `422` | The batch is in the wrong state \u2014 most often already fulfilled \u2014 or a value was unrecognized. |\n\nSuccess responses always carry `is_successful: true`.\n",
      "name": "Errors",
      "x-traitTag": true
    }
  ],
  "x-documentation-review": {
    "confirmed": "2026-11-09",
    "runtime_tested": false,
    "pending_clarifications": [],
    "resolved_clarifications": [
      {
        "id": "P1",
        "topic": "API verification permissions",
        "status": "Resolved",
        "answer": "An explicit API address_verification_type works independently of company-level CASS/NCOA enablement. The company setting is used only when the API request omits the field. SFTP verification is controlled entirely by the company setting; there is no file field. Verification cannot be combined with international mail."
      },
      {
        "id": "P2",
        "topic": "Auto Recharge",
        "status": "Resolved",
        "answer": "Auto Recharge attempts only one reload per order. The configured reload amount must cover the entire shortage. For a $500 order with $200 in the wallet, the shortage is $300. A $100 reload is insufficient: the order is refused and the card is not charged. Raise the configured reload amount or add funds before retrying the existing order."
      },
      {
        "id": "P3",
        "topic": "FedEx SFTP phone fields",
        "status": "Resolved",
        "answer": "Use Return Phone for the sender and Recipient Phone Number for the recipient. Return Phone Number is not recognized and is silently ignored; a FedEx file missing the required sender phone is rejected. Dashes, spaces, parentheses and dots are stripped. A leading 1 or +1 is accepted; the normalized number must contain exactly 10 U.S. digits. Plain 10-digit input is recommended."
      }
    ],
    "original_review": "Complete"
  }
}