@cloudpdf/contract 3.0.0-next.1 → 3.0.0-next.11

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/dist/openapi.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // src/openapi.ts
2
- import { zodToJsonSchema } from "zod-to-json-schema";
2
+ import { EngineErrorPayloadSchema as EngineErrorPayloadSchema2, PdfActionWireComponents } from "@embedpdf/engine-core/wire";
3
3
  import { z as z2 } from "zod";
4
- import { EngineErrorPayloadSchema as EngineErrorPayloadSchema2 } from "@embedpdf/engine-core/wire";
4
+ import { ignoreOverride, zodToJsonSchema } from "zod-to-json-schema";
5
5
 
6
6
  // src/index.ts
7
- import { z } from "zod";
8
7
  import {
9
8
  AnnotationListPageSnapshotSchema,
9
+ AnnotationListSnapshotAllPagesSchema,
10
10
  DocumentHeadSchema,
11
11
  DocumentManifestSchema,
12
12
  DocumentMetadataSchema,
@@ -16,26 +16,50 @@ import {
16
16
  PageTextSnapshotSchema,
17
17
  wireTemplates
18
18
  } from "@embedpdf/engine-core/wire";
19
+ import { z } from "zod";
19
20
  var sha256Hex = /^[0-9a-f]{64}$/i;
20
21
  var docIdPattern = /^[A-Za-z0-9_-]+$/;
21
22
  var tenantIdPattern = /^[A-Za-z0-9_-]+$/;
23
+ var originPattern = /^https?:\/\/(\*\.)?[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*(:\d{1,5})?$/;
24
+ var originPatternArray = z.array(
25
+ z.string().max(262).regex(
26
+ originPattern,
27
+ "must be a web origin like https://example.com (optionally one leading *. wildcard label, optional port, no path)"
28
+ )
29
+ ).min(1).max(32);
30
+ var shareTokenPattern = /^shr_[A-Za-z0-9_-]{24}$/;
22
31
  var adminWirePaths = {
23
32
  tenants: "/v1/tenants",
24
33
  tenant: (tenantId) => `/v1/tenants/${encodeURIComponent(tenantId)}`,
25
34
  documents: (tenantId) => `/v1/tenants/${encodeURIComponent(tenantId)}/documents`,
26
35
  documentsInit: (tenantId) => `/v1/tenants/${encodeURIComponent(tenantId)}/documents/init`,
36
+ documentsImport: (tenantId) => `/v1/tenants/${encodeURIComponent(tenantId)}/documents/import`,
27
37
  document: (tenantId, docId) => `/v1/tenants/${encodeURIComponent(tenantId)}/documents/${encodeURIComponent(docId)}`,
28
38
  documentCommit: (tenantId, docId) => `/v1/tenants/${encodeURIComponent(tenantId)}/documents/${encodeURIComponent(docId)}/commit`,
29
- documentUploadDirect: (tenantId, docId) => `/v1/tenants/${encodeURIComponent(tenantId)}/documents/${encodeURIComponent(docId)}/upload-direct`,
39
+ documentUploadProxy: (tenantId, docId) => `/v1/tenants/${encodeURIComponent(tenantId)}/documents/${encodeURIComponent(docId)}/upload-proxy`,
30
40
  documentDownload: (tenantId, docId) => `/v1/tenants/${encodeURIComponent(tenantId)}/documents/${encodeURIComponent(docId)}/download`,
31
41
  /** The warmed dashboard-tile artifact. Returns 404 while `pending`/`locked`. */
32
42
  documentThumbnail: (tenantId, docId) => `/v1/tenants/${encodeURIComponent(tenantId)}/documents/${encodeURIComponent(docId)}/thumbnail`,
33
43
  tokenIssue: (tenantId) => `/v1/tenants/${encodeURIComponent(tenantId)}/tokens`,
34
44
  tokenRevoke: (tenantId, jti) => `/v1/tenants/${encodeURIComponent(tenantId)}/tokens/${encodeURIComponent(jti)}/revoke`,
45
+ shares: (tenantId) => `/v1/tenants/${encodeURIComponent(tenantId)}/shares`,
46
+ share: (tenantId, shareId) => `/v1/tenants/${encodeURIComponent(tenantId)}/shares/${encodeURIComponent(shareId)}`,
47
+ tenantUsage: (tenantId) => `/v1/tenants/${encodeURIComponent(tenantId)}/usage`,
48
+ tenantSuspend: (tenantId) => `/v1/tenants/${encodeURIComponent(tenantId)}/suspend`,
49
+ tenantResume: (tenantId) => `/v1/tenants/${encodeURIComponent(tenantId)}/resume`,
50
+ /**
51
+ * The public share-session exchange. Deliberately outside any tenant
52
+ * subtree: the caller holds only a share token and does not know the
53
+ * tenant — the grant row resolves it server-side.
54
+ */
55
+ shareSessions: "/v1/share-sessions",
35
56
  /** Deployment-global singletons: API-token only, no tenant context. */
36
57
  deploymentLicenseStatus: "/v1/deployment/license/status"
37
58
  };
38
- var DedupModeSchema = z.enum(["always-create", "reuse-existing"]);
59
+ var DedupModeSchema = z.enum(["always-create", "reuse-existing"]).describe(
60
+ "always-create (default) creates a new document every time. reuse-existing returns a document that already holds the same content instead of storing it twice."
61
+ );
62
+ var UploadPreferenceSchema = z.enum(["auto", "presigned", "proxy"]);
39
63
  var DocumentStateSchema = z.enum(["pending", "ready", "failed", "deleting"]);
40
64
  var AdminDocumentRecordSchema = z.object({
41
65
  id: z.string(),
@@ -60,7 +84,8 @@ var AdminDocumentInitRequestSchema = z.object({
60
84
  idempotencyKey: z.string().optional(),
61
85
  dedupMode: DedupModeSchema.optional(),
62
86
  docId: z.string().regex(docIdPattern).optional(),
63
- uploadTtlSec: z.number().finite().min(60).max(3600).optional()
87
+ uploadTtlSec: z.number().finite().min(60).max(3600).optional(),
88
+ uploadPreference: UploadPreferenceSchema.optional()
64
89
  });
65
90
  var AdminPresignedUploadSchema = z.object({
66
91
  url: z.string(),
@@ -75,7 +100,7 @@ var AdminInitUploadSchema = z.discriminatedUnion("kind", [
75
100
  key: z.string()
76
101
  }),
77
102
  z.object({
78
- kind: z.literal("direct"),
103
+ kind: z.literal("proxy"),
79
104
  url: z.string(),
80
105
  key: z.string()
81
106
  })
@@ -102,6 +127,79 @@ var AdminDocumentCommitRequestSchema = z.object({
102
127
  var AdminDocumentCommitResponseSchema = z.object({
103
128
  document: AdminDocumentRecordSchema
104
129
  });
130
+ var utf8ByteLength = (s) => new TextEncoder().encode(s).length;
131
+ var AdminImportSourceSchema = z.discriminatedUnion("kind", [
132
+ z.object({
133
+ kind: z.literal("url"),
134
+ url: z.string().url().max(8192).describe(
135
+ "The URL to fetch. Must be allowed by the deployment import policy (scheme, network range, size) and must declare a length."
136
+ )
137
+ }).describe(
138
+ "The caller supplies the authority: a presigned S3/GCS/Azure/R2/MinIO GET, or any HTTPS endpoint the deployment import policy allows. The URL is a capability \u2014 treat it as a secret. CloudPDF never echoes its query string back in errors, logs, or stored failure reasons."
139
+ ),
140
+ z.object({
141
+ kind: z.literal("connection"),
142
+ connectionId: z.string().min(1).max(128).describe("The operator-registered storage connection to read from."),
143
+ key: z.string().min(1).refine((k) => !k.includes("\0"), "key must not contain NUL").refine((k) => utf8ByteLength(k) <= 1024, "key must be at most 1024 UTF-8 bytes").describe(
144
+ "The object key to read, inside the connection's configured scope. At most 1024 UTF-8 bytes."
145
+ ),
146
+ revision: z.string().min(1).max(1024).optional().describe(
147
+ "Pins a specific version of the object. Provider-interpreted (S3 VersionId, GCS generation, Azure version id); providers without versioning reject it."
148
+ )
149
+ }).describe(
150
+ "The operator pre-registered the authority: the request names a connection and a key inside it. Which provider backs the connection (S3, GCS, Azure Blob, filesystem, ...) is deployment configuration, never wire surface."
151
+ )
152
+ ]).describe(
153
+ "Where CloudPDF pulls the bytes from. The two shapes differ in WHO supplies the authority to read, not in which storage vendor holds the file."
154
+ );
155
+ var AdminDocumentImportRequestSchema = z.object({
156
+ source: AdminImportSourceSchema,
157
+ /**
158
+ * Optional integrity pins, enforced when present: `sizeBytes`
159
+ * against the source's declared Content-Length before the transfer,
160
+ * `sha256` against the server-observed digest after it. When absent
161
+ * the server-observed values become authoritative.
162
+ * `dedupMode=reuse-existing` REQUIRES `sha256` — without a declared
163
+ * hash the server cannot know what content to reuse.
164
+ */
165
+ expected: z.object({
166
+ sizeBytes: z.number().int().min(1).optional().describe("Checked against the source's declared Content-Length before the transfer."),
167
+ sha256: z.string().regex(sha256Hex).optional().describe(
168
+ "Checked against the server-observed digest after the transfer. Required when dedupMode is reuse-existing."
169
+ )
170
+ }).optional().describe(
171
+ "Integrity pins, enforced when present. When absent, the server-observed values become authoritative."
172
+ ),
173
+ metadata: z.record(z.string(), z.unknown()).optional(),
174
+ idempotencyKey: z.string().optional().describe(
175
+ "Retrying with the same key resumes the same document rather than importing a second copy \u2014 including after a 502."
176
+ ),
177
+ dedupMode: DedupModeSchema.optional(),
178
+ docId: z.string().regex(docIdPattern).optional(),
179
+ /**
180
+ * `sync` (default) holds the response open for the whole transfer.
181
+ * `async` accepts the request (202, document `pending`), transfers
182
+ * in the background, and the caller polls the document. Async
183
+ * requires a `connection` source (URLs are secrets and expire, so
184
+ * they cannot sit in a durable job); filesystem connections
185
+ * additionally require `expected.sha256`, since they have no
186
+ * revisions to pin retries to.
187
+ */
188
+ mode: z.enum(["sync", "async"]).optional().describe(
189
+ "sync (default) holds the response open for the whole transfer. async answers 202 with the document pending and transfers in the background; it requires a connection source, and filesystem connections additionally require expected.sha256."
190
+ )
191
+ });
192
+ var AdminDocumentImportResponseSchema = z.object({
193
+ /**
194
+ * `imported` — bytes were pulled, verified, and committed (sync).
195
+ * `deduped` — an existing document satisfied the request without a
196
+ * transfer (content dedup or idempotent replay).
197
+ * `accepted` — async: the job is queued (HTTP 202); poll the
198
+ * document until `ready` or `failed`.
199
+ */
200
+ tag: z.enum(["imported", "deduped", "accepted"]),
201
+ document: AdminDocumentRecordSchema
202
+ });
105
203
  var AdminDocumentResponseSchema = z.object({
106
204
  document: AdminDocumentRecordSchema
107
205
  });
@@ -121,7 +219,7 @@ var AdminDocumentListResponseSchema = z.object({
121
219
  */
122
220
  nextCursor: z.string().nullable().optional()
123
221
  });
124
- var AdminUploadDirectResponseSchema = z.object({
222
+ var AdminUploadProxyResponseSchema = z.object({
125
223
  sha256: z.string().regex(sha256Hex)
126
224
  });
127
225
  var AdminErrorPayloadSchema = z.object({
@@ -168,6 +266,13 @@ var AdminTenantRecordSchema = z.object({
168
266
  name: z.string(),
169
267
  /** True when the namespace materialized on first use rather than via explicit create. */
170
268
  autoProvisioned: z.boolean(),
269
+ /**
270
+ * `suspended` tenants fail closed: every tenant JWT, doc JWT, and
271
+ * share exchange is refused until resume. The API token is exempt —
272
+ * the operator must be able to inspect, resume, or delete a
273
+ * suspended tenant. Optional because older servers omit it.
274
+ */
275
+ status: z.enum(["active", "suspended"]).optional(),
171
276
  createdAt: z.number()
172
277
  });
173
278
  var AdminTenantCreateRequestSchema = z.object({
@@ -197,7 +302,8 @@ var adminTenantScopes = [
197
302
  "docs.read",
198
303
  "docs.delete",
199
304
  "tokens.issue-doc",
200
- "tokens.revoke"
305
+ "tokens.revoke",
306
+ "shares.manage"
201
307
  ];
202
308
  var AdminTokenIssueDocRequestSchema = z.object({
203
309
  kind: z.literal("doc"),
@@ -215,6 +321,14 @@ var AdminTokenIssueDocRequestSchema = z.object({
215
321
  displayName: z.string().max(256).optional(),
216
322
  groupId: z.string().max(256).optional(),
217
323
  groups: z.array(z.string().max(256)).max(64).optional(),
324
+ /**
325
+ * Origin lock: web origins (scheme + host, optional port; one leading
326
+ * `*.` wildcard label allowed) the minted token may be presented
327
+ * from. Enforced on every engine request that carries a browser
328
+ * `Origin` header — hotlink prevention, not DRM: non-browser callers
329
+ * are governed by the token itself.
330
+ */
331
+ origins: originPatternArray.optional(),
218
332
  /** Token lifetime in seconds. */
219
333
  expiresIn: z.number().int().min(60).max(60 * 60 * 24 * 90)
220
334
  });
@@ -235,9 +349,110 @@ var AdminTokenIssueResponseSchema = z.object({
235
349
  /** Unix seconds. */
236
350
  expiresAt: z.number()
237
351
  });
352
+ var ShareGrantRecordSchema = z.object({
353
+ /** Grant id; doubles as the public share token (`shr_…`). */
354
+ id: z.string(),
355
+ tenantId: z.string(),
356
+ docId: z.string(),
357
+ layerName: z.string(),
358
+ /** Doc capabilities the exchanged session carries. */
359
+ scope: z.array(z.string()),
360
+ /** Origin allowlist; null means any origin. */
361
+ origins: z.array(z.string()).nullable(),
362
+ /** True when a passphrase is required at exchange. The passphrase itself is never returned. */
363
+ passwordProtected: z.boolean(),
364
+ /** Lifetime of each exchanged session token, in seconds. */
365
+ sessionTtlSeconds: z.number(),
366
+ /** Paused grants refuse exchange (404) but keep their configuration. */
367
+ disabled: z.boolean(),
368
+ /** Unix epoch ms after which exchange returns 410; null = no expiry. */
369
+ expiresAt: z.number().nullable(),
370
+ /** Total successful exchanges — a dashboard convenience, not the usage meter. */
371
+ exchangeCount: z.number(),
372
+ lastExchangedAt: z.number().nullable(),
373
+ createdBy: z.string(),
374
+ createdAt: z.number(),
375
+ updatedAt: z.number()
376
+ });
377
+ var ShareGrantCreateRequestSchema = z.object({
378
+ docId: z.string().regex(docIdPattern),
379
+ layerName: z.string().min(1).max(256).optional(),
380
+ /**
381
+ * Doc capability scopes the exchanged session will carry. Validated
382
+ * server-side against the engine's scope vocabulary, same as
383
+ * `tokens.issue`.
384
+ */
385
+ scope: z.array(z.string().min(1).max(128)).min(1).max(64),
386
+ /** Origin allowlist; omit to allow any origin. */
387
+ origins: originPatternArray.optional(),
388
+ /** Optional passphrase gate, checked at exchange. Stored as a hash; never returned. */
389
+ password: z.string().min(1).max(1024).optional(),
390
+ /** Lifetime of exchanged session tokens. Default 600. */
391
+ sessionTtlSeconds: z.number().int().min(60).max(60 * 60).optional(),
392
+ /** Unix epoch ms after which the grant stops exchanging; omit for no expiry. */
393
+ expiresAt: z.number().int().positive().optional()
394
+ });
395
+ var ShareGrantUpdateRequestSchema = z.object({
396
+ scope: z.array(z.string().min(1).max(128)).min(1).max(64).optional(),
397
+ origins: originPatternArray.nullable().optional(),
398
+ password: z.string().min(1).max(1024).nullable().optional(),
399
+ sessionTtlSeconds: z.number().int().min(60).max(60 * 60).optional(),
400
+ disabled: z.boolean().optional(),
401
+ expiresAt: z.number().int().positive().nullable().optional()
402
+ });
403
+ var ShareGrantResponseSchema = z.object({
404
+ share: ShareGrantRecordSchema
405
+ });
406
+ var ShareGrantListQuerySchema = z.object({
407
+ limit: z.coerce.number().int().min(1).max(ADMIN_DOCUMENT_LIST_MAX_LIMIT).default(ADMIN_DOCUMENT_LIST_DEFAULT_LIMIT),
408
+ cursor: z.string().min(1).optional(),
409
+ /** Filter to one document's grants. */
410
+ docId: z.string().regex(docIdPattern).optional()
411
+ });
412
+ var ShareGrantListResponseSchema = z.object({
413
+ shares: z.array(ShareGrantRecordSchema),
414
+ /** Opaque cursor for the next page; `null` on the last page. */
415
+ nextCursor: z.string().nullable().optional()
416
+ });
417
+ var AdminTenantShareParamsSchema = z.object({
418
+ tenantId: z.string().regex(tenantIdPattern),
419
+ shareId: z.string().regex(shareTokenPattern)
420
+ });
421
+ var ShareExchangeRequestSchema = z.object({
422
+ shareToken: z.string().regex(shareTokenPattern),
423
+ /** Required when the grant is passphrase-protected (422 otherwise). */
424
+ password: z.string().min(1).max(1024).optional()
425
+ });
426
+ var ShareSessionResponseSchema = z.object({
427
+ /** A doc-scoped session JWT carrying the grant's capabilities and origin lock. */
428
+ token: z.string(),
429
+ docId: z.string(),
430
+ layerName: z.string(),
431
+ /** Unix seconds when the session token expires; exchange again for a fresh one. */
432
+ expiresAt: z.number()
433
+ });
434
+ var TenantUsageQuerySchema = z.object({
435
+ /** UTC month to report, `YYYY-MM`. Defaults to the current month. */
436
+ period: z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional()
437
+ });
438
+ var TenantUsageResponseSchema = z.object({
439
+ tenantId: z.string(),
440
+ periodStart: z.string(),
441
+ periodEnd: z.string(),
442
+ metrics: z.object({
443
+ "pdf.views": z.number(),
444
+ "pdf.uploads": z.number(),
445
+ "storage.bytes": z.number()
446
+ })
447
+ });
448
+ var TenantSuspendRequestSchema = z.object({
449
+ /** Optional operator reason, written to the security-events trail. */
450
+ reason: z.string().max(1024).optional()
451
+ });
238
452
  var adminOperations = {
239
453
  "tenants.create": {
240
454
  operationId: "tenants.create",
455
+ title: "Create tenant",
241
456
  summary: "Create a tenant, or confirm it already exists \u2014 ensure-style, idempotent.",
242
457
  method: "POST",
243
458
  path: adminWirePaths.tenants,
@@ -251,6 +466,7 @@ var adminOperations = {
251
466
  },
252
467
  "tenants.list": {
253
468
  operationId: "tenants.list",
469
+ title: "List tenants",
254
470
  summary: "List tenants, newest first, cursor-paginated.",
255
471
  method: "GET",
256
472
  path: adminWirePaths.tenants,
@@ -264,6 +480,7 @@ var adminOperations = {
264
480
  },
265
481
  "tenants.get": {
266
482
  operationId: "tenants.get",
483
+ title: "Get tenant",
267
484
  summary: "Fetch one tenant record.",
268
485
  method: "GET",
269
486
  path: "/v1/tenants/:tenantId",
@@ -277,6 +494,7 @@ var adminOperations = {
277
494
  },
278
495
  "tenants.delete": {
279
496
  operationId: "tenants.delete",
497
+ title: "Delete tenant",
280
498
  summary: "Delete a tenant and everything under it.",
281
499
  method: "DELETE",
282
500
  path: "/v1/tenants/:tenantId",
@@ -291,6 +509,7 @@ var adminOperations = {
291
509
  },
292
510
  "documents.init": {
293
511
  operationId: "documents.init",
512
+ title: "Initialize upload",
294
513
  summary: "Begin an upload: create (or resume/dedupe) a pending document and issue upload access.",
295
514
  method: "POST",
296
515
  path: "/v1/tenants/:tenantId/documents/init",
@@ -305,6 +524,7 @@ var adminOperations = {
305
524
  },
306
525
  "documents.commit": {
307
526
  operationId: "documents.commit",
527
+ title: "Commit upload",
308
528
  summary: "Finish an upload: verify the SHA-256 and promote the document to ready.",
309
529
  method: "POST",
310
530
  path: "/v1/tenants/:tenantId/documents/:id/commit",
@@ -318,24 +538,47 @@ var adminOperations = {
318
538
  404: { contentType: "application/json", schema: AdminErrorPayloadSchema }
319
539
  }
320
540
  },
321
- "documents.uploadDirect": {
322
- operationId: "documents.uploadDirect",
323
- summary: "Upload the PDF bytes through the origin instead of a presigned URL.",
541
+ "documents.uploadProxy": {
542
+ operationId: "documents.uploadProxy",
543
+ title: "Upload document through the origin",
544
+ summary: "Upload a bounded PDF through the API when the initialized transfer kind is proxy.",
324
545
  method: "POST",
325
- path: "/v1/tenants/:tenantId/documents/:id/upload-direct",
546
+ path: "/v1/tenants/:tenantId/documents/:id/upload-proxy",
326
547
  credentials: ["api-token", "tenant-jwt"],
327
548
  scope: ["docs.create"],
328
549
  params: AdminTenantDocParamsSchema,
329
550
  body: {
330
- contentType: ["application/pdf", "application/octet-stream", "multipart/form-data"]
551
+ contentType: "multipart/form-data"
331
552
  },
332
553
  responses: {
333
- 200: { contentType: "application/json", schema: AdminUploadDirectResponseSchema },
334
- 400: { contentType: "application/json", schema: AdminErrorPayloadSchema }
335
- }
554
+ 200: { contentType: "application/json", schema: AdminUploadProxyResponseSchema },
555
+ 400: { contentType: "application/json", schema: AdminErrorPayloadSchema },
556
+ 409: { contentType: "application/json", schema: AdminErrorPayloadSchema }
557
+ },
558
+ notes: "This bounded origin-mediated fallback must only be used after documents.init returns upload.kind=proxy. Auto mode prefers a presigned object-store PUT whenever available."
559
+ },
560
+ "documents.importFrom": {
561
+ operationId: "documents.importFrom",
562
+ title: "Import document",
563
+ summary: "Server-side pull: fetch a PDF from a caller-supplied URL (e.g. a presigned object-store GET) or an operator-registered storage connection into CloudPDF-owned storage, verify it, and commit it.",
564
+ method: "POST",
565
+ path: "/v1/tenants/:tenantId/documents/import",
566
+ credentials: ["api-token", "tenant-jwt"],
567
+ scope: ["docs.create"],
568
+ params: AdminTenantParamsSchema,
569
+ body: { contentType: "application/json", schema: AdminDocumentImportRequestSchema },
570
+ responses: {
571
+ 200: { contentType: "application/json", schema: AdminDocumentImportResponseSchema },
572
+ 202: { contentType: "application/json", schema: AdminDocumentImportResponseSchema },
573
+ 400: { contentType: "application/json", schema: AdminErrorPayloadSchema },
574
+ 403: { contentType: "application/json", schema: AdminErrorPayloadSchema },
575
+ 502: { contentType: "application/json", schema: AdminErrorPayloadSchema }
576
+ },
577
+ notes: "Default mode is synchronous and bounded: the response returns only after the transfer verified and committed (or failed). mode=async (connection sources only) answers 202 immediately and an in-process worker performs the transfer with leased, fenced retries; poll the document until ready/failed. The deployment import policy gates scheme, network range, and size; sources must declare a length. CloudPDF copies and owns the bytes \u2014 the source is never referenced in place. A 502 marks a retryable upstream failure: retry with the same idempotencyKey to resume the same document. URL sources are capabilities and never echoed back. Connection sources name operator-registered storage (bucket/prefix scope, allowed credential classes, and tenant bindings are deployment configuration); `revision` is provider-interpreted (S3 VersionId, GCS generation, Azure version id)."
336
578
  },
337
579
  "documents.list": {
338
580
  operationId: "documents.list",
581
+ title: "List documents",
339
582
  summary: "List documents in the tenant, newest first, cursor-paginated.",
340
583
  method: "GET",
341
584
  path: "/v1/tenants/:tenantId/documents",
@@ -350,6 +593,7 @@ var adminOperations = {
350
593
  },
351
594
  "documents.get": {
352
595
  operationId: "documents.get",
596
+ title: "Get document",
353
597
  summary: "Fetch one document record.",
354
598
  method: "GET",
355
599
  path: "/v1/tenants/:tenantId/documents/:id",
@@ -364,6 +608,7 @@ var adminOperations = {
364
608
  },
365
609
  "documents.download": {
366
610
  operationId: "documents.download",
611
+ title: "Download document",
367
612
  summary: "Download the stored base PDF bytes.",
368
613
  method: "GET",
369
614
  path: "/v1/tenants/:tenantId/documents/:id/download",
@@ -378,6 +623,7 @@ var adminOperations = {
378
623
  },
379
624
  "documents.delete": {
380
625
  operationId: "documents.delete",
626
+ title: "Delete document",
381
627
  summary: "Delete a document and its stored artifacts.",
382
628
  method: "DELETE",
383
629
  path: "/v1/tenants/:tenantId/documents/:id",
@@ -392,6 +638,7 @@ var adminOperations = {
392
638
  },
393
639
  "documents.thumbnail": {
394
640
  operationId: "documents.thumbnail",
641
+ title: "Get thumbnail",
395
642
  summary: "Fetch the warmed dashboard-tile render for a document.",
396
643
  method: "GET",
397
644
  path: "/v1/tenants/:tenantId/documents/:id/thumbnail",
@@ -405,6 +652,7 @@ var adminOperations = {
405
652
  },
406
653
  "deployment.licenseStatus": {
407
654
  operationId: "deployment.licenseStatus",
655
+ title: "License status",
408
656
  summary: "License decision plus usage-reporting and meter snapshots for this deployment.",
409
657
  method: "GET",
410
658
  path: adminWirePaths.deploymentLicenseStatus,
@@ -416,6 +664,7 @@ var adminOperations = {
416
664
  },
417
665
  "tokens.issue": {
418
666
  operationId: "tokens.issue",
667
+ title: "Issue token",
419
668
  summary: "Mint a delegated JWT: a doc token, or (API token only) a tenant token.",
420
669
  method: "POST",
421
670
  path: "/v1/tenants/:tenantId/tokens",
@@ -433,6 +682,7 @@ var adminOperations = {
433
682
  },
434
683
  "tokens.revoke": {
435
684
  operationId: "tokens.revoke",
685
+ title: "Revoke token",
436
686
  summary: "Revoke a token by jti; live sessions drop on their next heartbeat.",
437
687
  method: "POST",
438
688
  path: "/v1/tenants/:tenantId/tokens/:jti/revoke",
@@ -449,6 +699,153 @@ var adminOperations = {
449
699
  400: { contentType: "application/json", schema: AdminErrorPayloadSchema }
450
700
  },
451
701
  notes: "Mounted only when the deployment enables token revocation."
702
+ },
703
+ "tenants.usage": {
704
+ operationId: "tenants.usage",
705
+ title: "Tenant usage",
706
+ summary: "Per-tenant usage facts (views, uploads, stored bytes) for one UTC month.",
707
+ method: "GET",
708
+ path: "/v1/tenants/:tenantId/usage",
709
+ credentials: ["api-token", "tenant-jwt"],
710
+ scope: ["docs.read"],
711
+ params: AdminTenantParamsSchema,
712
+ query: TenantUsageQuerySchema,
713
+ responses: {
714
+ 200: { contentType: "application/json", schema: TenantUsageResponseSchema },
715
+ 400: { contentType: "application/json", schema: AdminErrorPayloadSchema },
716
+ 404: { contentType: "application/json", schema: AdminErrorPayloadSchema }
717
+ },
718
+ notes: "Facts only \u2014 no limits or billing state. Views count share exchanges plus authorized /v1/access grants, deduplicated across the two."
719
+ },
720
+ "tenants.suspend": {
721
+ operationId: "tenants.suspend",
722
+ title: "Suspend tenant",
723
+ summary: "Fail the tenant closed: refuse every tenant JWT, doc JWT, and share exchange.",
724
+ method: "POST",
725
+ path: "/v1/tenants/:tenantId/suspend",
726
+ credentials: ["api-token"],
727
+ scope: [],
728
+ params: AdminTenantParamsSchema,
729
+ body: {
730
+ contentType: "application/json",
731
+ schema: TenantSuspendRequestSchema,
732
+ required: false
733
+ },
734
+ responses: {
735
+ 204: {},
736
+ 404: { contentType: "application/json", schema: AdminErrorPayloadSchema }
737
+ },
738
+ notes: "Instantly reversible with resume. The API token is exempt, so a suspended tenant can still be inspected, exported, resumed, or deleted."
739
+ },
740
+ "tenants.resume": {
741
+ operationId: "tenants.resume",
742
+ title: "Resume tenant",
743
+ summary: "Lift a suspension; credentials for the tenant verify again.",
744
+ method: "POST",
745
+ path: "/v1/tenants/:tenantId/resume",
746
+ credentials: ["api-token"],
747
+ scope: [],
748
+ params: AdminTenantParamsSchema,
749
+ responses: {
750
+ 204: {},
751
+ 404: { contentType: "application/json", schema: AdminErrorPayloadSchema }
752
+ }
753
+ },
754
+ "shares.create": {
755
+ operationId: "shares.create",
756
+ title: "Create share",
757
+ summary: "Create a standing share grant for one document \u2014 the no-backend embed credential.",
758
+ method: "POST",
759
+ path: "/v1/tenants/:tenantId/shares",
760
+ credentials: ["api-token", "tenant-jwt"],
761
+ scope: ["shares.manage"],
762
+ params: AdminTenantParamsSchema,
763
+ body: { contentType: "application/json", schema: ShareGrantCreateRequestSchema },
764
+ responses: {
765
+ 200: { contentType: "application/json", schema: ShareGrantResponseSchema },
766
+ 400: { contentType: "application/json", schema: AdminErrorPayloadSchema },
767
+ 403: { contentType: "application/json", schema: AdminErrorPayloadSchema },
768
+ 404: { contentType: "application/json", schema: AdminErrorPayloadSchema }
769
+ },
770
+ notes: "The returned share id IS the public share token. Mounted only when the deployment can sign (HS256 mode) \u2014 exchange mints session JWTs, so grants exist only where minting does."
771
+ },
772
+ "shares.list": {
773
+ operationId: "shares.list",
774
+ title: "List shares",
775
+ summary: "List share grants in the tenant, newest first, cursor-paginated.",
776
+ method: "GET",
777
+ path: "/v1/tenants/:tenantId/shares",
778
+ credentials: ["api-token", "tenant-jwt"],
779
+ scope: ["shares.manage"],
780
+ params: AdminTenantParamsSchema,
781
+ query: ShareGrantListQuerySchema,
782
+ responses: {
783
+ 200: { contentType: "application/json", schema: ShareGrantListResponseSchema },
784
+ 400: { contentType: "application/json", schema: AdminErrorPayloadSchema }
785
+ }
786
+ },
787
+ "shares.get": {
788
+ operationId: "shares.get",
789
+ title: "Get share",
790
+ summary: "Fetch one share grant.",
791
+ method: "GET",
792
+ path: "/v1/tenants/:tenantId/shares/:shareId",
793
+ credentials: ["api-token", "tenant-jwt"],
794
+ scope: ["shares.manage"],
795
+ params: AdminTenantShareParamsSchema,
796
+ responses: {
797
+ 200: { contentType: "application/json", schema: ShareGrantResponseSchema },
798
+ 404: { contentType: "application/json", schema: AdminErrorPayloadSchema }
799
+ }
800
+ },
801
+ "shares.update": {
802
+ operationId: "shares.update",
803
+ title: "Update share",
804
+ summary: "Edit a grant in place \u2014 every embedded copy of the token follows the row.",
805
+ method: "PATCH",
806
+ path: "/v1/tenants/:tenantId/shares/:shareId",
807
+ credentials: ["api-token", "tenant-jwt"],
808
+ scope: ["shares.manage"],
809
+ params: AdminTenantShareParamsSchema,
810
+ body: { contentType: "application/json", schema: ShareGrantUpdateRequestSchema },
811
+ responses: {
812
+ 200: { contentType: "application/json", schema: ShareGrantResponseSchema },
813
+ 400: { contentType: "application/json", schema: AdminErrorPayloadSchema },
814
+ 404: { contentType: "application/json", schema: AdminErrorPayloadSchema }
815
+ }
816
+ },
817
+ "shares.delete": {
818
+ operationId: "shares.delete",
819
+ title: "Revoke share",
820
+ summary: "Delete a grant; exchange stops immediately, live sessions lapse at their exp.",
821
+ method: "DELETE",
822
+ path: "/v1/tenants/:tenantId/shares/:shareId",
823
+ credentials: ["api-token", "tenant-jwt"],
824
+ scope: ["shares.manage"],
825
+ params: AdminTenantShareParamsSchema,
826
+ responses: {
827
+ 204: {},
828
+ 404: { contentType: "application/json", schema: AdminErrorPayloadSchema }
829
+ }
830
+ },
831
+ "shares.exchange": {
832
+ operationId: "shares.exchange",
833
+ title: "Exchange share token",
834
+ summary: "Trade a public share token for a short-lived document session JWT.",
835
+ method: "POST",
836
+ path: adminWirePaths.shareSessions,
837
+ credentials: [],
838
+ scope: [],
839
+ body: { contentType: "application/json", schema: ShareExchangeRequestSchema },
840
+ responses: {
841
+ 200: { contentType: "application/json", schema: ShareSessionResponseSchema },
842
+ 400: { contentType: "application/json", schema: AdminErrorPayloadSchema },
843
+ 403: { contentType: "application/json", schema: AdminErrorPayloadSchema },
844
+ 404: { contentType: "application/json", schema: AdminErrorPayloadSchema },
845
+ 410: { contentType: "application/json", schema: AdminErrorPayloadSchema },
846
+ 422: { contentType: "application/json", schema: AdminErrorPayloadSchema }
847
+ },
848
+ notes: "Unauthenticated, but requires a browser Origin header, checked against the grant allowlist. Unknown, revoked, and disabled tokens are indistinguishable (404). Passphrase-protected grants return 422 SharePasswordRequired until `password` is supplied. Mounted only when the deployment can sign (HS256 mode)."
452
849
  }
453
850
  };
454
851
  var DocIdParamsSchema = z.object({
@@ -477,6 +874,7 @@ var MutationResponseSchema = z.object({ meta: MutationMetaSchema }).passthrough(
477
874
  var docOperations = {
478
875
  "doc.head": {
479
876
  operationId: "doc.head",
877
+ title: "Open document",
480
878
  summary: "Open a document and return its head (versions, page count, security).",
481
879
  method: "GET",
482
880
  path: wireTemplates.docHead,
@@ -492,6 +890,7 @@ var docOperations = {
492
890
  },
493
891
  "doc.manifest": {
494
892
  operationId: "doc.manifest",
893
+ title: "Get manifest",
495
894
  summary: "Full layer manifest at the layer's current version.",
496
895
  method: "GET",
497
896
  path: wireTemplates.layerManifest,
@@ -507,6 +906,7 @@ var docOperations = {
507
906
  },
508
907
  "doc.metadata.get": {
509
908
  operationId: "doc.metadata.get",
909
+ title: "Get metadata",
510
910
  summary: "Document metadata (PDF info dictionary view) for a layer.",
511
911
  method: "GET",
512
912
  path: wireTemplates.layerMetadata,
@@ -522,6 +922,7 @@ var docOperations = {
522
922
  },
523
923
  "doc.render": {
524
924
  operationId: "doc.render",
925
+ title: "Render page",
525
926
  summary: "Render one page as an image at the current layer version.",
526
927
  method: "GET",
527
928
  path: wireTemplates.layerRenderPage,
@@ -538,6 +939,7 @@ var docOperations = {
538
939
  },
539
940
  "doc.text": {
540
941
  operationId: "doc.text",
942
+ title: "Extract page text",
541
943
  summary: "Extracted text content for one page.",
542
944
  method: "GET",
543
945
  path: wireTemplates.layerTextPage,
@@ -553,6 +955,7 @@ var docOperations = {
553
955
  },
554
956
  "doc.annotations.list": {
555
957
  operationId: "doc.annotations.list",
958
+ title: "List annotations",
556
959
  summary: "One page's annotations at the current layer version.",
557
960
  method: "GET",
558
961
  path: wireTemplates.layerAnnotationItems,
@@ -566,8 +969,27 @@ var docOperations = {
566
969
  404: { contentType: "application/json", schema: EngineErrorPayloadSchema }
567
970
  }
568
971
  },
972
+ "doc.annotations.listAll": {
973
+ operationId: "doc.annotations.listAll",
974
+ title: "List all annotations",
975
+ summary: "All pages' annotations as one coherent snapshot at the current layer version.",
976
+ method: "GET",
977
+ path: wireTemplates.layerAnnotationItemsAll,
978
+ credentials: docCredentials,
979
+ scope: [],
980
+ docCapabilities: ["doc.annotate.read"],
981
+ requestHeaders: [documentPasswordHeader],
982
+ params: DocLayerParamsSchema,
983
+ notes: "Returns one entry per page plus the audit-log cursor for reconciling subsequent document events. Page order is unspecified; join by `pageState.pageObjectNumber` when display order matters.",
984
+ responses: {
985
+ 200: { contentType: "application/json", schema: AnnotationListSnapshotAllPagesSchema },
986
+ 404: { contentType: "application/json", schema: EngineErrorPayloadSchema },
987
+ 409: { contentType: "application/json", schema: EngineErrorPayloadSchema }
988
+ }
989
+ },
569
990
  "doc.annotations.create": {
570
991
  operationId: "doc.annotations.create",
992
+ title: "Create annotation",
571
993
  summary: "Create an annotation on a page.",
572
994
  method: "POST",
573
995
  path: wireTemplates.layerAnnotationItems,
@@ -586,6 +1008,7 @@ var docOperations = {
586
1008
  },
587
1009
  "doc.annotations.update": {
588
1010
  operationId: "doc.annotations.update",
1011
+ title: "Update annotation",
589
1012
  summary: "Update one annotation by key.",
590
1013
  method: "PATCH",
591
1014
  path: wireTemplates.layerAnnotationItem,
@@ -603,6 +1026,7 @@ var docOperations = {
603
1026
  },
604
1027
  "doc.annotations.delete": {
605
1028
  operationId: "doc.annotations.delete",
1029
+ title: "Delete annotation",
606
1030
  summary: "Delete one annotation by key.",
607
1031
  method: "DELETE",
608
1032
  path: wireTemplates.layerAnnotationItem,
@@ -618,6 +1042,7 @@ var docOperations = {
618
1042
  },
619
1043
  "doc.forms.get": {
620
1044
  operationId: "doc.forms.get",
1045
+ title: "Get form snapshot",
621
1046
  summary: "Reconciled form snapshot: fields, widgets, values.",
622
1047
  method: "GET",
623
1048
  path: wireTemplates.layerForm,
@@ -633,6 +1058,7 @@ var docOperations = {
633
1058
  },
634
1059
  "doc.forms.setValue": {
635
1060
  operationId: "doc.forms.setValue",
1061
+ title: "Set form value",
636
1062
  summary: "Set one form field's value.",
637
1063
  method: "POST",
638
1064
  path: wireTemplates.layerFormFieldValue,
@@ -650,6 +1076,7 @@ var docOperations = {
650
1076
  },
651
1077
  "doc.forms.reset": {
652
1078
  operationId: "doc.forms.reset",
1079
+ title: "Reset form field",
653
1080
  summary: "Reset one form field to its default value.",
654
1081
  method: "POST",
655
1082
  path: wireTemplates.layerFormFieldReset,
@@ -665,6 +1092,7 @@ var docOperations = {
665
1092
  },
666
1093
  "doc.forms.exportData": {
667
1094
  operationId: "doc.forms.exportData",
1095
+ title: "Export form data",
668
1096
  summary: "Export form data as FDF or XFDF.",
669
1097
  method: "GET",
670
1098
  path: wireTemplates.layerFormData,
@@ -681,6 +1109,7 @@ var docOperations = {
681
1109
  },
682
1110
  "doc.forms.importData": {
683
1111
  operationId: "doc.forms.importData",
1112
+ title: "Import form data",
684
1113
  summary: "Import form data (FDF/XFDF), filling matching fields.",
685
1114
  method: "POST",
686
1115
  path: wireTemplates.layerFormData,
@@ -698,6 +1127,7 @@ var docOperations = {
698
1127
  },
699
1128
  "doc.pages.move": {
700
1129
  operationId: "doc.pages.move",
1130
+ title: "Move pages",
701
1131
  summary: "Reorder pages.",
702
1132
  method: "POST",
703
1133
  path: wireTemplates.layerPagesMove,
@@ -715,6 +1145,7 @@ var docOperations = {
715
1145
  },
716
1146
  "doc.pages.rotate": {
717
1147
  operationId: "doc.pages.rotate",
1148
+ title: "Rotate pages",
718
1149
  summary: "Set absolute rotation on pages.",
719
1150
  method: "POST",
720
1151
  path: wireTemplates.layerPagesRotate,
@@ -732,6 +1163,7 @@ var docOperations = {
732
1163
  },
733
1164
  "doc.pages.delete": {
734
1165
  operationId: "doc.pages.delete",
1166
+ title: "Delete pages",
735
1167
  summary: "Delete pages.",
736
1168
  method: "POST",
737
1169
  path: wireTemplates.layerPagesDelete,
@@ -749,6 +1181,7 @@ var docOperations = {
749
1181
  },
750
1182
  "doc.pages.flatten": {
751
1183
  operationId: "doc.pages.flatten",
1184
+ title: "Flatten pages",
752
1185
  summary: "Flatten annotations and form fields into page content.",
753
1186
  method: "POST",
754
1187
  path: wireTemplates.layerPagesFlatten,
@@ -764,8 +1197,68 @@ var docOperations = {
764
1197
  404: { contentType: "application/json", schema: EngineErrorPayloadSchema }
765
1198
  }
766
1199
  },
1200
+ "doc.pages.insert": {
1201
+ operationId: "doc.pages.insert",
1202
+ title: "Insert pages from a PDF",
1203
+ summary: "Copy every page of an uploaded PDF into the document at an index.",
1204
+ method: "POST",
1205
+ path: wireTemplates.layerPagesInsert,
1206
+ credentials: docCredentials,
1207
+ scope: [],
1208
+ docCapabilities: ["doc.pages.assemble"],
1209
+ requestHeaders: [documentPasswordHeader],
1210
+ params: DocLayerParamsSchema,
1211
+ body: {
1212
+ contentType: "multipart/form-data"
1213
+ },
1214
+ responses: {
1215
+ 200: { contentType: "application/json", schema: MutationResponseSchema },
1216
+ 400: { contentType: "application/json", schema: EngineErrorPayloadSchema },
1217
+ 404: { contentType: "application/json", schema: EngineErrorPayloadSchema }
1218
+ },
1219
+ notes: 'Multipart mutation envelope: a `body` field holding `{"destIndex"?: number}` (omitted \u2192 append) plus a `resource:source` file part carrying the standalone PDF whose pages are copied in. The inserted copies get fresh page object numbers, returned in insertion order.'
1220
+ },
1221
+ "doc.pages.insertBlank": {
1222
+ operationId: "doc.pages.insertBlank",
1223
+ title: "Insert blank pages",
1224
+ summary: "Create blank pages of an explicit size at an index.",
1225
+ method: "POST",
1226
+ path: wireTemplates.layerPagesInsertBlank,
1227
+ credentials: docCredentials,
1228
+ scope: [],
1229
+ docCapabilities: ["doc.pages.assemble"],
1230
+ requestHeaders: [documentPasswordHeader],
1231
+ params: DocLayerParamsSchema,
1232
+ body: { contentType: "application/json", schema: looseJson },
1233
+ responses: {
1234
+ 200: { contentType: "application/json", schema: MutationResponseSchema },
1235
+ 400: { contentType: "application/json", schema: EngineErrorPayloadSchema },
1236
+ 404: { contentType: "application/json", schema: EngineErrorPayloadSchema }
1237
+ },
1238
+ notes: 'Body is `{"size": {"width", "height"}, "count"?, "destIndex"?}` \u2014 size in PDF points, count in [1, 100], destIndex omitted \u2192 append.'
1239
+ },
1240
+ "doc.pages.extract": {
1241
+ operationId: "doc.pages.extract",
1242
+ title: "Extract pages",
1243
+ summary: "Export the listed pages, in order, as a standalone PDF.",
1244
+ method: "POST",
1245
+ path: wireTemplates.layerPagesExtract,
1246
+ credentials: docCredentials,
1247
+ scope: [],
1248
+ docCapabilities: ["doc.download"],
1249
+ requestHeaders: [documentPasswordHeader],
1250
+ params: DocLayerParamsSchema,
1251
+ body: { contentType: "application/json", schema: looseJson },
1252
+ responses: {
1253
+ 200: { contentType: "application/pdf" },
1254
+ 400: { contentType: "application/json", schema: EngineErrorPayloadSchema },
1255
+ 404: { contentType: "application/json", schema: EngineErrorPayloadSchema }
1256
+ },
1257
+ notes: 'A read, not a mutation: the source document is untouched and no event is published. Body is `{"pageObjectNumbers": number[]}`; the response body is the new PDF.'
1258
+ },
767
1259
  "doc.redactions.apply": {
768
1260
  operationId: "doc.redactions.apply",
1261
+ title: "Apply redactions",
769
1262
  summary: "Apply pending redactions, permanently removing content.",
770
1263
  method: "POST",
771
1264
  path: wireTemplates.layerRedactionsApply,
@@ -783,6 +1276,7 @@ var docOperations = {
783
1276
  },
784
1277
  "doc.download": {
785
1278
  operationId: "doc.download",
1279
+ title: "Download PDF",
786
1280
  summary: "Download the layer's current PDF (base plus layer edits).",
787
1281
  method: "GET",
788
1282
  path: wireTemplates.layerDownload,
@@ -798,6 +1292,19 @@ var docOperations = {
798
1292
  }
799
1293
  };
800
1294
  var allOperations = { ...adminOperations, ...docOperations };
1295
+ var docsGroups = {
1296
+ deployment: { title: "Deployment" },
1297
+ tenants: { title: "Tenants" },
1298
+ documents: { title: "Tenant documents" },
1299
+ tokens: { title: "Tokens" },
1300
+ shares: { title: "Shares" },
1301
+ doc: { title: "Document operations", slug: "document-operations" },
1302
+ "doc.annotations": { title: "Annotations" },
1303
+ "doc.forms": { title: "Forms" },
1304
+ "doc.metadata": { title: "Metadata" },
1305
+ "doc.pages": { title: "Pages" },
1306
+ "doc.redactions": { title: "Redactions" }
1307
+ };
801
1308
 
802
1309
  // src/openapi.ts
803
1310
  var STATUS_TEXT = {
@@ -808,6 +1315,7 @@ var STATUS_TEXT = {
808
1315
  403: "Forbidden",
809
1316
  404: "Not found",
810
1317
  409: "Conflict",
1318
+ 410: "Gone",
811
1319
  422: "Unprocessable"
812
1320
  };
813
1321
  var SECURITY_SCHEME = {
@@ -817,12 +1325,23 @@ var SECURITY_SCHEME = {
817
1325
  };
818
1326
  var ADMIN_ERROR_REF = "#/components/schemas/AdminErrorPayload";
819
1327
  var ENGINE_ERROR_REF = "#/components/schemas/EngineErrorPayload";
1328
+ var SHARED_COMPONENT_SCHEMAS = PdfActionWireComponents;
1329
+ var SHARED_COMPONENT_BY_DEFINITION = new Map(
1330
+ Object.entries(SHARED_COMPONENT_SCHEMAS).map(([name, schema]) => [schema._def, name])
1331
+ );
1332
+ var SHARED_COMPONENT_REFS = new Set(
1333
+ Object.keys(SHARED_COMPONENT_SCHEMAS).map((name) => `#/components/schemas/${name}`)
1334
+ );
820
1335
  function buildAdminOpenApiDocument(opts) {
821
1336
  const paths = {};
822
1337
  const schemas = {};
823
1338
  registerSchema(schemas, "AdminErrorPayload", AdminErrorPayloadSchema);
824
1339
  registerSchema(schemas, "EngineErrorPayload", EngineErrorPayloadSchema2);
1340
+ for (const [name, schema] of Object.entries(SHARED_COMPONENT_SCHEMAS)) {
1341
+ registerSchema(schemas, name, schema);
1342
+ }
825
1343
  for (const op of Object.values(allOperations)) {
1344
+ assertDocsGroupsCover(op);
826
1345
  const openApiPath = toOpenApiPath(op.path);
827
1346
  const method = op.method.toLowerCase();
828
1347
  const entry = paths[openApiPath] ??= {};
@@ -837,23 +1356,29 @@ function buildAdminOpenApiDocument(opts) {
837
1356
  license: { name: "Apache-2.0", identifier: "Apache-2.0" }
838
1357
  },
839
1358
  paths: sortKeys(paths),
1359
+ // Docs navigation manifest: section titles and URL slugs for the API
1360
+ // reference, in sidebar order (see `docsGroups` in the registry).
1361
+ "x-docs-groups": docsGroups,
840
1362
  components: {
841
1363
  securitySchemes: {
842
1364
  apiToken: {
843
1365
  type: "http",
844
1366
  scheme: "bearer",
1367
+ "x-docs-title": "API token",
845
1368
  description: "The deployment's static root credential (CLOUDPDF_API_AUTH_TOKENS), valid on every surface."
846
1369
  },
847
1370
  tenantToken: {
848
1371
  type: "http",
849
1372
  scheme: "bearer",
850
1373
  bearerFormat: "JWT",
1374
+ "x-docs-title": "Tenant token",
851
1375
  description: "Delegated tenant JWT, valid only under its own /v1/tenants/{tenantId}/ subtree \u2014 the path tenant must equal the token's tenant_id. Doc-scoped viewer tokens are rejected on every admin route."
852
1376
  },
853
1377
  docToken: {
854
1378
  type: "http",
855
1379
  scheme: "bearer",
856
1380
  bearerFormat: "JWT",
1381
+ "x-docs-title": "Document token",
857
1382
  description: "Doc-scoped JWT, valid only on the /v1/docs/{docId} subtree it names, gated by the capability scopes it carries (each operation's x-required-capability)."
858
1383
  }
859
1384
  },
@@ -861,6 +1386,15 @@ function buildAdminOpenApiDocument(opts) {
861
1386
  }
862
1387
  };
863
1388
  }
1389
+ function assertDocsGroupsCover(op) {
1390
+ const { groups } = sdkOperationName(op.operationId);
1391
+ for (let depth = 1; depth <= groups.length; depth += 1) {
1392
+ const key = groups.slice(0, depth).join(".");
1393
+ if (!(key in docsGroups)) {
1394
+ throw new Error(`Operation ${op.operationId} has no docsGroups entry for "${key}"`);
1395
+ }
1396
+ }
1397
+ }
864
1398
  function operationObject(op, schemas) {
865
1399
  const parameters = [...pathParameters(op), ...queryParameters(op), ...headerParameters(op)];
866
1400
  const sdkName = sdkOperationName(op.operationId);
@@ -868,6 +1402,7 @@ function operationObject(op, schemas) {
868
1402
  operationId: op.operationId,
869
1403
  "x-fern-sdk-group-name": sdkName.groups,
870
1404
  "x-fern-sdk-method-name": sdkName.method,
1405
+ "x-docs-title": op.title,
871
1406
  summary: op.summary,
872
1407
  ...op.notes ? { description: op.notes } : {},
873
1408
  security: op.credentials.map((credential) => ({ [SECURITY_SCHEME[credential]]: [] })),
@@ -879,12 +1414,54 @@ function operationObject(op, schemas) {
879
1414
  };
880
1415
  return out;
881
1416
  }
1417
+ var RESERVED_SDK_SEGMENTS = [
1418
+ [
1419
+ "Java",
1420
+ /* @__PURE__ */ new Set([
1421
+ ...["abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class"],
1422
+ ...["const", "continue", "default", "do", "double", "else", "enum", "extends", "final"],
1423
+ ...["finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int"],
1424
+ ...["interface", "long", "native", "new", "package", "private", "protected", "public"],
1425
+ ...["return", "short", "static", "strictfp", "super", "switch", "synchronized", "this"],
1426
+ ...["throw", "throws", "transient", "try", "void", "volatile", "while"],
1427
+ ...["true", "false", "null"]
1428
+ ])
1429
+ ],
1430
+ [
1431
+ "Python",
1432
+ /* @__PURE__ */ new Set([
1433
+ ...["False", "None", "True", "and", "as", "assert", "async", "await", "break", "class"],
1434
+ ...["continue", "def", "del", "elif", "else", "except", "finally", "for", "from"],
1435
+ ...["global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass"],
1436
+ ...["raise", "return", "try", "while", "with", "yield"]
1437
+ ])
1438
+ ],
1439
+ [
1440
+ "Ruby",
1441
+ /* @__PURE__ */ new Set([
1442
+ ...["alias", "and", "begin", "break", "case", "class", "def", "do", "else", "elsif"],
1443
+ ...["end", "ensure", "false", "for", "if", "in", "module", "next", "nil", "not", "or"],
1444
+ ...["redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef"],
1445
+ ...["unless", "until", "when", "while", "yield"]
1446
+ ])
1447
+ ]
1448
+ ];
882
1449
  function sdkOperationName(operationId) {
883
1450
  const parts = operationId.split(".");
884
1451
  const method = parts.pop();
885
1452
  if (!method || parts.length === 0) {
886
1453
  throw new Error(`Operation ID must use resource.method form: ${operationId}`);
887
1454
  }
1455
+ for (const segment of [...parts, method]) {
1456
+ const collisions = RESERVED_SDK_SEGMENTS.filter(([, words]) => words.has(segment)).map(
1457
+ ([language]) => language
1458
+ );
1459
+ if (collisions.length > 0) {
1460
+ throw new Error(
1461
+ `Operation "${operationId}" cannot use "${segment}" as an SDK name segment: it is a reserved word in ${collisions.join(" and ")}, so SDK generators would rename or reject it. Pick a compound name instead (e.g. importFrom rather than import).`
1462
+ );
1463
+ }
1464
+ }
888
1465
  return { groups: parts, method };
889
1466
  }
890
1467
  function headerParameters(op) {
@@ -978,11 +1555,50 @@ function registerSchema(schemas, name, schema) {
978
1555
  const referenced = zodToJsonSchema(schema, {
979
1556
  target: "openApi3",
980
1557
  $refStrategy: "root",
981
- basePath: ["#", ...componentPath]
1558
+ basePath: ["#", ...componentPath],
1559
+ override: (definition) => {
1560
+ const sharedName = SHARED_COMPONENT_BY_DEFINITION.get(definition);
1561
+ if (!sharedName || sharedName === name) return ignoreOverride;
1562
+ return { $ref: `#/components/schemas/${sharedName}` };
1563
+ }
982
1564
  });
983
- schemas[name] = breakAncestorReferences(referenced, componentPath);
1565
+ const canonical = collapseSharedReferenceAliases(referenced, referenced, componentPath);
1566
+ schemas[name] = breakAncestorReferences(canonical, componentPath);
984
1567
  return { $ref: `#/components/schemas/${name}` };
985
1568
  }
1569
+ function collapseSharedReferenceAliases(value, component, componentPath) {
1570
+ if (Array.isArray(value)) {
1571
+ return value.map((child) => collapseSharedReferenceAliases(child, component, componentPath));
1572
+ }
1573
+ if (!value || typeof value !== "object") return value;
1574
+ const record = value;
1575
+ if (Object.keys(record).length === 1 && typeof record.$ref === "string") {
1576
+ const target = resolveComponentReference(component, record.$ref, componentPath);
1577
+ if (target && typeof target === "object" && !Array.isArray(target)) {
1578
+ const targetRecord = target;
1579
+ if (Object.keys(targetRecord).length === 1 && typeof targetRecord.$ref === "string" && SHARED_COMPONENT_REFS.has(targetRecord.$ref)) {
1580
+ return { $ref: targetRecord.$ref };
1581
+ }
1582
+ }
1583
+ }
1584
+ return Object.fromEntries(
1585
+ Object.entries(record).map(([key, child]) => [
1586
+ key,
1587
+ collapseSharedReferenceAliases(child, component, componentPath)
1588
+ ])
1589
+ );
1590
+ }
1591
+ function resolveComponentReference(component, reference, componentPath) {
1592
+ if (!reference.startsWith("#/")) return void 0;
1593
+ const target = reference.slice(2).split("/").map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"));
1594
+ if (target.length < componentPath.length || !componentPath.every((segment, index) => target[index] === segment)) {
1595
+ return void 0;
1596
+ }
1597
+ return target.slice(componentPath.length).reduce((current, segment) => {
1598
+ if (!current || typeof current !== "object") return void 0;
1599
+ return current[segment];
1600
+ }, component);
1601
+ }
986
1602
  function breakAncestorReferences(value, path) {
987
1603
  if (Array.isArray(value)) {
988
1604
  return value.map((child, index) => breakAncestorReferences(child, [...path, String(index)]));
@@ -1024,6 +1640,7 @@ function sortKeys(obj) {
1024
1640
  return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
1025
1641
  }
1026
1642
  export {
1027
- buildAdminOpenApiDocument
1643
+ buildAdminOpenApiDocument,
1644
+ sdkOperationName
1028
1645
  };
1029
1646
  //# sourceMappingURL=openapi.js.map