paperless-tax-mcp 0.1.0 → 0.2.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/README.md +79 -29
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +156 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +130 -7
- package/dist/client.js +206 -39
- package/dist/client.js.map +1 -1
- package/dist/export.d.ts +73 -0
- package/dist/export.js +220 -0
- package/dist/export.js.map +1 -0
- package/dist/index.js +0 -0
- package/dist/server.js +38 -19
- package/dist/server.js.map +1 -1
- package/docs/api-surface.md +282 -55
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
# paperless-tax-mcp
|
|
2
2
|
|
|
3
|
-
An **unofficial** MCP server for [paperless.tax](https://www.paperless.tax), the
|
|
4
|
-
bookkeeping/expense platform.
|
|
3
|
+
An **unofficial** MCP server and CLI for [paperless.tax](https://www.paperless.tax), the
|
|
4
|
+
Israeli bookkeeping/expense platform.
|
|
5
5
|
|
|
6
6
|
> ⚠️ Not affiliated with, endorsed by, or supported by Paperless. This talks to the app's
|
|
7
7
|
> own private backend, not a published developer API, and can break without notice.
|
|
8
8
|
> Use it against your own account.
|
|
9
9
|
|
|
10
|
-
> 🚧 **Authentication is unresolved.** The self-service API key the UI issues is *not*
|
|
11
|
-
> accepted as a bearer token on these endpoints — see
|
|
12
|
-
> [`docs/api-surface.md`](docs/api-surface.md#authentication--unresolved). The server is
|
|
13
|
-
> usable today only with a token taken from a logged-in browser session, which is
|
|
14
|
-
> short-lived. Getting this to a durable credential is the project's main open problem;
|
|
15
|
-
> help welcome.
|
|
16
|
-
|
|
17
10
|
The value here is as much [`docs/api-surface.md`](docs/api-surface.md) as the code — no
|
|
18
|
-
public documentation of this API exists anywhere, so the map is the point.
|
|
19
|
-
|
|
11
|
+
public documentation of this API exists anywhere, so the map is the point.
|
|
12
|
+
|
|
13
|
+
## Authentication
|
|
14
|
+
|
|
15
|
+
Use the **session JWT** the web app holds after login, as `Authorization: Bearer <jwt>`.
|
|
16
|
+
|
|
17
|
+
The self-service API key from the UI's API/webhooks modal is **not** a credential for these
|
|
18
|
+
endpoints — it returns 401 under every scheme, and paying for the API add-on does not change
|
|
19
|
+
that. Do not buy the add-on expecting it to help.
|
|
20
|
+
|
|
21
|
+
The good news, contrary to what you would assume of a "session" token: **its `exp` is about
|
|
22
|
+
ten months out**, so one interactive login covers scheduled work for most of a year. Login
|
|
23
|
+
is passwordless (email → entry code), so a fresh token needs a browser once; after that it
|
|
24
|
+
can be lifted from the browser profile.
|
|
25
|
+
[`docs/api-surface.md`](docs/api-surface.md#authentication--resolved) has both.
|
|
20
26
|
|
|
21
|
-
## Use
|
|
27
|
+
## Use as an MCP server
|
|
22
28
|
|
|
23
29
|
Register with any MCP client — Claude Code, Claude Desktop, or a gateway. No install step:
|
|
24
30
|
|
|
@@ -40,47 +46,91 @@ Claude Code, in one line:
|
|
|
40
46
|
claude mcp add paperless-tax --env PAPERLESS_TOKEN=... -- npx -y paperless-tax-mcp
|
|
41
47
|
```
|
|
42
48
|
|
|
49
|
+
## Use as a CLI
|
|
50
|
+
|
|
51
|
+
The same client, for exports and poking at the API:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
export PAPERLESS_TOKEN=...
|
|
55
|
+
|
|
56
|
+
npx paperless-tax whoami # verify the token, print the account id
|
|
57
|
+
npx paperless-tax probe # hit each read endpoint, report what answers
|
|
58
|
+
npx paperless-tax list --type 1 # expenses only
|
|
59
|
+
npx paperless-tax list --from 2025-01 --to 2025-12
|
|
60
|
+
npx paperless-tax export --out export/ # files plus metadata sidecars
|
|
61
|
+
npx paperless-tax raw POST documents/get/downloadurl --json '{"sDocID":"..."}'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`export` writes `{docid}.pdf` plus a `{docid}.json` sidecar under
|
|
65
|
+
`export/expenses/{YYYY}/{YYYY-MM}/`, with a `manifest.jsonl` index. It is resumable —
|
|
66
|
+
documents already on disk are skipped — and the manifest is rewritten from scratch each run,
|
|
67
|
+
so it describes the whole export rather than only what that run fetched.
|
|
68
|
+
|
|
69
|
+
`--from`/`--to` accept `YYYY-MM-DD`, `YYYY-MM` or the API's native `YYMM`. Omit them to get
|
|
70
|
+
everything: the default range is the app's own unbounded `1001..2912`.
|
|
71
|
+
|
|
72
|
+
### Use as a library
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { PaperlessClient } from "paperless-tax-mcp/client";
|
|
76
|
+
import { Exporter } from "paperless-tax-mcp/export";
|
|
77
|
+
|
|
78
|
+
const client = new PaperlessClient({ token });
|
|
79
|
+
const docs = await client.byDate(); // whole history, one call
|
|
80
|
+
const pdfUrl = await client.downloadUrl(docs[0].id);
|
|
81
|
+
```
|
|
82
|
+
|
|
43
83
|
### Configuration
|
|
44
84
|
|
|
45
85
|
| Variable | Required | Meaning |
|
|
46
86
|
|---|---|---|
|
|
47
|
-
| `PAPERLESS_TOKEN` | yes |
|
|
87
|
+
| `PAPERLESS_TOKEN` | yes | The session JWT — see above |
|
|
48
88
|
| `PAPERLESS_USER_ID` | no | 10-char account id; `whoami` discovers it |
|
|
49
89
|
| `PAPERLESS_BASE_URL` | no | Defaults to `https://api.paperless.tax/` |
|
|
50
90
|
|
|
51
91
|
### From source
|
|
52
92
|
|
|
53
93
|
```bash
|
|
54
|
-
npm install && npm run build &&
|
|
94
|
+
npm install && npm run build && npm test
|
|
55
95
|
```
|
|
56
96
|
|
|
57
97
|
## Tools
|
|
58
98
|
|
|
59
99
|
| Tool | Endpoint | Confidence |
|
|
60
100
|
|---|---|---|
|
|
61
|
-
| `whoami` | `GET users/get/params` |
|
|
62
|
-
| `pending_documents` | `GET documents/get/pending/{
|
|
63
|
-
| `list_documents` | `POST bydate` |
|
|
64
|
-
| `get_document` | `GET document/{id}` |
|
|
65
|
-
| `get_download_url` | `POST downloadurl` |
|
|
101
|
+
| `whoami` | `GET users/get/params` | confirmed live |
|
|
102
|
+
| `pending_documents` | `GET documents/get/pending/{uid}` | confirmed live |
|
|
103
|
+
| `list_documents` | `POST documents/get/bydate` | confirmed live |
|
|
104
|
+
| `get_document` | `GET documents/get/document/{id}` | confirmed live |
|
|
105
|
+
| `get_download_url` | `POST documents/get/downloadurl` | confirmed live |
|
|
66
106
|
| `raw_request` | anything | escape hatch for mapping new surface |
|
|
67
107
|
|
|
68
|
-
##
|
|
108
|
+
## Four things that will bite you
|
|
109
|
+
|
|
110
|
+
All documented at length in `docs/api-surface.md`, and all of them cost real time to find:
|
|
69
111
|
|
|
70
|
-
|
|
112
|
+
1. **The controller prefix is not optional.** `POST /bydate` is a 404; the route is
|
|
113
|
+
`POST documents/get/bydate`. Same for `downloadurl`, `searchdp`, `document/{id}`.
|
|
114
|
+
2. **A 401 does not mean the route exists.** Auth runs before routing, so unauthenticated
|
|
115
|
+
*every* path 401s — including nonsense ones. This is what produced the earlier, wrong
|
|
116
|
+
version of the endpoint map. Never infer existence from a 401 here.
|
|
117
|
+
3. **`bydate` takes `YYMM` period strings, not ISO dates.** `sStart: "2506"` is June 2025.
|
|
118
|
+
Give it a date and you get a narrower result set rather than an error.
|
|
119
|
+
4. **A 204 is not success.** Send no credentials at all and you get the same 204. Control-test
|
|
120
|
+
every auth scheme against a deliberately invalid credential.
|
|
71
121
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
122
|
+
Also worth knowing: `documents/get/bydate` returns an entire account history in one
|
|
123
|
+
unpaginated response, and its records are identical to what the per-document detail endpoint
|
|
124
|
+
returns — so a per-document loop is pure waste. And `export/download` with
|
|
125
|
+
`iExportType: 8` (Backup) answers 503 `NotImplementedException`; there is no whole-account
|
|
126
|
+
backup on that route.
|
|
77
127
|
|
|
78
128
|
## Contributing
|
|
79
129
|
|
|
80
130
|
Corrections to `docs/api-surface.md` are the most valuable contribution — especially
|
|
81
|
-
anything that moves a row from *inferred* to *confirmed
|
|
82
|
-
|
|
83
|
-
|
|
131
|
+
anything that moves a row from *inferred* to *confirmed*. Please mark what you verified
|
|
132
|
+
versus what you assumed, and never include account identifiers, tokens, or ingest addresses
|
|
133
|
+
in an issue or PR.
|
|
84
134
|
|
|
85
135
|
## Licence
|
|
86
136
|
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `paperless-tax` — command line access to the same client the MCP server uses.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately dependency-free argument parsing: this package ships as an npx
|
|
6
|
+
* one-liner, and a CLI framework would be most of its install weight.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `paperless-tax` — command line access to the same client the MCP server uses.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately dependency-free argument parsing: this package ships as an npx
|
|
6
|
+
* one-liner, and a CLI framework would be most of its install weight.
|
|
7
|
+
*/
|
|
8
|
+
import { PERIOD_MAX, PERIOD_MIN, PaperlessClient, PaperlessError, toPeriod } from "./client.js";
|
|
9
|
+
import { Exporter } from "./export.js";
|
|
10
|
+
const USAGE = `paperless-tax — unofficial CLI for api.paperless.tax
|
|
11
|
+
|
|
12
|
+
whoami Verify the token and print the account id
|
|
13
|
+
probe Hit each read endpoint once and report what answers
|
|
14
|
+
list [--from X] [--to Y] List documents without downloading
|
|
15
|
+
export [--out DIR] Export PDFs plus metadata sidecars
|
|
16
|
+
raw METHOD PATH [--json S] Call an arbitrary endpoint
|
|
17
|
+
|
|
18
|
+
Options
|
|
19
|
+
--from, --to YYYY-MM-DD, YYYY-MM or YYMM. Default is the app's unbounded
|
|
20
|
+
range (${PERIOD_MIN}..${PERIOD_MAX}), which returns everything.
|
|
21
|
+
--type N iDocType filter; 1 = expense
|
|
22
|
+
--out DIR Export destination (default: export)
|
|
23
|
+
--metadata-only Skip file downloads
|
|
24
|
+
|
|
25
|
+
Requires PAPERLESS_TOKEN — the SPA session JWT, not a self-service API key.
|
|
26
|
+
See docs/api-surface.md §Authentication.`;
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
const positional = [];
|
|
29
|
+
const flags = {};
|
|
30
|
+
for (let i = 0; i < argv.length; i++) {
|
|
31
|
+
const arg = argv[i];
|
|
32
|
+
if (arg.startsWith("--")) {
|
|
33
|
+
const key = arg.slice(2);
|
|
34
|
+
const next = argv[i + 1];
|
|
35
|
+
if (next && !next.startsWith("--")) {
|
|
36
|
+
flags[key] = next;
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
flags[key] = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
positional.push(arg);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { command: positional.shift(), positional, flags };
|
|
48
|
+
}
|
|
49
|
+
const period = (value, fallback) => typeof value === "string" ? toPeriod(value) : fallback;
|
|
50
|
+
async function main() {
|
|
51
|
+
const { command, positional, flags } = parseArgs(process.argv.slice(2));
|
|
52
|
+
if (!command || flags.help || command === "help") {
|
|
53
|
+
console.log(USAGE);
|
|
54
|
+
return command ? 0 : 2;
|
|
55
|
+
}
|
|
56
|
+
const client = new PaperlessClient();
|
|
57
|
+
switch (command) {
|
|
58
|
+
case "whoami": {
|
|
59
|
+
console.log(JSON.stringify(await client.getParams(), null, 2));
|
|
60
|
+
console.log(`\naccount id: ${await client.resolveUserId()}`);
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
case "probe": {
|
|
64
|
+
const periodStart = period(flags.from, PERIOD_MIN);
|
|
65
|
+
const periodEnd = period(flags.to, PERIOD_MAX);
|
|
66
|
+
const checks = [
|
|
67
|
+
["users/get/params", () => client.getParams()],
|
|
68
|
+
["documents/get/pending", () => client.pending()],
|
|
69
|
+
["documents/get/bydate", () => client.byDate({ periodStart, periodEnd })],
|
|
70
|
+
];
|
|
71
|
+
for (const [name, call] of checks) {
|
|
72
|
+
try {
|
|
73
|
+
const result = await call();
|
|
74
|
+
const count = Array.isArray(result) ? `${result.length} record(s)` : "ok";
|
|
75
|
+
console.log(` ${name.padEnd(24)} ok — ${count}`);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.log(` ${name.padEnd(24)} ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
case "list": {
|
|
84
|
+
let docs = await client.byDate({
|
|
85
|
+
periodStart: period(flags.from, PERIOD_MIN),
|
|
86
|
+
periodEnd: period(flags.to, PERIOD_MAX),
|
|
87
|
+
});
|
|
88
|
+
if (typeof flags.type === "string") {
|
|
89
|
+
docs = docs.filter((doc) => doc.iDocType === Number(flags.type));
|
|
90
|
+
}
|
|
91
|
+
for (const doc of docs) {
|
|
92
|
+
const amount = typeof doc.iAmount === "number" ? (doc.iAmount / 100).toFixed(2) : "";
|
|
93
|
+
console.log([
|
|
94
|
+
doc.id,
|
|
95
|
+
doc.sPeriod ?? "",
|
|
96
|
+
String(doc.iDocType ?? ""),
|
|
97
|
+
(doc.sSupplierName ?? "").slice(0, 28).padEnd(28),
|
|
98
|
+
amount.padStart(10),
|
|
99
|
+
].join(" "));
|
|
100
|
+
}
|
|
101
|
+
console.log(`\n${docs.length} document(s)`);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
case "export": {
|
|
105
|
+
const out = typeof flags.out === "string" ? flags.out : "export";
|
|
106
|
+
console.log(`account: ${await client.resolveUserId()}`);
|
|
107
|
+
const exporter = new Exporter(client, out);
|
|
108
|
+
const stats = await exporter.exportRange({
|
|
109
|
+
periodStart: period(flags.from, PERIOD_MIN),
|
|
110
|
+
periodEnd: period(flags.to, PERIOD_MAX),
|
|
111
|
+
docType: typeof flags.type === "string" ? Number(flags.type) : undefined,
|
|
112
|
+
metadataOnly: Boolean(flags["metadata-only"]),
|
|
113
|
+
});
|
|
114
|
+
console.log(`\nlisted ${stats.listed} downloaded ${stats.downloaded} skipped ${stats.skipped} ` +
|
|
115
|
+
`no attachment ${stats.noAttachment} failed ${stats.failed} ` +
|
|
116
|
+
`(${(stats.bytes / 1e6).toFixed(1)} MB)`);
|
|
117
|
+
if (stats.failed)
|
|
118
|
+
console.error(`failures logged to ${exporter.errorsPath}`);
|
|
119
|
+
return stats.failed ? 1 : 0;
|
|
120
|
+
}
|
|
121
|
+
case "raw": {
|
|
122
|
+
const [method, path] = positional;
|
|
123
|
+
if (!method || !path) {
|
|
124
|
+
console.error("raw needs METHOD and PATH");
|
|
125
|
+
return 2;
|
|
126
|
+
}
|
|
127
|
+
let body;
|
|
128
|
+
if (typeof flags.json === "string") {
|
|
129
|
+
body = JSON.parse(flags.json);
|
|
130
|
+
if (body && typeof body === "object") {
|
|
131
|
+
const record = body;
|
|
132
|
+
if (record.sUserID === "auto")
|
|
133
|
+
record.sUserID = await client.resolveUserId();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
console.log(JSON.stringify(await client.request(method, path, body), null, 2));
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
default:
|
|
140
|
+
console.error(`unknown command: ${command}\n`);
|
|
141
|
+
console.error(USAGE);
|
|
142
|
+
return 2;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
main()
|
|
146
|
+
.then((code) => process.exit(code))
|
|
147
|
+
.catch((error) => {
|
|
148
|
+
if (error instanceof PaperlessError) {
|
|
149
|
+
console.error(`error: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
153
|
+
}
|
|
154
|
+
process.exit(1);
|
|
155
|
+
});
|
|
156
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,17 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* HTTP client for api.paperless.tax.
|
|
3
3
|
*
|
|
4
|
-
* Endpoint names were read out of the vendor's own Angular
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Endpoint names and request shapes were read out of the vendor's own Angular
|
|
5
|
+
* bundle and then checked against the live API. See docs/api-surface.md for what
|
|
6
|
+
* is confirmed versus inferred, and for the traps that produced earlier wrong
|
|
7
|
+
* answers — chiefly that this API authenticates *before* it routes, so an
|
|
8
|
+
* unauthenticated 401 says nothing about whether a path exists.
|
|
7
9
|
*/
|
|
8
10
|
export declare const DEFAULT_BASE_URL = "https://api.paperless.tax/";
|
|
11
|
+
/** Enum values read from the SPA bundle. */
|
|
12
|
+
export declare const DocType: {
|
|
13
|
+
readonly Undefined: 0;
|
|
14
|
+
readonly Expense: 1;
|
|
15
|
+
readonly Income: 2;
|
|
16
|
+
readonly Other: 3;
|
|
17
|
+
readonly IncomeExpense: 4;
|
|
18
|
+
};
|
|
19
|
+
export declare const DateMode: {
|
|
20
|
+
readonly Year: 1;
|
|
21
|
+
readonly Month: 2;
|
|
22
|
+
readonly Day: 3;
|
|
23
|
+
};
|
|
24
|
+
export declare const DateType: {
|
|
25
|
+
readonly ByPeriod: 0;
|
|
26
|
+
readonly ByTrans: 1;
|
|
27
|
+
};
|
|
28
|
+
export declare const ExportType: {
|
|
29
|
+
readonly BKMV: 1;
|
|
30
|
+
readonly Backup: 8;
|
|
31
|
+
readonly MOVEIN: 9;
|
|
32
|
+
readonly DocumentGroup: 12;
|
|
33
|
+
};
|
|
34
|
+
/** The app's own defaults for an unbounded range: YYMM, Jan 2010 → Dec 2029. */
|
|
35
|
+
export declare const PERIOD_MIN = "1001";
|
|
36
|
+
export declare const PERIOD_MAX = "2912";
|
|
37
|
+
/**
|
|
38
|
+
* Coerce YYYY-MM-DD / YYYY-MM / YYMM to the YYMM the API wants.
|
|
39
|
+
*
|
|
40
|
+
* `bydate` takes period strings, not dates. Given something else it returns a
|
|
41
|
+
* narrower set rather than complaining, so this throws instead of guessing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function toPeriod(value: string): string;
|
|
44
|
+
/** A document as returned by `documents/get/bydate`. Fields beyond `id` are optional. */
|
|
45
|
+
export interface PaperlessDocument {
|
|
46
|
+
id: string;
|
|
47
|
+
sID?: string;
|
|
48
|
+
sPeriod?: string;
|
|
49
|
+
dtTrans?: string;
|
|
50
|
+
dtCapture?: string;
|
|
51
|
+
iDocType?: number;
|
|
52
|
+
sDocNumber?: string;
|
|
53
|
+
sSupplierName?: string;
|
|
54
|
+
sCurrency?: string;
|
|
55
|
+
/** Integer agorot — iAmount 10341 is ₪103.41. */
|
|
56
|
+
iAmount?: number;
|
|
57
|
+
iVAT?: number;
|
|
58
|
+
iExpenseDeductible?: number;
|
|
59
|
+
aTags?: string[];
|
|
60
|
+
/** Page renders: _1000.jpg for page 1, then _1.jpg, _2.jpg … at 707×1000. */
|
|
61
|
+
aDocURLs?: string[];
|
|
62
|
+
sDocURLEdit?: string;
|
|
63
|
+
sDocTNURL?: string;
|
|
64
|
+
[key: string]: unknown;
|
|
65
|
+
}
|
|
9
66
|
export declare class PaperlessError extends Error {
|
|
10
67
|
status?: number;
|
|
11
68
|
body?: string;
|
|
12
69
|
constructor(message: string, status?: number, body?: string);
|
|
13
70
|
}
|
|
14
|
-
/** 401/403
|
|
71
|
+
/** 401/403. A self-service API key is not a credential here; use the session JWT. */
|
|
15
72
|
export declare class PaperlessAuthError extends PaperlessError {
|
|
16
73
|
constructor(message: string, status?: number, body?: string);
|
|
17
74
|
}
|
|
@@ -32,16 +89,82 @@ export declare class PaperlessClient {
|
|
|
32
89
|
private maxRetries;
|
|
33
90
|
private lastCall;
|
|
34
91
|
constructor(options?: ClientOptions);
|
|
92
|
+
/**
|
|
93
|
+
* Join a path. Every route sits behind a controller prefix — the SPA builds
|
|
94
|
+
* URLs as base + controller + "/" + action + "/" + param, and the prefix is
|
|
95
|
+
* not optional: `POST /bydate` is a 404, `POST documents/get/bydate` is not.
|
|
96
|
+
*/
|
|
35
97
|
buildUrl(...parts: string[]): string;
|
|
36
98
|
private throttle;
|
|
99
|
+
private headersFor;
|
|
100
|
+
/** Shared transport: throttle, retry on 5xx and network errors, classify failures. */
|
|
101
|
+
private send;
|
|
37
102
|
request(method: string, path: string, body?: unknown): Promise<unknown>;
|
|
103
|
+
/** Fetch binary content — presigned S3 URLs and `export/download` blobs. */
|
|
104
|
+
requestBinary(method: string, path: string, body?: unknown): Promise<Uint8Array>;
|
|
38
105
|
getParams(): Promise<unknown>;
|
|
106
|
+
/**
|
|
107
|
+
* The 10-character account id.
|
|
108
|
+
*
|
|
109
|
+
* `users/get/params` carries no `sUserID` field at all, despite that being the
|
|
110
|
+
* parameter name every other endpoint expects. The id is `sID` at the top
|
|
111
|
+
* level; the nested business record repeats it as "V_" + id.
|
|
112
|
+
*/
|
|
39
113
|
resolveUserId(): Promise<string>;
|
|
40
114
|
pending(): Promise<unknown>;
|
|
41
|
-
|
|
115
|
+
/**
|
|
116
|
+
* `POST documents/get/bydate` — the listing call.
|
|
117
|
+
*
|
|
118
|
+
* Returns a bare JSON array with no envelope and no pagination; one call can
|
|
119
|
+
* carry an entire account history. The records are complete — `document()` on
|
|
120
|
+
* any of them returns the same field set, so there is no reason to follow up
|
|
121
|
+
* per document.
|
|
122
|
+
*
|
|
123
|
+
* Periods are YYMM strings, not ISO dates.
|
|
124
|
+
*/
|
|
125
|
+
byDate(options?: {
|
|
126
|
+
periodStart?: string;
|
|
127
|
+
periodEnd?: string;
|
|
128
|
+
dateMode?: number;
|
|
129
|
+
dateType?: number;
|
|
130
|
+
extra?: Record<string, unknown>;
|
|
131
|
+
}): Promise<PaperlessDocument[]>;
|
|
132
|
+
/** `GET documents/get/document/{id}`. Same fields as the listing; for spot checks. */
|
|
42
133
|
document(docId: string): Promise<unknown>;
|
|
43
|
-
|
|
134
|
+
/**
|
|
135
|
+
* `POST documents/get/downloadurl` — presigned URL for the document PDF.
|
|
136
|
+
*
|
|
137
|
+
* Answers with a bare JSON string. `english` asks for the `_eng.pdf` variant.
|
|
138
|
+
* Documents with nothing scanned against them have no PDF and this fails
|
|
139
|
+
* rather than returning an empty URL — check {@link hasAttachment} first.
|
|
140
|
+
*/
|
|
141
|
+
downloadUrl(docId: string, english?: boolean): Promise<string>;
|
|
142
|
+
/** Fetch a presigned URL. No Authorization header — the signature does not cover one. */
|
|
143
|
+
fetchFile(url: string): Promise<Uint8Array>;
|
|
144
|
+
/**
|
|
145
|
+
* `POST export/download` with iExportType=12 — the document's pages as a zip
|
|
146
|
+
* of JPEGs. A lossy alternative to {@link downloadUrl}, kept because it is the
|
|
147
|
+
* app's own download path. Requires a doc id; without one the server answers
|
|
148
|
+
* 503 NotImplementedException rather than producing a bulk archive.
|
|
149
|
+
*/
|
|
150
|
+
documentZip(docId: string): Promise<Uint8Array>;
|
|
44
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Whether a record has any file behind it.
|
|
154
|
+
*
|
|
155
|
+
* A sizeable minority are hand-keyed ledger entries with nothing scanned; they
|
|
156
|
+
* carry no render URLs, and `downloadurl` errors rather than returning nothing.
|
|
157
|
+
*/
|
|
158
|
+
export declare function hasAttachment(doc: PaperlessDocument): boolean;
|
|
159
|
+
/** Pull the URL out of a `downloadurl` response — a bare string, or wrapped. */
|
|
160
|
+
export declare function resolveFileUrl(payload: unknown): string | undefined;
|
|
161
|
+
/**
|
|
162
|
+
* Locate the document list in a response.
|
|
163
|
+
*
|
|
164
|
+
* `bydate` returns a bare array, but other endpoints wrap their payload, so this
|
|
165
|
+
* finds the longest document-like list by structure rather than by a fixed path.
|
|
166
|
+
*/
|
|
167
|
+
export declare function extractDocuments(payload: unknown): PaperlessDocument[];
|
|
45
168
|
/**
|
|
46
169
|
* Depth-first search for a key anywhere in a nested structure. Response
|
|
47
170
|
* envelopes are inconsistent between endpoints, so locating a field by name is
|