openapi: 3.1.0
info:
  title: PostalForm Projects Public API
  version: 2026-05-06
  description: Public PostalForm Projects API for customer SDKs. Includes document uploads, quotes,
    mail orders, credits, API keys, and signed customer webhooks.
servers:
  - url: https://projects.postalform.com
paths:
  /api/v1/documents/upload-intent:
    post:
      summary: Create a PDF upload intent.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                content_type:
                  type: string
                  const: application/pdf
                byte_size:
                  type: integer
                  minimum: 1
                page_count:
                  type: integer
                  minimum: 1
                  description: Optional known PDF page count. Used for routing and pricing if supplied.
              required:
                - content_type
                - byte_size
      responses:
        "200":
          description: Upload instructions.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UploadIntent"
      operationId: createUploadIntent
  /api/v1/documents/{document_id}/complete:
    post:
      summary: Mark an uploaded document complete after the object is present.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/DocumentId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                page_count:
                  type: integer
                  minimum: 1
                  description: Optional PDF page count if it was not supplied during upload-intent creation.
      responses:
        "200":
          description: Validated document.
          content:
            application/json:
              schema:
                type: object
                properties:
                  document_id:
                    type: string
                  status:
                    type: string
                    enum:
                      - validated
      operationId: completeDocumentUpload
  /api/v1/letters/quotes:
    post:
      summary: Quote a mailpiece.
      description: Quote a letter and choose provider routing from document size, country codes, mail
        class, and proof-mail settings. Country codes default to US when omitted. U.S./Canada
        standard mail prefers Lob when eligible, U.S./Canada Express routes to Click2Mail, supported
        international local-print destinations can route to PinGen, and selected PinGen destinations
        support Express via PinGen Fast. Long or ineligible standard pieces fall back to PostGrid
        where supported.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateQuoteRequest"
      responses:
        "200":
          description: Quote.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Quote"
      operationId: createLetterQuote
  /api/v1/letters:
    post:
      summary: Create a test or live mail order from a quote.
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateLetterRequest"
      responses:
        "200":
          description: Idempotent replay.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Letter"
        "201":
          description: Created order.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Letter"
      operationId: createLetter
  /api/v1/letters/{order_id}:
    get:
      summary: Retrieve a mail order, provider submissions, timeline, and customer webhook events.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/OrderId"
      responses:
        "200":
          description: Order detail.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Letter"
                  - type: object
                    properties:
                      timeline:
                        type: array
                        items:
                          $ref: "#/components/schemas/MailOrderEvent"
                      provider_submissions:
                        type: array
                        items:
                          $ref: "#/components/schemas/ProviderSubmission"
                      webhook_events:
                        type: array
                        items:
                          $ref: "#/components/schemas/WebhookEvent"
      operationId: getLetter
  /api/v1/letters/{order_id}/document.pdf:
    get:
      summary: Preview or download the PDF for a letter order.
      description: Streams the prepared PDF when available, otherwise the original uploaded PDF while
        preparation is pending. Documents follow the workspace document retention window.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/OrderId"
        - name: version
          in: query
          required: false
          schema:
            type: string
            enum:
              - current
              - original
              - prepared
            default: current
          description: current returns the prepared PDF when available and otherwise the original upload.
        - name: disposition
          in: query
          required: false
          schema:
            type: string
            enum:
              - inline
              - attachment
            default: inline
          description: Use attachment to download instead of previewing inline.
      responses:
        "200":
          description: PDF bytes.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
      operationId: getLetterDocument
  /api/v1/postcards/quotes:
    post:
      summary: Quote a postcard mailpiece.
      description: Postcards require a fully composed two-page PDF and a postcard_size of 4x6, 6x9, or
        11x6. Postcards force first-class color double-sided mail, do not support certified or
        registered mail, and do not route to Click2Mail. Lob-routed international postcards require
        postcard_size=4x6 and a U.S. sender return address; larger international postcards or
        non-U.S. return addresses route to PostGrid where available.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePostcardQuoteRequest"
      responses:
        "200":
          description: Postcard quote.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Quote"
      operationId: createPostcardQuote
  /api/v1/postcards:
    post:
      summary: Create a test or live postcard order from a postcard quote.
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateLetterRequest"
      responses:
        "200":
          description: Idempotent replay.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Letter"
        "201":
          description: Created postcard order.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Letter"
      operationId: createPostcard
  /api/v1/postcards/{order_id}:
    get:
      summary: Retrieve a postcard order, provider submissions, timeline, and customer webhook events.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/OrderId"
      responses:
        "200":
          description: Postcard order detail.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Letter"
                  - type: object
                    properties:
                      timeline:
                        type: array
                        items:
                          $ref: "#/components/schemas/MailOrderEvent"
                      provider_submissions:
                        type: array
                        items:
                          $ref: "#/components/schemas/ProviderSubmission"
                      webhook_events:
                        type: array
                        items:
                          $ref: "#/components/schemas/WebhookEvent"
      operationId: getPostcard
  /api/v1/postcards/{order_id}/document.pdf:
    get:
      summary: Preview or download the PDF for a postcard order.
      description: Streams the prepared postcard PDF when available, otherwise the original uploaded PDF
        while preparation is pending. Documents follow the workspace document retention window.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/OrderId"
        - name: version
          in: query
          required: false
          schema:
            type: string
            enum:
              - current
              - original
              - prepared
            default: current
          description: current returns the prepared PDF when available and otherwise the original upload.
        - name: disposition
          in: query
          required: false
          schema:
            type: string
            enum:
              - inline
              - attachment
            default: inline
          description: Use attachment to download instead of previewing inline.
      responses:
        "200":
          description: PDF bytes.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
      operationId: getPostcardDocument
  /api/v1/webhook-endpoints:
    get:
      summary: List customer webhook endpoints for the workspace.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Endpoints.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookEndpoint"
      operationId: listWebhookEndpoints
    post:
      summary: Configure a customer webhook endpoint for status events.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                  pattern: ^https://
                  description: HTTPS endpoint that receives signed PostalForm status webhooks.
                signing_secret:
                  type: string
                  description: Optional. Generated if omitted.
              required:
                - url
      responses:
        "200":
          description: Created endpoint.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpointSecretResponse"
      operationId: createWebhookEndpoint
  /api/v1/webhook-endpoints/{endpoint_id}:
    delete:
      summary: Disable a customer webhook endpoint.
      security:
        - bearerAuth: []
      parameters:
        - name: endpoint_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Disabled endpoint.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpoint"
      operationId: disableWebhookEndpoint
  /api/v1/webhook-endpoints/{endpoint_id}/rotate-secret:
    post:
      summary: Rotate a customer webhook endpoint signing secret.
      description: Returns the new signing secret once. Store it securely; list endpoints does not expose
        secrets.
      security:
        - bearerAuth: []
      parameters:
        - name: endpoint_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Rotated endpoint signing secret.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpointSecretResponse"
      operationId: rotateWebhookEndpointSecret
  /api/v1/webhook-events:
    get:
      summary: List customer webhook events for the workspace.
      description: Customer webhook events are provider status-change events only. Event names use
        `postalform.{letter|postcard}.{status}` and are emitted for `accepted_by_provider`,
        `in_transit`, `delivered`, `returned`, `failed`, and `canceled`.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Events.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookEvent"
      operationId: listWebhookEvents
  /api/v1/webhook-events/{event_id}/replay:
    post:
      summary: Queue a customer webhook event for replay.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/EventId"
      responses:
        "200":
          description: Replay queued.
      operationId: replayWebhookEvent
  /api/v1/credits/balance:
    get:
      summary: Get prepaid credit balance.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Balance.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditBalance"
      operationId: getCreditBalance
  /api/v1/credits/payment-methods:
    get:
      summary: List saved live billing payment methods.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Saved payment methods for live auto-refill.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/BillingPaymentMethod"
      operationId: listPaymentMethods
  /api/v1/credits/payment-methods/setup-session:
    post:
      summary: Create a Checkout setup session for live credit auto-refill.
      description: Requires a live API key. The returned Checkout URL lets the customer save a payment
        method for future off-session auto-refill charges.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Checkout setup session URL.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentMethodSetupSession"
      operationId: createPaymentMethodSetupSession
  /api/v1/credits/auto-refill:
    get:
      summary: Get credit auto-refill threshold policies.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Auto-refill policies for test and live credit balances.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditAutoRefillPolicies"
      operationId: getCreditAutoRefillPolicies
    post:
      summary: Configure the credit auto-refill threshold for the authenticated key mode.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreditAutoRefillRequest"
      responses:
        "200":
          description: Updated auto-refill policy.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CreditAutoRefillPolicy"
      operationId: configureCreditAutoRefill
  /api/v1/credits/ledger:
    get:
      summary: List prepaid credit ledger entries.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Ledger entries.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/CreditLedgerEntry"
      operationId: listCreditLedger
  /api/v1/api-keys:
    get:
      summary: List API key prefixes and status.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: API keys.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKey"
      operationId: listApiKeys
  /api/v1/api-keys/rotate:
    post:
      summary: Rotate a test or live API key.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                mode:
                  $ref: "#/components/schemas/Mode"
              required:
                - mode
      responses:
        "200":
          description: New API key secret. Returned once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyRotation"
      operationId: rotateApiKey
  /api/v1/credits/checkout-session:
    post:
      summary: Create a prepaid credit top-up checkout session.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                amount_cents:
                  type: integer
                  minimum: 1
      responses:
        "200":
          description: Stripe Checkout URL in configured environments, or a local checkout stub URL for local
            development.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditCheckoutSession"
      operationId: createCreditCheckoutSession
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  schemas:
    UploadIntent:
      type: object
      properties:
        document_id:
          type: string
        upload_url:
          type: string
          format: uri
        upload_method:
          type: string
          enum:
            - PUT
        expires_at:
          type: string
          format: date-time
    CreateQuoteRequest:
      type: object
      description: Letter quote request. Provider selection is automatic; provider_strategy is not
        supported on public API requests. PostGrid fallback quotes from Canada to the Netherlands
        include a sheet-count postage guard when PinGen cannot accept the letter.
      properties:
        document_id:
          type: string
        page_count:
          type: integer
          minimum: 1
          description: Optional explicit PDF page count. Used for provider routing and pricing when present.
        mail_class:
          type: string
          default: usps_first_class
          description: Standard/USPS First Class by default. Accepts standard/usps_first_class,
            priority/usps_priority, and express/usps_express. Priority/Express routes through
            Click2Mail for U.S./Canada destinations and through PinGen Fast for eligible AT, CH, DE,
            GB, IN, and NL local-print destinations. Priority/Express cannot be combined with
            certified or registered proof mail.
        color:
          type: boolean
          default: false
        double_sided:
          type: boolean
          default: true
        certified:
          type: boolean
          default: false
          description: Proof-mail add-on for letters only. Uses USPS Certified Mail for eligible U.S. standard
            letters and PinGen registered mail for eligible Belgium, Switzerland, and France
            standard letters.
        certified_return_receipt:
          type: boolean
          default: false
          description: Electronic return receipt for eligible U.S. Certified Mail letters.
        signature_required:
          type: boolean
          default: false
          description: Signature confirmation for eligible U.S. Priority and Express letters.
        destination_country_code:
          type: string
          minLength: 2
          maxLength: 2
          default: US
          description: ISO 3166-1 alpha-2 destination country code used for provider routing. Defaults to US.
            First-party Projects destinations are US, CA, AT, BE, CH, DE, FR, GB, IN, LU, and NL.
            Other destinations may fall back to PostGrid where available.
        origin_country_code:
          type: string
          minLength: 2
          maxLength: 2
          default: US
          description: ISO 3166-1 alpha-2 sender/origin country code used for provider routing. Defaults to US.
      required:
        - document_id
    Quote:
      type: object
      properties:
        quote_id:
          type: string
        mailpiece_type:
          type: string
          enum:
            - letter
            - postcard
        postcard_size:
          type:
            - string
            - "null"
          enum:
            - 4x6
            - 6x9
            - 11x6
            - null
        price_cents:
          type: integer
        currency:
          type: string
          enum:
            - usd
        pricing_version:
          type: string
        certified_return_receipt:
          type: boolean
        signature_required:
          type: boolean
        expires_at:
          type: string
          format: date-time
    CreateLetterRequest:
      type: object
      description: Create an order from a quote. Requires Idempotency-Key header. Sender is optional for
        some providers but required for PinGen-routed international mail; include sender for all
        international sends.
      properties:
        quote_id:
          type: string
        recipient:
          $ref: "#/components/schemas/MailingAddress"
        sender:
          $ref: "#/components/schemas/MailingAddress"
        metadata:
          type: object
          description: Optional caller metadata stored on the order and surfaced in reads/webhook payloads.
          additionalProperties: true
      required:
        - quote_id
        - recipient
    Letter:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
          enum:
            - letter
            - postcard
        mailpiece_type:
          type: string
          enum:
            - letter
            - postcard
        postcard_size:
          type:
            - string
            - "null"
          enum:
            - 4x6
            - 6x9
            - 11x6
            - null
        status:
          $ref: "#/components/schemas/MailOrderStatus"
        mode:
          $ref: "#/components/schemas/Mode"
        price_cents:
          type: integer
        currency:
          type: string
        funds_status:
          type: string
        billing_rail:
          type: string
        selected_provider:
          type: string
          description: Provider chosen by quote routing, using mock_* provider names for test keys.
        idempotent_replay:
          type: boolean
    MailOrderEvent:
      type: object
      properties:
        id:
          type: string
        event_type:
          type: string
        status_before:
          type: string
        status_after:
          type: string
        source:
          type: string
        provider:
          type: string
        provider_letter_id:
          type: string
        created_at:
          type: string
          format: date-time
    ProviderSubmission:
      type: object
      properties:
        id:
          type: string
        provider:
          type: string
        provider_letter_id:
          type: string
        provider_status:
          type: string
        provider_status_normalized:
          type: string
        tracking_number:
          type: string
        raw_response:
          type: object
          additionalProperties: true
    WebhookEvent:
      type: object
      properties:
        id:
          type: string
        workspaceId:
          type: string
        orderId:
          type: string
        eventType:
          $ref: "#/components/schemas/CustomerWebhookEventType"
        payload:
          $ref: "#/components/schemas/CustomerWebhookPayload"
        createdAt:
          type: string
          format: date-time
        deliveries:
          type: array
          items:
            $ref: "#/components/schemas/WebhookDeliveryAttempt"
    CreatePostcardQuoteRequest:
      type: object
      description: Postcard quote request. Postcards force first-class color double-sided mail and cannot
        use certified or registered mail. Lob-routed international postcards require
        postcard_size=4x6 and origin_country_code=US; larger international postcards or non-U.S.
        return addresses route to PostGrid where available.
      properties:
        document_id:
          type: string
        page_count:
          type: integer
          minimum: 1
          description: Optional explicit PDF page count. Postcard source PDFs should be fully composed.
        postcard_size:
          type: string
          enum:
            - 4x6
            - 6x9
            - 11x6
        destination_country_code:
          type: string
          minLength: 2
          maxLength: 2
          default: US
          description: ISO 3166-1 alpha-2 destination country code used for provider routing. Defaults to US.
        origin_country_code:
          type: string
          minLength: 2
          maxLength: 2
          default: US
          description: ISO 3166-1 alpha-2 sender/origin country code used for provider routing. Defaults to US.
      required:
        - document_id
        - postcard_size
    WebhookEndpoint:
      type: object
      properties:
        id:
          type: string
        url:
          type: string
          format: uri
          pattern: ^https://
        status:
          type: string
        created_at:
          type: string
          format: date-time
    WebhookEndpointSecretResponse:
      type: object
      properties:
        id:
          type: string
        url:
          type: string
          format: uri
          pattern: ^https://
        status:
          type: string
        created_at:
          type: string
          format: date-time
        signing_secret:
          type: string
          description: Returned only when the endpoint is created or its signing secret is rotated.
    CreditBalance:
      type: object
      properties:
        mode:
          type: string
          enum:
            - test
            - live
        billing_rail:
          type: string
          enum:
            - mock_test_credits
            - prepaid_credits
            - future_projects_spt
            - future_metronome_streaming
            - future_mpp_tempo
            - manual_invoice
        available_cents:
          type: integer
        reserved_cents:
          type: integer
        currency:
          type: string
        status:
          type: string
        test:
          $ref: "#/components/schemas/CreditBalanceSnapshot"
        live:
          $ref: "#/components/schemas/CreditBalanceSnapshot"
        auto_refill:
          $ref: "#/components/schemas/CreditAutoRefillPolicies"
    BillingPaymentMethod:
      type: object
      properties:
        id:
          type: string
        provider:
          type: string
          enum:
            - stripe
        type:
          type: string
        brand:
          type: string
        last4:
          type: string
        exp_month:
          type: integer
        exp_year:
          type: integer
        status:
          type: string
          enum:
            - active
            - disabled
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        last_used_at:
          type: string
          format: date-time
    PaymentMethodSetupSession:
      type: object
      properties:
        mode:
          type: string
          enum:
            - stripe_checkout
            - local_checkout_stub
        checkout_url:
          type: string
          format: uri
    CreditAutoRefillPolicies:
      type: object
      properties:
        data:
          type: object
          properties:
            test:
              anyOf:
                - $ref: "#/components/schemas/CreditAutoRefillPolicy"
                - type: "null"
            live:
              anyOf:
                - $ref: "#/components/schemas/CreditAutoRefillPolicy"
                - type: "null"
        test:
          anyOf:
            - $ref: "#/components/schemas/CreditAutoRefillPolicy"
            - type: "null"
        live:
          anyOf:
            - $ref: "#/components/schemas/CreditAutoRefillPolicy"
            - type: "null"
    CreditAutoRefillRequest:
      type: object
      properties:
        mode:
          $ref: "#/components/schemas/Mode"
        enabled:
          type: boolean
          default: true
        threshold_cents:
          type: integer
          minimum: 100
          maximum: 100000
        refill_amount_cents:
          type: integer
          minimum: 100
          maximum: 100000
      required:
        - threshold_cents
        - refill_amount_cents
    CreditAutoRefillPolicy:
      type: object
      properties:
        id:
          type: string
        workspace_id:
          type: string
        mode:
          $ref: "#/components/schemas/Mode"
        billing_rail:
          type: string
          enum:
            - mock_test_credits
            - prepaid_credits
        enabled:
          type: boolean
        threshold_cents:
          type: integer
        refill_amount_cents:
          type: integer
        provider:
          type: string
          enum:
            - test_grant
            - stripe_checkout
            - local_checkout_stub
        status:
          type: string
          enum:
            - active
            - disabled
            - payment_action_required
            - payment_pending
            - payment_failed
        last_triggered_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CreditLedgerEntry:
      type: object
      properties:
        id:
          type: string
        workspace_id:
          type: string
        credit_account_id:
          type: string
        order_id:
          type: string
        mode:
          type: string
          enum:
            - test
            - live
        billing_rail:
          type: string
          enum:
            - mock_test_credits
            - prepaid_credits
            - future_projects_spt
            - future_metronome_streaming
            - future_mpp_tempo
            - manual_invoice
        type:
          type: string
          enum:
            - test_grant
            - topup
            - reserve
            - capture
            - release
            - refund
            - adjustment
        amount_cents:
          type: integer
        currency:
          type: string
        external_reference:
          type: string
        metadata:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
    ApiKey:
      type: object
      properties:
        id:
          type: string
        mode:
          $ref: "#/components/schemas/Mode"
        prefix:
          type: string
        status:
          type: string
          enum:
            - active
            - disabled
            - revoked
        created_at:
          type: string
          format: date-time
        last_used_at:
          type: string
          format: date-time
    Mode:
      type: string
      enum:
        - test
        - live
    ApiKeyRotation:
      type: object
      properties:
        mode:
          $ref: "#/components/schemas/Mode"
        api_key:
          type: string
          description: Full API key secret. Returned once.
        api_key_prefix:
          type: string
    CreditCheckoutSession:
      type: object
      properties:
        id:
          type: string
          description: PostalForm top-up ID.
        mode:
          type: string
          enum:
            - stripe_checkout
            - local_checkout_stub
        checkout_url:
          type: string
          format: uri
        amount_cents:
          type: integer
        currency:
          type: string
    MailingAddress:
      type: object
      description: Flexible mailing address object. Pass countryCode, country_code, country,
        address_country, or addressCountry for international destinations; country defaults to US
        when omitted.
      additionalProperties: true
      properties:
        name:
          type: string
        company:
          type: string
        line1:
          type: string
          description: Also accepts street1, address_line1, or addressLine1.
        line2:
          type: string
          description: Also accepts street2, address_line2, or addressLine2.
        city:
          type: string
          description: Also accepts address_city or addressCity.
        state:
          type: string
          description: Also accepts province, provinceOrState, address_state, or addressState.
        postal_code:
          type: string
          description: Also accepts postalCode, postalOrZip, zip, address_zip, or addressZip.
        countryCode:
          type: string
          minLength: 2
          maxLength: 2
          default: US
          description: ISO 3166-1 alpha-2 country code. Defaults to US when omitted.
    MailOrderStatus:
      type: string
      enum:
        - queued
        - document_preparing
        - document_prepared
        - submitted_to_provider
        - accepted_by_provider
        - in_transit
        - delivered
        - returned
        - submission_pending
        - failed
        - canceled
      description: Order timeline status. `submission_pending` means the provider-submit job entered a
        physical-mail safety window where PostalForm cannot blindly retry without risking duplicate
        mail; it requires reconciliation if it remains the current order status.
    CustomerWebhookEventType:
      type: string
      description: Customer webhook event names emitted for provider status changes. Replace `letter` with
        `postcard` for postcard mailpieces.
      enum:
        - postalform.letter.accepted_by_provider
        - postalform.letter.in_transit
        - postalform.letter.delivered
        - postalform.letter.returned
        - postalform.letter.failed
        - postalform.letter.canceled
        - postalform.postcard.accepted_by_provider
        - postalform.postcard.in_transit
        - postalform.postcard.delivered
        - postalform.postcard.returned
        - postalform.postcard.failed
        - postalform.postcard.canceled
    CustomerWebhookPayload:
      type: object
      description: JSON body POSTed to customer webhook endpoints.
      properties:
        id:
          type: string
          example: evt_123
        type:
          $ref: "#/components/schemas/CustomerWebhookEventType"
        data:
          type: object
          properties:
            object:
              $ref: "#/components/schemas/Letter"
        provider:
          type: object
          properties:
            name:
              $ref: "#/components/schemas/Provider"
            provider_letter_id:
              type: string
            status_raw:
              type: string
              nullable: true
            status_normalized:
              type: string
              nullable: true
            imb_status:
              type: string
              nullable: true
            tracking_number:
              type: string
              nullable: true
            tracking_status:
              type: string
              nullable: true
    WebhookDeliveryAttempt:
      type: object
      properties:
        id:
          type: string
        webhookEventId:
          type: string
        endpointId:
          type: string
        attemptNumber:
          type: integer
        status:
          type: string
          enum:
            - succeeded
            - failed
        httpStatus:
          type: integer
        responseBodySnippet:
          type: string
        attemptedAt:
          type: string
          format: date-time
        nextRetryAt:
          type: string
          format: date-time
    CreditBalanceSnapshot:
      type: object
      properties:
        available_cents:
          type: integer
        reserved_cents:
          type: integer
        currency:
          type: string
        status:
          type: string
    Provider:
      type: string
      enum:
        - click2mail
        - lob
        - postgrid
        - pingen
        - mock_click2mail
        - mock_lob
        - mock_postgrid
        - mock_pingen
      description: Provider selected for fulfillment. Test keys return mock_* providers; live keys use the
        real provider names.
  parameters:
    DocumentId:
      name: document_id
      in: path
      required: true
      schema:
        type: string
    OrderId:
      name: order_id
      in: path
      required: true
      schema:
        type: string
    EventId:
      name: event_id
      in: path
      required: true
      schema:
        type: string
