> ## Documentation Index
> Fetch the complete documentation index at: https://paperplane-justin-winter-s-projects.vercel.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Price a letter before sending it

> Free — creates nothing and never touches payment. The price is all-in (postage, printing and the envelope included); `breakdown` lists the piece and any options that make up the total. Quoting first is the recommended shape for agents: it is the safe call, and it tells the user what a send will cost.

Supply `to` plus `text` (or `pdf_url`) and the response also carries a single-use `confirmation_token` bound to that exact letter and price. `POST /v1/orders` requires one on the agent path, so this is where a send begins.



## OpenAPI

````yaml /openapi.json post /v1/quotes
openapi: 3.1.0
info:
  title: paperplane API
  version: 1.0.0
  summary: Send real physical mail from code.
  description: >-
    Upload a PDF or paste text; we print it and hand it to USPS the next
    business day. No account required — pay per letter with a Stripe link or a
    prepaid credit code, and use `sandbox: true` to exercise the whole flow for
    free.


    Every failure returns the same envelope: `status`, a stable machine-readable
    `code`, a human `reason`, and a `next` array of concrete recovery steps.


    This document is generated from the same zod schemas the server validates
    with, so it cannot describe an endpoint that does not exist.
  contact:
    name: paperplane support
    url: https://sendpaperplane.com/contact
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
  termsOfService: https://sendpaperplane.com/terms
servers:
  - url: https://sendpaperplane.com
    description: Production
security: []
tags:
  - name: Mail
    description: Quote, send, track, and cancel letters.
  - name: Credits
    description: Prepaid balances that pay for letters without a card.
  - name: Reviews
    description: Post-delivery feedback. Identity is derived from the order, not supplied.
  - name: Addresses
    description: >-
      Typeahead and reverse geocoding. Convenience only; USPS verification is
      authoritative.
  - name: Meta
    description: Machine-readable description of this API.
paths:
  /v1/quotes:
    post:
      tags:
        - Mail
      summary: Price a letter before sending it
      description: >-
        Free — creates nothing and never touches payment. The price is all-in
        (postage, printing and the envelope included); `breakdown` lists the
        piece and any options that make up the total. Quoting first is the
        recommended shape for agents: it is the safe call, and it tells the user
        what a send will cost.


        Supply `to` plus `text` (or `pdf_url`) and the response also carries a
        single-use `confirmation_token` bound to that exact letter and price.
        `POST /v1/orders` requires one on the agent path, so this is where a
        send begins.
      operationId: createQuote
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteRequest'
            example:
              mail_class: first_class
              page_count: 1
              color: false
              tracking: false
      responses:
        '200':
          description: >-
            Priced quote. `confirmation_token`, `expires_in_minutes`, and `next`
            appear only when `to` and content were supplied.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuoteResponse'
              example:
                status: ok
                total_cents: 199
                total: $1.99
                breakdown:
                  - id: first_class_letter_1_page
                    label: First-Class letter, 1 page
                    amount_cents: 199
                note: >-
                  All-in price. Postage, printing and the envelope are included;
                  the breakdown lists what makes up the total.
        '400':
          description: >-
            Invalid request, or the letter violates a pricing rule (page limit,
            tracking on a class that already includes it).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: >-
            `invalid_key` — an Authorization header was sent and it did not
            verify. A presented credential is never silently downgraded to
            anonymous.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: >-
            `key_disabled`, or `insufficient_scope` when the key is valid but
            does not carry the scope this route needs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: >-
            `key_store_unavailable` — the key could not be checked, so it is not
            honoured. Retry, or omit the header to call anonymously.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - {}
        - bearerAuth: []
components:
  schemas:
    QuoteRequest:
      type: object
      properties:
        mail_class:
          $ref: '#/components/schemas/MailClass'
        page_count:
          type: integer
          minimum: 1
          maximum: 12
        color:
          type: boolean
        tracking:
          type: boolean
        to:
          $ref: '#/components/schemas/Address'
        text:
          type: string
          maxLength: 50000
        upload_key:
          type: string
          pattern: ^up_[0-9a-f]{32}\.pdf$
        pdf_url:
          type: string
          format: uri
      required:
        - mail_class
        - page_count
    QuoteResponse:
      type: object
      properties:
        status:
          type: string
          const: ok
        total_cents:
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        total:
          type: string
        breakdown:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              label:
                type: string
              amount_cents:
                type: integer
                minimum: -9007199254740991
                maximum: 9007199254740991
            required:
              - id
              - label
              - amount_cents
        note:
          type: string
        confirmation_token:
          type: string
        expires_in_minutes:
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
        next:
          type: array
          items:
            type: string
      required:
        - status
        - total_cents
        - total
        - breakdown
        - note
    Error:
      type: object
      properties:
        status:
          type: string
          enum:
            - failed
            - action_required
        code:
          type: string
        reason:
          type: string
        next:
          type: array
          items:
            type: string
      required:
        - status
        - code
        - reason
        - next
    MailClass:
      type: string
      enum:
        - first_class
        - certified
        - certified_err
        - priority
    Address:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 80
        line1:
          type: string
          minLength: 1
          maxLength: 120
        line2:
          type: string
          maxLength: 120
        city:
          type: string
          minLength: 1
          maxLength: 80
        state:
          type: string
          minLength: 2
          maxLength: 2
        zip:
          type: string
          pattern: ^\d{5}(-\d{4})?$
        country:
          type: string
          const: US
      required:
        - name
        - line1
        - city
        - state
        - zip
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Optional developer API key: `Authorization: Bearer pp_live_…` (or
        `pp_test_…` for a sandbox-only key). Omitting it is a supported way to
        call every route. A key that is sent and does not verify is refused with
        401 rather than treated as anonymous, so a typo fails loudly instead of
        quietly losing the restriction you meant to apply. Enabled per
        deployment via `SCOPED_API_KEYS`.

````