@papierapi/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Papier API
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,450 @@
1
+ # @papierapi/sdk
2
+
3
+ The official TypeScript SDK for the Papier API. It provides a typed,
4
+ runtime-validated ESM client for preparing, approving, paying for, dispatching,
5
+ and tracking physical letters and faxes.
6
+
7
+ The SDK is designed for trusted server-side applications. It validates request
8
+ inputs before sending them, validates every API response against the published
9
+ contract, and keeps real dispatch behind an explicit human approval.
10
+
11
+ ## Features
12
+
13
+ - Typed letter and fax workflows
14
+ - PDF uploads up to 20 MiB
15
+ - Structured letter content without generating a PDF yourself
16
+ - Human approval links before payment or dispatch
17
+ - Direct-payment and wallet-backed dispatch flows
18
+ - Idempotency keys for retry-safe payment and dispatch creation
19
+ - Dispatch status, event, summary, and proof retrieval
20
+ - Runtime response validation
21
+ - Typed API, transport, and contract errors
22
+ - Machine-readable operation metadata
23
+ - Injectable access-token provider and Fetch implementation
24
+
25
+ ## Requirements
26
+
27
+ - Node.js `>=20 <26`
28
+ - An ESM application
29
+ - A Papier API OAuth access token or API key
30
+ - A configured sender profile for letter or fax drafts
31
+
32
+ The package ships ESM JavaScript and TypeScript declarations. CommonJS
33
+ `require()` is not supported.
34
+
35
+ Create API credentials and sender profiles in the
36
+ [Papier API developer area](https://app.papierapi.de/developers).
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ npm install @papierapi/sdk
42
+ ```
43
+
44
+ Equivalent package-manager commands:
45
+
46
+ ```bash
47
+ pnpm add @papierapi/sdk
48
+ yarn add @papierapi/sdk
49
+ ```
50
+
51
+ ## Client setup
52
+
53
+ ```ts
54
+ import { PapierApiClient } from "@papierapi/sdk";
55
+
56
+ const api = new PapierApiClient({
57
+ baseUrl: "https://api.papierapi.de",
58
+ accessToken: process.env.PAPIERAPI_TOKEN!,
59
+ });
60
+ ```
61
+
62
+ Recommended environment variables:
63
+
64
+ ```dotenv
65
+ PAPIERAPI_TOKEN=pap_...
66
+ PAPIERAPI_SENDER_PROFILE_ID=snd_...
67
+ ```
68
+
69
+ Keep credentials in a server-side secret manager. Do not bundle a Papier API
70
+ token into browser JavaScript, mobile applications, source control, logs, or
71
+ prompts.
72
+
73
+ ### Rotating or short-lived tokens
74
+
75
+ `accessToken` may be a string or a synchronous or asynchronous provider. The
76
+ provider is called immediately before each request.
77
+
78
+ ```ts
79
+ const api = new PapierApiClient({
80
+ baseUrl: "https://api.papierapi.de",
81
+ accessToken: async () => secretManager.read("PAPIERAPI_TOKEN"),
82
+ });
83
+ ```
84
+
85
+ ### Client options
86
+
87
+ | Option | Type | Default | Description |
88
+ | --- | --- | --- | --- |
89
+ | `baseUrl` | `string` | required | Absolute API origin. HTTPS is required except for localhost. Do not include `/v1`, a path, query, or fragment. |
90
+ | `accessToken` | `string \| (() => string \| Promise<string>)` | required | OAuth access token or API key sent as a Bearer token. |
91
+ | `fetcher` | Fetch-compatible function | `globalThis.fetch` | Custom transport for tests, tracing, proxies, or platform integration. |
92
+ | `timeoutMs` | `number` | `30000` | Per-request timeout. Valid range: 1 to 120,000 ms. |
93
+ | `maxResponseBytes` | `number` | `2097152` | Maximum accepted response body. Valid range: 256 bytes to 10 MiB. |
94
+
95
+ ## Workflow
96
+
97
+ A real letter or fax is never sent by a draft or review call. The normal flow
98
+ is:
99
+
100
+ 1. Upload a PDF or provide structured letter content.
101
+ 2. Create a draft.
102
+ 3. Quote the immutable draft.
103
+ 4. Create a human approval request.
104
+ 5. Let a person review the document, recipient, options, and final price.
105
+ 6. After approval, create a direct-payment session or a wallet-backed dispatch.
106
+ 7. Read the server-confirmed payment and dispatch status.
107
+
108
+ Changing the document, recipient, options, or price requires a new approval.
109
+
110
+ ## Prepare a PDF letter for review
111
+
112
+ `prepareLetterForReview()` creates a draft, quotes it, and creates an approval
113
+ link. It does not pay for or send the letter.
114
+
115
+ ```ts
116
+ import { readFile } from "node:fs/promises";
117
+ import { PapierApiClient } from "@papierapi/sdk";
118
+
119
+ const api = new PapierApiClient({
120
+ baseUrl: "https://api.papierapi.de",
121
+ accessToken: process.env.PAPIERAPI_TOKEN!,
122
+ });
123
+
124
+ const pdf = await readFile("./contract.pdf");
125
+ const upload = await api.uploadDocument(pdf);
126
+
127
+ const review = await api.prepareLetterForReview({
128
+ sender_profile_id: process.env.PAPIERAPI_SENDER_PROFILE_ID!,
129
+ recipient: {
130
+ type: "street",
131
+ company: "Example Property Management GmbH",
132
+ name: "Contracts Department",
133
+ street: "Invalidenstrasse",
134
+ house_number: "1",
135
+ postal_code: "10115",
136
+ city: "Berlin",
137
+ country: "DE",
138
+ },
139
+ source: {
140
+ mode: "raw_pdf",
141
+ document_id: upload.data.document.id,
142
+ },
143
+ options: {
144
+ print: "grayscale",
145
+ duplex: true,
146
+ service: "standard",
147
+ },
148
+ metadata: {
149
+ external_reference: "contract-2026-0042",
150
+ },
151
+ });
152
+
153
+ console.log({
154
+ approvalRequestId: review.approval.id,
155
+ draftId: review.draft.id,
156
+ pages: review.draft.page_count,
157
+ totalCents: review.quote.total_cents,
158
+ approvalUrl: review.approval.approval_url,
159
+ });
160
+ ```
161
+
162
+ Open `approvalUrl` in a user-controlled browser. Preparing a review package is
163
+ not evidence that the document was paid for, submitted to a provider, or sent.
164
+
165
+ ### Supported PDF input types
166
+
167
+ `uploadDocument()` accepts:
168
+
169
+ - `Blob`
170
+ - `ArrayBuffer`
171
+ - `Uint8Array`, including Node.js `Buffer`
172
+
173
+ The local size limit is 1 byte through 20 MiB. The API performs additional PDF
174
+ and layout validation.
175
+
176
+ ## Prepare a structured letter
177
+
178
+ Use structured content when Papier API should render the document. The same
179
+ draft, quote, and approval model applies.
180
+
181
+ ```ts
182
+ const review = await api.prepareLetterForReview({
183
+ sender_profile_id: process.env.PAPIERAPI_SENDER_PROFILE_ID!,
184
+ recipient: {
185
+ type: "street",
186
+ name: "Max Mustermann",
187
+ street: "Beispielweg",
188
+ house_number: "4",
189
+ postal_code: "10115",
190
+ city: "Berlin",
191
+ country: "DE",
192
+ },
193
+ source: {
194
+ mode: "structured",
195
+ content: {
196
+ locale: "en-DE",
197
+ date: "2026-07-20",
198
+ subject: "Contract notice",
199
+ salutation: "Dear Mr Mustermann,",
200
+ blocks: [
201
+ {
202
+ type: "paragraph",
203
+ text: "This letter confirms the requested contractual notice.",
204
+ },
205
+ {
206
+ type: "list",
207
+ ordered: false,
208
+ items: ["Reference: 2026-0042", "Effective date: 31 July 2026"],
209
+ },
210
+ ],
211
+ closing: "Kind regards",
212
+ signatory: "Example Operations Team",
213
+ },
214
+ },
215
+ options: {
216
+ print: "grayscale",
217
+ duplex: false,
218
+ service: "registered_insertion",
219
+ },
220
+ });
221
+
222
+ console.log(review.approval.approval_url);
223
+ ```
224
+
225
+ Structured blocks support paragraphs, headings, ordered or unordered lists,
226
+ tables, spacers, closings, and signature placeholders. TypeScript exposes the
227
+ accepted shapes through `CreateLetterDraftInput` and related exported types.
228
+
229
+ ## Prepare a fax for review
230
+
231
+ Fax drafts use an uploaded PDF and an E.164 recipient number.
232
+
233
+ ```ts
234
+ import { readFile } from "node:fs/promises";
235
+
236
+ const upload = await api.uploadDocument(await readFile("./signed-contract.pdf"));
237
+
238
+ const review = await api.prepareFaxForReview({
239
+ sender_profile_id: process.env.PAPIERAPI_SENDER_PROFILE_ID!,
240
+ recipient: {
241
+ fax_number_e164: "+493012345678",
242
+ company: "Example Recipient GmbH",
243
+ name: "Contracts Department",
244
+ recipient_reference: "contract-2026-0042",
245
+ },
246
+ document_id: upload.data.document.id,
247
+ cover: {
248
+ enabled: true,
249
+ subject: "Signed contract",
250
+ note: "Please route this document to the contracts team.",
251
+ },
252
+ options: {
253
+ max_attempts: 2,
254
+ purpose: "contractual",
255
+ },
256
+ });
257
+
258
+ console.log(review.approval.approval_url);
259
+ ```
260
+
261
+ Fax availability depends on the active Papier API environment and sender
262
+ profile. A prepared fax is not proof of transmission.
263
+
264
+ ## Responses
265
+
266
+ Individual client methods return a validated API envelope:
267
+
268
+ ```ts
269
+ const response = await api.getDispatch("dsp_...");
270
+
271
+ console.log(response.request_id);
272
+ console.log(response.data.status);
273
+ console.log(response.next_actions);
274
+ ```
275
+
276
+ The concrete `data` type is inferred for every method. Responses that do not
277
+ match the published schema are rejected with `PapierApiContractError` instead
278
+ of being returned as untrusted data.
279
+
280
+ ## Client API
281
+
282
+ ### Documents, drafts, quotes, and approvals
283
+
284
+ | Method | Purpose |
285
+ | --- | --- |
286
+ | `uploadDocument(document)` | Upload a PDF and return its document ID. |
287
+ | `createLetterDraft(input)` | Create a letter draft from structured content or an uploaded PDF. |
288
+ | `createFaxDraft(input)` | Create a fax draft from an uploaded PDF. |
289
+ | `getDraft(draftId)` | Read a draft and its current verification state. |
290
+ | `quoteDraft(draftId)` | Calculate a time-limited quote for a draft. |
291
+ | `requestApproval(draftId, input)` | Create a human approval request and approval URL. |
292
+ | `prepareLetterForReview(input, expiresInSeconds?)` | Create, quote, and request approval for a letter. Default approval lifetime: 3,600 seconds. |
293
+ | `prepareFaxForReview(input, expiresInSeconds?)` | Create, quote, and request approval for a fax. Default approval lifetime: 3,600 seconds. |
294
+
295
+ ### Payment, dispatch, and status
296
+
297
+ | Method | Purpose |
298
+ | --- | --- |
299
+ | `createDirectPaymentSession(draftId, input, idempotencyKey)` | Create user-controlled Checkout after approval. Opening Checkout does not dispatch. |
300
+ | `getDirectPayment(directPaymentId)` | Read server-confirmed payment and resulting dispatch state. |
301
+ | `createDispatch(draftId, input, idempotencyKey)` | Create a wallet-backed dispatch after approval. |
302
+ | `listDispatches(limit?)` | List up to 100 dispatches. Default: 25. |
303
+ | `getDispatch(dispatchId)` | Read the canonical dispatch state. |
304
+ | `getDispatchSummary(dispatchId)` | Read a redacted channel-specific summary. |
305
+ | `getDispatchEvents(dispatchId)` | Read persisted dispatch events. |
306
+ | `getDispatchProof(dispatchId)` | Read the proof bundle and event-chain verification result. |
307
+ | `cancelDispatch(dispatchId)` | Request cancellation of a dispatch. |
308
+ | `getWallet()` | Read the current wallet balance. |
309
+
310
+ The SDK does not automatically approve, pay for, or dispatch a draft.
311
+
312
+ ## Idempotency and retries
313
+
314
+ The SDK does not automatically retry requests.
315
+
316
+ `createDispatch()` and `createDirectPaymentSession()` require an idempotency
317
+ key. Reuse a key only when retrying the identical logical operation.
318
+
319
+ ```ts
320
+ async function createCheckout(draftId: string, quoteId: string, approvalId: string) {
321
+ const idempotencyKey = `checkout:${draftId}:${approvalId}`;
322
+
323
+ return api.createDirectPaymentSession(
324
+ draftId,
325
+ {
326
+ quote_id: quoteId,
327
+ approval_id: approvalId,
328
+ },
329
+ idempotencyKey,
330
+ );
331
+ }
332
+ ```
333
+
334
+ Idempotency keys must contain 8 to 200 letters, digits, dots, underscores,
335
+ colons, or dashes. Consult `papierApiOperations[name].safeToRetry` before adding
336
+ custom retry behavior around another operation.
337
+
338
+ ## Error handling
339
+
340
+ ```ts
341
+ import {
342
+ PapierApiContractError,
343
+ PapierApiError,
344
+ PapierApiTransportError,
345
+ } from "@papierapi/sdk";
346
+
347
+ try {
348
+ const dispatch = await api.getDispatch("dsp_...");
349
+ console.log(dispatch.data.status);
350
+ } catch (error) {
351
+ if (error instanceof PapierApiError) {
352
+ console.error({
353
+ status: error.status,
354
+ code: error.code,
355
+ requestId: error.requestId,
356
+ retryable: error.retryable,
357
+ fieldPath: error.fieldPath,
358
+ suggestedAction: error.suggestedAction,
359
+ });
360
+ } else if (error instanceof PapierApiTransportError) {
361
+ console.error({ reason: error.reason, retryable: error.retryable });
362
+ } else if (error instanceof PapierApiContractError) {
363
+ console.error({ operationId: error.operationId, status: error.status });
364
+ } else {
365
+ throw error;
366
+ }
367
+ }
368
+ ```
369
+
370
+ Error categories:
371
+
372
+ | Error | Meaning |
373
+ | --- | --- |
374
+ | `PapierApiError` | The API returned a valid structured error response. |
375
+ | `PapierApiTransportError` | The request timed out or the network was unavailable. |
376
+ | `PapierApiContractError` | A response was malformed, too large, or incompatible with the SDK contract. |
377
+ | `TypeError` | Client configuration, token, timeout, response limit, or PDF bounds are invalid. |
378
+ | `ZodError` | A method input, identifier, query parameter, or idempotency key violates the published schema. |
379
+
380
+ Use `requestId` when contacting support about an API response.
381
+
382
+ ## Operation registry
383
+
384
+ The canonical machine-readable operation registry is exported from the
385
+ side-effect-free `@papierapi/sdk/operations` entry point.
386
+
387
+ ```ts
388
+ import {
389
+ papierApiOperations,
390
+ type PapierApiOperationName,
391
+ type PapierApiOperationResponse,
392
+ } from "@papierapi/sdk/operations";
393
+
394
+ const operationName = "getDispatch" satisfies PapierApiOperationName;
395
+ const operation = papierApiOperations[operationName];
396
+
397
+ console.log({
398
+ method: operation.method,
399
+ path: operation.path,
400
+ safeToRetry: operation.safeToRetry,
401
+ requiresIdempotencyKey: operation.requiresIdempotencyKey,
402
+ });
403
+
404
+ type GetDispatchResponse = PapierApiOperationResponse<"getDispatch">;
405
+ ```
406
+
407
+ Each registry entry includes the HTTP method, path, request kind, path and query
408
+ schemas, response schema, retry classification, and idempotency requirement.
409
+
410
+ ## Custom Fetch implementation
411
+
412
+ Inject `fetcher` to integrate tracing, testing, a proxy, or another compatible
413
+ runtime. It must implement the standard Fetch signature and return a standard
414
+ `Response`.
415
+
416
+ ```ts
417
+ const api = new PapierApiClient({
418
+ baseUrl: "http://127.0.0.1:8787",
419
+ accessToken: "pap_test_token",
420
+ fetcher: async (input, init) => {
421
+ telemetry.recordRequest(input, init);
422
+ return fetch(input, init);
423
+ },
424
+ timeoutMs: 10_000,
425
+ });
426
+ ```
427
+
428
+ Plain HTTP is accepted only for `localhost` and `127.0.0.1`. Production origins
429
+ must use HTTPS.
430
+
431
+ ## Security and runtime behavior
432
+
433
+ - Bearer tokens are resolved immediately before every request.
434
+ - Request inputs and identifiers are validated before network access.
435
+ - API responses are size-bounded and schema-validated before being returned.
436
+ - Requests time out through `AbortController`.
437
+ - PDF uploads are copied into a request `Blob` where required.
438
+ - The SDK does not persist credentials, documents, responses, or approval data.
439
+ - The SDK does not perform hidden retries or background dispatches.
440
+
441
+ ## API contract and support
442
+
443
+ - [OpenAPI 3.1 contract](https://api.papierapi.de/openapi.json)
444
+ - [Developer area](https://app.papierapi.de/developers)
445
+ - [Product documentation](https://papierapi.de/entwickler)
446
+ - Technical support: [support@papierapi.de](mailto:support@papierapi.de)
447
+
448
+ ## License
449
+
450
+ MIT. See [LICENSE](LICENSE).