Skip to main content
GET
/
api
/
public
/
billing
/
invoices
/
{invoiceId}
/
pdf
{
  "401 Unauthorized": {},
  "404 Not Found": {}
}

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:
VariantURLAuth
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

invoiceId
number
required
The unique identifier of the invoice.

Query Parameters

t
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.

Obtaining the Media JWT

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.

Example: fetching and using the media JWT

// 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

invoiceId
number
required
The unique identifier of the invoice.

Query Parameters

token
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.

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:
  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

// 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

401 Unauthorized
error
The current user is not authenticated, or the media JWT or invoice token is invalid or expired.
404 Not Found
error
Invoice with the given ID does not exist or is not accessible to the caller.