# 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

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

- API credentials (a Bearer token) provisioned for your integration.
- The `workspaceId` of the operator workspace.
- The `bookingId` you want documents for (when scoping to a single booking).

## Step 1 — Authenticate

Send GraphQL requests as HTTP POST with your Bearer token:

```http
POST https://api.lavanda.app/graphql
Authorization: Bearer <API_TOKEN>
Content-Type: application/json
```

## 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`.

```graphql
query Documents($filters: AccountingDocumentInvoiceFilters) {
  accounting {
    invoices(filters: $filters) {
      edges {
        node {
          id
          documentNumber
          issuedAt
          billingPeriod { from to }
          amount { value currency }
          pdfs {
            locale
            location { url expiresAt }
          }
        }
      }
    }
  }
}
```

```json
{
  "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:

- `invoices` uses `AccountingDocumentInvoiceFilters`
- `creditNotes` uses `AccountingDocumentCreditNoteFilters`
- `receipts` uses `AccountingDocumentReceiptFilters`
- `refundNotes` uses `AccountingDocumentRefundNoteFilters`
- `reversalNotes` uses `AccountingDocumentReversalNoteFilters`

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

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:

```json
{
  "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`:

```graphql
mutation GeneratePdf($docId: ID!, $locale: String!) {
  accounting {
    documents {
      generatePdf(id: $docId, locale: $locale) {
        url
      }
    }
  }
}
```

```json
{ "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

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

## Notes

- Only **issued** documents have a legal document number and a retrievable PDF. Always filter
  `issued: true` for 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
  `generatePdf` call. Fall back to `generatePdf` only when the wanted locale's `url` is null.
