@hunt-labs/bytekit-sdk 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,122 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  This file records only the delta between versions. For how to use the package
9
9
  today, see [README.md](./README.md), which remains the authoritative usage guide.
10
10
 
11
+ ## [0.4.0] - 2026-07-24
12
+
13
+ ### Added
14
+
15
+ - `RequestOptions` (per-call `requestTimeoutMs` + caller `signal`) is now accepted by
16
+ **every** resource method, not just `scrape.create` / `scrape.get`. It is always a
17
+ trailing optional parameter — on `screenshots.create` it is the third positional, after
18
+ `params` — so every existing call site compiles and serializes byte-identically. Per-call
19
+ options continue to beat the constructor's `timeoutMs` default; omitting them leaves the
20
+ constructor default in force exactly as before. (#2547)
21
+ - `ApiResponse<T>` (`{ data, headers, status }`), a generic `client.requestWithResponse<T>()`
22
+ transport, and three typed siblings — `sitemap.getWithResponse`,
23
+ `screenshots.createWithResponse`, `screenshots.getWithResponse` — make the documented
24
+ `X-Raw-Bytes`, `X-Billable-Bytes`, `X-Billing-Multiplier` and `X-Screenshot-*` response
25
+ headers reachable. This surface is purely **additive**: it issues the identical request
26
+ with the identical options as the base method, and no existing return type changes.
27
+ (#2547)
28
+ - `MonitorStatus` and `ScrapeCookie` are exported from the package entrypoint. (#2547)
29
+
30
+ ### Changed (**BREAKING** at compile time)
31
+
32
+ - `ScrapeOpts.cookies` is now `ScrapeCookie[]`, derived from the spec's `Cookie` schema
33
+ (`name` and `value` required; `domain`, `path`, `secure`, `httpOnly` optional), instead of
34
+ an open bag of untyped records. A cookie missing `name` used to compile and fail as a 422
35
+ on the wire; it is a compile error now. Serialization is unchanged. (#2547)
36
+ - `ListMonitorsParams.status` is now the spec enum
37
+ (`'active' | 'processing' | 'paused' | 'cancelled' | 'suspended'`), derived from the
38
+ generated `listMonitors` query type, instead of `string`. A typo like `'activee'` used to
39
+ compile and 422; it is a compile error now. The serialized query string is unchanged.
40
+ (#2547)
41
+
42
+ ### Fixed
43
+
44
+ - `timeoutMs` / `requestTimeoutMs` (and a caller-supplied `signal`) now cover the
45
+ **response body read**, not just the time to first byte of headers. The abort budget was
46
+ armed for `fetch` and cleared the moment headers arrived, after which `request()`'s
47
+ `response.text()`, `requestRaw()`'s `response.arrayBuffer()`, and the error-body
48
+ `response.json()` on a non-2xx all read the body unbounded — so a server that answered
49
+ with headers and then stalled its body hung the caller forever. A mid-body abort now
50
+ surfaces as `ByteKitConnectionError` with `code: 'timeout'` (budget) or `'aborted'`
51
+ (caller signal) instead of a bare `TypeError: terminated`.
52
+
53
+ The default is **unchanged at 120 s**, and the precedence table for
54
+ `signal` / `requestTimeoutMs` / `timeoutMs` is untouched. One consequence worth planning
55
+ for: a legitimately large body (a multi-MB `/v1/fetch` document) now counts against that
56
+ same budget, where previously only the wait for headers did. (#2546)
57
+
58
+ - The README's webhook-retry sample used an invalid delivery id prefix (`whd_`) that its own
59
+ branded type rejects. The sample is corrected, and a new test compiles every TypeScript
60
+ sample in the README against the SDK's sources so a non-compiling sample can no longer
61
+ ship. (#2547)
62
+
63
+ ### Fixed (**BREAKING** at compile time)
64
+
65
+ - `fetch.get` no longer accepts the eleven markdown-tuning options (`markdown_mode`,
66
+ `markdown_query`, `markdown_links`, `markdown_images`, `with_links_summary`,
67
+ `with_images_summary`, `markdown_compact`, `markdown_filter_images`,
68
+ `markdown_include_media`, `markdown_include_warnings`, `markdown_include_stats`).
69
+ The spec's `GET /v1/fetch` declares only `url`, `format`, `country`, `timeout_ms`
70
+ and `cache_ttl`, so those options were silently dropped by the query builder — a
71
+ caller asking for `markdown_mode: 'llm'` compiled and received default article-mode
72
+ markdown. They are now compile errors instead of silent no-ops; use `fetch.create`
73
+ (POST), which accepts and serializes all of them unchanged. `FetchGetOpts` is now
74
+ derived from the generated `getFetch` query type, so future spec drift fails
75
+ `tsc --noEmit` rather than reaching production. (#2539)
76
+
77
+ ## [0.3.0] - 2026-07-21
78
+
79
+ ### Removed (**BREAKING**)
80
+
81
+ - The deprecated `RapidCrawl`, `RapidCrawlError`, and `RapidCrawlConnectionError`
82
+ exports and the `RapidCrawlOptions` type alias (kept as a one-minor-version
83
+ bridge in #2421) are removed. Import the `ByteKit*` names instead. (#2473)
84
+ - The `RecordingId` type export is removed. Recordings are deactivated
85
+ (`/v1/recordings` returns 410), so the branded id had no reachable use.
86
+ Breaking in theory only — unused in practice. (#2481)
87
+
88
+ ### Fixed
89
+
90
+ - `fetch.create` / `fetch.get` no longer throw a bare `SyntaxError` on every
91
+ non-JSON body. `/v1/fetch` returns the fetched content AS the response body
92
+ (metadata in `X-Fetch-*` headers), but both methods routed through the
93
+ JSON-parsing transport. They now use a raw-capable transport and resolve to a
94
+ typed `FetchResult` — raw `body`, byte-exact `bytes`, plus `id`,
95
+ `creditsCharged`, `cache`, `custom`, `contentType`, and `headers` parsed from
96
+ the `X-Fetch-*` metadata. (#2466)
97
+ - A trailing slash on the constructor `baseUrl` (e.g. `"https://api.bytekit.com/"`)
98
+ no longer produces double-slash request URLs — it is normalized once, preserving
99
+ a path-carrying baseUrl. (#2481)
100
+ - `SearchResult.date` is now optional (`string | null | undefined`): `images`
101
+ search results omit the field entirely. (#2481)
102
+
103
+ ### Added
104
+
105
+ - New `usage` resource (`get`, `daily`, `byEndpoint` — `GET /v1/usage`,
106
+ `/v1/usage/daily`, `/v1/usage/by-endpoint`) and `webhooks` resource (`list`,
107
+ `retry` — `GET /v1/webhook-deliveries`, `POST /v1/webhook-deliveries/{id}/retry`),
108
+ with their request/response types exported. (#2471)
109
+ - `ScrapeOpts` and `FetchOpts` now cover the full documented request field set
110
+ (10 previously missing `ScrapeRequest` fields; 11 markdown-tuning
111
+ `FetchRequest` fields), plus the `'text'` markdown-links mode. A compile-time
112
+ drift guard keeps them aligned with the generated OpenAPI schema. (#2475)
113
+ - `monitors`, `sitemap`, `bulk`, and `fetch.bulk` methods are now fully typed from
114
+ the generated OpenAPI `operations` (previously `Record<string, unknown>` in /
115
+ `Promise<unknown>` out); their request/response types are re-exported. (#2481)
116
+ - `monitors.list` accepts typed query params and returns a typed response
117
+ (`ListMonitorsParams` / `ListMonitorsResponse`, both exported). (#2480)
118
+
119
+ ### Changed (**BREAKING**)
120
+
121
+ - `fetch.get` now rejects a `custom` option at compile time: `GET /v1/fetch` has
122
+ no `custom` query param, so the value was silently dropped before. Use
123
+ `fetch.create` (POST) to echo a `custom` payload. (#2481)
124
+ - `FetchOpts.format` is narrowed from `string` to the spec union
125
+ `'markdown' | 'html'`; off-spec values now fail to compile. (#2475)
126
+
11
127
  ## [0.2.2] - 2026-07-16
12
128
 
13
129
  ### Changed
@@ -85,6 +201,7 @@ against 0.1.0 — read each one before upgrading.
85
201
  (scrape, screenshot, search, and related endpoints), published under the original
86
202
  `@rapidcrawl/sdk` name.
87
203
 
204
+ [0.3.0]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/sdk-typescript
88
205
  [0.2.2]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/sdk-typescript
89
206
  [0.2.1]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/sdk-typescript
90
207
  [0.2.0]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/sdk-typescript
package/README.md CHANGED
@@ -27,16 +27,15 @@ Create an API key at [app.bytekit.com](https://app.bytekit.com) (keys are
27
27
  prefixed `sk_live_`) and pass it to the client constructor:
28
28
 
29
29
  ```ts
30
- import { RapidCrawl } from '@hunt-labs/bytekit-sdk';
30
+ import { ByteKit } from '@hunt-labs/bytekit-sdk';
31
31
 
32
- const client = new RapidCrawl({ apiKey: process.env.BYTEKIT_API_KEY! }); // sk_live_...
32
+ const client = new ByteKit({ apiKey: process.env.BYTEKIT_API_KEY! }); // sk_live_...
33
33
  ```
34
34
 
35
35
  The key is sent as `Authorization: Bearer <apiKey>` on every request. Keep it
36
36
  server-side — it is not safe to ship in a browser bundle.
37
37
 
38
- > The client class is named `RapidCrawl` for historical reasons (ByteKit's
39
- > previous product name). It is the SDK's only entrypoint class.
38
+ > `ByteKit` is the SDK's only entrypoint class.
40
39
 
41
40
  ## Base-URL override
42
41
 
@@ -44,7 +43,7 @@ Requests go to `https://api.bytekit.com` by default. Point the client at
44
43
  staging, a local gateway, or a proxy with the `baseUrl` option:
45
44
 
46
45
  ```ts
47
- const client = new RapidCrawl({
46
+ const client = new ByteKit({
48
47
  apiKey: process.env.BYTEKIT_API_KEY!,
49
48
  baseUrl: 'https://api-stg.bytekit.com', // default: https://api.bytekit.com
50
49
  });
@@ -69,8 +68,27 @@ const scrape = await client.scrape.get('sc_123abc');
69
68
 
70
69
  ### Raw fetch (unprocessed response body)
71
70
 
71
+ `/v1/fetch` returns the fetched content **as the body**, with metadata in
72
+ `X-Fetch-*` response headers. `fetch.create` / `fetch.get` resolve a
73
+ `FetchResult` — `body` is the raw content string (a JSON-serving upstream
74
+ comes back as the raw JSON string, never a parsed object), and the named
75
+ fields decode the metadata headers. `body` is UTF-8-decoded, so for true
76
+ binary content (images, PDFs, etc. — `/v1/fetch` supports raw passthrough)
77
+ use `bytes` instead, which is byte-for-byte identical to the wire content:
78
+
72
79
  ```ts
73
- const raw = await client.fetch.create({ url: 'https://example.com' });
80
+ const res = await client.fetch.create({ url: 'https://example.com' });
81
+
82
+ console.log(res.body); // raw content (e.g. "<!doctype html>…") — UTF-8 decoded
83
+ console.log(res.bytes); // Uint8Array — byte-exact, safe for binary content
84
+ console.log(res.id); // X-Fetch-ID, e.g. "ft_0123abcd…"
85
+ console.log(res.creditsCharged); // X-Fetch-Credits-Charged, as a number
86
+ console.log(res.cache); // "hit" | "miss" | "bypass"
87
+ console.log(res.custom); // base64 X-Fetch-Custom echo, or null
88
+ console.log(res.contentType); // response Content-Type, e.g. "text/html"
89
+
90
+ // Anything not surfaced as a named field is on res.headers:
91
+ console.log(res.headers.get('X-Fetch-Waf-Vendor'));
74
92
  ```
75
93
 
76
94
  ### Web search
@@ -124,7 +142,10 @@ await client.bulk.screenshots.list('bulk_123abc');
124
142
  ```ts
125
143
  const monitor = await client.monitors.create({
126
144
  url: 'https://example.com/pricing',
145
+ type: 'screenshot',
127
146
  interval_type: 'daily',
147
+ change_threshold: 5,
148
+ notify_on: 'change',
128
149
  webhook_url: 'https://your.app/hooks/bytekit',
129
150
  });
130
151
  await client.monitors.list();
@@ -134,7 +155,17 @@ await client.monitors.delete('mon_123abc');
134
155
  ### Sitemap crawl
135
156
 
136
157
  ```ts
137
- const crawl = await client.sitemap.create({ url: 'https://example.com' });
158
+ // `url` is the only required field — every other option can be omitted.
159
+ const minimal = await client.sitemap.create({ url: 'https://example.com' });
160
+
161
+ const crawl = await client.sitemap.create({
162
+ url: 'https://example.com',
163
+ strategy: 'standard',
164
+ max_depth: 2,
165
+ max_urls: 5000,
166
+ cache_ttl: '30d',
167
+ compact: false,
168
+ });
138
169
  const sitemap = await client.sitemap.get('sm_123abc');
139
170
  ```
140
171
 
@@ -143,20 +174,59 @@ const sitemap = await client.sitemap.get('sm_123abc');
143
174
  ```ts
144
175
  const account = await client.account.get();
145
176
  const keys = await client.account.apiKeys.list();
177
+
178
+ // Usage (Bearer API key auth)
179
+ const usage = await client.usage.get();
180
+ const daily = await client.usage.daily({ from: '2026-01-01', to: '2026-01-31' });
181
+ const byEndpoint = await client.usage.byEndpoint();
182
+
183
+ // Webhook deliveries
184
+ const deliveries = await client.webhooks.list();
185
+ await client.webhooks.retry('wh_123abc');
146
186
  ```
147
187
 
188
+ > **Note:** `client.account.*` (including `client.account.apiKeys.*`) targets the
189
+ > `/v1/account/*` endpoints, which authenticate with a Clerk dashboard session —
190
+ > **not** a Bearer API key. Calling them with an API-key client will fail auth.
191
+ > They remain on the SDK surface for dashboard/session consumers and are marked
192
+ > `x-sdk-scope: internal` in the spec, so they are intentionally excluded from the
193
+ > v0.1 Bearer-key parity guarantee.
194
+
195
+ ### Response headers
196
+
197
+ The API carries billing and capture metadata in `X-*` response headers alongside the
198
+ JSON body — `X-Raw-Bytes`, `X-Billable-Bytes`, `X-Billing-Multiplier`, and the
199
+ `X-Screenshot-*` family. Every resource method returns just the parsed body, so use a
200
+ `*WithResponse` sibling when you also need the headers:
201
+
202
+ ```ts
203
+ const { data, headers, status } = await client.sitemap.getWithResponse('sm_123abc');
204
+
205
+ console.log(data.status); // exactly what sitemap.get('sm_123abc') resolves to
206
+ console.log(headers.get('X-Raw-Bytes')); // e.g. "12345"
207
+ console.log(status); // 200
208
+ ```
209
+
210
+ `client.screenshots.createWithResponse()` and `client.screenshots.getWithResponse()`
211
+ work the same way. Any other endpoint's headers are reachable through the generic
212
+ transport, `client.requestWithResponse('GET', '/v1/scrape/sc_123abc')`.
213
+
214
+ These siblings are purely additive: they issue the identical request and accept the
215
+ identical options as their base methods, and `data` is exactly what the base method
216
+ resolves to — no existing return type changes.
217
+
148
218
  ## Error handling
149
219
 
150
- Any 4xx/5xx response throws a `RapidCrawlError` carrying the HTTP status and
220
+ Any 4xx/5xx response throws a `ByteKitError` carrying the HTTP status and
151
221
  the API's error code:
152
222
 
153
223
  ```ts
154
- import { RapidCrawl, RapidCrawlError } from '@hunt-labs/bytekit-sdk';
224
+ import { ByteKit, ByteKitError } from '@hunt-labs/bytekit-sdk';
155
225
 
156
226
  try {
157
227
  await client.scrape.create({ url: 'https://example.com' });
158
228
  } catch (err) {
159
- if (err instanceof RapidCrawlError) {
229
+ if (err instanceof ByteKitError) {
160
230
  console.error(err.status, err.code, err.message);
161
231
  }
162
232
  }
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * To add a method: annotate the operation in the spec first, then add it here.
10
10
  */
11
- export declare const V01_METHODS: readonly ["scrape.create", "scrape.get", "screenshots.create", "screenshots.get", "bulk.create", "bulk.get", "bulk.cancel", "bulk.screenshots.list", "scrape.bulk.create", "scrape.bulk.get", "fetch.create", "fetch.get", "fetch.bulk.create", "fetch.bulk.get", "monitors.create", "monitors.list", "monitors.get", "monitors.update", "monitors.delete", "monitors.captures.list", "sitemap.create", "sitemap.get", "search.query", "account.get"];
11
+ export declare const V01_METHODS: readonly ["scrape.create", "scrape.get", "screenshots.create", "screenshots.get", "bulk.create", "bulk.get", "bulk.cancel", "bulk.screenshots.list", "scrape.bulk.create", "scrape.bulk.get", "fetch.create", "fetch.get", "fetch.bulk.create", "fetch.bulk.get", "monitors.create", "monitors.list", "monitors.get", "monitors.update", "monitors.delete", "monitors.captures.list", "sitemap.create", "sitemap.get", "search.query", "account.get", "usage.get", "usage.daily", "usage.byEndpoint", "webhooks.list", "webhooks.retry"];
12
12
  export type V01Method = (typeof V01_METHODS)[number];
package/dist/allowlist.js CHANGED
@@ -34,4 +34,11 @@ export const V01_METHODS = [
34
34
  'sitemap.get',
35
35
  'search.query',
36
36
  'account.get',
37
+ // usage + webhook-delivery ops — resource surface shipped in #2471, brought
38
+ // under the parity guard in #2476.
39
+ 'usage.get',
40
+ 'usage.daily',
41
+ 'usage.byEndpoint',
42
+ 'webhooks.list',
43
+ 'webhooks.retry',
37
44
  ];
package/dist/client.d.ts CHANGED
@@ -6,12 +6,16 @@ import { MonitorsResource } from './resources/monitors.js';
6
6
  import { SitemapResource } from './resources/sitemap.js';
7
7
  import { SearchResource } from './resources/search.js';
8
8
  import { AccountResource } from './resources/account.js';
9
+ import { UsageResource } from './resources/usage.js';
10
+ import { WebhooksResource } from './resources/webhooks.js';
9
11
  export interface ByteKitOptions {
10
12
  apiKey: string;
11
13
  baseUrl?: string;
12
14
  /**
13
15
  * Default CLIENT-SIDE request timeout in ms — the abort budget for the whole HTTP
14
- * round-trip made by this SDK. `0` disables the SDK-side timeout. Default 120000.
16
+ * round-trip made by this SDK, covering time-to-headers **and** the response body
17
+ * read (so a large legitimate body counts against it too). `0` disables the SDK-side
18
+ * timeout. Default 120000.
15
19
  *
16
20
  * NOT the same thing as `ScrapeOpts.timeout_ms`, which is the SERVER-side budget the
17
21
  * gateway applies to the scrape itself. Override per call with
@@ -24,12 +28,14 @@ export interface RequestOptions {
24
28
  /**
25
29
  * Caller-owned abort signal. When supplied WITHOUT a per-call `requestTimeoutMs`, it
26
30
  * fully replaces the constructor timeout — supply `requestTimeoutMs` alongside it to
27
- * keep a bound (the request then aborts on whichever fires first).
31
+ * keep a bound (the request then aborts on whichever fires first). Firing it aborts an
32
+ * in-flight response body read as well, not only the wait for headers.
28
33
  */
29
34
  signal?: AbortSignal;
30
35
  /**
31
- * Per-call CLIENT-SIDE abort budget in ms. Wins over the constructor's `timeoutMs`.
32
- * `0` disables the SDK-side timeout for this call.
36
+ * Per-call CLIENT-SIDE abort budget in ms, covering headers **and** the response body
37
+ * read. Wins over the constructor's `timeoutMs`. `0` disables the SDK-side timeout for
38
+ * this call.
33
39
  *
34
40
  * Deliberately NOT named `timeoutMs`: it rides on the same call as `ScrapeOpts.timeout_ms`
35
41
  * (the SERVER-side scrape budget), and the two mean different things —
@@ -38,6 +44,29 @@ export interface RequestOptions {
38
44
  */
39
45
  requestTimeoutMs?: number;
40
46
  }
47
+ /**
48
+ * A JSON response together with its transport metadata (#2547).
49
+ *
50
+ * Returned by {@link ByteKit.requestWithResponse} and by the `*WithResponse` sibling
51
+ * methods. `data` is exactly what the corresponding base method resolves to, so a caller
52
+ * can migrate from `x = await r.get(id)` to `{ data: x, headers } = await
53
+ * r.getWithResponse(id)` with no change to how `x` is used.
54
+ *
55
+ * This is an ADDITIVE surface: it never changes the return type, the request bytes, or the
56
+ * abort semantics of any existing method.
57
+ */
58
+ export interface ApiResponse<T> {
59
+ /** The parsed JSON body — identical to what the base method returns. `undefined` on 204/empty. */
60
+ data: T;
61
+ /**
62
+ * The full RESPONSE headers, e.g. `X-Raw-Bytes`, `X-Billable-Bytes`,
63
+ * `X-Billing-Multiplier`. The `Authorization` header lives on the REQUEST and is never
64
+ * reachable here.
65
+ */
66
+ headers: Headers;
67
+ /** The HTTP status code of the successful response (2xx; non-2xx already threw). */
68
+ status: number;
69
+ }
41
70
  /** Why a request never produced an HTTP response. */
42
71
  export type ConnectionErrorCode = 'timeout' | 'aborted' | 'connection';
43
72
  /** Thrown when the API responds with a 4xx or 5xx status code. */
@@ -71,6 +100,8 @@ export declare class ByteKit {
71
100
  readonly sitemap: SitemapResource;
72
101
  readonly search: SearchResource;
73
102
  readonly account: AccountResource;
103
+ readonly usage: UsageResource;
104
+ readonly webhooks: WebhooksResource;
74
105
  constructor(options: ByteKitOptions);
75
106
  /**
76
107
  * Resolve the abort budget for one request from the caller's options and the
@@ -86,6 +117,31 @@ export declare class ByteKit {
86
117
  * A timeout of `0` (either level) means "no SDK-side timeout".
87
118
  */
88
119
  private resolveSignal;
120
+ /**
121
+ * Perform the authenticated round-trip and return whatever the caller's `readBody`
122
+ * continuation makes of the successful response, having already thrown for the two
123
+ * non-body failure modes:
124
+ *
125
+ * - `ByteKitConnectionError` when no HTTP response was produced at all
126
+ * (DNS/socket/TLS failure, client-side timeout, or caller abort) — including one
127
+ * that fires while the body is streaming.
128
+ * - `ByteKitError` on any 4xx/5xx, carrying the API's `code`/`message`.
129
+ *
130
+ * The body is read through a continuation each caller supplies — JSON text for
131
+ * `request()`, raw bytes for `requestRaw()`. This is the single place that owns auth,
132
+ * the abort budget, and the error taxonomy; the two public transports differ only in
133
+ * how they read a successful body.
134
+ *
135
+ * The abort budget spans headers **and** body (#2546). Returning the `Response` used to
136
+ * end the guarded region at the headers boundary, leaving `response.text()` /
137
+ * `response.arrayBuffer()` — and the `!response.ok` branch's `response.json()` —
138
+ * unbounded: a server that sent headers and then stalled its body hung the caller
139
+ * forever. `readBody` must therefore ONLY consume the stream; `JSON.parse` and
140
+ * `TextDecoder` run in the callers, outside the guard, so a malformed body still
141
+ * surfaces its own parse error rather than being remapped to a connection error.
142
+ * `cleanup()` still runs exactly once per call, now after the body completes.
143
+ */
144
+ private send;
89
145
  /**
90
146
  * Make an authenticated JSON request to the ByteKit API.
91
147
  *
@@ -94,6 +150,44 @@ export declare class ByteKit {
94
150
  * timeout, or caller abort).
95
151
  */
96
152
  request<T>(method: string, path: string, body?: unknown, options?: RequestOptions): Promise<T>;
153
+ /**
154
+ * Make an authenticated request whose response body IS the content, not a JSON
155
+ * envelope — the `/v1/fetch` contract, where metadata rides in `X-Fetch-*`
156
+ * response headers.
157
+ *
158
+ * Returns BOTH a UTF-8-decoded string AND the raw, byte-for-byte body, plus the
159
+ * response `Headers` and status, WITHOUT ever calling `JSON.parse`. This is
160
+ * deliberately a separate transport from `request()`: the generic JSON parse
161
+ * there is shared by every other resource and must not change (#2466).
162
+ *
163
+ * `/v1/fetch` supports true binary passthrough (images, PDFs, etc.) as a
164
+ * first-class mode, and UTF-8 decoding a non-UTF-8 byte stream is lossy — any
165
+ * invalid byte sequence gets silently replaced with U+FFFD. Reading the body
166
+ * once via `arrayBuffer()` lets both forms be derived from the same bytes: `bytes`
167
+ * is always byte-exact, `body` is a best-effort string for callers who know the
168
+ * content is text.
169
+ */
170
+ requestRaw(method: string, path: string, body?: unknown, options?: RequestOptions): Promise<{
171
+ body: string;
172
+ bytes: Uint8Array;
173
+ headers: Headers;
174
+ status: number;
175
+ }>;
176
+ /**
177
+ * Make an authenticated JSON request and return the parsed body TOGETHER WITH the
178
+ * response headers and status (#2547).
179
+ *
180
+ * Identical to {@link ByteKit.request} in every observable respect — same method, path,
181
+ * body, `RequestOptions` precedence, error taxonomy, and empty-body handling (both drain
182
+ * the body through {@link readSuccessText} and parse it through {@link parseJsonBody}). It
183
+ * differs only in what it returns, so nothing about an existing call changes by switching
184
+ * to it.
185
+ *
186
+ * Use it to read the documented `X-*` metadata the API emits alongside a JSON envelope —
187
+ * `X-Raw-Bytes`, `X-Billable-Bytes`, `X-Billing-Multiplier`, the `X-Screenshot-*` family
188
+ * — for any endpoint. `sitemap.getWithResponse`, `screenshots.createWithResponse` and
189
+ * `screenshots.getWithResponse` are typed shorthands over this for the endpoints whose
190
+ * headers are documented in the API reference.
191
+ */
192
+ requestWithResponse<T>(method: string, path: string, body?: unknown, options?: RequestOptions): Promise<ApiResponse<T>>;
97
193
  }
98
- export { ByteKit as RapidCrawl, ByteKitError as RapidCrawlError, ByteKitConnectionError as RapidCrawlConnectionError, };
99
- export type { ByteKitOptions as RapidCrawlOptions };