Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "scrapebadger",
"version": "0.18.0",
"version": "0.19.0",
"description": "Official Node.js SDK for ScrapeBadger - Async web scraping APIs for Twitter, Google, Vinted, Reddit, and more",
"type": "module",
"main": "./dist/index.js",
Expand Down
5 changes: 5 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { LeboncoinClient } from "./leboncoin/client.js";
import { ZillowClient } from "./zillow/client.js";
import { ImmobiliareClient } from "./immobiliare/client.js";
import { LoopNetClient } from "./loopnet/client.js";
import { DepopClient } from "./depop/client.js";

/**
* ScrapeBadger API client.
Expand Down Expand Up @@ -106,6 +107,9 @@ export class ScrapeBadger {
/** LoopNet scraper API client — commercial real estate across US/CA/UK/FR/ES (search, listing, broker, markets, property types) */
readonly loopnet: LoopNetClient;

/** Depop scraper API client — search, product, user, user products, markets (www.depop.com, 10 markets) */
readonly depop: DepopClient;

/**
* Create a new ScrapeBadger client.
*
Expand Down Expand Up @@ -161,5 +165,6 @@ export class ScrapeBadger {
this.zillow = new ZillowClient(this.baseClient);
this.immobiliare = new ImmobiliareClient(this.baseClient);
this.loopnet = new LoopNetClient(this.baseClient);
this.depop = new DepopClient(this.baseClient);
}
}
156 changes: 156 additions & 0 deletions src/depop/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* Depop API client.
*
* Depop endpoints: search (product search), getProduct (full single-product
* detail, by slug), getUser (shop / seller profile, by username),
* getUserProducts (a seller's products), and listMarkets. Single host
* (www.depop.com) localised by a `market` param → country + currency.
*
* Data is browser-rendered (cloakbrowser), so each call takes a few seconds
* to return.
*/

import type { BaseClient } from "../internal/client.js";
import type {
DepopSearchParams,
DepopProductParams,
DepopUserParams,
DepopUserProductsParams,
DepopSearchResponse,
DepopProductDetail,
DepopShopProfile,
DepopUserProductsResponse,
DepopMarketsResponse,
} from "./types.js";

/**
* Client for all Depop API operations.
*
* @example
* ```typescript
* const client = new ScrapeBadger({ apiKey: "key" });
*
* // Search products
* const results = await client.depop.search("vintage denim jacket", { market: "gb" });
* for (const product of results.products) {
* console.log(`${product.brand} — ${product.price} ${product.currency}`);
* }
*
* // Full single-product detail
* const detail = await client.depop.getProduct("some-product-slug");
* console.log(detail.title);
*
* // Shop profile
* const shop = await client.depop.getUser("someseller");
*
* // A seller's products
* const items = await client.depop.getUserProducts("someseller");
*
* // Supported markets
* const markets = await client.depop.listMarkets();
* ```
*/
export class DepopClient {
private readonly client: BaseClient;

constructor(client: BaseClient) {
this.client = client;
}

/**
* Search Depop for products.
*
* Costs 10 credits.
*
* @param query - Search keywords (required).
* @param options - Optional parameters (market, perPage, page, price/brand/
* size/colour/condition/gender filters, sort).
* @returns Search response with matching product cards and pagination meta.
* @throws AuthenticationError - If the API key is invalid.
* @throws ValidationError - If the parameters are invalid.
*/
async search(query: string, options: DepopSearchParams = {}): Promise<DepopSearchResponse> {
return this.client.request<DepopSearchResponse>("/v1/depop/search", {
params: {
query,
market: options.market ?? "us",
per_page: options.perPage,
page: options.page,
price_min: options.priceMin,
price_max: options.priceMax,
brands: options.brands,
sizes: options.sizes,
colours: options.colours,
conditions: options.conditions,
gender: options.gender,
sort: options.sort,
},
});
}

/**
* Get a single Depop product's full detail by slug.
*
* Costs 10 credits.
*
* @param slug - The Depop product slug.
* @param options - Optional parameters (market).
* @returns Full product detail.
* @throws NotFoundError - If the product doesn't exist.
*/
async getProduct(slug: string, options: DepopProductParams = {}): Promise<DepopProductDetail> {
return this.client.request<DepopProductDetail>(`/v1/depop/products/${slug}`, {
params: { market: options.market ?? "us" },
});
}

/**
* Get a Depop shop / seller profile by username.
*
* Costs 10 credits.
*
* @param username - The Depop seller username.
* @param options - Optional parameters (market).
* @returns Shop profile.
* @throws NotFoundError - If the user doesn't exist.
*/
async getUser(username: string, options: DepopUserParams = {}): Promise<DepopShopProfile> {
return this.client.request<DepopShopProfile>(`/v1/depop/users/${username}`, {
params: { market: options.market ?? "us" },
});
}

/**
* Get a Depop seller's products.
*
* Costs 10 credits.
*
* @param username - The Depop seller username.
* @param options - Optional parameters (market, perPage, page).
* @returns User products response with product cards and pagination meta.
* @throws NotFoundError - If the user doesn't exist.
*/
async getUserProducts(
username: string,
options: DepopUserProductsParams = {}
): Promise<DepopUserProductsResponse> {
return this.client.request<DepopUserProductsResponse>(`/v1/depop/users/${username}/products`, {
params: {
market: options.market ?? "us",
per_page: options.perPage,
page: options.page,
},
});
}

/**
* Get all supported Depop markets.
*
* Free — this endpoint costs no credits.
*
* @returns Markets response with all supported markets.
*/
async listMarkets(): Promise<DepopMarketsResponse> {
return this.client.request<DepopMarketsResponse>("/v1/depop/markets");
}
}
26 changes: 26 additions & 0 deletions src/depop/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Depop API module.
*
* @module depop
*/

export { DepopClient } from "./client.js";

// Export all types
export type {
// Shared
DepopCard,
SearchMeta as DepopSearchMeta,
DepopMarket,
// Response envelopes
DepopSearchResponse,
DepopProductDetail,
DepopShopProfile,
DepopUserProductsResponse,
DepopMarketsResponse,
// Request params
DepopSearchParams,
DepopProductParams,
DepopUserParams,
DepopUserProductsParams,
} from "./types.js";
156 changes: 156 additions & 0 deletions src/depop/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* TypeScript types for Depop API responses.
*
* These interfaces mirror the backend `depop_scraper` response schema
* field-for-field. Keys are snake_case exactly as the backend serialises
* them (`seller_username`, `original_price`, `is_sold`); optional / nullable
* backend fields are typed as `Type | null` or left `?`-optional.
*
* Depop is a single-host target (www.depop.com) localised by a `market`
* param → country + currency. Data is browser-rendered, so responses carry
* the raw parsed cards/detail rather than an underlying JSON API shape.
*/

// =============================================================================
// Shared
// =============================================================================

/** One Depop product card (search / user-products result element). */
export interface DepopCard {
/** Product slug (last URL segment). */
slug: string;
/** Full product URL. */
url: string;
/** Seller's Depop username. */
seller_username?: string;
/** Brand name. */
brand?: string;
/** Item size label. */
size?: string;
/** Current price (as displayed). */
price?: string;
/** Original price before markdown. */
original_price?: string;
/** Currency code / symbol for the price. */
currency?: string;
/** Primary product image URL. */
image?: string;
/** Whether the item is marked sold. */
is_sold: boolean;
}

/** Result-set metadata for a paginated listing. */
export interface SearchMeta {
result_count: number;
page: number;
has_more: boolean;
}

/** A supported Depop market (for /markets). */
export interface DepopMarket {
code: string;
country_code: string;
currency: string;
name: string;
}

// =============================================================================
// Response Envelopes
// =============================================================================

export interface DepopSearchResponse {
products: DepopCard[];
meta: SearchMeta;
market: string;
query: string;
}

/** Full Depop product detail. */
export interface DepopProductDetail {
id?: number;
slug: string;
title?: string | null;
description?: string | null;
brand?: string | null;
condition?: string | null;
price?: string | null;
currency?: string | null;
availability?: string | null;
seller_username?: string | null;
images: string[];
url: string;
}

/** A Depop shop / seller profile. */
export interface DepopShopProfile {
username: string;
name?: string | null;
description?: string | null;
rating_value?: string | null;
rating_count?: number | null;
follower_count?: number | null;
url: string;
}

export interface DepopUserProductsResponse {
username: string;
products: DepopCard[];
meta: SearchMeta;
market: string;
}

export interface DepopMarketsResponse {
markets: DepopMarket[];
}

// =============================================================================
// Request Parameter Types
// =============================================================================

/** Options for the /search endpoint. */
export interface DepopSearchParams {
/** Market code (default: "us"). Localises country + currency. */
market?: string;
/** Results per page (1-24, default: 24). */
perPage?: number;
/** Page number (default: 1). */
page?: number;
/** Minimum price filter. */
priceMin?: number;
/** Maximum price filter. */
priceMax?: number;
/** Brand filter(s). */
brands?: string;
/** Size filter(s). */
sizes?: string;
/** Colour filter(s). */
colours?: string;
/** Condition filter(s). */
conditions?: string;
/** Gender filter. */
gender?: string;
/** Sort order. */
sort?: string;
}

/** Options for the /products/{slug} endpoint. */
export interface DepopProductParams {
/** Market code (default: "us"). */
market?: string;
}

/** Options for the /users/{username} endpoint. */
export interface DepopUserParams {
/** Market code (default: "us"). */
market?: string;
}

/** Options for the /users/{username}/products endpoint. */
export interface DepopUserProductsParams {
/** Market code (default: "us"). */
market?: string;
/** Results per page (1-24, default: 24). */
perPage?: number;
/** Page number (default: 1). */
page?: number;
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ export * from "./immobiliare/index.js";
// Re-export LoopNet module — LoopNetClient + LoopNet*-prefixed types
export * from "./loopnet/index.js";

// Re-export Depop module — DepopClient + Depop*-prefixed types
export * from "./depop/index.js";

// Re-export Leboncoin module — LeboncoinClient + Leboncoin*-prefixed sub-clients and types
export * from "./leboncoin/index.js";

Expand Down
Loading