@shipstatic/types 2.5.0-beta.2 → 2.5.0-beta.21

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/index.d.ts CHANGED
@@ -12,6 +12,28 @@ export declare const DeploymentStatus: {
12
12
  readonly DELETING: "deleting";
13
13
  };
14
14
  export type DeploymentStatusType = (typeof DeploymentStatus)[keyof typeof DeploymentStatus];
15
+ /**
16
+ * Which client made a deployment — the origin-tracking vocabulary.
17
+ *
18
+ * A closed set with many authors: the CLI, the SDK, the dashboard, both MCP
19
+ * transports, the GitHub Action, the n8n node and the VS Code extension each
20
+ * name themselves here. It lived in the API's config until 2026-08-06, where
21
+ * being server-side made it unenforceable in the one direction that matters —
22
+ * every client wrote a bare string, and a value outside the set was **silently
23
+ * dropped** by the server, so a typo did not fail anywhere. It stopped
24
+ * recording where deploys came from and said nothing.
25
+ */
26
+ export declare const DeploymentVia: {
27
+ readonly WEB: "web";
28
+ readonly SDK: "sdk";
29
+ readonly CLI: "cli";
30
+ readonly MCP: "mcp";
31
+ readonly GIT: "git";
32
+ readonly N8N: "n8n";
33
+ readonly GPT: "gpt";
34
+ readonly VSC: "vsc";
35
+ };
36
+ export type DeploymentViaType = (typeof DeploymentVia)[keyof typeof DeploymentVia];
15
37
  /**
16
38
  * Core deployment object - used in both API responses and SDK
17
39
  */
@@ -32,7 +54,15 @@ export interface Deployment {
32
54
  readonly password: boolean;
33
55
  /** Labels for categorization and filtering (lowercase, alphanumeric with separators). Always present, empty array when none. */
34
56
  labels: string[];
35
- /** The client/tool used to create this deployment (e.g., 'web', 'sdk', 'cli'), null if unknown */
57
+ /**
58
+ * The client/tool that created this deployment, null if unknown.
59
+ *
60
+ * Deliberately wider than {@link DeploymentViaType}: this is stored data,
61
+ * and rows predate the vocabulary being closed. Narrowing the ENTITY would
62
+ * be a claim about every row already in the database; narrowing the
63
+ * REQUEST option ({@link DeploymentUploadOptions.via}) is a claim about
64
+ * what a client may send, which is ours to make.
65
+ */
36
66
  readonly via: string | null;
37
67
  /** Unix timestamp (seconds) when deployment was created */
38
68
  readonly created: number;
@@ -49,16 +79,157 @@ export interface DeploymentCreateResponse extends Deployment {
49
79
  /** Claim URL for public deployments. Present when deployed without credentials. */
50
80
  readonly claim?: string;
51
81
  }
82
+ /**
83
+ * Every path the public API answers on, declared once.
84
+ *
85
+ * The URL surface was written out in four places — the API's mounts, the
86
+ * SDK's client, the dashboard's client, and the post-deploy smoke — so a
87
+ * rename meant finding all four. The first three now read this table.
88
+ *
89
+ * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
90
+ * five of its nine paths are `/admin/*`, which this table excludes by
91
+ * design, and splitting one list between a registry and literals reads worse
92
+ * than keeping it uniform.
93
+ *
94
+ * **What this guarantees, exactly.** Collection paths are mounted from here,
95
+ * so producer and consumer cannot diverge. Item paths are declared here and
96
+ * consumed by clients, but the API spells them relative to their mount
97
+ * (`/:deployment/config`), so the table does not *generate* them — it is
98
+ * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
99
+ * any entry names a path no route answers. Some entries have no client yet
100
+ * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
101
+ * deliberately does not reach); the fence is what keeps those honest rather
102
+ * than merely asserted.
103
+ *
104
+ * **The operator surface is deliberately absent.** `/admin/*` paths belong
105
+ * to `web/my`, for the same reason its row types do: this package is
106
+ * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
107
+ * types"). A path here is a promise to every npm consumer; `/admin` is a
108
+ * promise to one dashboard.
109
+ *
110
+ * Item paths are functions rather than templates so the key is interpolated
111
+ * in one place, encoded the same way by every caller.
112
+ */
113
+ export declare const API_PATHS: {
114
+ readonly DEPLOYMENTS: "/deployments";
115
+ readonly DEPLOYMENT: (deployment: string) => string;
116
+ readonly DEPLOYMENT_CONFIG: (deployment: string) => string;
117
+ readonly DOMAINS: "/domains";
118
+ readonly DOMAIN: (domain: string) => string;
119
+ readonly DOMAIN_VERIFY: (domain: string) => string;
120
+ readonly DOMAIN_DNS: (domain: string) => string;
121
+ readonly DOMAIN_RECORDS: (domain: string) => string;
122
+ readonly DOMAIN_SHARE: (domain: string) => string;
123
+ readonly DOMAIN_PROPAGATION: (domain: string) => string;
124
+ readonly DOMAINS_VALIDATE: "/domains/validate";
125
+ readonly TOKENS: "/tokens";
126
+ readonly TOKEN: (token: string) => string;
127
+ readonly ACCOUNT: "/account";
128
+ readonly ACCOUNT_KEY: "/account/key";
129
+ readonly ACCOUNT_CLAIM: "/account/claim";
130
+ readonly ACTIVITIES: "/activities";
131
+ readonly LABELS: "/labels";
132
+ readonly LIMITS: "/limits";
133
+ readonly PING: "/ping";
134
+ readonly SETUP: "/setup";
135
+ readonly SPA_CHECK: "/spa-check";
136
+ readonly UPLOAD: "/upload";
137
+ };
138
+ /**
139
+ * The deploy request's multipart field names — the other half of the wire
140
+ * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party
141
+ * `/upload`) is multipart/form-data, and these are the names the API reads.
142
+ *
143
+ * Declared once because the body has three independent WRITERS — the SDK's
144
+ * Node and browser body builders, and the n8n community node's hand-rolled
145
+ * client (which cannot import this under n8n Cloud's zero-dependency rule,
146
+ * and fences its restated copy instead) — and until this export every writer
147
+ * restated the strings the API parses, with nothing comparing them.
148
+ *
149
+ * `FILES` carries one entry per file (the API reads it with `getAll`); every
150
+ * other field is single. The `@internal` flags are serialized as the literal
151
+ * string `'true'` and belong to first-party surfaces only.
152
+ */
153
+ export declare const DEPLOY_FIELDS: {
154
+ /** One entry per file — read with `getAll`. */
155
+ readonly FILES: "files[]";
156
+ /** JSON array of MD5 hex digests, index-aligned with `FILES`. */
157
+ readonly CHECKSUMS: "checksums";
158
+ /** JSON array of label strings. */
159
+ readonly LABELS: "labels";
160
+ /** The deploying surface's {@link DeploymentVia} member. */
161
+ readonly VIA: "via";
162
+ /** Plaintext password — the API hashes it server-side. */
163
+ readonly PASSWORD: "password";
164
+ /** @internal Server-processing flag — first-party `/upload` only. */
165
+ readonly BUILD: "build";
166
+ /** @internal Server-processing flag — first-party `/upload` only. */
167
+ readonly PRERENDER: "prerender";
168
+ /** @internal Server-processing flag — first-party `/upload` only. */
169
+ readonly SPA: "spa";
170
+ /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
171
+ readonly CAPTCHA: "captcha";
172
+ };
173
+ /**
174
+ * The half of a list response that is identical on every list.
175
+ *
176
+ * `GET /<collection>` answers exactly two fields — the collection under its
177
+ * own plural noun, and this cursor — so the cursor is declared once here and
178
+ * each response below adds only its noun. `cursor: null` means last page and
179
+ * is the ENTIRE has-more signal, which is why there is no `has_more`.
180
+ *
181
+ * There is deliberately no `total`. A count is an aggregate over a
182
+ * collection, not a property of a page; producing one would cost a COUNT
183
+ * beside every page read, which is precisely what keyset pagination exists
184
+ * to avoid. Counts live on the resource that summarises the collection —
185
+ * `GET /account`'s `usage` for one caller, `GET /admin/stats` platform-wide.
186
+ */
187
+ export interface ListResponse {
188
+ /** Opaque cursor from this page; `null` on the last page. */
189
+ cursor: string | null;
190
+ }
52
191
  /**
53
192
  * Response for listing deployments
54
193
  */
55
- export interface DeploymentListResponse {
194
+ export interface DeploymentListResponse extends ListResponse {
56
195
  /** Array of deployments */
57
196
  deployments: Deployment[];
58
- /** Cursor for pagination, null if no more pages */
59
- cursor: string | null;
60
- /** Total number of deployments */
61
- total: number;
197
+ }
198
+ /**
199
+ * Acknowledgement of `DELETE /deployments/:deployment` — and the shape every
200
+ * mutation with no entity left to return follows.
201
+ *
202
+ * **The law:** a mutation answers with the resource it affected. If the
203
+ * resource still exists, that means the entity itself (`Deployment`,
204
+ * `Domain`, …). Otherwise it means this: the resource noun carrying the
205
+ * item's canonical key, plus the resource's own state field — and ONLY when
206
+ * the resource survived in a transitional state, as an async deletion's does.
207
+ * Where the resource is simply gone, the key alone is the whole answer
208
+ * ({@link DomainDeleteResponse}, {@link TokenDeleteResponse}).
209
+ *
210
+ * Put positively: **an acknowledgement is a projection of the resource** —
211
+ * its key, plus its own state field where the state changed. That is the
212
+ * test to apply, and it is sharper than "no constant", which this shape
213
+ * would fail on its own terms: `status` here is the literal `'deleting'` on
214
+ * every success, exactly as fixed as a `changed: true` would be.
215
+ *
216
+ * The difference is not how predictable the value is, it is what the field
217
+ * IS. `status` is the deployment's own field — the same one `GET
218
+ * /deployments/:deployment` returns — so this response is `Deployment`
219
+ * narrowed to two members, and a client renders it with the code it already
220
+ * has. `changed: true`, `queued: true` and `success: true` are not fields of
221
+ * any entity; they exist only to assert that the call worked, which the
222
+ * status code already said. Sync versus accepted is likewise the status
223
+ * code's job — 200 versus 202 — not a boolean's.
224
+ *
225
+ * No prose either (`message`): an acknowledgement is data, and each surface
226
+ * composes its own copy.
227
+ */
228
+ export interface DeploymentDeleteResponse {
229
+ /** The deployment hostname that was marked for removal */
230
+ readonly deployment: string;
231
+ /** The state the deployment is in while background cleanup runs */
232
+ readonly status: DeploymentStatusType;
62
233
  }
63
234
  /**
64
235
  * Domain status constants
@@ -91,7 +262,7 @@ export interface Domain {
91
262
  labels: string[];
92
263
  /** Unix timestamp (seconds) when domain was created */
93
264
  readonly created: number;
94
- /** When deployment was last linked (Unix timestamp), null if never linked */
265
+ /** Unix timestamp (seconds) when deployment was last linked, null if never linked */
95
266
  linked: number | null;
96
267
  /** Total deployment links */
97
268
  links: number;
@@ -113,13 +284,28 @@ export interface DomainSetResult extends Domain {
113
284
  /**
114
285
  * Response for listing domains
115
286
  */
116
- export interface DomainListResponse {
287
+ export interface DomainListResponse extends ListResponse {
117
288
  /** Array of domains */
118
289
  domains: Domain[];
119
- /** Cursor for pagination, null if no more pages */
120
- cursor: string | null;
121
- /** Total number of domains */
122
- total: number;
290
+ }
291
+ /**
292
+ * Acknowledgement of `DELETE /domains/:domain`. The row is gone, so there is
293
+ * no state to state — the canonical domain name is the whole answer. See
294
+ * {@link DeploymentDeleteResponse} for the law.
295
+ */
296
+ export interface DomainDeleteResponse {
297
+ /** The domain name that was removed, normalized */
298
+ readonly domain: string;
299
+ }
300
+ /**
301
+ * Acknowledgement of `POST /domains/:domain/verify` (202). The DNS check is
302
+ * queued, not performed — the accepted status code says so, and the domain's
303
+ * own status is unchanged until the check runs, which is why none is stated
304
+ * here. See {@link DeploymentDeleteResponse} for the law.
305
+ */
306
+ export interface DomainVerifyResponse {
307
+ /** The domain whose DNS verification was queued, normalized */
308
+ readonly domain: string;
123
309
  }
124
310
  /**
125
311
  * DNS record types supported for domain configuration
@@ -146,16 +332,46 @@ export interface DnsProvider {
146
332
  /**
147
333
  * Response for domain DNS provider lookup
148
334
  */
335
+ /**
336
+ * What a DNS lookup found for a domain. An envelope rather than a bare
337
+ * {@link DnsProvider} because a lookup can succeed and learn more than the
338
+ * provider later; the shape is named so a consumer can hold one.
339
+ */
340
+ export interface DnsLookup {
341
+ /** The provider serving this domain's DNS, absent when unidentified */
342
+ provider?: DnsProvider;
343
+ }
344
+ /**
345
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
346
+ * "A report answers a question").
347
+ */
149
348
  export interface DomainDnsResponse {
150
349
  /** The domain name */
151
350
  domain: string;
152
351
  /** DNS provider information, null if not yet looked up */
153
- dns: {
154
- provider?: DnsProvider;
155
- } | null;
352
+ dns: DnsLookup | null;
353
+ }
354
+ /**
355
+ * Response for `GET /domains/:domain/share` — the domain plus the salted
356
+ * hash that lets someone else complete its DNS setup without an account.
357
+ *
358
+ * `/admin/domains/:domain/share` answers the same shape, which is the admin
359
+ * law working: the operator surface is the public grammar with a prefix.
360
+ *
361
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
362
+ * "A report answers a question").
363
+ */
364
+ export interface DomainShareResponse {
365
+ /** The domain the setup link is for */
366
+ readonly domain: string;
367
+ /** The salted setup hash that authorizes the share */
368
+ readonly hash: string;
156
369
  }
157
370
  /**
158
371
  * Response for domain DNS records
372
+ *
373
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
374
+ * "A report answers a question").
159
375
  */
160
376
  export interface DomainRecordsResponse {
161
377
  /** The domain name */
@@ -166,7 +382,92 @@ export interface DomainRecordsResponse {
166
382
  records: DnsRecord[];
167
383
  }
168
384
  /**
169
- * Response for domain validation
385
+ * The envelope an `Idempotency-Key` must fit, and how long a replay lasts.
386
+ *
387
+ * Format lives here rather than on the server alone by the format-vs-policy
388
+ * rule: a client can decide offline whether a key is well-formed, and the
389
+ * API would reject the same value the same way.
390
+ */
391
+ export declare const IDEMPOTENCY_KEY_CONSTRAINTS: {
392
+ /**
393
+ * HTTP header name. Here for the same reason {@link CALLER.HEADER} is: a
394
+ * wire header has two ends, and the package that owns the value's format
395
+ * is the only place both ends can read its name from.
396
+ */
397
+ readonly HEADER: "Idempotency-Key";
398
+ readonly MAX_LENGTH: 256;
399
+ /** How long a stored 201 stays replayable. */
400
+ readonly WINDOW_SECONDS: number;
401
+ };
402
+ /**
403
+ * Normalize a `via` value from any transport — trimmed, lowercased, and a
404
+ * member of {@link DeploymentVia}, or `undefined`.
405
+ *
406
+ * A format rule by this package's own test: a client can decide offline
407
+ * whether a value is well-formed, and the API reaches the same verdict on the
408
+ * same input. It lived server-side until 2026-08-06, which meant clients could
409
+ * only learn their label was unusable by noticing analytics had gone quiet.
410
+ *
411
+ * **Not knowing your `via` is not an error** — an unrecognized value yields
412
+ * `undefined` rather than throwing, because origin tracking is telemetry and a
413
+ * deploy must never fail over it. A caller that has an honest default should
414
+ * prefer it (`normalizeVia(process.env.SHIP_VIA) ?? DeploymentVia.CLI`): the
415
+ * deploy really did come from the CLI, so recording that beats recording
416
+ * nothing.
417
+ */
418
+ export declare function normalizeVia(value: unknown): DeploymentViaType | undefined;
419
+ /**
420
+ * Validate an idempotency key, returning the trimmed value or `undefined`
421
+ * when none was supplied. Throws {@link ShipError.validation} when the value
422
+ * cannot be sent — the same verdict the API would reach, reached earlier.
423
+ */
424
+ export declare function validateIdempotencyKey(value: unknown): string | undefined;
425
+ /**
426
+ * Response for `GET /labels` — every label in use across the caller's
427
+ * deployments, domains and tokens, grouped and ordered by last use.
428
+ *
429
+ * The one plural noun outside the list contract, deliberately: labels have
430
+ * no identity, no row and no `created`, so there is nothing for a keyset
431
+ * cursor to resume after, and its consumer is an autocomplete that wants the
432
+ * whole set. Bounded by `PAGINATION.GLOBAL_LIMIT` rather than paginated.
433
+ *
434
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
435
+ * "A report answers a question").
436
+ */
437
+ export interface LabelsResponse {
438
+ readonly labels: string[];
439
+ }
440
+ /**
441
+ * Response for `POST /setup` — the DNS instructions for one domain, written
442
+ * for a human to follow at their registrar.
443
+ *
444
+ * `custom` is the provider-specific walkthrough when the provider is known;
445
+ * `generic` always answers, so a caller never has nothing to show.
446
+ *
447
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
448
+ * "A report answers a question").
449
+ */
450
+ export interface SetupInstructionsResponse {
451
+ /** The domain the instructions are for — a report names its subject */
452
+ readonly domain: string;
453
+ /** One-line summary of what to do */
454
+ readonly tldr: string;
455
+ /** Provider-specific instructions, null when the provider is unknown */
456
+ readonly custom: string | null;
457
+ /** Provider-agnostic instructions — always present */
458
+ readonly generic: string;
459
+ /** The identified DNS provider, null when unknown */
460
+ readonly provider: string | null;
461
+ }
462
+ /**
463
+ * `POST /domains/validate` — a report answering "is this name usable, and if
464
+ * not, why".
465
+ *
466
+ * An unusable name is a legitimate ANSWER, not a failure, so this is a 200 and
467
+ * the verdict rides the body. `reason` was named `error` until 2026-07-29,
468
+ * which collided with {@link ErrorResponse}'s reserved key — there `error` is
469
+ * an `ErrorType` a client branches on, here it is prose a client displays, and
470
+ * one key cannot mean both. See {@link DeploymentDeleteResponse} for the law.
170
471
  */
171
472
  export interface DomainValidateResponse {
172
473
  /** Whether the domain is valid */
@@ -175,15 +476,17 @@ export interface DomainValidateResponse {
175
476
  normalized: string | null;
176
477
  /** Whether the domain is available, null when invalid */
177
478
  available: boolean | null;
178
- /** Error message, null when valid */
179
- error: string | null;
479
+ /** Why the name is unusable, null when valid — displayed verbatim. */
480
+ reason: string | null;
180
481
  }
181
482
  /**
182
- * Token as returned by the list endpoint.
183
- * The secret is shown once at creation and never again — listings carry
184
- * only the management identifier and lifecycle metadata.
483
+ * Core deploy token object - used in both API responses and SDK.
484
+ *
485
+ * The secret is never here: it is shown once at creation
486
+ * ({@link TokenCreateResponse.secret}) and never again, so an entity read
487
+ * carries only the management identifier and lifecycle metadata.
185
488
  */
186
- export interface TokenListItem {
489
+ export interface Token {
187
490
  /** 7-char management identifier (e.g., "a1b2c3d") */
188
491
  readonly token: string;
189
492
  /** Labels for categorization and filtering. Always present, empty array when none. */
@@ -198,24 +501,28 @@ export interface TokenListItem {
198
501
  /**
199
502
  * Response for listing tokens
200
503
  */
201
- export interface TokenListResponse {
202
- /** Array of tokens (security-redacted for list display) */
203
- tokens: TokenListItem[];
204
- /** Total number of tokens */
205
- total: number;
504
+ export interface TokenListResponse extends ListResponse {
505
+ /** Array of tokens (the secret is never among them) */
506
+ tokens: Token[];
206
507
  }
207
508
  /**
208
- * Response for token creation
509
+ * Response from token creation. Extends Token with the one field that
510
+ * exists only on creation — the same shape as
511
+ * {@link DeploymentCreateResponse}, because a 201 returns the resource it
512
+ * created plus whatever is knowable only once.
209
513
  */
210
- export interface TokenCreateResponse {
211
- /** 7-char management identifier */
212
- token: string;
514
+ export interface TokenCreateResponse extends Token {
213
515
  /** The raw credential value (shown once at creation, then never again) */
214
- secret: string;
215
- /** Labels for categorization and filtering. Always present, empty array when none. */
216
- labels: string[];
217
- /** Unix timestamp (seconds) when token expires, null for never */
218
- expires: number | null;
516
+ readonly secret: string;
517
+ }
518
+ /**
519
+ * Acknowledgement of `DELETE /tokens/:token`. The credential is revoked and
520
+ * its row is gone, so the management identifier is the whole answer. See
521
+ * {@link DeploymentDeleteResponse} for the law.
522
+ */
523
+ export interface TokenDeleteResponse {
524
+ /** The 7-char management identifier that was revoked */
525
+ readonly token: string;
219
526
  }
220
527
  /**
221
528
  * Account plan constants
@@ -232,10 +539,34 @@ export declare const AccountPlan: {
232
539
  export type AccountPlanType = (typeof AccountPlan)[keyof typeof AccountPlan];
233
540
  /**
234
541
  * Account usage metrics — always available regardless of billing provider.
542
+ *
543
+ * This is where a caller's own totals live. Lists answer pages and carry no
544
+ * `total` (see {@link ListOptions}); a count is an aggregate over a
545
+ * collection, so it belongs to the summary resource that owns the
546
+ * collection. `GET /account` is that resource for one caller, `GET
547
+ * /admin/stats` for the platform.
548
+ *
549
+ * The counted dimensions are the ones the plan caps — deployments and
550
+ * domains (`PlatformLimits`) — plus the billable custom-domain subset, so a
551
+ * surface can render "3 of 10" without a second request.
235
552
  */
236
553
  export interface AccountUsage {
237
554
  /** Number of active custom domains (excludes paused) */
238
555
  customDomains: number;
556
+ /**
557
+ * Deployments counted against the plan's deployment cap — every row
558
+ * whatever its status, because that is what the cap counts, so a surface
559
+ * renders "3 of 10" against the denominator the 403 divides by. (`GET
560
+ * /deployments` lists successful ones only; that is a different question
561
+ * asked of a different resource.) Optional by the additive-evolution law:
562
+ * an API predating this field omits it.
563
+ */
564
+ deployments?: number;
565
+ /**
566
+ * Domains counted against the plan's domain cap — every domain, platform
567
+ * and custom alike, unlike `customDomains`. Optional for the same reason.
568
+ */
569
+ domains?: number;
239
570
  }
240
571
  /**
241
572
  * Core account object - used in both API responses and SDK
@@ -282,6 +613,35 @@ export interface AccountGetResponse extends Account {
282
613
  /** Present only during read-only admin impersonation: the operator's account id. */
283
614
  readonly impersonatedBy?: string;
284
615
  }
616
+ /**
617
+ * Acknowledgement of `DELETE /account` (202). Termination is asynchronous —
618
+ * a cleanup consumer finishes the job — so the account survives long enough
619
+ * to state the plan it is transitioning through. `plan` is the account's
620
+ * state field, the way `status` is a deployment's. See
621
+ * {@link DeploymentDeleteResponse} for the law.
622
+ */
623
+ export interface AccountDeleteResponse {
624
+ /** The account that was marked for termination */
625
+ readonly account: string;
626
+ /** The plan the account is in while cleanup runs */
627
+ readonly plan: AccountPlanType;
628
+ }
629
+ /**
630
+ * Response from `PUT /account/key` — the account's single API key, minted in
631
+ * place of whatever was there before.
632
+ *
633
+ * There is no entity to return: only the key's last-4 `hint` is durable
634
+ * (`Account.hint`), and the plaintext exists exactly once, in this response.
635
+ * The raw credential is `secret` on every surface that mints one — the same
636
+ * field `TokenCreateResponse` carries — because one concept gets one name.
637
+ *
638
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
639
+ * "A report answers a question").
640
+ */
641
+ export interface AccountKeyResponse {
642
+ /** The raw API key (shown once at mint, then never again) */
643
+ readonly secret: string;
644
+ }
285
645
  /**
286
646
  * Account-specific configuration overrides
287
647
  * Allows per-account customization of limits without changing plan
@@ -308,7 +668,15 @@ export interface AccountOverrides {
308
668
  * (`DeploymentStatus`, `DomainStatus`, `AccountPlan`, `AuthMethod`) follow.
309
669
  */
310
670
  export declare const ErrorType: {
311
- /** Validation failed (400). Input shape is wrong. */
671
+ /**
672
+ * Validation failed. Input shape is wrong.
673
+ *
674
+ * Carries 400 when an API judged it — including a client-side pre-check of a
675
+ * rule the server enforces too, which keeps the error identical wherever it
676
+ * was caught. **Statusless** when a client rejects something no API judges,
677
+ * such as a CLI's own command grammar: `status` is documented "(API
678
+ * contexts)" on `ErrorResponse`, so there is none to report.
679
+ */
312
680
  readonly Validation: "validation_failed";
313
681
  /** Resource not found (404). */
314
682
  readonly NotFound: "not_found";
@@ -388,7 +756,8 @@ export declare class ShipError extends Error {
388
756
  * Routing:
389
757
  * - Already a `ShipError` → returned as-is (caller's intent preserved)
390
758
  * - `AbortError` → `ShipError.cancelled(...)`
391
- * - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
759
+ * - A transport failure → `ShipError.network(...)` — see `isTransportFailure`
760
+ * for what each runtime offers as evidence
392
761
  * - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
393
762
  * - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
394
763
  *
@@ -421,6 +790,18 @@ export declare class ShipError extends Error {
421
790
  static file(message: string, details?: unknown): ShipError;
422
791
  static config(message: string, details?: unknown): ShipError;
423
792
  static api(message: string, status?: number, details?: unknown): ShipError;
793
+ /**
794
+ * The caller is at fault — by HTTP's own definition of a 4xx, or by a type
795
+ * that is client-attributable without ever having a status (`Config`,
796
+ * `File`, raised locally by the SDK).
797
+ *
798
+ * Both arms are load-bearing, because type and status are independent
799
+ * axes. `fromHttpResponse` trusts `body.error` only when it names a
800
+ * server-producible type; a non-OK response without one is status-derived,
801
+ * so a CDN 404 or any intermediary error arrives as `Api` — a server-fault
802
+ * *type* carrying a client *status*. Judging by type alone would report it
803
+ * as a platform failure and bury the server's own message.
804
+ */
424
805
  isClientError(): boolean;
425
806
  isNetworkError(): boolean;
426
807
  isAuthError(): boolean;
@@ -447,6 +828,9 @@ export declare function isShipError(error: unknown): error is ShipError;
447
828
  *
448
829
  * These are the *platform's* posted caps for the current account — server
449
830
  * truth delivered at runtime, never hard-coded on the client.
831
+ *
832
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
833
+ * "A report answers a question").
450
834
  */
451
835
  export interface PlatformLimits {
452
836
  /** Maximum size in bytes for a single file. */
@@ -480,6 +864,28 @@ export declare const BLOCKED_EXTENSIONS: ReadonlySet<string>;
480
864
  * isBlockedExtension('README') // false
481
865
  */
482
866
  export declare function isBlockedExtension(filename: string): boolean;
867
+ /**
868
+ * The `accept` attribute value for a browser file picker offering web files.
869
+ *
870
+ * **This is a hint, never a rule.** `BLOCKED_EXTENSIONS` is the platform's
871
+ * gate and the only thing that decides what may be hosted; this constant
872
+ * decides what a *file dialog* shows first. The two are not two halves of one
873
+ * policy, and this one must never be consulted to accept or reject a file.
874
+ *
875
+ * The distinction is structural, not stylistic. `accept` can express only an
876
+ * allowlist, while the platform's rule is a blocklist — so this list is
877
+ * necessarily *narrower* than what the platform hosts, and reading it as
878
+ * authority would reject files the platform serves happily. It is also not
879
+ * enforcement in the browser's own terms: every file dialog offers an
880
+ * all-files escape, and **drag-and-drop ignores `accept` entirely**. The
881
+ * dropzone and the picker must reach the same verdict on the same files, and
882
+ * they do — because the verdict is `validateFiles`, downstream of both.
883
+ *
884
+ * Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets
885
+ * `tests/validation-constants.test.ts` fence the invariant that matters: the
886
+ * picker must never offer a file the platform will refuse.
887
+ */
888
+ export declare const WEB_FILE_ACCEPT: string;
483
889
  /**
484
890
  * Characters that are unsafe in filenames for static hosting.
485
891
  *
@@ -517,13 +923,20 @@ export declare const UNBUILT_PROJECT_MARKERS: ReadonlySet<string>;
517
923
  */
518
924
  export declare function hasUnbuiltMarker(filePath: string): boolean;
519
925
  /**
520
- * Simple ping response for health checks
926
+ * `GET /ping` a report of the server clock.
927
+ *
928
+ * Liveness is the STATUS CODE's answer, not a field's: a 200 means reachable,
929
+ * and any other outcome throws before a body is read. So the body carries the
930
+ * one thing a status code cannot — the server's own clock, which is what lets a
931
+ * client detect skew against a token expiry. It read `{ success: true,
932
+ * timestamp? }` until 2026-07-29, where `success` was a literal constant in the
933
+ * route (zero bits, and the platform's own named anti-pattern) while the field
934
+ * that IS the payload was optional. See {@link DeploymentDeleteResponse} for
935
+ * the law, and `tests/response-shapes.test.ts` for the fence that holds it.
521
936
  */
522
937
  export interface PingResponse {
523
- /** Always true if service is healthy */
524
- success: boolean;
525
938
  /** Server time in unix seconds — the one wire unit for timestamps. */
526
- timestamp?: number;
939
+ readonly timestamp: number;
527
940
  }
528
941
  /**
529
942
  * Where human identity is mounted on the API host. The API mounts Better
@@ -645,6 +1058,56 @@ export declare const SPA_DEFAULT_CONFIG: {
645
1058
  readonly destination: "/index.html";
646
1059
  }];
647
1060
  };
1061
+ /**
1062
+ * The `/spa-check` pre-flight's client-side envelope: which file is the
1063
+ * check's subject, and how large it may be before a client skips the call.
1064
+ *
1065
+ * One fact with three holders until this export — the API's config declared
1066
+ * the cap, the SDK's `checkSPA` hardcoded `100 * 1024`, and prose restated
1067
+ * "100KB". `INDEX_FILE` is the selection rule (the file whose content rides
1068
+ * `SPACheckRequest.index`), restated by every client that builds the request.
1069
+ *
1070
+ * Neither member is a validation boundary: a client over the cap simply
1071
+ * skips the pre-flight, because the server answers an oversized index
1072
+ * `isSPA: false` anyway. A consumer that cannot import this (n8n) needs no
1073
+ * size copy at all — outcome parity is the server's, not the client's.
1074
+ */
1075
+ export declare const SPA_CHECK_CONSTRAINTS: {
1076
+ /** The file whose content is the check's subject. */
1077
+ readonly INDEX_FILE: "index.html";
1078
+ /** Skip the pre-flight above this size — the server would answer false. */
1079
+ readonly MAX_INDEX_BYTES: number;
1080
+ };
1081
+ /**
1082
+ * Assert that a ship.json file is *syntactically* loadable. Syntax only —
1083
+ * never schema.
1084
+ *
1085
+ * ship.json is validated and compiled on the server, deliberately: the schema
1086
+ * and the compiler evolve, and a client that judged them would reject configs
1087
+ * a newer platform accepts. That reasoning bounds what a client may check to
1088
+ * the properties which are true of *every* past and future schema:
1089
+ *
1090
+ * 1. it parses as JSON — JSON syntax is frozen (RFC 8259), so text that
1091
+ * does not parse can never be a valid config;
1092
+ * 2. its top level is an object — ship.json is `{ ... }` in every version.
1093
+ *
1094
+ * Both are monotonic: neither can ever reject something the server would
1095
+ * accept. Everything beyond them (field names, types, rule semantics, which
1096
+ * keys are permitted) stays server-side, where it can change.
1097
+ *
1098
+ * The payoff is the common case. Hand-edited JSON fails on a trailing comma,
1099
+ * a `//` comment, single quotes, unquoted keys, or smart quotes pasted from
1100
+ * documentation — mistakes that otherwise cost a full upload round-trip to
1101
+ * discover. A UTF-8 BOM (Windows editors, PowerShell redirects) is stripped
1102
+ * before parsing rather than rejected, because the server accepts it too;
1103
+ * diverging there would reintroduce exactly the false rejection this
1104
+ * function exists to avoid.
1105
+ *
1106
+ * @throws {ShipError} `ErrorType.Config` — the same type the server's own
1107
+ * config rejection carries, so the error contract is identical wherever the
1108
+ * failure is detected.
1109
+ */
1110
+ export declare function assertShipJsonSyntax(text: string): void;
648
1111
  /**
649
1112
  * Validate API key format
650
1113
  */
@@ -687,16 +1150,26 @@ export interface SPACheckRequest {
687
1150
  /**
688
1151
  * Response from SPA check endpoint
689
1152
  */
1153
+ /**
1154
+ * Which of the classifier's tiers reached the verdict, and why. Named rather
1155
+ * than inline so the API's own `checkSPA` can return `SPACheckResponse`
1156
+ * instead of restating its shape.
1157
+ */
1158
+ export interface SPACheckDebug {
1159
+ /** Which tier made the detection */
1160
+ tier: 'exclusions' | 'inclusions' | 'scoring' | 'ai' | 'fallback';
1161
+ /** The reason for the detection result */
1162
+ reason: string;
1163
+ }
1164
+ /**
1165
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
1166
+ * "A report answers a question").
1167
+ */
690
1168
  export interface SPACheckResponse {
691
1169
  /** Whether the project is detected as a Single Page Application */
692
1170
  isSPA: boolean;
693
1171
  /** Debugging information about detection */
694
- debug: {
695
- /** Which tier made the detection: 'exclusions', 'inclusions', 'scoring', 'ai', or 'fallback' */
696
- tier: 'exclusions' | 'inclusions' | 'scoring' | 'ai' | 'fallback';
697
- /** The reason for the detection result */
698
- reason: string;
699
- };
1172
+ debug: SPACheckDebug;
700
1173
  }
701
1174
  /**
702
1175
  * Represents a file that has been processed and is ready for deploy.
@@ -729,6 +1202,50 @@ export interface StaticFile {
729
1202
  }
730
1203
  /** Default API URL if not otherwise configured. */
731
1204
  export declare const DEFAULT_API = "https://api.shipstatic.com";
1205
+ /**
1206
+ * The Node SDK's ambient configuration pair — the ONLY environment variables
1207
+ * the SDK reads, and therefore the COMPLETE list an embedding host must
1208
+ * scrub (per `npm/ship`'s strict-isolation contract, scrubbing is the host's
1209
+ * job, not the SDK's). A host that derives its scrub from this object's
1210
+ * values — as the VS Code extension's child-process env block does — picks
1211
+ * up a grown contract at the next pin bump instead of by remembered prose.
1212
+ *
1213
+ * Browser builds read no environment at all, and the CLI-only variables
1214
+ * (`SHIP_PASSWORD`, `SHIP_VIA`) are deliberately NOT here: they are the
1215
+ * CLI's operational levers, not the SDK's ambient contract — see
1216
+ * `npm/ship/CLAUDE.md`, "CLI-only env vars".
1217
+ */
1218
+ export declare const SHIP_ENV: {
1219
+ /** The one credential slot — any platform token. */
1220
+ readonly TOKEN: "SHIP_TOKEN";
1221
+ /** The API endpoint override. */
1222
+ readonly API_URL: "SHIP_API_URL";
1223
+ };
1224
+ /**
1225
+ * Where a human creates an API key — the console deep link quoted by every
1226
+ * surface that teaches authentication (the CLI's config wizard, the VS Code
1227
+ * and n8n listings, the n8n rate-limit hint and credential copy). Written
1228
+ * out in five files across three repos until this export.
1229
+ *
1230
+ * Production-branded by design: published artifacts name the product, never
1231
+ * an environment (root `CLAUDE.md`, "Environment-Aware URLs").
1232
+ */
1233
+ export declare const MY_API_KEY_URL = "https://my.shipstatic.com/api-key";
1234
+ /**
1235
+ * How long an anonymous deployment lives before it expires.
1236
+ *
1237
+ * The lifetime of the public tier, and one fact with several readers. The API
1238
+ * stamps a deployment's `expires` from it and gives a claim code exactly the
1239
+ * same window — a live site with a dead claim link is a coherence bug, so the
1240
+ * two are one constant rather than two that agree. Both MCP transports quote
1241
+ * the duration in prose an agent reads, and derive it from here rather than
1242
+ * writing it out, which they did in eight places until this export existed.
1243
+ *
1244
+ * Seconds, spelled in the name: this platform has both second- and
1245
+ * millisecond-valued durations, and the pair is only safe when each says which
1246
+ * it is.
1247
+ */
1248
+ export declare const PUBLIC_DEPLOYMENT_TTL_SECONDS: number;
732
1249
  /**
733
1250
  * Universal deploy input — the union of every shape the SDK accepts.
734
1251
  *
@@ -747,8 +1264,12 @@ export type DeployInput = File[] | string | string[];
747
1264
  export interface DeploymentUploadOptions {
748
1265
  /** Optional labels for categorization and filtering */
749
1266
  labels?: string[];
750
- /** Client identifier (e.g., 'cli', 'sdk', 'web') */
751
- via?: string;
1267
+ /**
1268
+ * Which client is making this deploy. Closed, because the server silently
1269
+ * ignores anything outside the set — so an unchecked string turned a typo
1270
+ * into missing analytics rather than an error. See {@link DeploymentVia}.
1271
+ */
1272
+ via?: DeploymentViaType;
752
1273
  /**
753
1274
  * Optional password that protects this deployment.
754
1275
  *
@@ -768,12 +1289,41 @@ export interface DeploymentUploadOptions {
768
1289
  spa?: boolean;
769
1290
  /** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
770
1291
  captcha?: string;
1292
+ /**
1293
+ * Makes this deploy replayable instead of repeatable.
1294
+ *
1295
+ * A deploy is not naturally idempotent: a client-side timeout on a slow
1296
+ * one leaves the caller unable to tell "it never landed" from "it landed
1297
+ * and the response was lost", and retrying produces a second deployment.
1298
+ * Send the same key on the retry and the platform replays the original
1299
+ * 201 verbatim rather than creating anything
1300
+ * ({@link IDEMPOTENCY_KEY_CONSTRAINTS.WINDOW_SECONDS}).
1301
+ *
1302
+ * **Agents are the audience.** A human notices a duplicate; an automated
1303
+ * retry does not. Pick a key that identifies the ATTEMPT — a run id, a
1304
+ * commit sha, a uuid minted before the first try — never one minted fresh
1305
+ * on each retry, which would defeat the point.
1306
+ *
1307
+ * The replay is per-caller, and it stores successes only: a failed deploy
1308
+ * retries fresh under the same key.
1309
+ */
1310
+ idempotencyKey?: string;
771
1311
  }
772
1312
  /**
773
- * Pagination options for the paginated list endpoints (`GET /deployments`,
774
- * `GET /domains`). The response's `cursor` feeds the next request; a `null`
775
- * cursor on the response means the last page. Omitting both returns the
776
- * server's default first page.
1313
+ * Pagination options for every list endpoint. The response's `cursor` feeds
1314
+ * the next request; a `null` cursor means the last page. Omitting both
1315
+ * returns the server's default first page.
1316
+ *
1317
+ * A list answers `{ <collection>, cursor }` and nothing else — `cursor`
1318
+ * carries the entire has-more signal, so no redundant boolean, and no
1319
+ * `total`. **A count is an aggregate over a collection, not a property of a
1320
+ * page:** including one makes every read pay for a full scan it did not ask
1321
+ * for, which is precisely the cost keyset pagination exists to avoid.
1322
+ *
1323
+ * Counts therefore live on the summary resource that owns them —
1324
+ * `GET /account` (`usage`) for a caller's own totals, `GET /admin/stats` for
1325
+ * platform-wide ones. Ask for a count when you want a count; ask for a page
1326
+ * when you want a page.
777
1327
  */
778
1328
  export interface ListOptions {
779
1329
  /** Maximum number of items to return in one page. */
@@ -781,6 +1331,33 @@ export interface ListOptions {
781
1331
  /** Opaque cursor from the previous page's response. */
782
1332
  cursor?: string;
783
1333
  }
1334
+ /**
1335
+ * What a caller may change on an existing deployment.
1336
+ *
1337
+ * Labels and nothing else: a deployment's content is immutable by design, so
1338
+ * this is the whole mutable surface rather than a subset someone chose.
1339
+ */
1340
+ export interface DeploymentSetOptions {
1341
+ labels: string[];
1342
+ }
1343
+ /**
1344
+ * What `domains.set()` may create or change. Every field is optional because
1345
+ * the call is a natural-key upsert: omitting `deployment` reserves the
1346
+ * domain, naming one links or re-points it, and labels travel either way.
1347
+ *
1348
+ * `deployment` is deliberately not nullable — unlinking is refused (400).
1349
+ * See `npm/ship/CLAUDE.md`, "Domain Write Semantics".
1350
+ */
1351
+ export interface DomainSetOptions {
1352
+ deployment?: string;
1353
+ labels?: string[];
1354
+ }
1355
+ /** What a caller may set when minting a deploy token. */
1356
+ export interface TokenCreateOptions {
1357
+ /** Seconds until expiry; omit for a token that never expires. */
1358
+ ttl?: number;
1359
+ labels?: string[];
1360
+ }
784
1361
  /**
785
1362
  * Deployment resource interface - the contract all implementations must follow.
786
1363
  *
@@ -793,32 +1370,22 @@ export interface DeploymentResource<UploadOptions extends DeploymentUploadOption
793
1370
  upload: (input: DeployInput, options?: UploadOptions) => Promise<DeploymentCreateResponse>;
794
1371
  list: (options?: ListOptions) => Promise<DeploymentListResponse>;
795
1372
  get: (id: string) => Promise<Deployment>;
796
- set: (id: string, options: {
797
- labels: string[];
798
- }) => Promise<Deployment>;
799
- remove: (id: string) => Promise<void>;
1373
+ set: (id: string, options: DeploymentSetOptions) => Promise<Deployment>;
1374
+ delete: (id: string) => Promise<DeploymentDeleteResponse>;
800
1375
  }
801
1376
  /**
802
1377
  * Domain resource interface - the contract all implementations must follow
803
1378
  */
804
1379
  export interface DomainResource {
805
- set: (name: string, options?: {
806
- deployment?: string;
807
- labels?: string[];
808
- }) => Promise<DomainSetResult>;
1380
+ set: (name: string, options?: DomainSetOptions) => Promise<DomainSetResult>;
809
1381
  list: (options?: ListOptions) => Promise<DomainListResponse>;
810
1382
  get: (name: string) => Promise<Domain>;
811
- remove: (name: string) => Promise<void>;
812
- verify: (name: string) => Promise<{
813
- message: string;
814
- }>;
1383
+ delete: (name: string) => Promise<DomainDeleteResponse>;
1384
+ verify: (name: string) => Promise<DomainVerifyResponse>;
815
1385
  validate: (name: string) => Promise<DomainValidateResponse>;
816
1386
  dns: (name: string) => Promise<DomainDnsResponse>;
817
1387
  records: (name: string) => Promise<DomainRecordsResponse>;
818
- share: (name: string) => Promise<{
819
- domain: string;
820
- hash: string;
821
- }>;
1388
+ share: (name: string) => Promise<DomainShareResponse>;
822
1389
  }
823
1390
  /**
824
1391
  * Account resource interface - the contract all implementations must follow
@@ -830,12 +1397,10 @@ export interface AccountResource {
830
1397
  * Token resource interface - the contract all implementations must follow
831
1398
  */
832
1399
  export interface TokenResource {
833
- create: (options?: {
834
- ttl?: number;
835
- labels?: string[];
836
- }) => Promise<TokenCreateResponse>;
837
- list: () => Promise<TokenListResponse>;
838
- remove: (token: string) => Promise<void>;
1400
+ create: (options?: TokenCreateOptions) => Promise<TokenCreateResponse>;
1401
+ list: (options?: ListOptions) => Promise<TokenListResponse>;
1402
+ get: (token: string) => Promise<Token>;
1403
+ delete: (token: string) => Promise<TokenDeleteResponse>;
839
1404
  }
840
1405
  /**
841
1406
  * Billing status response from GET /billing/status
@@ -855,6 +1420,25 @@ export interface BillingStatus {
855
1420
  /** Link to Creem customer portal for billing management, null if unavailable */
856
1421
  portal: string | null;
857
1422
  }
1423
+ /**
1424
+ * Acknowledgement of `POST /billing/cancel`.
1425
+ *
1426
+ * Cancelling leaves no billing entity to return, so it answers with the
1427
+ * account and the one field of the account the call changed — the plan it
1428
+ * landed on. See {@link DeploymentDeleteResponse} for the law.
1429
+ *
1430
+ * This read `{ success: true, message: 'Subscription canceled successfully…' }`
1431
+ * until 2026-07-29, an anonymous shape that `web/my` redeclared inline and
1432
+ * whose prose no surface ever displayed: both callers await the promise and
1433
+ * discard the body, then compose their own toast. The message was written,
1434
+ * serialized, and thrown away on every cancellation.
1435
+ */
1436
+ export interface BillingCancelResponse {
1437
+ /** The account whose subscription was cancelled */
1438
+ readonly account: string;
1439
+ /** The plan the account now holds — `free` on a successful cancellation */
1440
+ readonly plan: AccountPlanType;
1441
+ }
858
1442
  /**
859
1443
  * Checkout session response from POST /billing/checkout
860
1444
  */
@@ -929,7 +1513,7 @@ export interface ActivityMeta {
929
1513
  /**
930
1514
  * Response from GET /activities endpoint
931
1515
  */
932
- export interface ActivityListResponse {
1516
+ export interface ActivityListResponse extends ListResponse {
933
1517
  /** Array of activities */
934
1518
  activities: Activity[];
935
1519
  }