firecrawl 4.42.0 → 4.42.1

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.
@@ -12,7 +12,7 @@ var require_package = __commonJS({
12
12
  "package.json"(exports, module) {
13
13
  module.exports = {
14
14
  name: "@mendable/firecrawl-js",
15
- version: "4.42.0",
15
+ version: "4.42.1",
16
16
  description: "JavaScript SDK for the Firecrawl API: web scraping, crawling, web search, and scientific literature search over a research paper index of PubMed, bioRxiv, medRxiv and arXiv abstracts",
17
17
  main: "dist/index.js",
18
18
  types: "dist/index.d.ts",
package/dist/index.cjs CHANGED
@@ -39,7 +39,7 @@ var require_package = __commonJS({
39
39
  "package.json"(exports2, module2) {
40
40
  module2.exports = {
41
41
  name: "@mendable/firecrawl-js",
42
- version: "4.42.0",
42
+ version: "4.42.1",
43
43
  description: "JavaScript SDK for the Firecrawl API: web scraping, crawling, web search, and scientific literature search over a research paper index of PubMed, bioRxiv, medRxiv and arXiv abstracts",
44
44
  main: "dist/index.js",
45
45
  types: "dist/index.d.ts",
@@ -150,6 +150,21 @@ function getVersion() {
150
150
  }
151
151
  }
152
152
 
153
+ // src/utils/apiOrigin.ts
154
+ function pinToApiOrigin(apiUrl, url) {
155
+ const normalized = url.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020]+/, "");
156
+ if (!/^([a-z][a-z\d+\-.]*:|[\\/]{2})/i.test(normalized)) return url;
157
+ const base = URL.canParse(apiUrl) ? new URL(apiUrl) : null;
158
+ if (!base?.host) {
159
+ throw new Error(`apiUrl must be an absolute URL, got ${JSON.stringify(apiUrl)}`);
160
+ }
161
+ const target = new URL(normalized, base);
162
+ const pinned = new URL(base.origin);
163
+ pinned.pathname = target.pathname;
164
+ pinned.search = target.search;
165
+ return pinned.href;
166
+ }
167
+
153
168
  // src/v2/utils/httpClient.ts
154
169
  var HttpClient = class {
155
170
  instance;
@@ -190,6 +205,7 @@ var HttpClient = class {
190
205
  config.headers = {
191
206
  ...config.headers || {}
192
207
  };
208
+ if (config.url) config.url = pinToApiOrigin(this.apiUrl, config.url);
193
209
  let lastError;
194
210
  for (let attempt = 0; attempt < this.maxRetries; attempt++) {
195
211
  try {
@@ -3020,7 +3036,7 @@ var FirecrawlApp = class {
3020
3036
  throw new FirecrawlError("No crawl ID provided", 400);
3021
3037
  }
3022
3038
  const headers = this.prepareHeaders();
3023
- const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/crawl/${id}`);
3039
+ const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/crawl/${id}`, this.apiUrl);
3024
3040
  if (skip !== void 0) {
3025
3041
  targetURL.searchParams.set("skip", skip.toString());
3026
3042
  }
@@ -3265,7 +3281,7 @@ var FirecrawlApp = class {
3265
3281
  throw new FirecrawlError("No batch scrape ID provided", 400);
3266
3282
  }
3267
3283
  const headers = this.prepareHeaders();
3268
- const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/batch/scrape/${id}`);
3284
+ const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/batch/scrape/${id}`, this.apiUrl);
3269
3285
  if (skip !== void 0) {
3270
3286
  targetURL.searchParams.set("skip", skip.toString());
3271
3287
  }
@@ -3477,7 +3493,7 @@ var FirecrawlApp = class {
3477
3493
  */
3478
3494
  async getRequest(url, headers) {
3479
3495
  try {
3480
- return await import_axios3.default.get(url, { headers });
3496
+ return await import_axios3.default.get(pinToApiOrigin(this.apiUrl, url), { headers });
3481
3497
  } catch (error) {
3482
3498
  if (error instanceof import_axios3.AxiosError && error.response) {
3483
3499
  return error.response;
package/dist/index.d.cts CHANGED
@@ -1676,6 +1676,8 @@ interface CrawlErrorsResponse$1 {
1676
1676
  url: string;
1677
1677
  code?: string;
1678
1678
  error: string;
1679
+ /** Present when the page needs provider terms accepted first. */
1680
+ requiresAction?: RequiresAction;
1679
1681
  }[];
1680
1682
  robotsBlocked: string[];
1681
1683
  }
@@ -2993,6 +2995,8 @@ interface CrawlErrorsResponse {
2993
2995
  url: string;
2994
2996
  code?: string;
2995
2997
  error: string;
2998
+ /** Present when the page needs provider terms accepted first. */
2999
+ requiresAction?: RequiresAction;
2996
3000
  }[];
2997
3001
  /**
2998
3002
  * URLs blocked by robots.txt
package/dist/index.d.ts CHANGED
@@ -1676,6 +1676,8 @@ interface CrawlErrorsResponse$1 {
1676
1676
  url: string;
1677
1677
  code?: string;
1678
1678
  error: string;
1679
+ /** Present when the page needs provider terms accepted first. */
1680
+ requiresAction?: RequiresAction;
1679
1681
  }[];
1680
1682
  robotsBlocked: string[];
1681
1683
  }
@@ -2993,6 +2995,8 @@ interface CrawlErrorsResponse {
2993
2995
  url: string;
2994
2996
  code?: string;
2995
2997
  error: string;
2998
+ /** Present when the page needs provider terms accepted first. */
2999
+ requiresAction?: RequiresAction;
2996
3000
  }[];
2997
3001
  /**
2998
3002
  * URLs blocked by robots.txt
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  require_package
3
- } from "./chunk-OCJTYRX5.js";
3
+ } from "./chunk-YG554MNR.js";
4
4
 
5
5
  // src/v2/utils/httpClient.ts
6
6
  import axios from "axios";
@@ -18,6 +18,21 @@ function getVersion() {
18
18
  }
19
19
  }
20
20
 
21
+ // src/utils/apiOrigin.ts
22
+ function pinToApiOrigin(apiUrl, url) {
23
+ const normalized = url.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020]+/, "");
24
+ if (!/^([a-z][a-z\d+\-.]*:|[\\/]{2})/i.test(normalized)) return url;
25
+ const base = URL.canParse(apiUrl) ? new URL(apiUrl) : null;
26
+ if (!base?.host) {
27
+ throw new Error(`apiUrl must be an absolute URL, got ${JSON.stringify(apiUrl)}`);
28
+ }
29
+ const target = new URL(normalized, base);
30
+ const pinned = new URL(base.origin);
31
+ pinned.pathname = target.pathname;
32
+ pinned.search = target.search;
33
+ return pinned.href;
34
+ }
35
+
21
36
  // src/v2/utils/httpClient.ts
22
37
  var HttpClient = class {
23
38
  instance;
@@ -58,6 +73,7 @@ var HttpClient = class {
58
73
  config.headers = {
59
74
  ...config.headers || {}
60
75
  };
76
+ if (config.url) config.url = pinToApiOrigin(this.apiUrl, config.url);
61
77
  let lastError;
62
78
  for (let attempt = 0; attempt < this.maxRetries; attempt++) {
63
79
  try {
@@ -2673,7 +2689,7 @@ var FirecrawlApp = class {
2673
2689
  if (typeof process !== "undefined" && process.env && process.env.npm_package_version) {
2674
2690
  return process.env.npm_package_version;
2675
2691
  }
2676
- const packageJson = await import("./package-ECWDBUY3.js");
2692
+ const packageJson = await import("./package-FOKY5K5X.js");
2677
2693
  return packageJson.default.version;
2678
2694
  } catch (error) {
2679
2695
  const isTest = typeof process !== "undefined" && (process.env.JEST_WORKER_ID != null || false);
@@ -2888,7 +2904,7 @@ var FirecrawlApp = class {
2888
2904
  throw new FirecrawlError("No crawl ID provided", 400);
2889
2905
  }
2890
2906
  const headers = this.prepareHeaders();
2891
- const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/crawl/${id}`);
2907
+ const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/crawl/${id}`, this.apiUrl);
2892
2908
  if (skip !== void 0) {
2893
2909
  targetURL.searchParams.set("skip", skip.toString());
2894
2910
  }
@@ -3133,7 +3149,7 @@ var FirecrawlApp = class {
3133
3149
  throw new FirecrawlError("No batch scrape ID provided", 400);
3134
3150
  }
3135
3151
  const headers = this.prepareHeaders();
3136
- const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/batch/scrape/${id}`);
3152
+ const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/batch/scrape/${id}`, this.apiUrl);
3137
3153
  if (skip !== void 0) {
3138
3154
  targetURL.searchParams.set("skip", skip.toString());
3139
3155
  }
@@ -3345,7 +3361,7 @@ var FirecrawlApp = class {
3345
3361
  */
3346
3362
  async getRequest(url, headers) {
3347
3363
  try {
3348
- return await axios2.get(url, { headers });
3364
+ return await axios2.get(pinToApiOrigin(this.apiUrl, url), { headers });
3349
3365
  } catch (error) {
3350
3366
  if (error instanceof AxiosError && error.response) {
3351
3367
  return error.response;
@@ -1,4 +1,4 @@
1
1
  import {
2
2
  require_package
3
- } from "./chunk-OCJTYRX5.js";
3
+ } from "./chunk-YG554MNR.js";
4
4
  export default require_package();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firecrawl",
3
- "version": "4.42.0",
3
+ "version": "4.42.1",
4
4
  "description": "JavaScript SDK for the Firecrawl API: web scraping, crawling, web search, and scientific literature search over a research paper index of PubMed, bioRxiv, medRxiv and arXiv abstracts",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -7,7 +7,9 @@ overrides:
7
7
  "rollup@<4.59.0": ">=4.59.0"
8
8
  "picomatch@<4.0.4": ">=4.0.4"
9
9
  handlebars: ">=4.7.9"
10
- "brace-expansion@>=5.0.0 <5.0.9": "5.0.9"
10
+ # GHSA-q2hr-2g5m-vwhr / GHSA-qhr7-859c-m2p7 / GHSA-6j4f-fj2g-mc7p:
11
+ # pin the 5.x line only. Exact 5.0.12 cannot resolve to 6.x.
12
+ "brace-expansion@>=5.0.0 <5.0.12": "5.0.12"
11
13
  "js-yaml@>=3.0.0 <3.15.2": "3.15.2"
12
14
  "js-yaml@>=4.0.0 <4.3.2": "4.3.2"
13
15
  "axios@>=1.0.0 <1.16.0": "1.16.1"
@@ -0,0 +1,81 @@
1
+ import { describe, test, expect, jest, afterEach } from "@jest/globals";
2
+ import axios from "axios";
3
+ import FirecrawlApp from "../../../v1";
4
+
5
+ const API_URL = "https://api.firecrawl.dev";
6
+ const API_KEY = "fc-test";
7
+
8
+ const origins: Array<[string, string]> = [
9
+ ["same origin", API_URL],
10
+ ["cross host", "https://evil.example"],
11
+ ["protocol-relative", "//evil.example"],
12
+ ["different port", "https://api.firecrawl.dev:8443"],
13
+ ["different scheme", "http://api.firecrawl.dev"],
14
+ ["whitespace-prefixed", "\n https://evil.example"],
15
+ ];
16
+
17
+ function mockGet(respond: (url: URL) => unknown) {
18
+ const sent: Array<{ url: string; authorization: unknown }> = [];
19
+ jest.spyOn(axios, "get").mockImplementation(async (url: string, config?: any) => {
20
+ sent.push({ url, authorization: config?.headers?.Authorization });
21
+ return { status: 200, data: respond(new URL(url)) };
22
+ });
23
+ return sent;
24
+ }
25
+
26
+ function expectPinned(sent: Array<{ url: string; authorization: unknown }>, expectedUrls: string[]) {
27
+ expect(sent.map(req => req.url)).toEqual(expectedUrls);
28
+ for (const req of sent) {
29
+ expect(new URL(req.url).origin).toBe(API_URL);
30
+ expect(req.url).not.toContain("evil.example");
31
+ expect(req.authorization).toBe(`Bearer ${API_KEY}`);
32
+ }
33
+ }
34
+
35
+ const completedPage = (next: string) => ({
36
+ success: true,
37
+ status: "completed",
38
+ total: 2,
39
+ completed: 2,
40
+ expiresAt: "2030-01-01T00:00:00Z",
41
+ data: [{ markdown: "a" }],
42
+ next,
43
+ });
44
+ const lastPage = () => ({ success: true, status: "completed", data: [{ markdown: "b" }] });
45
+
46
+ describe("v1 pins next URLs to the api_url origin", () => {
47
+ afterEach(() => {
48
+ jest.restoreAllMocks();
49
+ });
50
+
51
+ describe.each(origins)("%s next", (_label, origin) => {
52
+ const app = new FirecrawlApp({ apiKey: API_KEY, apiUrl: API_URL });
53
+
54
+ test.each([
55
+ ["checkCrawlStatus", "/v1/crawl/abc"],
56
+ ["checkBatchScrapeStatus", "/v1/batch/scrape/abc"],
57
+ ] as const)("%s getAllData follows server next", async (method, path) => {
58
+ const sent = mockGet(url => (url.searchParams.has("skip") ? lastPage() : completedPage(`${origin}${path}?skip=10`)));
59
+ const res: any = await app[method]("abc", true);
60
+ expect(res.data).toHaveLength(2);
61
+ expectPinned(sent, [`${API_URL}${path}`, `${API_URL}${path}?skip=10`]);
62
+ });
63
+
64
+ test.each([
65
+ ["checkCrawlStatus", "/v1/crawl/abc"],
66
+ ["checkBatchScrapeStatus", "/v1/batch/scrape/abc"],
67
+ ] as const)("%s caller-supplied nextURL", async (method, path) => {
68
+ const sent = mockGet(lastPage);
69
+ await app[method]("abc", false, `${origin}${path}?skip=10`);
70
+ expectPinned(sent, [`${API_URL}${path}?skip=10`]);
71
+ });
72
+
73
+ test("monitorJobStatus follows server next", async () => {
74
+ const path = "/v1/crawl/abc";
75
+ const sent = mockGet(url => (url.searchParams.has("skip") ? lastPage() : completedPage(`${origin}${path}?skip=10`)));
76
+ const res: any = await app.monitorJobStatus("abc", app.prepareHeaders(), 0);
77
+ expect(res.data).toHaveLength(2);
78
+ expectPinned(sent, [`${API_URL}${path}`, `${API_URL}${path}?skip=10`]);
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,104 @@
1
+ import { describe, test, expect } from "@jest/globals";
2
+ import axios, { type AxiosAdapter } from "axios";
3
+ import { FirecrawlClient } from "../../../v2/client";
4
+ import { pinToApiOrigin } from "../../../utils/apiOrigin";
5
+
6
+ const API_URL = "https://api.firecrawl.dev";
7
+ const API_KEY = "fc-test";
8
+
9
+ const origins: Array<[string, string]> = [
10
+ ["same origin", API_URL],
11
+ ["cross host", "https://evil.example"],
12
+ ["protocol-relative", "//evil.example"],
13
+ ["different port", "https://api.firecrawl.dev:8443"],
14
+ ["different scheme", "http://api.firecrawl.dev"],
15
+ ["whitespace-prefixed", "\n https://evil.example"],
16
+ ];
17
+
18
+ function makeClient(firstPage: (next: string) => unknown, lastPage: unknown, next: string) {
19
+ const client = new FirecrawlClient({ apiKey: API_KEY, apiUrl: API_URL });
20
+ const sent: Array<{ url: string; authorization: unknown }> = [];
21
+ const adapter: AxiosAdapter = async config => {
22
+ const url = axios.getUri(config);
23
+ sent.push({ url, authorization: config.headers.Authorization });
24
+ const data = new URL(url).searchParams.has("skip") ? lastPage : firstPage(next);
25
+ return { data, status: 200, statusText: "OK", headers: {}, config };
26
+ };
27
+ (client as any).http.instance.defaults.adapter = adapter;
28
+ return { client, sent };
29
+ }
30
+
31
+ function expectPinned(sent: Array<{ url: string; authorization: unknown }>, path: string) {
32
+ expect(sent).toHaveLength(2);
33
+ expect(sent[1]!.url).toBe(`${API_URL}${path}?skip=10`);
34
+ for (const req of sent) {
35
+ expect(new URL(req.url).origin).toBe(API_URL);
36
+ expect(req.url).not.toContain("evil.example");
37
+ expect(req.authorization).toBe(`Bearer ${API_KEY}`);
38
+ }
39
+ }
40
+
41
+ describe("v2 pagination pins next URLs to the api_url origin", () => {
42
+ describe.each(origins)("%s next", (_label, origin) => {
43
+ test("crawl status", async () => {
44
+ const path = "/v2/crawl/abc";
45
+ const { client, sent } = makeClient(
46
+ next => ({ success: true, status: "completed", completed: 2, total: 2, next, data: [{ markdown: "a" }] }),
47
+ { success: true, next: null, data: [{ markdown: "b" }] },
48
+ `${origin}${path}?skip=10`,
49
+ );
50
+ const job = await client.getCrawlStatus("abc");
51
+ expect(job.data).toHaveLength(2);
52
+ expectPinned(sent, path);
53
+ });
54
+
55
+ test("batch scrape status", async () => {
56
+ const path = "/v2/batch/scrape/abc";
57
+ const { client, sent } = makeClient(
58
+ next => ({ success: true, status: "completed", completed: 2, total: 2, next, data: [{ markdown: "a" }] }),
59
+ { success: true, next: null, data: [{ markdown: "b" }] },
60
+ `${origin}${path}?skip=10`,
61
+ );
62
+ const job = await client.getBatchScrapeStatus("abc");
63
+ expect(job.data).toHaveLength(2);
64
+ expectPinned(sent, path);
65
+ });
66
+
67
+ test("monitor check", async () => {
68
+ const path = "/v2/monitor/m1/checks/c1";
69
+ const { client, sent } = makeClient(
70
+ next => ({ success: true, data: { id: "c1", pages: [{ url: "https://a.example" }], next } }),
71
+ { success: true, data: { pages: [{ url: "https://b.example" }], next: null } },
72
+ `${origin}${path}?skip=10`,
73
+ );
74
+ const check = await client.getMonitorCheck("m1", "c1");
75
+ expect(check.pages).toHaveLength(2);
76
+ expectPinned(sent, path);
77
+ });
78
+ });
79
+ });
80
+
81
+ describe("pinToApiOrigin", () => {
82
+ test("rewrites absolute URLs onto the api origin, keeping path and query and dropping the fragment", () => {
83
+ expect(pinToApiOrigin(API_URL, "https://evil.example/v2/crawl/abc?skip=10#frag")).toBe(
84
+ `${API_URL}/v2/crawl/abc?skip=10`,
85
+ );
86
+ expect(pinToApiOrigin("http://localhost:3002", "https://evil.example:444/v2/crawl/abc?skip=10")).toBe(
87
+ "http://localhost:3002/v2/crawl/abc?skip=10",
88
+ );
89
+ expect(pinToApiOrigin(API_URL, "\\\\evil.example/v2/crawl/abc")).toBe(`${API_URL}/v2/crawl/abc`);
90
+ for (const url of ["\nhttps://evil.example/v2/crawl/abc", "h\tttps://evil.example/v2/crawl/abc", "\u0000 //evil.example/v2/crawl/abc", "/\\evil.example/v2/crawl/abc"]) {
91
+ expect(pinToApiOrigin(API_URL, url)).toBe(`${API_URL}/v2/crawl/abc`);
92
+ }
93
+ });
94
+
95
+ test("leaves relative URLs unchanged", () => {
96
+ expect(pinToApiOrigin(API_URL, "/v2/crawl/abc?skip=10")).toBe("/v2/crawl/abc?skip=10");
97
+ });
98
+
99
+ test("throws when apiUrl is not absolute and the URL needs pinning", () => {
100
+ expect(() => pinToApiOrigin("api.firecrawl.dev", "https://evil.example/v2/crawl/abc")).toThrow(
101
+ "apiUrl must be an absolute URL",
102
+ );
103
+ });
104
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Rewrite an absolute or protocol-relative URL onto apiUrl's scheme, host and
3
+ * port (keeping path and query, dropping any fragment) so credentials never
4
+ * leave the configured API origin. Relative URLs are returned unchanged.
5
+ * Throws if apiUrl is not an absolute URL.
6
+ */
7
+ export function pinToApiOrigin(apiUrl: string, url: string): string {
8
+ const normalized = url.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020]+/, "");
9
+ if (!/^([a-z][a-z\d+\-.]*:|[\\/]{2})/i.test(normalized)) return url;
10
+ const base = URL.canParse(apiUrl) ? new URL(apiUrl) : null;
11
+ if (!base?.host) {
12
+ throw new Error(`apiUrl must be an absolute URL, got ${JSON.stringify(apiUrl)}`);
13
+ }
14
+ const target = new URL(normalized, base);
15
+ const pinned = new URL(base.origin);
16
+ pinned.pathname = target.pathname;
17
+ pinned.search = target.search;
18
+ return pinned.href;
19
+ }
package/src/v1/index.ts CHANGED
@@ -2,6 +2,8 @@ import axios, { type AxiosResponse, type AxiosRequestHeaders, AxiosError } from
2
2
  import * as zt from "zod";
3
3
  import { zodSchemaToJsonSchema } from "../utils/zodSchemaToJson";
4
4
  import { TypedEventTarget } from "typescript-event-target";
5
+ import type { RequiresAction } from "../v2/types";
6
+ import { pinToApiOrigin } from "../utils/apiOrigin";
5
7
 
6
8
  /**
7
9
  * Configuration interface for FirecrawlApp.
@@ -439,6 +441,8 @@ export interface CrawlErrorsResponse {
439
441
  url: string,
440
442
  code?: string,
441
443
  error: string,
444
+ /** Present when the page needs provider terms accepted first. */
445
+ requiresAction?: RequiresAction,
442
446
  }[];
443
447
 
444
448
  /**
@@ -919,7 +923,7 @@ export default class FirecrawlApp {
919
923
  }
920
924
 
921
925
  const headers: AxiosRequestHeaders = this.prepareHeaders();
922
- const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/crawl/${id}`);
926
+ const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/crawl/${id}`, this.apiUrl);
923
927
  if (skip !== undefined) {
924
928
  targetURL.searchParams.set("skip", skip.toString());
925
929
  }
@@ -1207,7 +1211,7 @@ export default class FirecrawlApp {
1207
1211
  }
1208
1212
 
1209
1213
  const headers: AxiosRequestHeaders = this.prepareHeaders();
1210
- const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/batch/scrape/${id}`);
1214
+ const targetURL = new URL(nextURL ?? `${this.apiUrl}/v1/batch/scrape/${id}`, this.apiUrl);
1211
1215
  if (skip !== undefined) {
1212
1216
  targetURL.searchParams.set("skip", skip.toString());
1213
1217
  }
@@ -1448,7 +1452,7 @@ export default class FirecrawlApp {
1448
1452
  headers: AxiosRequestHeaders
1449
1453
  ): Promise<AxiosResponse> {
1450
1454
  try {
1451
- return await axios.get(url, { headers });
1455
+ return await axios.get(pinToApiOrigin(this.apiUrl, url), { headers });
1452
1456
  } catch (error) {
1453
1457
  if (error instanceof AxiosError && error.response) {
1454
1458
  return error.response as AxiosResponse;
@@ -152,7 +152,7 @@ export async function getBatchScrapeErrors(
152
152
  try {
153
153
  const res = await http.get<{
154
154
  success?: boolean;
155
- data?: { errors: Array<Record<string, string>>; robotsBlocked: string[] };
155
+ data?: CrawlErrorsResponse;
156
156
  }>(`/v2/batch/scrape/${jobId}/errors`);
157
157
  if (res.status !== 200) throwForBadResponse(res, "get batch scrape errors");
158
158
  const payload = res.data?.data ?? (res.data as any);
@@ -162,7 +162,7 @@ export async function crawl(http: HttpClient, request: CrawlRequest, pollInterva
162
162
 
163
163
  export async function getCrawlErrors(http: HttpClient, crawlId: string): Promise<CrawlErrorsResponse> {
164
164
  try {
165
- const res = await http.get<{ success?: boolean; data?: { errors: Array<Record<string, string>>; robotsBlocked: string[] } }>(`/v2/crawl/${crawlId}/errors`);
165
+ const res = await http.get<{ success?: boolean; data?: CrawlErrorsResponse }>(`/v2/crawl/${crawlId}/errors`);
166
166
  if (res.status !== 200) throwForBadResponse(res, "get crawl errors");
167
167
  const payload = res.data?.data ?? (res.data as any);
168
168
  return { errors: payload.errors || [], robotsBlocked: payload.robotsBlocked || [] };
package/src/v2/types.ts CHANGED
@@ -1955,6 +1955,8 @@ export interface CrawlErrorsResponse {
1955
1955
  url: string;
1956
1956
  code?: string;
1957
1957
  error: string;
1958
+ /** Present when the page needs provider terms accepted first. */
1959
+ requiresAction?: RequiresAction;
1958
1960
  }[];
1959
1961
  robotsBlocked: string[];
1960
1962
  }
@@ -4,6 +4,7 @@ import axios, {
4
4
  type AxiosResponse,
5
5
  } from "axios";
6
6
  import { getVersion } from "./getVersion";
7
+ import { pinToApiOrigin } from "../../utils/apiOrigin";
7
8
 
8
9
  export interface HttpClientOptions {
9
10
  apiKey: string;
@@ -64,6 +65,7 @@ export class HttpClient {
64
65
  config.headers = {
65
66
  ...(config.headers || {}),
66
67
  };
68
+ if (config.url) config.url = pinToApiOrigin(this.apiUrl, config.url);
67
69
 
68
70
  let lastError: any;
69
71
  for (let attempt = 0; attempt < this.maxRetries; attempt++) {