Fetching financial document PDFs via the AR-API
This guide explains how to retrieve issued financial documents for a booking and download their PDFs using the Accounts Receivable (AR) API. It applies to any integrator building a resident-facing portal or back-office integration.
Document families
Section titled “Document families”The AR-API exposes every issued financial document. Documents belong to six families. Five have their
own list query, and a generic documents query spans all of them.
| Family | Query | Covers |
|---|---|---|
| Invoices | invoices |
Invoices, corrective invoices (identified by the corrects field), charge notes, damages invoices, downpayment invoices |
| Credit notes | creditNotes |
Credit notes, credit charge notes, downpayment credit notes, credit reversal notes |
| Receipts | receipts |
Receipts, deposit receipts, downpayment receipts |
| Refund notes | refundNotes |
Refund notes |
| Refund reversal notes | documents (filter documentGenericType: { in: [REFUND_REVERSAL_NOTE] }) |
Refund reversal notes |
| Payment reversal notes | reversalNotes |
Payment reversal notes |
A corrective invoice is returned by the invoices query like any other invoice. Its corrects
field identifies the document it corrects.
A refund reversal note has its own PDF, titled “Refund reversal note - RRN-…”. It shows which refund note it reverses and why, and the reversed refund note’s PDF shows “Reversed by: RRN-…”.
Prerequisites
Section titled “Prerequisites”- API credentials (a Bearer token) provisioned for your integration.
- The
workspaceIdof the operator workspace. - The
bookingIdyou want documents for (when scoping to a single booking).
Step 1 — Authenticate
Section titled “Step 1 — Authenticate”Send GraphQL requests as HTTP POST with your Bearer token:
POST https://api.lavanda.app/graphqlAuthorization: Bearer <API_TOKEN>Content-Type: application/jsonStep 2 — List the documents
Section titled “Step 2 — List the documents”Query the relevant family connection. workspaceId is required. To return only the documents that
should be visible to a resident, filter by bookingId and issued: true. Request the document
id, any display fields you need, and the per-locale pdfs.
query Documents($filters: AccountingDocumentInvoiceFilters) { accounting { invoices(filters: $filters) { edges { node { id documentNumber issuedAt billingPeriod { from to } amount { value currency } pdfs { locale location { url expiresAt } } } } } }}{ "filters": { "workspaceId": { "eq": "<workspace-id>" }, "bookingId": { "eq": "<booking-id>" }, "issued": { "eq": true } }}The same shape works for the other families by swapping the connection name and its filter input:
invoicesusesAccountingDocumentInvoiceFilterscreditNotesusesAccountingDocumentCreditNoteFiltersreceiptsusesAccountingDocumentReceiptFiltersrefundNotesusesAccountingDocumentRefundNoteFiltersreversalNotesusesAccountingDocumentReversalNoteFilters
To pull every family in one call, use the generic documents query and narrow with
documentGenericType.
Common filter fields (all families):
| Filter | Purpose |
|---|---|
workspaceId (required) |
The operator workspace |
bookingId |
Scope to a single booking. Mutually exclusive with id |
id |
Fetch specific documents by ID. Mutually exclusive with bookingId |
issued |
true returns only issued documents (those with a legal document number and a retrievable PDF) |
billingPeriodFrom / billingPeriodTo |
Narrow by billing period |
family-specific subtype filter (e.g. invoiceType, receiptType) |
Narrow within a family |
Step 3 — Get each PDF
Section titled “Step 3 — Get each PDF”The pdfs field lists per-locale availability. Each entry is a locale for which a PDF is either
ready (non-null location.url) or can be generated on demand (location.url is null). Locales that
are entirely unavailable are omitted.
For the locale you want:
If location.url is present, download the PDF binary directly from that URL. It is a short-lived
pre-signed link (expires in about 1 hour). Fetch it on demand and do not cache or store it.
Example listing response:
{ "data": { "accounting": { "invoices": { "edges": [ { "node": { "id": "31c44172-6e0e-4d94-aea7-bad6de7a2fc9", "pdfs": [ { "locale": "pt", "location": { "url": "https://lvdam-invoicing.s3.eu-west-2.amazonaws.com/...", "expiresAt": "2026-07-29T12:39:43.382215Z" } } ] } } ] } } }}If location.url is null for the locale you need, generate it on demand with the generatePdf
mutation, then download from the returned url:
mutation GeneratePdf($docId: ID!, $locale: String!) { accounting { documents { generatePdf(id: $docId, locale: $locale) { url } } }}{ "docId": "31c44172-6e0e-4d94-aea7-bad6de7a2fc9", "locale": "pt" }generatePdf works for a document in any family (it takes the document id).
Step 4 — Locale rules
Section titled “Step 4 — Locale rules”Pass the locale that matches the resident’s language.
| Market | Available PDF locales |
|---|---|
| Portugal (PT) | pt only. PT documents are the compliant Portuguese “Original” (ATCUD + QR); no other language is issued. |
| Non-PT markets | en, es, fr |
- Only issued documents have a legal document number and a retrievable PDF. Always filter
issued: truefor resident-facing use. - Pre-signed URLs are short-lived (about 1 hour). Never persist them; regenerate on demand.
- The list response can return the PDF URL inline, so in many cases you do not need a separate
generatePdfcall. Fall back togeneratePdfonly when the wanted locale’surlis null.