diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 69ae82f0..1d8e9898 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -55,14 +55,18 @@ jobs:
run: ./scripts/build
- name: Get GitHub OIDC Token
- if: github.repository == 'stainless-sdks/browserbase-node'
+ if: |-
+ github.repository == 'stainless-sdks/browserbase-node' &&
+ !startsWith(github.ref, 'refs/heads/stl/')
id: github-oidc
uses: actions/github-script@v8
with:
script: core.setOutput('github_token', await core.getIDToken());
- name: Upload tarball
- if: github.repository == 'stainless-sdks/browserbase-node'
+ if: |-
+ github.repository == 'stainless-sdks/browserbase-node' &&
+ !startsWith(github.ref, 'refs/heads/stl/')
env:
URL: https://pkg.stainless.com/s
AUTH: ${{ steps.github-oidc.outputs.github_token }}
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 6ed9c801..4fbbea26 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "2.7.0"
+ ".": "2.8.0-alpha.1"
}
diff --git a/.stats.yml b/.stats.yml
index 05de0332..3aabff9c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-215bc4361122162181eecce83c0dbdda7c45a21801e7addb75102e8011413069.yml
-openapi_spec_hash: c4fadc5bb6b84cd3988c8d864b67bf61
-config_hash: a106b247c7cdf02ac1033077402cfe2d
+configured_endpoints: 20
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-b20f9fea14d79990ab1af3d276f931e026cd955ac623ec6ace80b2af90de170f.yml
+openapi_spec_hash: 943ff4b3297014503fdc9854544cb9a4
+config_hash: 55c54fdafc9e80be584829b5724b00ab
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ae2ab95..102116db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,46 @@
# Changelog
+## 2.8.0-alpha.1 (2026-03-11)
+
+Full Changelog: [v2.7.0...v2.8.0-alpha.1](https://github.com/browserbase/sdk-node/compare/v2.7.0...v2.8.0-alpha.1)
+
+### Features
+
+* [CORE-1804][apps/api] Add fetch API schema ([969d581](https://github.com/browserbase/sdk-node/commit/969d58179bdbccc926871e9fdfec572a5aa73a97))
+* **api:** api update ([#19](https://github.com/browserbase/sdk-node/issues/19)) ([8df9cc8](https://github.com/browserbase/sdk-node/commit/8df9cc8c76e4a615ba63d4568086bd60e88a0dfb))
+* **api:** update via SDK Studio ([#10](https://github.com/browserbase/sdk-node/issues/10)) ([2c6c15d](https://github.com/browserbase/sdk-node/commit/2c6c15d148d1a5e74a48f92087580d8c590288af))
+* **api:** update via SDK Studio ([#11](https://github.com/browserbase/sdk-node/issues/11)) ([9191ed6](https://github.com/browserbase/sdk-node/commit/9191ed631256b52a39de1aafcf1d7deb2788efdb))
+* **api:** update via SDK Studio ([#12](https://github.com/browserbase/sdk-node/issues/12)) ([ebeaf82](https://github.com/browserbase/sdk-node/commit/ebeaf82736747398450d739e27d6874603c4f942))
+* **api:** update via SDK Studio ([#13](https://github.com/browserbase/sdk-node/issues/13)) ([c0ffdce](https://github.com/browserbase/sdk-node/commit/c0ffdce615059b9523817d22ef18e01e778f3454))
+* **api:** update via SDK Studio ([#16](https://github.com/browserbase/sdk-node/issues/16)) ([a96a86c](https://github.com/browserbase/sdk-node/commit/a96a86c2e7c5025cacd7edd89f88b914de3596e3))
+* **api:** update via SDK Studio ([#17](https://github.com/browserbase/sdk-node/issues/17)) ([52cf741](https://github.com/browserbase/sdk-node/commit/52cf741bc4c5712f2fe15ee6eaa48e4c3643ec58))
+* **api:** update via SDK Studio ([#3](https://github.com/browserbase/sdk-node/issues/3)) ([8a06f81](https://github.com/browserbase/sdk-node/commit/8a06f81c1cd5dc8a64b9fd10ed26f8547469fdeb))
+* **api:** update via SDK Studio ([#4](https://github.com/browserbase/sdk-node/issues/4)) ([cf6309b](https://github.com/browserbase/sdk-node/commit/cf6309bb5de1376139fb0ba6b8e45b5d0801bee5))
+* **api:** update via SDK Studio ([#5](https://github.com/browserbase/sdk-node/issues/5)) ([4275810](https://github.com/browserbase/sdk-node/commit/427581035437844d44333c0e3471db675dff89c7))
+* **api:** update via SDK Studio ([#6](https://github.com/browserbase/sdk-node/issues/6)) ([299d77e](https://github.com/browserbase/sdk-node/commit/299d77ebc0994b0ab39a9c908157d42d605e9765))
+* **api:** update via SDK Studio ([#7](https://github.com/browserbase/sdk-node/issues/7)) ([e1e4738](https://github.com/browserbase/sdk-node/commit/e1e47381dc142f94bd61e7f5819eafbf5b4797a7))
+* **api:** update via SDK Studio ([#8](https://github.com/browserbase/sdk-node/issues/8)) ([2d845ff](https://github.com/browserbase/sdk-node/commit/2d845ffc683ea988980e42ea2c03699034af6980))
+* **api:** update via SDK Studio ([#9](https://github.com/browserbase/sdk-node/issues/9)) ([a17fadc](https://github.com/browserbase/sdk-node/commit/a17fadcac323079f3e36e0c965f6d6c1be26f1d8))
+
+
+### Bug Fixes
+
+* **client:** preserve URL params already embedded in path ([81f76ab](https://github.com/browserbase/sdk-node/commit/81f76aba8e0ac48fe758e9c567ba9e3fb174cfc5))
+* use npm publish instead of yarn publish ([#173](https://github.com/browserbase/sdk-node/issues/173)) ([05b3225](https://github.com/browserbase/sdk-node/commit/05b3225efe94473f2b49988dab63a9e67beb9802))
+
+
+### Chores
+
+* **ci:** skip uploading artifacts on stainless-internal branches ([0c1949d](https://github.com/browserbase/sdk-node/commit/0c1949d9237aa69c6ca9adda699376672f398f24))
+* go live ([#1](https://github.com/browserbase/sdk-node/issues/1)) ([bc9e36e](https://github.com/browserbase/sdk-node/commit/bc9e36ec17be8df65bc4bd43bad6ad6617e4973c))
+* **internal:** codegen related update ([d8b846c](https://github.com/browserbase/sdk-node/commit/d8b846cae415cec3a826e26b1a8cae47cca9e943))
+* **internal:** move stringifyQuery implementation to internal function ([fb87469](https://github.com/browserbase/sdk-node/commit/fb8746921c5bad61c77f44b6e3fe8bf164c1e5b5))
+* **release:** prepare alpha ([#36](https://github.com/browserbase/sdk-node/issues/36)) ([c5e0f61](https://github.com/browserbase/sdk-node/commit/c5e0f61f324c607421449db10cf861a0a5100b4a))
+* **release:** v2.0.0 ([#39](https://github.com/browserbase/sdk-node/issues/39)) ([2ecb572](https://github.com/browserbase/sdk-node/commit/2ecb5720bc6102a3487ecd71f06fd3c548b5f760))
+* **test:** do not count install time for mock server timeout ([1fd6556](https://github.com/browserbase/sdk-node/commit/1fd655639e75cf32f66d361c5c287960f6fc3982))
+* update placeholder string ([fb97b1c](https://github.com/browserbase/sdk-node/commit/fb97b1c30905958b6a427a13e6922a4da30c2c15))
+* update SDK settings ([#21](https://github.com/browserbase/sdk-node/issues/21)) ([dd2956f](https://github.com/browserbase/sdk-node/commit/dd2956f0b22aac232b6ae3cdd572a4d6b688099d))
+
## 2.7.0-alpha.3 (2026-02-26)
Full Changelog: [v2.7.0-alpha.2...v2.7.0-alpha.3](https://github.com/browserbase/sdk-node/compare/v2.7.0-alpha.2...v2.7.0-alpha.3)
diff --git a/api.md b/api.md
index e0533626..f4ff5038 100644
--- a/api.md
+++ b/api.md
@@ -25,6 +25,16 @@ Methods:
- client.extensions.retrieve(id) -> Extension
- client.extensions.delete(id) -> void
+# FetchAPI
+
+Types:
+
+- FetchAPICreateResponse
+
+Methods:
+
+- client.fetchAPI.create({ ...params }) -> FetchAPICreateResponse
+
# Projects
Types:
diff --git a/package-lock.json b/package-lock.json
index 03380abc..e7a3e5c2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@browserbasehq/sdk",
- "version": "2.7.0",
+ "version": "2.8.0-alpha.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@browserbasehq/sdk",
- "version": "2.7.0",
+ "version": "2.8.0-alpha.1",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
diff --git a/package.json b/package.json
index 36735b06..35ff0bef 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@browserbasehq/sdk",
- "version": "2.7.0",
+ "version": "2.8.0-alpha.1",
"description": "The official Node.js library for the Browserbase API",
"author": "Browserbase ",
"types": "dist/index.d.ts",
diff --git a/scripts/mock b/scripts/mock
index 0b28f6ea..bcf3b392 100755
--- a/scripts/mock
+++ b/scripts/mock
@@ -21,11 +21,22 @@ echo "==> Starting mock server with URL ${URL}"
# Run prism mock on the given spec
if [ "$1" == "--daemon" ]; then
+ # Pre-install the package so the download doesn't eat into the startup timeout
+ npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism --version
+
npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log &
- # Wait for server to come online
+ # Wait for server to come online (max 30s)
echo -n "Waiting for server"
+ attempts=0
while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do
+ attempts=$((attempts + 1))
+ if [ "$attempts" -ge 300 ]; then
+ echo
+ echo "Timed out waiting for Prism server to start"
+ cat .prism.log
+ exit 1
+ fi
echo -n "."
sleep 0.1
done
diff --git a/src/core.ts b/src/core.ts
index 350006f3..821e4e5e 100644
--- a/src/core.ts
+++ b/src/core.ts
@@ -6,6 +6,7 @@ import {
APIConnectionTimeoutError,
APIUserAbortError,
} from './error';
+import { stringifyQuery } from './internal/utils/query';
import {
kind as shimsKind,
type Readable,
@@ -523,32 +524,20 @@ export abstract class APIClient {
: new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
const defaultQuery = this.defaultQuery();
- if (!isEmptyObj(defaultQuery)) {
- query = { ...defaultQuery, ...query } as Req;
+ const pathQuery = Object.fromEntries(url.searchParams);
+ if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
+ query = { ...pathQuery, ...defaultQuery, ...query } as Req;
}
if (typeof query === 'object' && query && !Array.isArray(query)) {
- url.search = this.stringifyQuery(query as Record);
+ url.search = this.stringifyQuery(query);
}
return url.toString();
}
- protected stringifyQuery(query: Record): string {
- return Object.entries(query)
- .filter(([_, value]) => typeof value !== 'undefined')
- .map(([key, value]) => {
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
- return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
- }
- if (value === null) {
- return `${encodeURIComponent(key)}=`;
- }
- throw new BrowserbaseError(
- `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`,
- );
- })
- .join('&');
+ protected stringifyQuery(query: object | Record): string {
+ return stringifyQuery(query);
}
async fetchWithTimeout(
@@ -630,9 +619,9 @@ export abstract class APIClient {
}
}
- // If the API asks us to wait a certain amount of time (and it's a reasonable amount),
- // just do what it says, but otherwise calculate a default
- if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) {
+ // If the API asks us to wait a certain amount of time, do what it says.
+ // Otherwise calculate a default.
+ if (timeoutMillis === undefined) {
const maxRetries = options.maxRetries ?? this.maxRetries;
timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries);
}
diff --git a/src/index.ts b/src/index.ts
index 0c12342c..f87f749c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -13,6 +13,7 @@ import {
Contexts,
} from './resources/contexts';
import { Extension, ExtensionCreateParams, Extensions } from './resources/extensions';
+import { FetchAPI, FetchAPICreateParams, FetchAPICreateResponse } from './resources/fetch-api';
import { Project, ProjectListResponse, ProjectUsage, Projects } from './resources/projects';
import {
Session,
@@ -144,6 +145,7 @@ export class Browserbase extends Core.APIClient {
contexts: API.Contexts = new API.Contexts(this);
extensions: API.Extensions = new API.Extensions(this);
+ fetchAPI: API.FetchAPI = new API.FetchAPI(this);
projects: API.Projects = new API.Projects(this);
sessions: API.Sessions = new API.Sessions(this);
@@ -192,6 +194,7 @@ export class Browserbase extends Core.APIClient {
Browserbase.Contexts = Contexts;
Browserbase.Extensions = Extensions;
+Browserbase.FetchAPI = FetchAPI;
Browserbase.Projects = Projects;
Browserbase.Sessions = Sessions;
@@ -212,6 +215,12 @@ export declare namespace Browserbase {
type ExtensionCreateParams as ExtensionCreateParams,
};
+ export {
+ FetchAPI as FetchAPI,
+ type FetchAPICreateResponse as FetchAPICreateResponse,
+ type FetchAPICreateParams as FetchAPICreateParams,
+ };
+
export {
Projects as Projects,
type Project as Project,
diff --git a/src/internal/utils/query.ts b/src/internal/utils/query.ts
new file mode 100644
index 00000000..f8d7aad8
--- /dev/null
+++ b/src/internal/utils/query.ts
@@ -0,0 +1,23 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { BrowserbaseError } from '../../error';
+
+/**
+ * Basic re-implementation of `qs.stringify` for primitive types.
+ */
+export function stringifyQuery(query: object | Record) {
+ return Object.entries(query)
+ .filter(([_, value]) => typeof value !== 'undefined')
+ .map(([key, value]) => {
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
+ }
+ if (value === null) {
+ return `${encodeURIComponent(key)}=`;
+ }
+ throw new BrowserbaseError(
+ `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`,
+ );
+ })
+ .join('&');
+}
diff --git a/src/resources/fetch-api.ts b/src/resources/fetch-api.ts
new file mode 100644
index 00000000..4823d2a3
--- /dev/null
+++ b/src/resources/fetch-api.ts
@@ -0,0 +1,77 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { APIResource } from '../resource';
+import * as Core from '../core';
+
+export class FetchAPI extends APIResource {
+ /**
+ * Fetch a page and return its content, headers, and metadata.
+ */
+ create(body: FetchAPICreateParams, options?: Core.RequestOptions): Core.APIPromise {
+ return this._client.post('/v1/fetch', { body, ...options });
+ }
+}
+
+/**
+ * Response body for fetch
+ */
+export interface FetchAPICreateResponse {
+ /**
+ * Unique identifier for the fetch request
+ */
+ id: string;
+
+ /**
+ * The response body content
+ */
+ content: string;
+
+ /**
+ * The MIME type of the response
+ */
+ contentType: string;
+
+ /**
+ * The character encoding of the response
+ */
+ encoding: string;
+
+ /**
+ * Response headers as key-value pairs
+ */
+ headers: { [key: string]: string };
+
+ /**
+ * HTTP status code of the fetched response
+ */
+ statusCode: number;
+}
+
+export interface FetchAPICreateParams {
+ /**
+ * The URL to fetch
+ */
+ url: string;
+
+ /**
+ * Whether to bypass TLS certificate verification
+ */
+ allowInsecureSsl?: boolean;
+
+ /**
+ * Whether to follow HTTP redirects
+ */
+ allowRedirects?: boolean;
+
+ /**
+ * Whether to enable proxy support for the request
+ */
+ proxies?: boolean;
+}
+
+export declare namespace FetchAPI {
+ export {
+ type FetchAPICreateResponse as FetchAPICreateResponse,
+ type FetchAPICreateParams as FetchAPICreateParams,
+ };
+}
diff --git a/src/resources/index.ts b/src/resources/index.ts
index 96c5a425..c58768fc 100644
--- a/src/resources/index.ts
+++ b/src/resources/index.ts
@@ -8,6 +8,7 @@ export {
type ContextCreateParams,
} from './contexts';
export { Extensions, type Extension, type ExtensionCreateParams } from './extensions';
+export { FetchAPI, type FetchAPICreateResponse, type FetchAPICreateParams } from './fetch-api';
export { Projects, type Project, type ProjectUsage, type ProjectListResponse } from './projects';
export {
Sessions,
diff --git a/src/version.ts b/src/version.ts
index 9556c21b..3bf861c9 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export const VERSION = '2.7.0'; // x-release-please-version
+export const VERSION = '2.8.0-alpha.1'; // x-release-please-version
diff --git a/tests/api-resources/extensions.test.ts b/tests/api-resources/extensions.test.ts
index f839e395..3ebdf1a6 100644
--- a/tests/api-resources/extensions.test.ts
+++ b/tests/api-resources/extensions.test.ts
@@ -11,7 +11,7 @@ const client = new Browserbase({
describe('resource extensions', () => {
test('create: only required params', async () => {
const responsePromise = client.extensions.create({
- file: await toFile(Buffer.from('# my file contents'), 'README.md'),
+ file: await toFile(Buffer.from('Example data'), 'README.md'),
});
const rawResponse = await responsePromise.asResponse();
expect(rawResponse).toBeInstanceOf(Response);
@@ -24,7 +24,7 @@ describe('resource extensions', () => {
test('create: required and optional params', async () => {
const response = await client.extensions.create({
- file: await toFile(Buffer.from('# my file contents'), 'README.md'),
+ file: await toFile(Buffer.from('Example data'), 'README.md'),
});
});
diff --git a/tests/api-resources/fetch-api.test.ts b/tests/api-resources/fetch-api.test.ts
new file mode 100644
index 00000000..7e250d35
--- /dev/null
+++ b/tests/api-resources/fetch-api.test.ts
@@ -0,0 +1,31 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import Browserbase from '@browserbasehq/sdk';
+import { Response } from 'node-fetch';
+
+const client = new Browserbase({
+ apiKey: 'My API Key',
+ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
+});
+
+describe('resource fetchAPI', () => {
+ test('create: only required params', async () => {
+ const responsePromise = client.fetchAPI.create({ url: 'https://example.com' });
+ const rawResponse = await responsePromise.asResponse();
+ expect(rawResponse).toBeInstanceOf(Response);
+ const response = await responsePromise;
+ expect(response).not.toBeInstanceOf(Response);
+ const dataAndResponse = await responsePromise.withResponse();
+ expect(dataAndResponse.data).toBe(response);
+ expect(dataAndResponse.response).toBe(rawResponse);
+ });
+
+ test('create: required and optional params', async () => {
+ const response = await client.fetchAPI.create({
+ url: 'https://example.com',
+ allowInsecureSsl: true,
+ allowRedirects: true,
+ proxies: true,
+ });
+ });
+});
diff --git a/tests/api-resources/sessions/uploads.test.ts b/tests/api-resources/sessions/uploads.test.ts
index c5872c31..6b3abc91 100644
--- a/tests/api-resources/sessions/uploads.test.ts
+++ b/tests/api-resources/sessions/uploads.test.ts
@@ -11,7 +11,7 @@ const client = new Browserbase({
describe('resource uploads', () => {
test('create: only required params', async () => {
const responsePromise = client.sessions.uploads.create('id', {
- file: await toFile(Buffer.from('# my file contents'), 'README.md'),
+ file: await toFile(Buffer.from('Example data'), 'README.md'),
});
const rawResponse = await responsePromise.asResponse();
expect(rawResponse).toBeInstanceOf(Response);
@@ -24,7 +24,7 @@ describe('resource uploads', () => {
test('create: required and optional params', async () => {
const response = await client.sessions.uploads.create('id', {
- file: await toFile(Buffer.from('# my file contents'), 'README.md'),
+ file: await toFile(Buffer.from('Example data'), 'README.md'),
});
});
});
diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts
index e5433927..9812e9ba 100644
--- a/tests/stringifyQuery.test.ts
+++ b/tests/stringifyQuery.test.ts
@@ -1,8 +1,6 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-import { Browserbase } from '@browserbasehq/sdk';
-
-const { stringifyQuery } = Browserbase.prototype as any;
+import { stringifyQuery } from '@browserbasehq/sdk/internal/utils/query';
describe(stringifyQuery, () => {
for (const [input, expected] of [
@@ -15,7 +13,7 @@ describe(stringifyQuery, () => {
'e=f',
)}=${encodeURIComponent('g&h')}`,
],
- ]) {
+ ] as const) {
it(`${JSON.stringify(input)} -> ${expected}`, () => {
expect(stringifyQuery(input)).toEqual(expected);
});