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

# Download invoice PDF

> On success, returns a `302` redirect to the PDF URL — **no JSON envelope
on the 302 itself**, this is the one endpoint that writes a raw HTTP
response instead of the standard envelope. Error responses are still the
standard JSON envelope.

Resolution precedence, in order: (1) a previously-stored S3 copy of the
invoice PDF; (2) if a Stripe invoice PDF and invoice id are both on
file, persist it to S3 now and use that URL, falling back to (3) the
raw Stripe invoice PDF URL if that persist attempt fails; (4) the
Stripe receipt URL; (5) the raw Stripe invoice PDF URL alone (no
invoice id on file). The Stripe fallback URLs are temporary/time-limited
by Stripe.




## OpenAPI

````yaml /openapi.yaml get /api/v1.1/invoices/{id}/pdf
openapi: 3.1.0
info:
  title: Spenza Partner API
  version: 3.0.0
  summary: >-
    Partner API for SIMs, eSIMs, plans, billing, users, groups and numbers —
    v1.1 surface.
  description: >
    The **Spenza Partner API** lets you manage SIMs, eSIMs, plans,
    subscriptions,

    billing, users, phone numbers, teams and device/SIM/user groups
    programmatically.


    All endpoints follow one consistent standard:


    - **Auth** — exchange your API `key` + `secret` for a bearer token, then
    send
      `Authorization: Bearer <token>` on every request.
    - **Envelopes** — success responses are `{ success, data, meta? }`; errors
    are
      `{ success: false, error: { code, message, details? } }`.
    - **Pagination** — list endpoints accept `page` (1-indexed) and `pageSize`
      (max 100); totals are returned in `meta`.
    - **Idempotency** — many write endpoints honor an optional `Idempotency-Key`
      request header so a retried call replays the original result instead of
      repeating the side effect.
    - **Async operations** — a handful of endpoints (eSIM provisioning, port-in,
      the `/api/v3/...` purchase variants, top-up) return `202` (`201` for
      top-up) with a `transactionId` + `statusEndpoint` instead of the finished
      resource. Poll
      `GET /api/v3/transactions/{transactionId}` (no auth required) until the
      transaction reaches a terminal status.

    One resource — **port-in submission** — is a documented exception: it lives
    at

    `POST /api/v3/port-in`, requires an Admin-tier account role rather than just
    a

    valid bearer token, and is **not** on the standard envelope (see that
    operation's

    description for its exact response shape).
  contact:
    name: Spenza API Support
    email: support@spenza.com
    url: https://spenza.com
  license:
    name: Proprietary
    url: https://spenza.com/terms
  x-logo:
    url: https://spenza.com/logo.png
    altText: Spenza
servers:
  - url: https://api.spenza.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Authentication
    description: Exchange API credentials for a bearer token.
  - name: SIMs
    description: List, inspect, assign and manage SIMs.
  - name: Subscriptions
    description: Plan subscriptions attached to SIMs.
  - name: Plans & Catalog
    description: Purchasable plans and SIM products.
  - name: eSIM
    description: Provision eSIMs and fetch activation QR / install status.
  - name: Billing
    description: Invoices, credit balance and top-ups.
  - name: Users
    description: End users (employees) on your account that SIMs/devices are assigned to.
  - name: Orders & Transactions
    description: Order history and financial transactions.
  - name: Numbers & Port-in
    description: Check port-in eligibility and port existing numbers into Spenza.
  - name: Messaging
    description: Send outbound SMS from a provisioned number.
  - name: Webhooks
    description: Register endpoints for operator SMS/voice events and inspect deliveries.
  - name: Notifications
    description: In-account notifications and delivery preferences.
  - name: Device Groups
    description: Organize devices into named groups with spend/data limits.
  - name: SIM Groups
    description: Organize SIMs into named groups with spend/data limits.
  - name: User Groups
    description: Organize users (departments) into named groups with spend/data limits.
  - name: Devices
    description: The device catalog (phones/tablets) and their assignment to users.
  - name: Team Members
    description: Admins who manage your Spenza account (distinct from Users/end-users).
  - name: Async Status
    description: Universal status poll for any asynchronous (v3) operation.
paths:
  /api/v1.1/invoices/{id}/pdf:
    get:
      tags:
        - Billing
      summary: Download invoice PDF
      description: |
        On success, returns a `302` redirect to the PDF URL — **no JSON envelope
        on the 302 itself**, this is the one endpoint that writes a raw HTTP
        response instead of the standard envelope. Error responses are still the
        standard JSON envelope.

        Resolution precedence, in order: (1) a previously-stored S3 copy of the
        invoice PDF; (2) if a Stripe invoice PDF and invoice id are both on
        file, persist it to S3 now and use that URL, falling back to (3) the
        raw Stripe invoice PDF URL if that persist attempt fails; (4) the
        Stripe receipt URL; (5) the raw Stripe invoice PDF URL alone (no
        invoice id on file). The Stripe fallback URLs are temporary/time-limited
        by Stripe.
      operationId: getInvoicePdf
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: 64f1a2b3c4d5e6f7a8b9c0d1
      responses:
        '302':
          description: Redirect to the PDF URL (Location header). No response body.
        '404':
          description: Invoice not found, or no downloadable copy exists.
          content:
            application/json:
              examples:
                notFound:
                  value:
                    success: false
                    error:
                      code: INVOICE_NOT_FOUND
                      message: We couldn't find that invoice.
                noPdf:
                  value:
                    success: false
                    error:
                      code: INVOICE_NOT_FOUND
                      message: We couldn't find a downloadable copy of that invoice.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Bearer token obtained from `POST /api/v1.1/auth/token`.

````