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

# Download Invoice PDF

# Download Invoice PDF

Returns the PDF file for a given invoice. Two variants are available depending on whether the request is made by an authenticated customer or a guest using a one-time token:

| Variant                | URL                                                                             | Auth                     |
| ---------------------- | ------------------------------------------------------------------------------- | ------------------------ |
| Authenticated customer | `/api/public/billing/invoices/{invoiceId}/pdf?t={mediaJwt}`                     | Bearer token + media JWT |
| Guest / token-based    | `/api/public/billing/invoices/{invoiceId}/pdfByToken?token={basketSessionGuid}` | Basket session GUID only |

## Authenticated Variant

### Path Parameters

<ParamField path="invoiceId" type="number" required>
  The unique identifier of the invoice.
</ParamField>

### Query Parameters

<ParamField query="t" type="string" required>
  A short-lived media JWT obtained from `GET /api/auth/media/customer`. This token authorises temporary access to the binary file. Pass the `jwt`
  field from the response object directly as this query parameter value.
</ParamField>

### Obtaining the Media JWT

Call `GET /api/auth/media/customer` with a valid Bearer token. The endpoint returns a single-field JSON object:

```json theme={null}
{
  "jwt": "<short-lived-token>"
}
```

Key properties of this token:

* **Short-lived** – expires after approximately **60 seconds**. In the portal, the token is cached and automatically refreshed every **50 seconds** to ensure it is always valid when a download link is constructed.
* **Scoped** – it only grants access to protected binary/media resources and cannot be used in place of a regular API Bearer token.
* **One-time style** – a fresh token should be fetched for each user session; do not persist it across sessions.

#### Example: fetching and using the media JWT

```ts theme={null}
// 1. Fetch a media JWT (requires a valid Bearer token in the Authorization header)
const { jwt } = await fetch('/api/auth/media/customer', {
  headers: { Authorization: `Bearer ${customerBearerToken}` },
}).then((r) => r.json())

// 2. Build the PDF URL using the jwt field
const pdfUrl = `https://${invoice.BusinessWebAddress}${config.publicBaseUrl}/api/public/billing/invoices/${invoice.Id}/pdf?t=${jwt}`

// 3. Open directly — no additional fetch needed
window.open(pdfUrl, '_blank')
```

In the portal this is handled by the `withMediaJwt` helper (`src/states/withMediaJwt.ts`), which wraps `endpoints.system.mediaToken` (`/api/auth/media/customer`) and keeps the token fresh:

```ts theme={null}
// src/states/withMediaJwt.ts
const { resource: mediaJwt } = useData<JwtMedia>(httpClient, endpoints.system.mediaToken, {
  queryConfig: { refetchInterval: 50 * 1000, staleTime: 50 * 1000 },
})
// mediaJwt.jwt is then passed to endpoints.billing.invoices.pdf(invoiceId, mediaJwt)
```

## Guest / Token Variant (`pdfByToken`)

### Path Parameters

<ParamField path="invoiceId" type="number" required>
  The unique identifier of the invoice.
</ParamField>

### Query Parameters

<ParamField query="token" type="string" required>
  The **basket session GUID** associated with the completed checkout. This is the payment-gateway session ID (e.g. the Stripe, Spreedly, or PayPal
  session ID) that was used to process the payment. It is available as `stripe_session_id`, `spreedly_session_id`, or `paypal_session_id` in the
  checkout completion URL and is passed directly to this endpoint — no authenticated session is required.
</ParamField>

### How the basket session GUID is obtained

When a guest checkout completes, the payment gateway redirects back to the portal's completion page with the session ID as a URL query parameter:

```
/en/invoices/complete?stripe_session_id=<guid>
/en/invoices/complete?spreedly_session_id=<guid>
/en/invoices/complete?paypal_session_id=<guid>
```

The portal reads one of those parameters and uses it as the `token` for both `pdfByToken` and `registerByToken`:

```ts theme={null}
// src/views/public/checkout/complete/index.tsx
const stripeSessionId = searchParams.get('stripe_session_id')   // GUID
const spreedlySessionId = searchParams.get('spreedly_session_id') // GUID
const paypalSessionId = searchParams.get('paypal_session_id')   // GUID

// whichever is present is passed straight through as the token
<CompleteContents token={stripeSessionId} invoice={status.invoice!} ... />

// and then used to build the PDF link:
const pdfUrl = `https://${invoice.BusinessWebAddress}${config.publicBaseUrl}${endpoints.billing.invoices.pdfByToken(invoice.Id, token)}`
```

## Response

Both variants return the raw PDF binary (`application/pdf`). The portal constructs a full URL and opens it in a new browser tab or as a direct link rather than fetching it through the API client.

## Usage in Portal

These endpoints are used to generate PDF download links in two contexts:

1. **My Invoices section** (authenticated customers) – constructs the `pdf` URL using the media JWT.

   * File: `src/views/user/activity/invoices/MyInvoicesSection.tsx`

2. **Invoice basket summary** – shows a download link during checkout.

   * File: `src/views/checkout/components/InvoiceBasketSummary.tsx`

3. **Guest checkout complete page** – uses `pdfByToken` with the payment-gateway basket session GUID to allow PDF download without a session.
   * File: `src/views/public/checkout/complete/index.tsx`

### Typical integration pattern

```ts theme={null}
// Authenticated PDF URL construction
const pdfUrl = `https://${invoice.BusinessWebAddress}${config.publicBaseUrl}${endpoints.billing.invoices.pdf(invoice.Id, mediaJwt)}`

// Guest PDF URL construction — token is the basket session GUID (e.g. stripe_session_id / spreedly_session_id / paypal_session_id)
const pdfUrl = `https://${invoice.BusinessWebAddress}${config.publicBaseUrl}${endpoints.billing.invoices.pdfByToken(invoice.Id, basketSessionGuid)}`

// Then open or link to pdfUrl directly — no fetch required
```

## Related Endpoints

* `GET /api/public/billing/invoices/{invoiceId}` – Get full invoice details
* `GET /api/auth/media/customer` – Obtain a short-lived media JWT (`{ jwt: string }`) for authenticated downloads. See [Obtaining the Media JWT](#obtaining-the-media-jwt) above for full usage details.

## Error Responses

<ResponseField name="401 Unauthorized" type="error">
  The current user is not authenticated, or the media JWT or invoice token is invalid or expired.
</ResponseField>

<ResponseField name="404 Not Found" type="error">
  Invoice with the given ID does not exist or is not accessible to the caller.
</ResponseField>
