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
The unique identifier of the invoice.
Query Parameters
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.
Call GET /api/auth/media/customer with a valid Bearer token. The endpoint returns a single-field JSON object:
{
"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.
// 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:
// 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
The unique identifier of the invoice.
Query Parameters
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.
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:
// 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:
-
My Invoices section (authenticated customers) – constructs the
pdf URL using the media JWT.
- File:
src/views/user/activity/invoices/MyInvoicesSection.tsx
-
Invoice basket summary – shows a download link during checkout.
- File:
src/views/checkout/components/InvoiceBasketSummary.tsx
-
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
// 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
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 above for full usage details.
Error Responses
The current user is not authenticated, or the media JWT or invoice token is invalid or expired.
Invoice with the given ID does not exist or is not accessible to the caller.