@andrewcaires/api 5.7.2 → 5.8.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 +367 -11
- package/dist/index.cjs.js +2 -2
- package/dist/index.d.ts +140 -267
- package/dist/index.esm.js +2 -2
- package/package.json +16 -8
package/README.md
CHANGED
|
@@ -13,33 +13,101 @@ The module is now available on npm! `npm i @andrewcaires/api`
|
|
|
13
13
|
|
|
14
14
|
## Example usage
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
### Environment
|
|
17
|
+
|
|
18
|
+
Copy `.env.example` to `.env` and replace every value enclosed in angle brackets. The values below are intended for local development; use a secret manager in staging and production.
|
|
18
19
|
|
|
20
|
+
```dotenv
|
|
21
|
+
# Runtime
|
|
19
22
|
NODE_ENV=development
|
|
23
|
+
API_NAME=api
|
|
24
|
+
CONSOLE_ERROR=true
|
|
25
|
+
|
|
26
|
+
# Audit
|
|
27
|
+
AUDIT_IGNORE=hash,password
|
|
28
|
+
DATABASE_AUDIT=true
|
|
20
29
|
|
|
21
|
-
|
|
30
|
+
# Database
|
|
31
|
+
DATABASE_URI=mariadb://api:api@127.0.0.1:3306/api
|
|
22
32
|
DATABASE_LOG=false
|
|
33
|
+
DATABASE_PREFIX=
|
|
34
|
+
DATABASE_TIMEZONE=+00:00
|
|
23
35
|
|
|
24
|
-
|
|
36
|
+
# Encryption
|
|
37
|
+
# Generate with: openssl rand -base64 32
|
|
38
|
+
ENCRYPTION_SECRET=<base64-encoded-32-byte-key>
|
|
39
|
+
|
|
40
|
+
# HTTP
|
|
41
|
+
HTTP_CROSS=http://localhost:5173
|
|
42
|
+
HTTP_HYBRID=false
|
|
43
|
+
HTTP_NOTFOUND=
|
|
25
44
|
HTTP_PATH=/api
|
|
26
|
-
|
|
27
|
-
|
|
45
|
+
HTTP_PORT=3000
|
|
46
|
+
HTTP_PUBLIC=./public
|
|
47
|
+
HTTP_REDIRECT=
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
# TLS (optional)
|
|
50
|
+
# HTTP_TLS_CA=./ssl/ca.pem
|
|
51
|
+
# HTTP_TLS_CRT=./ssl/certificate.pem
|
|
52
|
+
# HTTP_TLS_KEY=./ssl/private-key.pem
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
# Models
|
|
55
|
+
MODEL_LIMIT=10
|
|
56
|
+
MODEL_LIMIT_MAX=100
|
|
57
|
+
|
|
58
|
+
# MCP
|
|
59
|
+
MCP_ALLOWED_HOSTS=localhost,127.0.0.1,[::1]
|
|
60
|
+
MCP_ALLOWED_ORIGINS=localhost,127.0.0.1,[::1]
|
|
61
|
+
MCP_MAX_REQUEST_BODY_SIZE=4194304
|
|
62
|
+
|
|
63
|
+
# Password hashing
|
|
64
|
+
PASSWORD_ALGORITHM=2
|
|
65
|
+
PASSWORD_MEMORY_COST=4096
|
|
66
|
+
PASSWORD_PARALLELISM=1
|
|
67
|
+
PASSWORD_PEPPER=<random-password-pepper>
|
|
68
|
+
PASSWORD_REUSE=false
|
|
69
|
+
PASSWORD_TIME_COST=3
|
|
70
|
+
|
|
71
|
+
# Request delays
|
|
72
|
+
SLEEP_AUTH=2s
|
|
73
|
+
SLEEP_FORGOT=5s
|
|
74
|
+
SLEEP_REGISTRY=5s
|
|
75
|
+
|
|
76
|
+
# Tenant
|
|
77
|
+
TENANT_HEADER=X-Tenant-ID
|
|
78
|
+
|
|
79
|
+
# Tokens and API keys
|
|
35
80
|
TOKEN_COOKIE=authorization
|
|
36
81
|
TOKEN_COOKIE_SET=true
|
|
37
82
|
TOKEN_HEADER=authorization
|
|
83
|
+
TOKEN_PEPPER=<random-token-pepper>
|
|
84
|
+
TOKEN_PREFIX=api
|
|
85
|
+
TOKEN_QUERY=api_key
|
|
86
|
+
TOKEN_SECRET=<random-token-secret>
|
|
87
|
+
TOKEN_TTL=1d
|
|
88
|
+
TOKEN_TTL_FORGOT=1d
|
|
89
|
+
TOKEN_TTL_RENEW=1d
|
|
38
90
|
TOKEN_TYPE=bearer
|
|
39
91
|
|
|
92
|
+
# Request body
|
|
93
|
+
API_BODYPARSER_STRICT=true
|
|
94
|
+
API_BODYPARSER_INFLATE=true
|
|
95
|
+
API_BODYPARSER_LIMIT=10mb
|
|
96
|
+
API_BODYPARSER_TYPE=json
|
|
97
|
+
|
|
98
|
+
# Sessions
|
|
99
|
+
API_SESSION_NAME=session.id
|
|
100
|
+
API_SESSION_LIFETIME=1d
|
|
101
|
+
API_SESSION_SECRET=<random-session-secret>
|
|
102
|
+
|
|
103
|
+
# WebSocket
|
|
40
104
|
API_WEBSOCKET_START=false
|
|
41
105
|
```
|
|
42
106
|
|
|
107
|
+
`DATABASE_URI` is required. Replace every secret placeholder with an independently generated value; never reuse the token, password, encryption, or session secrets. Keep `.env` out of version control. `SETUP_ADMIN_PASSWORD` is intentionally omitted because it should only be injected into the one-shot initial setup process.
|
|
108
|
+
|
|
109
|
+
### Application
|
|
110
|
+
|
|
43
111
|
```js
|
|
44
112
|
// index.ts
|
|
45
113
|
|
|
@@ -80,6 +148,294 @@ const main = async () => {
|
|
|
80
148
|
main().catch(console.log);
|
|
81
149
|
```
|
|
82
150
|
|
|
151
|
+
## HTTP endpoints
|
|
152
|
+
|
|
153
|
+
The paths below use the default `HTTP_PATH=/api`. Replace `/api` with the configured value when a different HTTP prefix is used. Only controllers passed to `Application` are exposed.
|
|
154
|
+
|
|
155
|
+
`Authenticated` means the route uses `@IsAuthenticated`. Most authenticated routes also require action, scope, or resource permissions through decorators such as `CanRead`, `CanCreate`, `CanUpdate`, `CanDelete`, `HasScope`, or `HasPermissions`.
|
|
156
|
+
|
|
157
|
+
### Audits
|
|
158
|
+
|
|
159
|
+
| Method | Path | Access |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| `GET` | `/api/audits` | Authenticated |
|
|
162
|
+
| `GET` | `/api/audits/record/:record` | Authenticated |
|
|
163
|
+
| `GET` | `/api/audits/:id` | Authenticated |
|
|
164
|
+
|
|
165
|
+
### Authentication
|
|
166
|
+
|
|
167
|
+
| Method | Path | Access |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| `POST` | `/api/auth/login` | Public |
|
|
170
|
+
| `GET` | `/api/auth/logout` | Authenticated |
|
|
171
|
+
| `GET` | `/api/auth/me` | Authenticated |
|
|
172
|
+
| `POST` | `/api/auth/renew` | Public |
|
|
173
|
+
|
|
174
|
+
### Files
|
|
175
|
+
|
|
176
|
+
| Method | Path | Access |
|
|
177
|
+
| --- | --- | --- |
|
|
178
|
+
| `GET` | `/api/files/path` | Authenticated |
|
|
179
|
+
| `GET` | `/api/files/path/*path` | Authenticated |
|
|
180
|
+
|
|
181
|
+
### Password recovery
|
|
182
|
+
|
|
183
|
+
| Method | Path | Access |
|
|
184
|
+
| --- | --- | --- |
|
|
185
|
+
| `GET` | `/api/forgot/check` | Public |
|
|
186
|
+
| `POST` | `/api/forgot` | Public |
|
|
187
|
+
| `POST` | `/api/forgot/reset` | Public |
|
|
188
|
+
|
|
189
|
+
### Groups
|
|
190
|
+
|
|
191
|
+
| Method | Path | Access |
|
|
192
|
+
| --- | --- | --- |
|
|
193
|
+
| `POST` | `/api/groups` | Authenticated |
|
|
194
|
+
| `GET` | `/api/groups` | Authenticated |
|
|
195
|
+
| `GET` | `/api/groups/count` | Authenticated |
|
|
196
|
+
| `GET` | `/api/groups/:id` | Authenticated |
|
|
197
|
+
| `PUT` | `/api/groups/:id` | Authenticated |
|
|
198
|
+
| `DELETE` | `/api/groups/:id` | Authenticated |
|
|
199
|
+
| `GET` | `/api/groups/:id/users` | Authenticated |
|
|
200
|
+
| `GET` | `/api/groups/:id/permissions` | Authenticated |
|
|
201
|
+
| `POST` | `/api/groups/:id/roles` | Authenticated |
|
|
202
|
+
| `GET` | `/api/groups/:id/roles` | Authenticated |
|
|
203
|
+
| `PUT` | `/api/groups/:id/roles` | Authenticated |
|
|
204
|
+
| `DELETE` | `/api/groups/:id/roles` | Authenticated |
|
|
205
|
+
|
|
206
|
+
### Health check
|
|
207
|
+
|
|
208
|
+
| Method | Path | Access |
|
|
209
|
+
| --- | --- | --- |
|
|
210
|
+
| `ALL` | `/api/health/` | Public |
|
|
211
|
+
|
|
212
|
+
`ALL` registers the health check for every HTTP method.
|
|
213
|
+
|
|
214
|
+
### Logs
|
|
215
|
+
|
|
216
|
+
| Method | Path | Access |
|
|
217
|
+
| --- | --- | --- |
|
|
218
|
+
| `GET` | `/api/logs` | Authenticated |
|
|
219
|
+
|
|
220
|
+
### MCP
|
|
221
|
+
|
|
222
|
+
| Method | Path | Access |
|
|
223
|
+
| --- | --- | --- |
|
|
224
|
+
| `ALL` | `/api/mcp` | Bearer-authenticated, except CORS preflight |
|
|
225
|
+
| `ALL` | `/api/mcp/public` | Public |
|
|
226
|
+
|
|
227
|
+
Both endpoints use the official Model Context Protocol TypeScript SDK v2 with a stateless server created for each request. Register `McpController` alongside the application controllers that expose MCP tools. The authenticated endpoint includes every discovered tool; the public endpoint includes only tools explicitly declared with `public: true`.
|
|
228
|
+
|
|
229
|
+
Every request to `/api/mcp` is authenticated independently with `Authorization: Bearer <token>`. Application sessions are not reused as MCP sessions, no `MCP-Session-Id` is persisted, and no MCP session TTL needs to be configured. `/api/mcp/public` does not accept authentication as a substitute for tool visibility; its catalog is restricted at server creation.
|
|
230
|
+
|
|
231
|
+
`HEAD` requests return `204 No Content` for compatibility with connectivity checks. The authenticated endpoint still requires a valid Bearer token; the public endpoint does not. These requests do not initialize an MCP session or invoke a tool.
|
|
232
|
+
|
|
233
|
+
When the authenticated user belongs to one or more tenants, the request must also send `X-Tenant-ID` (or the configured `TENANT_HEADER`). Permissions are loaded only for that tenant; handlers should use `context.principal.tenantId` as their tenant boundary.
|
|
234
|
+
|
|
235
|
+
`MCP_ALLOWED_HOSTS` and `MCP_ALLOWED_ORIGINS` are comma-separated hostname allowlists without scheme or port. Both default to `localhost,127.0.0.1,[::1]`. `MCP_MAX_REQUEST_BODY_SIZE` limits MCP request bodies in bytes and defaults to `4194304` (4 MiB).
|
|
236
|
+
|
|
237
|
+
Clients should send `Content-Type: application/json` and advertise both supported response formats with `Accept: application/json, text/event-stream`. Use an official MCP client instead of managing protocol initialization or transport headers manually. See the official [MCP Streamable HTTP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) and [TypeScript SDK HTTP documentation](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/serving/http.md).
|
|
238
|
+
|
|
239
|
+
#### Tool permissions
|
|
240
|
+
|
|
241
|
+
Tools are private by default. Set `public: true` only when a tool can be safely discovered and called without an authenticated identity:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
@McpTool({
|
|
245
|
+
name: "status",
|
|
246
|
+
title: "Service status",
|
|
247
|
+
description: "Returns public service availability.",
|
|
248
|
+
public: true,
|
|
249
|
+
input: {},
|
|
250
|
+
output: {
|
|
251
|
+
available: Validation.boolean(),
|
|
252
|
+
},
|
|
253
|
+
annotations: {
|
|
254
|
+
readOnlyHint: true,
|
|
255
|
+
destructiveHint: false,
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
protected async status() {
|
|
259
|
+
|
|
260
|
+
return { available: true };
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Public tools are available at both `/api/mcp/public` and the authenticated `/api/mcp` endpoint. A public tool cannot declare `requiredPermissions`; this ambiguous configuration fails during startup. Omitting both options keeps the tool authenticated without requiring a specific application permission.
|
|
265
|
+
|
|
266
|
+
Declare the application permissions required to call a tool with `requiredPermissions`:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
@McpTool({
|
|
270
|
+
name: "tickets.find",
|
|
271
|
+
title: "Find tickets",
|
|
272
|
+
description: "Finds tickets in the selected tenant.",
|
|
273
|
+
input: {},
|
|
274
|
+
requiredPermissions: ["tickets.read"],
|
|
275
|
+
})
|
|
276
|
+
protected async findTickets(_args: TypeAnyObject, context: McpToolContext) {
|
|
277
|
+
|
|
278
|
+
return this.ticketService.find({ tenantId: context.principal?.tenantId });
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
All declared permissions are required. An empty or omitted array allows every authenticated MCP principal to call the tool. Missing permissions return an MCP tool error without invoking the decorated method.
|
|
283
|
+
|
|
284
|
+
`McpController` resolves permissions from the authenticated user's active roles and groups for the selected tenant. Applications with tenants must also send the configured tenant header, `X-Tenant-ID` by default. The application principal remains authoritative for permission checks.
|
|
285
|
+
|
|
286
|
+
### Pages
|
|
287
|
+
|
|
288
|
+
| Method | Path | Access |
|
|
289
|
+
| --- | --- | --- |
|
|
290
|
+
| `POST` | `/api/pages` | Authenticated |
|
|
291
|
+
| `GET` | `/api/pages` | Authenticated |
|
|
292
|
+
| `GET` | `/api/pages/count` | Authenticated |
|
|
293
|
+
| `GET` | `/api/pages/:id` | Authenticated |
|
|
294
|
+
| `PUT` | `/api/pages/:id` | Authenticated |
|
|
295
|
+
| `DELETE` | `/api/pages/:id` | Authenticated |
|
|
296
|
+
| `PUT` | `/api/pages/position` | Authenticated |
|
|
297
|
+
|
|
298
|
+
### Permissions
|
|
299
|
+
|
|
300
|
+
| Method | Path | Access |
|
|
301
|
+
| --- | --- | --- |
|
|
302
|
+
| `POST` | `/api/permissions` | Authenticated |
|
|
303
|
+
| `GET` | `/api/permissions` | Authenticated |
|
|
304
|
+
| `GET` | `/api/permissions/count` | Authenticated |
|
|
305
|
+
| `GET` | `/api/permissions/:id` | Authenticated |
|
|
306
|
+
| `PUT` | `/api/permissions/:id` | Authenticated |
|
|
307
|
+
| `DELETE` | `/api/permissions/:id` | Authenticated |
|
|
308
|
+
| `GET` | `/api/permissions/:id/groups` | Authenticated |
|
|
309
|
+
|
|
310
|
+
### Profile
|
|
311
|
+
|
|
312
|
+
| Method | Path | Access |
|
|
313
|
+
| --- | --- | --- |
|
|
314
|
+
| `GET` | `/api/profile` | Authenticated |
|
|
315
|
+
| `POST` | `/api/profile/avatar` | Authenticated |
|
|
316
|
+
| `DELETE` | `/api/profile/avatar` | Authenticated |
|
|
317
|
+
| `POST` | `/api/profile/password` | Authenticated |
|
|
318
|
+
|
|
319
|
+
### Registration
|
|
320
|
+
|
|
321
|
+
| Method | Path | Access |
|
|
322
|
+
| --- | --- | --- |
|
|
323
|
+
| `POST` | `/api/register` | Public |
|
|
324
|
+
| `POST` | `/api/register/confirm` | Public |
|
|
325
|
+
| `POST` | `/api/register/send` | Public |
|
|
326
|
+
|
|
327
|
+
### Roles
|
|
328
|
+
|
|
329
|
+
| Method | Path | Access |
|
|
330
|
+
| --- | --- | --- |
|
|
331
|
+
| `POST` | `/api/roles` | Authenticated |
|
|
332
|
+
| `GET` | `/api/roles` | Authenticated |
|
|
333
|
+
| `GET` | `/api/roles/count` | Authenticated |
|
|
334
|
+
| `GET` | `/api/roles/:id` | Authenticated |
|
|
335
|
+
| `PUT` | `/api/roles/:id` | Authenticated |
|
|
336
|
+
| `DELETE` | `/api/roles/:id` | Authenticated |
|
|
337
|
+
| `GET` | `/api/roles/:id/groups` | Authenticated |
|
|
338
|
+
| `POST` | `/api/roles/:id/permissions` | Authenticated |
|
|
339
|
+
| `GET` | `/api/roles/:id/permissions` | Authenticated |
|
|
340
|
+
| `PUT` | `/api/roles/:id/permissions` | Authenticated |
|
|
341
|
+
| `DELETE` | `/api/roles/:id/permissions` | Authenticated |
|
|
342
|
+
|
|
343
|
+
### Tenants
|
|
344
|
+
|
|
345
|
+
| Method | Path | Access |
|
|
346
|
+
| --- | --- | --- |
|
|
347
|
+
| `POST` | `/api/tenants` | Authenticated |
|
|
348
|
+
| `GET` | `/api/tenants` | Authenticated |
|
|
349
|
+
| `GET` | `/api/tenants/count` | Authenticated |
|
|
350
|
+
| `GET` | `/api/tenants/:id` | Authenticated |
|
|
351
|
+
| `PUT` | `/api/tenants/:id` | Authenticated |
|
|
352
|
+
| `DELETE` | `/api/tenants/:id` | Authenticated |
|
|
353
|
+
| `GET` | `/api/tenants/:id/users` | Authenticated |
|
|
354
|
+
|
|
355
|
+
### Test
|
|
356
|
+
|
|
357
|
+
| Method | Path | Access |
|
|
358
|
+
| --- | --- | --- |
|
|
359
|
+
| `GET` | `/api/test` | Public |
|
|
360
|
+
|
|
361
|
+
The test controller is intended for development and should not be registered in production applications.
|
|
362
|
+
|
|
363
|
+
### Users
|
|
364
|
+
|
|
365
|
+
#### User records and passwords
|
|
366
|
+
|
|
367
|
+
| Method | Path | Access |
|
|
368
|
+
| --- | --- | --- |
|
|
369
|
+
| `POST` | `/api/users` | Authenticated |
|
|
370
|
+
| `GET` | `/api/users` | Authenticated |
|
|
371
|
+
| `GET` | `/api/users/count` | Authenticated |
|
|
372
|
+
| `GET` | `/api/users/:id` | Authenticated |
|
|
373
|
+
| `PUT` | `/api/users/:id` | Authenticated |
|
|
374
|
+
| `DELETE` | `/api/users/:id` | Authenticated |
|
|
375
|
+
| `POST` | `/api/users/:id/password` | Authenticated |
|
|
376
|
+
|
|
377
|
+
#### User groups
|
|
378
|
+
|
|
379
|
+
| Method | Path | Access |
|
|
380
|
+
| --- | --- | --- |
|
|
381
|
+
| `POST` | `/api/users/:id/groups` | Authenticated |
|
|
382
|
+
| `GET` | `/api/users/:id/groups` | Authenticated |
|
|
383
|
+
| `PUT` | `/api/users/:id/groups` | Authenticated |
|
|
384
|
+
| `DELETE` | `/api/users/:id/groups` | Authenticated |
|
|
385
|
+
|
|
386
|
+
#### User tenants
|
|
387
|
+
|
|
388
|
+
| Method | Path | Access |
|
|
389
|
+
| --- | --- | --- |
|
|
390
|
+
| `POST` | `/api/users/:id/tenants` | Authenticated |
|
|
391
|
+
| `GET` | `/api/users/:id/tenants` | Authenticated |
|
|
392
|
+
| `PUT` | `/api/users/:id/tenants` | Authenticated |
|
|
393
|
+
| `DELETE` | `/api/users/:id/tenants` | Authenticated |
|
|
394
|
+
|
|
395
|
+
#### User API keys
|
|
396
|
+
|
|
397
|
+
| Method | Path | Access |
|
|
398
|
+
| --- | --- | --- |
|
|
399
|
+
| `POST` | `/api/users/keys` | Authenticated |
|
|
400
|
+
| `GET` | `/api/users/keys` | Authenticated |
|
|
401
|
+
| `GET` | `/api/users/keys/:id` | Authenticated |
|
|
402
|
+
| `POST` | `/api/users/keys/:id/revoke` | Authenticated |
|
|
403
|
+
| `DELETE` | `/api/users/keys/:id` | Authenticated |
|
|
404
|
+
| `POST` | `/api/users/:id/keys` | Authenticated |
|
|
405
|
+
| `GET` | `/api/users/:id/keys` | Authenticated |
|
|
406
|
+
| `GET` | `/api/users/:user/keys/:id` | Authenticated |
|
|
407
|
+
| `POST` | `/api/users/:id/keys/:key/revoke` | Authenticated |
|
|
408
|
+
| `DELETE` | `/api/users/:id/keys/:key` | Authenticated |
|
|
409
|
+
|
|
410
|
+
## Initial setup
|
|
411
|
+
|
|
412
|
+
Initial users and authorization records are created through `SetupService`, not through an HTTP endpoint. Run it from a one-shot script after preparing the application and before starting the server.
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
import { Application, SetupService } from "@andrewcaires/api";
|
|
416
|
+
import { controllers } from "./controllers";
|
|
417
|
+
|
|
418
|
+
const password = process.env.SETUP_ADMIN_PASSWORD;
|
|
419
|
+
|
|
420
|
+
if (!password) {
|
|
421
|
+
|
|
422
|
+
throw new Error("SETUP_ADMIN_PASSWORD is required");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const app = new Application(controllers);
|
|
426
|
+
|
|
427
|
+
await app.prepare();
|
|
428
|
+
|
|
429
|
+
await new SetupService().setup({
|
|
430
|
+
name: "Admin",
|
|
431
|
+
email: "admin@example.com",
|
|
432
|
+
username: "admin",
|
|
433
|
+
password,
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
The setup is transactional, refuses to run when any user already exists, and requires an administrator password with at least 12 characters. Inject `SETUP_ADMIN_PASSWORD` only for the one-shot process through a secret manager or protected environment; do not commit it to an `.env` file.
|
|
438
|
+
|
|
83
439
|
### Links
|
|
84
440
|
|
|
85
441
|
* [Docs](https://www.npmjs.com/package/@andrewcaires/api)
|