/** * getpeppr SDK Client * * The main entry point. Designed to feel like Stripe's SDK: * * const peppol = new Peppol({ apiKey: "sk_live_..." }); * const result = await peppol.invoices.send({ from, to, lines }); * * All requests go through the getpeppr API gateway (api.getpeppr.dev), * which handles Peppol delivery, billing, and usage tracking. * Use `baseUrl` to point to a custom instance or localhost. */ import type { PeppolConfig, InvoiceInput, CreditNoteInput, SendResult, ValidationResult, WebhookEvent, DocumentStatus, WaitForOptions, PaginatedResult, InvoiceSummary, GetStatusOptions, ListInvoicesOptions, DirectoryEntry, DirectorySearchOptions, DirectorySearchResult, PeppolId, DocumentFormat, ServerValidationResult, EventEntry, ListEventsOptions, BatchSendOptions, BatchSendResult, InvoiceOperationOptions, IdempotentRequestOptions, Contact, ContactInput, ListContactsOptions, BankAccount, BankAccountInput, ListBankAccountsOptions, ImportInvoiceOptions, TransportType, Transport, TransportInput, TransportUpdateInput, MarkAsState, MarkAsOptions, InvoiceUpdateInput, LegalEntityInput, LegalEntity, ListLegalEntitiesOptions, ArchiveLegalEntityResult, AttestationInput, AttestationResult, LegalEntityRequestOptions, AccountIdentity } from "../types/invoice.js"; import type { ApiResult, ApiResultCode, ApiResultRemediation } from "./api-result.js"; /** * Backend adapter interface for the SDK's transport layer. * The default implementation hits the getpeppr API gateway. * * @internal transport contract. This interface is NOT meant to be implemented by * consumers — `PeppolConfig` exposes no adapter injection point, so the only * implementer is the built-in `GetpepprAdapter`. New gateway features add methods * here as minor releases (as contacts/bank-accounts/transports did); external * `implements BackendAdapter` is unsupported and may break across minor versions. */ export interface BackendAdapter { /** Provider name (for logging) */ readonly name: string; /** Send an invoice as structured JSON (gateway handles UBL generation) */ sendInvoice(input: InvoiceInput, options?: InvoiceOperationOptions): Promise; /** @deprecated The current Storecove gateway rejects drafts with 422. */ createInvoice(input: InvoiceInput, options?: InvoiceOperationOptions): Promise; /** @deprecated The current Storecove gateway rejects draft sending with 501. */ sendInvoiceById(id: string, options?: IdempotentRequestOptions): Promise; /** @deprecated Credit notes now route through sendInvoice with isCreditNote: true */ sendCreditNote(input: CreditNoteInput): Promise; /** Validate an invoice server-side (free, no metering) */ validateDocument(input: InvoiceInput): Promise<{ valid: boolean; errors: string[]; }>; /** List invoices with pagination and filtering */ listInvoices(options?: ListInvoicesOptions): Promise>; /** Get document status by ID */ getStatus(documentId: string, options?: GetStatusOptions): Promise; /** Look up a Peppol participant in the directory */ lookupDirectory(scheme: string, id: string): Promise; /** Search the Peppol Directory for participants */ searchDirectory?(params: Record): Promise; /** Export an invoice in a specific format (e.g., PDF) — returns raw binary */ /** * Fetch the stored document the format selects, as raw bytes. * * ⚠️ A format naming an XML schema resolves only on a document written in it * — see `InvoiceOperations.getAs` for what that means for a caller. */ getInvoiceAs(id: string, format: DocumentFormat): Promise; /** Validate an invoice server-side through the getpeppr gateway's offline SDK-backed checks. */ validateDocumentServer(input: InvoiceInput): Promise; /** List events with optional filtering and pagination */ listEvents(options?: ListEventsOptions): Promise>; /** @deprecated The current Storecove gateway rejects acknowledgement with 501. */ acknowledgeInvoice(id: string, options?: IdempotentRequestOptions): Promise; /** List contacts with optional filtering and pagination */ listContacts(options?: ListContactsOptions): Promise>; /** Get a single contact by ID */ getContact(id: string): Promise; /** Create a new contact */ createContact(input: ContactInput, options?: IdempotentRequestOptions): Promise; /** Update an existing contact */ updateContact(id: string, input: Partial): Promise; /** Delete a contact */ deleteContact(id: string): Promise; /** List bank accounts with optional pagination */ listBankAccounts(options?: ListBankAccountsOptions): Promise>; /** Get a single bank account by ID */ getBankAccount(id: string): Promise; /** Create a new bank account */ createBankAccount(input: BankAccountInput, options?: IdempotentRequestOptions): Promise; /** Update an existing bank account */ updateBankAccount(id: string, input: Partial): Promise; /** Delete a bank account */ deleteBankAccount(id: string): Promise; /** Import an invoice from a file (XML, PDF, etc.) */ importInvoice(options: ImportInvoiceOptions): Promise; /** List all available transport types (global, not account-scoped) */ listTransportTypes(): Promise; /** List configured transports for this account */ listTransports(): Promise; /** Get a single transport by code */ getTransport(code: string): Promise; /** Create a new transport */ createTransport(input: TransportInput): Promise; /** Update an existing transport */ updateTransport(code: string, input: TransportUpdateInput): Promise; /** Delete a transport */ deleteTransport(code: string): Promise; /** @deprecated The current Storecove gateway rejects invoice updates with 501. */ updateInvoice(id: string, input: InvoiceUpdateInput): Promise; /** @deprecated The current Storecove gateway rejects invoice deletion with 501. */ deleteInvoice(id: string): Promise; /** Report a French CTC invoice as paid; other state transitions return 501. */ markInvoiceAs(id: string, state: MarkAsState, options?: MarkAsOptions): Promise; /** Create a sub-tenant Legal Entity (master key). */ createLegalEntity(input: LegalEntityInput, options?: LegalEntityRequestOptions): Promise; /** Fetch a single sub-tenant Legal Entity by id (master key). */ getLegalEntity(id: string): Promise; /** List sub-tenant Legal Entities (master key), paginated. */ listLegalEntities(options?: ListLegalEntitiesOptions): Promise>; /** Archive (soft-delete) a sub-tenant Legal Entity (master key). */ archiveLegalEntity(id: string): Promise; /** Request (or resend) a sub-tenant attestation — production only (master key). */ requestLegalEntityAttestation(id: string, input: AttestationInput, options?: LegalEntityRequestOptions): Promise; /** Read the Peppol identity of the account behind this API key — works with ANY key. */ getIdentity(): Promise; } /** * Case-insensitive header lookup. Per RFC 7230 §3.2, HTTP header names are * case-insensitive. Some adapters (axios default, Cloudflare Workers, proxies) lowercase * header keys, which made the strict bracket lookup miss user-supplied lowercase * keys and silently disabled retry safety on POST requests with idempotency keys. * * When multiple headers match (e.g. "Idempotency-Key" and "idempotency-key" both present), * returns the value of the first matching key in insertion order. * * @internal — exported for testing only; not part of the public SDK surface. */ export declare function findHeaderCaseInsensitive(headers: Record | undefined, name: string): string | undefined; /** * The four bytes the transport strips from the edges of a header value before * it goes on the wire: HTAB `%x09`, LF `%x0A`, CR `%x0D`, SP `%x20`. * * ⛔ The source is the WHATWG Fetch "normalize a potential value" algorithm, * NOT RFC 9110 §5.6.3 — which this comment cited until a gate checked it * (GPR-1188). RFC 9110 §5.6.3 reads `OWS = *( SP / HTAB )`, verbatim: no CR, no * LF. The behaviour described below is right; the citation was not. * * ⚠️ NOT `String.prototype.trim()`, which also eats NBSP and every other Unicode * space. The wire keeps those, so trimming them here would make the SDK's idea * of the key differ from the gateway's — the very gap this file exists to close. * * @internal — exported for testing only; not part of the public SDK surface. */ export declare function normalizeHeaderValue(value: string): string; /** * Write the `Idempotency-Key` header, or refuse a key that cannot protect * anything. Every write surface that accepts `options.idempotencyKey` goes * through here, so the rule lives in one place. * * ⛔ A key made of whitespace is TRUTHY in JavaScript but EMPTY on the wire. The * SDK used to read it as "a key was supplied" and unlock its POST retry, while * the gateway saw no key at all, skipped its cache and its lock, and treated * every attempt as new — one `POST /v1/invoices` leaving FOUR times under the * default retry config (`maxRetries: 3`), each able to submit the invoice. A key that does not protect is worse than no key: it * removes the very guard its absence would have kept shut. * * ⚠️ The type says `string`, but the SDK runs on the caller's machine, which may * not be typed. `[]` is the sharp case — truthy, and `String([])` is `""`. * * @internal — exported for testing only; not part of the public SDK surface. */ export declare function applyIdempotencyKey(headers: Record, options: { idempotencyKey?: string; } | undefined): void; /** * Whether these headers carry an idempotency key the gateway can actually USE. * * The second, independent lock. `applyIdempotencyKey` guards nine call sites — * four until GPR-1189 opened the header on every operation the contract lists * it on — and a tenth added later would skip it. This sits on the single path * every retry goes through, and it reads the value that would TRAVEL rather * than the presence of a property — so a blank header cannot unlock a replay * whatever put it there. * * @internal — exported for testing only; not part of the public SDK surface. */ export declare function carriesUsableIdempotencyKey(headers: Record | undefined): boolean; /** Convert ArrayBuffer or Uint8Array to base64 string (works in all runtimes). */ export declare function arrayBufferToBase64(buffer: ArrayBuffer | Uint8Array): string; /** Detect MIME type from a filename's extension. */ export declare function detectMimeType(filename: string): string; /** @internal — exported for testing only; not part of the public SDK surface. */ export declare function mapStatus(raw: string): DocumentStatus; export declare class PeppolError extends Error { constructor(message: string); } export declare class PeppolValidationError extends PeppolError { readonly validation: ValidationResult; constructor(message: string, validation: ValidationResult); } /** * The gateway answered 2xx with a body the SDK cannot honestly parse — a field * the contract makes mandatory is missing. * * The SDK raises this instead of substituting a plausible value: a fabricated * status is indistinguishable from a measured one for anyone reading `status` * (GPR-1061). Nothing you sent causes it and retrying will not clear it — it is * worth reporting, with the caveat on `responseBody` below. */ export declare class PeppolProtocolError extends PeppolError { /** The field the response lacked, or `"body"` when it is not an object. */ readonly field: string; /** * The offending response body, serialised and capped at 2000 characters. * * This is your own document data as the gateway returned it. It is here so * you can see the shape that broke — treat it like any other payload before * putting it somewhere it will be retained. */ readonly responseBody: string; constructor(message: string, /** The field the response lacked, or `"body"` when it is not an object. */ field: string, /** * The offending response body, serialised and capped at 2000 characters. * * This is your own document data as the gateway returned it. It is here so * you can see the shape that broke — treat it like any other payload before * putting it somewhere it will be retained. */ responseBody: string); } export declare class PeppolApiError extends PeppolError { readonly statusCode: number; readonly responseBody: string; /** * Parsed `Retry-After` delay in milliseconds. * * `undefined` unless this response is a **429** AND carried a readable * `Retry-After`. No other status reads that header, whatever its remediation * says — measured, all 22 `retry_after` entries in the catalogue are 429s. * The gateway does not attach the header to every throttled answer either. */ readonly retryAfterMs?: number; /** * The canonical result the gateway declared for this response, read from its * six headers — no body parsing required. * * `undefined` against a gateway that has not activated the result catalogue, * and behind any hop that strips unknown headers. The flattened accessors * below all read from here, so they are `undefined` together. */ readonly result?: ApiResult; constructor(message: string, statusCode: number, responseBody: string, retryAfterMs?: number, result?: ApiResult); /** * Stable getpeppr result code for this failure (e.g. `"auth.api_key_invalid"`). * * ⛔ NOT the same field as {@link code}, and they can both be present with * different values: this one is the catalogue's global code, `code` is the * route's own sub-reason from the body. * * `undefined` when the gateway sent no result headers. */ get resultCode(): ApiResultCode | undefined; /** * The catalogue's sentence for {@link resultCode}. * * ⚠️ Usually SHORTER on detail than `.message`, which is built from the * response body and can name the offending field or rule. Show `.message` to * a human; use this one when you want the stable phrasing. * * `undefined` when the gateway sent no result headers. */ get resultMessage(): string | undefined; /** * Server-generated correlation id for this exact request. Quote it to support. * * `undefined` when the gateway sent no result headers — which includes every * response from a deployment predating the catalogue. */ get requestId(): string | undefined; /** * Whether retrying this same request can succeed, per the catalogue. * * ⚠️ `undefined` means "the gateway did not say", NOT "no" — the SDK then * falls back to its historic status policy. A `false` here is an explicit * refusal and the SDK will not retry, whatever the status. */ get retryable(): boolean | undefined; /** * What to do about it: `"none"`, `"fix_request"`, `"authenticate"`, * `"retry"`, `"retry_after"`, `"wait"` or `"contact_support"` today. * * Typed open — a value added server-side reaches you rather than vanishing. * `undefined` when the gateway sent no result headers. */ get remediation(): ApiResultRemediation | undefined; /** * Documentation link for {@link resultCode}, when the catalogue provides one. * * `undefined` when the gateway sent no result headers, when the catalogue * entry has no docs link, or when the value was not a plain `https://` URL * (`http:`, credentials in the authority, and anything the URL parser would * have to repair are all refused). */ get docs(): string | undefined; /** * The gateway's machine-readable error code, parsed from the JSON response body * (e.g. "le_cap_exceeded", "identifier_immutable", "legal_entity_locked", "forbidden"). * Returns undefined when the body is not JSON or carries no string `code`. */ get code(): string | undefined; } export declare class Peppol { private adapter; readonly invoices: InvoiceOperations; readonly creditNotes: CreditNoteOperations; readonly directory: DirectoryOperations; readonly events: EventOperations; readonly contacts: ContactOperations; readonly bankAccounts: BankAccountOperations; readonly transports: TransportOperations; /** * Your own account's Peppol identity — works with ANY key, standard keys * included. `peppol.identity.get()` answers "who am I on the Peppol * network?" for the account behind the key making the call. */ readonly identity: IdentityOperations; /** * Sub-tenant Legal Entities — **platform accounts only**. * * Requires a platform account and a **master API key**. With a standard key * every call here fails with 403 `master_key_required`. * * Onboarding your OWN company is not done through this API: your legal entity * is managed in the console, on the Peppol identity page — and READ from the * API with `peppol.identity.get()`, which works with any key. This surface is * for platforms that onboard their customers as sub-tenants. * * **Getting access:** in the sandbox, an organisation admin starts the * platform sandbox trial from the console overview (or chooses "A platform * for my customers" at signup), then creates the sandbox master key at * https://console.getpeppr.dev/api-keys. Production platform access is set up * with our team — email hello@getpeppr.dev to request it. * * @see https://getpeppr.dev/docs/platform/legal-entities/ */ readonly legalEntities: LegalEntityOperations; constructor(config: PeppolConfig); /** * Validate the structured JSON send payload without sending it. * Useful for pre-flight checks in your UI; provider-side normalization still * applies on send. `toXml()` adds the stricter direct-UBL builder checks. */ validate(input: InvoiceInput): ValidationResult; /** * Generate UBL XML without sending. * Useful for debugging or manual submission. */ toXml(input: InvoiceInput): string; } /** @internal — exported for testing only; not part of the public SDK surface. */ export declare function paginate(fetchPage: (offset: number, limit: number) => Promise>, options?: { limit?: number; }): AsyncGenerator; declare class InvoiceOperations { private adapter; constructor(adapter: BackendAdapter); /** * Request draft creation from the gateway. * * @deprecated The current Storecove-backed gateway does not support drafts * and returns 422 `drafts_not_supported`. Submit the final document with * `invoices.send()` instead. * @throws {PeppolApiError} 422 with code `drafts_not_supported` */ create(input: InvoiceInput, options?: InvoiceOperationOptions): Promise; /** * Request sending of an existing draft invoice by ID. * * @deprecated The current Storecove-backed gateway has no draft lifecycle and * always returns 501. Submit the final document with `invoices.send()`. * @throws {PeppolApiError} 501 with the current gateway provider */ sendById(id: string, options?: IdempotentRequestOptions): Promise; /** * Send an invoice via Peppol. * * @example * ```ts * const result = await peppol.invoices.send({ * number: "INV-001", * from: { name: "My Company", peppolId: "0208:0685660237", country: "BE" }, * to: { name: "Client Co", peppolId: "0208:0685660237", country: "BE" }, * lines: [ * { description: "Consulting", quantity: 10, unitPrice: 150, vatRate: 21 } * ] * }); * ``` */ send(input: InvoiceInput, options?: InvoiceOperationOptions): Promise; /** List invoices with pagination, filtering, and proper metadata */ list(options?: ListInvoicesOptions): Promise>; /** * Async iterator over all invoices, automatically handling pagination. * * @example * ```ts * for await (const invoice of peppol.invoices.listAll()) { * console.log(invoice.id, invoice.status); * } * ``` */ listAll(options?: Omit): AsyncIterable; /** * Get the status of a sent invoice. * * @param options.includeEvidence Ask the gateway to read the sending evidence * from the Peppol network so the result carries `peppolMessageId`. Costs one * provider round trip, so it is off by default; if the document has not gone * out yet, or the read fails, the field is simply absent and everything else * is unaffected. * * @example * ```ts * const status = await peppol.invoices.getStatus(id); * const proof = await peppol.invoices.getStatus(id, { includeEvidence: true }); * ``` */ getStatus(documentId: string, options?: GetStatusOptions): Promise; /** * Export an invoice in a specific format (e.g., PDF, UBL XML). * Returns raw binary data as an ArrayBuffer. * * The bytes are the stored document the format selects, served under that * format's media type — a resolved call never hands back another media type. * * A format that names an XML schema is checked against the BUSINESS DOCUMENT * before it is served: `xml.ubl.invoice.bis3` resolves only when that * document is UBL and `xml.facturae.3.2` only when it is Facturae — * otherwise the call rejects with `404 invoices.export_format_unavailable`, * listing the available media types. * * ⛔ The check looks THROUGH the Peppol envelope; the bytes still carry it. * `original` and `xml.ubl.invoice.bis3` both return the transmitted Standard * Business Document, whose root is ``, not * ``. Feed a UBL validator `payload` — the only format that returns * the business document bare. * * ⚠️ On the Peppol network the document is UBL, so `xml.facturae.3.2` * normally has nothing to return. `original` and `payload` name no schema and * resolve on any XML on file: ask for those when you want the document as * transmitted. * * ⚠️ For a short window after a send, the evidence is not registered yet and * every format answers 404, `original` included. Poll rather than treating * the first one as a failure. * * @example * ```ts * const pdf = await peppol.invoices.getAs("inv-123", "pdf"); * fs.writeFileSync("invoice.pdf", Buffer.from(pdf)); * ``` */ getAs(id: string, format: DocumentFormat): Promise; /** * Validate an invoice server-side using the getpeppr gateway's offline SDK-backed checks. * The gateway runs SDK validation, verifies UBL XML generation, and evaluates offline * pre-flight checks without sending the invoice to Storecove. This is not a Peppol * conformance verdict. * * @example * ```ts * const result = await peppol.invoices.validateServer({ * number: "INV-001", * to: { name: "Acme", peppolId: "0208:0685660237", country: "BE" }, * lines: [{ description: "Item", quantity: 1, unitPrice: 100, vatRate: 21 }] * }); * console.log(result.valid, result.schematron.errors); * ``` * Validation findings return a structured result with valid=false. Transport, auth, * malformed request, and unexpected gateway failures still throw PeppolApiError. */ validateServer(input: InvoiceInput): Promise; /** * Send a UBL Invoice or CreditNote you built yourself. * * getpeppr does not regenerate, normalise, or repair the document — we * forward the bytes you supplied, unchanged, to the network. Only UBL Invoice * and CreditNote are accepted — a PDF, a CII document, or an XML that is * neither is refused. The file is base64-encoded into a JSON body; there is * no multipart upload. * * ⚠️ Byte-for-byte equality is NOT guaranteed, because the network * re-serialises the document in transit. Measured 2026-08-18 on a test * document: namespace declarations come back reordered, numeric character * references are resolved (`A` → `A`), whitespace inside tags is dropped, * and no element was added, removed or altered. That is one document and four * kinds of difference — indicative, not a warranty of what is preserved. * **If you seal your documents, hash a canonical form (C14N) rather than the * raw bytes.** * * The receipt says this itself, so your code need not rely on this comment: * a successful import carries `transmission`, whose `bytePreservation` this * gateway sets to `"not_guaranteed"` (GPR-1089). It is a guarantee we decline * to give, not a claim that your document was altered. * * ⚠️ Read the value, do not assume it. This SDK talks to whatever gateway * version you point it at: one predating GPR-1089 returns no `transmission` * at all, and the field is typed `string` so a later value reaches you rather * than being dropped. **Absent is not `false`** — it means the gateway did * not say, never that your bytes are safe. * * `to` is required and is never read from the document. Routing decides * delivery, the document travels as payload, and getpeppr will not guess a * destination by parsing your XML. * * Before transmission the document is validated against the complete official * OpenPeppol rulebooks. A document violating a `fatal` rule is refused and is * NOT sent; the error names the rule. * * @example * ```ts * const xmlBytes = fs.readFileSync("invoice.xml"); * const result = await peppol.invoices.importFile({ * file: xmlBytes, * filename: "invoice.xml", * to: { peppolId: "0208:0685660237" }, * }); * console.log(result.id, result.status); * ``` * * @throws {PeppolApiError} 400 — `invalid_base64`, or a missing `file` / * `filename`. `missing_recipient` when `to.peppolId` is absent. * @throws {PeppolApiError} 422 — the document was refused and NOT sent. Two * families, and they do NOT retry the same way: * * - **The document was rejected** (`validation_failed`, `not_ubl_document`, * `document_too_complex`, `undecodable_document`, `unsupported_encoding`). * Terminal: the same bytes fail identically forever. Fix the document — * retrying is pure waste, and `validation_failed` names the rule. * - **The account may not send right now** (`peppol_identity_incomplete`, * `peppol_identity_not_verified`, `platform_billing_not_active`, * `production_access_expired`). ⛔ NOT terminal: these describe account * state, and account state changes — a verification completes, a contract * is activated. The identical document will go through once it does. * * Treating the second family as terminal costs a customer a real invoice; * treating the first as retryable costs an infinite loop. See the API * reference for the full list. */ importFile(options: ImportInvoiceOptions): Promise; /** * Request acknowledgement of a received invoice. * * @deprecated The current Storecove-backed gateway does not support * acknowledgement and always returns 501. * @throws {PeppolApiError} 501 with the current gateway provider */ acknowledge(id: string, options?: IdempotentRequestOptions): Promise; /** * Request an update to an existing invoice. * * @deprecated Storecove documents are immutable after submission. The * current gateway always returns 501; issue a credit note instead. * @throws {PeppolApiError} 501 with the current gateway provider */ update(id: string, input: InvoiceUpdateInput): Promise; /** * Request deletion of an invoice. * * @deprecated The current Storecove-backed gateway does not support invoice * deletion and always returns 501. * @throws {PeppolApiError} 501 with the current gateway provider */ delete(id: string): Promise; /** * Report a French CTC invoice as paid. * Other state transitions are retained for API compatibility but the current * Storecove-backed gateway returns 501 for them. * * `"paid"` on a French CTC invoice reports the payment collection * (« signalement d'encaissement ») to the tax authority via the gateway — * a legal obligation of the French mandate for service invoices. The full * amount is reported from the invoice's stored tax breakdown (no amount to * pass), at most once per invoice: replays return the same report (200), * a concurrent report returns 409, a non-French invoice returns 422. * The invoice's own status becomes `paid` later, when the network confirms * (webhook / polling), not synchronously with this call. * * @example * ```ts * // France: report that the customer paid this invoice * await peppol.invoices.markAs("inv-123", "paid"); * ``` * @throws {PeppolApiError} 422 for "paid" on a non-French-CTC invoice; 501 for states the provider does not support */ markAs(id: string, state: MarkAsState, options?: MarkAsOptions): Promise; /** * Send multiple invoices in parallel with controlled concurrency. * Each invoice is validated and sent individually — failures don't affect other invoices * unless `stopOnError: true` is set. * * The SDK's built-in retry logic (including 429 Retry-After) provides automatic * rate-limit handling at the request level. * * @example * ```ts * const result = await peppol.invoices.sendBatch([invoice1, invoice2, invoice3], { * concurrency: 3, * }); * console.log(`${result.succeeded.length} sent, ${result.failed.length} failed`); * ``` */ sendBatch(inputs: InvoiceInput[], options?: BatchSendOptions): Promise; /** * Poll until an invoice reaches a target status. * * @example * ```ts * const result = await peppol.invoices.waitFor(id, "accepted", { timeout: 60000 }); * ``` */ waitFor(documentId: string, targetStatus: DocumentStatus | DocumentStatus[], options?: WaitForOptions): Promise; } /** @deprecated Use peppol.invoices.send() with isCreditNote: true instead */ declare class CreditNoteOperations { private adapter; constructor(adapter: BackendAdapter); /** * Send a credit note via Peppol. * @deprecated Use peppol.invoices.send({ ...input, isCreditNote: true }) instead. */ send(input: CreditNoteInput): Promise; } declare class DirectoryOperations { private adapter; constructor(adapter: BackendAdapter); /** * Look up a Peppol participant in the directory. * * @example * ```ts * const entry = await peppol.directory.lookup("0208:0685660237"); * console.log(entry.name, entry.capabilities); * ``` */ lookup(peppolId: PeppolId): Promise; /** * Search the Peppol Directory for participants. * Pagination is exact and participant-based. Queries wider than the public * Directory's accessible result window are rejected; add narrower criteria. * * @example * ```ts * const result = await peppol.directory.search({ name: "Acme", country: "BE" }); * console.log(result.data); // DirectoryEntry[] * console.log(result.meta.totalCount); * ``` */ search(options: DirectorySearchOptions): Promise; /** * Search the Peppol Directory by VAT number. * Convenience method — equivalent to `search({ vatNumber })`. * * @example * ```ts * const result = await peppol.directory.searchByVat("BE0685660237"); * ``` */ searchByVat(vatNumber: string): Promise; } declare class EventOperations { private adapter; constructor(adapter: BackendAdapter); /** * List events with optional filtering and pagination. * * @example * ```ts * const result = await peppol.events.list({ limit: 10 }); * console.log(result.data, result.meta); * * // Filter by provider document ID or getpeppr submission ID * const invoiceEvents = await peppol.events.list({ documentId: "inv-123" }); * ``` */ list(options?: ListEventsOptions): Promise>; /** * Async iterator over all events, automatically handling pagination. * * @example * ```ts * for await (const event of peppol.events.listAll({ documentId: "inv-123" })) { * console.log(event.name, event.createdAt); * } * ``` */ listAll(options?: Omit): AsyncIterable; } declare class ContactOperations { private adapter; constructor(adapter: BackendAdapter); /** * List contacts with optional filtering and pagination. * * @example * ```ts * const result = await peppol.contacts.list({ limit: 10, isClient: true }); * console.log(result.data, result.meta); * ``` */ list(options?: ListContactsOptions): Promise>; /** * Get a single contact by ID. * * @example * ```ts * const contact = await peppol.contacts.get("123"); * console.log(contact.name, contact.peppolId); * ``` */ get(id: string): Promise; /** * Create a new contact. * * @example * ```ts * const contact = await peppol.contacts.create({ * name: "ACMEDIA", * peppolId: "0208:0685660237", * country: "BE", * isClient: true, * }); * ``` */ create(input: ContactInput, options?: IdempotentRequestOptions): Promise; /** * Update an existing contact. * * @example * ```ts * const updated = await peppol.contacts.update("123", { email: "new@acme.com" }); * ``` */ update(id: string, input: Partial): Promise; /** * Delete a contact. * * @example * ```ts * await peppol.contacts.delete("123"); * ``` */ delete(id: string): Promise; /** * Async iterator over all contacts, automatically handling pagination. * * @example * ```ts * for await (const contact of peppol.contacts.listAll({ isClient: true })) { * console.log(contact.name, contact.peppolId); * } * ``` */ listAll(options?: Omit): AsyncIterable; } /** * Your own account's Peppol identity — readable with ANY API key. * * Unlike `peppol.legalEntities` (platform accounts, master key only), this * surface answers "who am I on the Peppol network?" for the account behind * the key making the call — standard keys included. */ declare class IdentityOperations { private adapter; constructor(adapter: BackendAdapter); /** * Read the Peppol identity of your own account: the environment this key * operates in, your legal entity as the gateway holds it, and the Peppol * identifiers registered for it. * * Works with ANY API key — standard keys included; no platform mode or * master key required. This is the read counterpart to onboarding: your * legal entity is created and edited in the console (Peppol identity page) * or via onboarding, and this call is how you READ it from the API. * * @example * ```ts * const me = await peppol.identity.get(); * console.log(me.environment, me.legalEntity?.companyName); * for (const id of me.identifiers) { * console.log(`${id.scheme}:${id.value} — ${id.status}`); * } * ``` */ get(): Promise; } /** * Sub-tenant Legal Entity operations — **platform accounts only**. * * Every method here requires a platform account and a master API key; a * standard key gets 403 `master_key_required`. Each one repeats the * requirement because an IDE shows only the member being hovered. * * @see https://getpeppr.dev/docs/platform/legal-entities/ */ declare class LegalEntityOperations { private adapter; constructor(adapter: BackendAdapter); /** * Create a sub-tenant Legal Entity for one of your customers. * * **Platform accounts only — requires a master API key.** In the sandbox, * an organisation admin starts the platform sandbox trial from the console * overview and creates a sandbox master key at * https://console.getpeppr.dev/api-keys; production platform access is set * up with our team (hello@getpeppr.dev). * * Your own company's legal entity is managed in the console, on the Peppol * identity page (and read from the API with `peppol.identity.get()`); this * creates an entity for a customer of yours. * * Idempotent on `externalId`: repeated calls with the same `externalId` return * the existing entity (HTTP 200) instead of creating a duplicate. Transient 5xx * failures are NOT auto-retried unless you pass `options.idempotencyKey`. * * @example * ```ts * const le = await peppol.legalEntities.create({ * externalId: "tenant-42", * companyName: "Acme Health AB", * country: "SE", * address: { line1: "Storgatan 1", city: "Stockholm", zip: "11122" }, * identifier: { scheme: "0007", value: "5560000001" }, * }, { idempotencyKey: "tenant-42-create" }); * ``` */ create(input: LegalEntityInput, options?: LegalEntityRequestOptions): Promise; /** * Fetch a single sub-tenant Legal Entity by id. * * **Platform accounts only — requires a master API key.** In the sandbox, * an organisation admin starts the platform sandbox trial from the console * overview and creates a sandbox master key at * https://console.getpeppr.dev/api-keys; production platform access is set * up with our team (hello@getpeppr.dev). * * For production entities the `status` reflects the attestation lifecycle * (awaiting_authz → attested → active). */ get(id: string): Promise; /** * List your sub-tenant Legal Entities, newest first. * * **Platform accounts only — requires a master API key.** In the sandbox, * an organisation admin starts the platform sandbox trial from the console * overview and creates a sandbox master key at * https://console.getpeppr.dev/api-keys; production platform access is set * up with our team (hello@getpeppr.dev). * * This lists the customers you have onboarded, never your own legal entity. * * Pass `externalId` to find one customer by YOUR reference, so you never need * to have stored our id — the page then carries that entity alone, or nothing * if you have no live customer under that reference. * * @example * ```ts * const { data } = await peppol.legalEntities.list({ externalId: "acme-42" }); * const customer = data[0]; // undefined if you never registered that one * ``` */ list(options?: ListLegalEntitiesOptions): Promise>; /** * Async iterator over all sub-tenant Legal Entities, handling pagination. * * **Platform accounts only — requires a master API key.** In the sandbox, * an organisation admin starts the platform sandbox trial from the console * overview and creates a sandbox master key at * https://console.getpeppr.dev/api-keys; production platform access is set * up with our team (hello@getpeppr.dev). * * Takes every `list()` option except `offset`, which it owns — including * `externalId` (GPR-1231). Filtering by a reference that is unique among your * live customers makes the iterator yield that one customer, or nothing; it * is `list()` you normally want for that, and this form exists so a filter * built once can be handed to either. * * @example * ```ts * for await (const le of peppol.legalEntities.listAll()) console.log(le.id, le.status); * for await (const le of peppol.legalEntities.listAll({ externalId: "acme-42" })) console.log(le.id); * ``` */ listAll(options?: Omit): AsyncIterable; /** * Archive (soft-delete) a sub-tenant Legal Entity. The id stays resolvable * for audit. * * **Platform accounts only — requires a master API key.** In the sandbox, * an organisation admin starts the platform sandbox trial from the console * overview and creates a sandbox master key at * https://console.getpeppr.dev/api-keys; production platform access is set * up with our team (hello@getpeppr.dev). */ archive(id: string): Promise; /** * Request a sub-tenant attestation (production only). Emails the co-branded * confirmation link to the sub-tenant contact and returns the pending status. * * **Platform accounts only — requires a master API key.** In the sandbox, * an organisation admin starts the platform sandbox trial from the console * overview and creates a sandbox master key at * https://console.getpeppr.dev/api-keys; production platform access is set * up with our team (hello@getpeppr.dev). * * Transient failures are NOT auto-retried unless you pass `options.idempotencyKey`; * re-issuing mints a fresh token, so a retried call is safe. * * Pass `language` (a BCP 47 tag such as `"en"`) to choose the language of the * email and the confirmation page; omit it for English. The result echoes the * language used and whether it was `"explicit"` or the `"default"`. * * @example * ```ts * await peppol.legalEntities.requestAttestation(le.id, { contactEmail: "owner@acme.example" }); * ``` */ requestAttestation(id: string, input: AttestationInput, options?: LegalEntityRequestOptions): Promise; } declare class BankAccountOperations { private adapter; constructor(adapter: BackendAdapter); /** * List bank accounts with optional pagination. * * @example * ```ts * const result = await peppol.bankAccounts.list({ limit: 10 }); * console.log(result.data, result.meta); * ``` */ list(options?: ListBankAccountsOptions): Promise>; /** * Get a single bank account by ID. * * @example * ```ts * const account = await peppol.bankAccounts.get("123"); * console.log(account.name, account.iban); * ``` */ get(id: string): Promise; /** * Create a new bank account. * * @example * ```ts * const account = await peppol.bankAccounts.create({ * name: "Main Account", * iban: "BE68539007547034", * bic: "BBRUBEBB", * country: "BE", * }); * ``` */ create(input: BankAccountInput, options?: IdempotentRequestOptions): Promise; /** * Update an existing bank account. * * @example * ```ts * const updated = await peppol.bankAccounts.update("123", { name: "Updated Name" }); * ``` */ update(id: string, input: Partial): Promise; /** * Delete a bank account. * * @example * ```ts * await peppol.bankAccounts.delete("123"); * ``` */ delete(id: string): Promise; /** * Async iterator over all bank accounts, automatically handling pagination. * * @example * ```ts * for await (const account of peppol.bankAccounts.listAll()) { * console.log(account.name, account.iban); * } * ``` */ listAll(options?: Omit): AsyncIterable; } declare class TransportOperations { private adapter; constructor(adapter: BackendAdapter); /** * List all available transport types in the network. * Returns global transport types (not account-scoped). * * @example * ```ts * const types = await peppol.transports.listTypes(); * console.log(types); // [{ code: "peppol", name: "Peppol BIS 3.0" }, ...] * ``` */ listTypes(): Promise; /** * List configured transports for this account. * * @example * ```ts * const transports = await peppol.transports.list(); * console.log(transports); // [{ id: "t-1", transportTypeCode: "peppol", name: "..." }, ...] * ``` */ list(): Promise; /** * Get a single transport by code. * * @example * ```ts * const transport = await peppol.transports.get("peppol"); * ``` */ get(code: string): Promise; /** * Create a new transport. * * @example * ```ts * const transport = await peppol.transports.create({ * transportTypeCode: "peppol", * email: "billing@acme.com", * }); * ``` */ create(input: TransportInput): Promise; /** * Update an existing transport. * * @example * ```ts * const transport = await peppol.transports.update("peppol", { email: "new@acme.com" }); * ``` */ update(code: string, input: TransportUpdateInput): Promise; /** * Delete a transport. * * @example * ```ts * await peppol.transports.delete("peppol"); * ``` */ delete(code: string): Promise; } /** * Parse and verify a webhook payload from getpeppr. * * getpeppr signs webhooks with HMAC-SHA256. The signature header format is: * `Getpeppr-Signature: t={timestamp},s={hmac_sha256_hex}` * * The signed payload is: `{timestamp}.{raw_json_body}` * * @example * ```ts * import { webhooks } from "@getpeppr/sdk"; * * app.post("/webhooks/peppol", async (req, res) => { * try { * const event = await webhooks.constructEvent( * req.body, // raw body string (NOT parsed JSON) * String(req.headers["getpeppr-signature"] ?? ""), // signature header * "whsec_your_webhook_secret", // your endpoint's signing secret * ); * switch (event.type) { * case "inbound.invoice.received": { * // `event.data` is `unknown` on the envelope — narrow it per type. * // ⚠️ `peppolId` is nullable, and the null is meaningful (GPR-1259): * // it is read from the document's own AccountingSupplierParty and is * // never guessed, so null means the document did not state a supplier * // endpoint. Do not reconcile a supplier on a value that is absent. * const data = event.data as { sender: { peppolId: string | null } }; * console.log("New invoice from:", data.sender.peppolId ?? "(sender not stated)"); * break; * } * } * res.sendStatus(200); * } catch (err) { * res.status(400).send("Webhook verification failed"); * } * }); * ``` */ export declare const webhooks: { /** * Parse a webhook payload without signature verification. * Use `constructEvent()` for verified parsing in production. */ parse(payload: unknown): WebhookEvent; /** * Verify and parse a webhook payload using HMAC-SHA256 signature. * Throws `PeppolError` if verification fails. * * @param rawBody — The raw request body string (NOT parsed JSON) * @param signatureHeader — The `Getpeppr-Signature` header value * @param secret — Your webhook secret from getpeppr * @param toleranceSeconds — Max age of the webhook in seconds (default: 300 = 5 min) */ constructEvent(rawBody: string, signatureHeader: string, secret: string, toleranceSeconds?: number): Promise; }; export {}; //# sourceMappingURL=client.d.ts.map