Skip to content

DBS Order Workflows

This guide covers common workflows for processing DBS (Delivery by Seller) orders with the Wildberries TypeScript SDK.

Migration Notice: Legacy single-order methods are deprecated and will be disabled on April 13, 2026. All workflows in this guide use the recommended bulk methods. See the Migration Guide for details.

Table of Contents

Order Lifecycle Overview

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│    NEW      │────▶│   CONFIRM   │────▶│   DELIVER   │────▶│   RECEIVE   │────▶│    SOLD     │
│             │     │             │     │             │     │             │     │             │
│ (new order) │     │ (accepted)  │     │ (en route)  │     │ (handover)  │     │ (complete)  │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
       │                   │                   │                   │
       │                   │                   │                   │
       ▼                   ▼                   ▼                   ▼
┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   CANCEL    │     │   CANCEL    │     │   CANCEL    │     │   REJECT    │
│             │     │             │     │             │     │             │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘

Status Meanings

StatusSeller ActionDescription
new-New order awaiting processing
confirmconfirmBulk()Order accepted for delivery
deliverdeliverBulk()Out for delivery
receivereceiveBulk()Customer received goods
rejectrejectBulk()Customer refused delivery
cancelcancelBulk()Order cancelled

New Order Processing

Complete Processing Flow

typescript
import { WildberriesSDK } from 'daytona-wildberries-typescript-sdk';

const sdk = new WildberriesSDK({ apiKey: process.env.WB_API_KEY! });

async function processNewOrders() {
  // Step 1: Fetch new orders
  const { orders } = await sdk.ordersDBS.getNewOrders();

  if (!orders?.length) {
    console.log('No new orders');
    return;
  }

  console.log(`Processing ${orders.length} new orders`);

  // Step 2: Get customer contact info
  const orderIds = orders.map(o => o.id!).filter(Boolean);
  const { orders: clients } = await sdk.ordersDBS.getClientInfo(orderIds);

  // Step 3: Create order map with all data
  const orderMap = new Map();

  for (const order of orders) {
    const client = clients?.find(c => c.orderID === order.id);

    orderMap.set(order.id, {
      order,
      client,
      address: order.address?.fullAddress,
      gps: order.address?.latitude && order.address?.longitude
        ? { lat: order.address.latitude, lng: order.address.longitude }
        : null,
      deliveryWindow: {
        date: order.ddate,
        from: order.dTimeFrom,
        to: order.dTimeTo
      },
      requiredMeta: order.requiredMeta ?? [],
      customerPhone: client?.phone
        ? `+${client.phoneCode}${client.phone}`
        : null
    });
  }

  // Step 4: Set metadata using bulk methods (grouped by type)
  await processMetadataBulk(orders);

  // Step 5: Confirm all orders at once
  const confirmableIds = Array.from(orderMap.keys());
  const result = await sdk.ordersDBS.confirmBulk(confirmableIds);

  for (const r of result.results ?? []) {
    if (r.isError) {
      console.error(`Failed to confirm ${r.orderId}:`, r.errors);
    } else {
      const data = orderMap.get(r.orderId);
      console.log(`Confirmed order ${r.orderId} for ${data?.address}`);
    }
  }
}

async function processMetadataBulk(orders: any[]) {
  // Group orders by required metadata type, then set in bulk
  const imeiOrders = [];
  const sgtinOrders = [];
  const uinOrders = [];
  const gtinOrders = [];
  const cdOrders = [];

  for (const order of orders) {
    const meta = order.requiredMeta ?? [];
    const values = await getMetadataFromInventory(order.id);

    if (meta.includes('imei') && values.imei) {
      imeiOrders.push({ orderId: order.id, imei: values.imei });
    }
    if (meta.includes('sgtin') && values.sgtins?.length) {
      sgtinOrders.push({ orderId: order.id, sgtins: values.sgtins });
    }
    if (meta.includes('uin') && values.uin) {
      uinOrders.push({ orderId: order.id, uin: values.uin });
    }
    if (meta.includes('gtin') && values.gtin) {
      gtinOrders.push({ orderId: order.id, gtin: values.gtin });
    }
    if (meta.includes('customsDeclaration') && values.customsDeclaration) {
      cdOrders.push({ orderId: order.id, customsDeclaration: values.customsDeclaration });
    }
  }

  // Set all metadata in bulk (one API call per type)
  if (imeiOrders.length > 0) {
    await sdk.ordersDBS.setImeiBulk({ orders: imeiOrders });
  }
  if (sgtinOrders.length > 0) {
    await sdk.ordersDBS.setSgtinBulk({ orders: sgtinOrders });
  }
  if (uinOrders.length > 0) {
    await sdk.ordersDBS.setUinBulk({ orders: uinOrders });
  }
  if (gtinOrders.length > 0) {
    await sdk.ordersDBS.setGtinBulk({ orders: gtinOrders });
  }
  if (cdOrders.length > 0) {
    await sdk.ordersDBS.setCustomsDeclarationBulk({ orders: cdOrders });
  }
}

Delivery Route Planning

Group Orders by Location

typescript
interface DeliveryRoute {
  orders: Array<{
    orderId: number;
    address: string;
    gps: { lat: number; lng: number };
    deliveryWindow: { date: string; from: string; to: string };
    customerPhone: string;
  }>;
  totalDistance?: number;
  estimatedTime?: number;
}

async function planDeliveryRoutes(): Promise<DeliveryRoute[]> {
  const { orders } = await sdk.ordersDBS.getNewOrders();

  if (!orders?.length) return [];

  // Get customer info
  const orderIds = orders.map(o => o.id!).filter(Boolean);
  const { orders: clients } = await sdk.ordersDBS.getClientInfo(orderIds);

  // Group by delivery date
  const byDate = new Map<string, typeof orders>();

  for (const order of orders) {
    const date = order.ddate ?? 'unscheduled';
    if (!byDate.has(date)) {
      byDate.set(date, []);
    }
    byDate.get(date)!.push(order);
  }

  // Create routes for each day
  const routes: DeliveryRoute[] = [];

  for (const [date, dayOrders] of byDate) {
    const routeOrders = dayOrders
      .filter(o => o.address?.latitude && o.address?.longitude)
      .map(order => {
        const client = clients?.find(c => c.orderID === order.id);
        return {
          orderId: order.id!,
          address: order.address!.fullAddress!,
          gps: {
            lat: order.address!.latitude!,
            lng: order.address!.longitude!
          },
          deliveryWindow: {
            date: order.ddate!,
            from: order.dTimeFrom!,
            to: order.dTimeTo!
          },
          customerPhone: client?.phone
            ? `+${client.phoneCode}${client.phone}`
            : ''
        };
      });

    // Sort by delivery time window
    routeOrders.sort((a, b) => {
      return a.deliveryWindow.from.localeCompare(b.deliveryWindow.from);
    });

    routes.push({
      orders: routeOrders
    });
  }

  return routes;
}

// Example: Export to Google Maps
function generateGoogleMapsUrl(route: DeliveryRoute): string {
  const waypoints = route.orders
    .map(o => `${o.gps.lat},${o.gps.lng}`)
    .join('/');

  return `https://www.google.com/maps/dir/${waypoints}`;
}

Time Window Optimization

typescript
interface TimeSlot {
  from: string;
  to: string;
  orders: number[];
}

function groupByTimeSlot(orders: any[]): TimeSlot[] {
  const slots = new Map<string, number[]>();

  for (const order of orders) {
    const slotKey = `${order.dTimeFrom}-${order.dTimeTo}`;
    if (!slots.has(slotKey)) {
      slots.set(slotKey, []);
    }
    slots.get(slotKey)!.push(order.id);
  }

  return Array.from(slots.entries())
    .map(([key, orderIds]) => {
      const [from, to] = key.split('-');
      return { from, to, orders: orderIds };
    })
    .sort((a, b) => a.from.localeCompare(b.from));
}

Bulk Metadata Workflow

The bulk metadata methods replace the deprecated single-order methods and allow you to set, retrieve, and delete metadata for multiple orders in a single API call.

Set-Verify-Confirm Pattern

typescript
async function setVerifyConfirm(orders: any[]) {
  const orderIds = orders.map(o => o.id!).filter(Boolean);

  // 1. Set metadata in bulk (grouped by type)
  await processMetadataBulk(orders);

  // 2. Verify all metadata was set correctly
  const metaResult = await sdk.ordersDBS.getMetaBulk({ orders: orderIds });
  const incomplete: number[] = [];

  for (const orderMeta of metaResult.orders ?? []) {
    const order = orders.find(o => o.id === orderMeta.orderId);
    const required = order?.requiredMeta ?? [];

    for (const metaType of required) {
      if (!checkMetadataValue(orderMeta, metaType)) {
        incomplete.push(orderMeta.orderId!);
        console.warn(`Order ${orderMeta.orderId}: missing ${metaType}`);
        break;
      }
    }
  }

  // 3. Confirm only orders with complete metadata
  const readyIds = orderIds.filter(id => !incomplete.includes(id));
  if (readyIds.length > 0) {
    const result = await sdk.ordersDBS.confirmBulk(readyIds);
    console.log(`Confirmed ${readyIds.length} orders`);
  }

  if (incomplete.length > 0) {
    console.warn(`${incomplete.length} orders skipped (missing metadata)`);
  }
}

Bulk Delete and Re-Set Metadata

typescript
// Correct metadata for multiple orders at once
async function correctMetadata(
  orderIds: number[],
  metaType: string,
  newValues: Map<number, string>
) {
  // 1. Delete old metadata in bulk
  await sdk.ordersDBS.deleteMetaBulk({
    orders: orderIds,
    key: metaType as any
  });

  // 2. Set corrected values in bulk
  switch (metaType) {
    case 'imei':
      await sdk.ordersDBS.setImeiBulk({
        orders: orderIds.map(id => ({
          orderId: id,
          imei: newValues.get(id)!
        }))
      });
      break;
    case 'gtin':
      await sdk.ordersDBS.setGtinBulk({
        orders: orderIds.map(id => ({
          orderId: id,
          gtin: newValues.get(id)!
        }))
      });
      break;
    // ... handle other types similarly
  }

  // 3. Verify corrections
  const meta = await sdk.ordersDBS.getMetaBulk({ orders: orderIds });
  for (const m of meta.orders ?? []) {
    console.log(`Order ${m.orderId}: ${metaType} updated`);
  }
}

Metadata Compliance

Automated Metadata Workflow

typescript
interface MetadataResult {
  orderId: number;
  success: boolean;
  errors: string[];
}

async function ensureMetadataCompliance(
  orderIds: number[]
): Promise<MetadataResult[]> {
  const results: MetadataResult[] = [];

  // Get current orders to check required metadata
  const { orders } = await sdk.ordersDBS.getNewOrders();

  // Get metadata for all orders in one bulk call
  const metaBulk = await sdk.ordersDBS.getMetaBulk({ orders: orderIds });
  const metaMap = new Map(
    (metaBulk.orders ?? []).map(m => [m.orderId, m])
  );

  for (const orderId of orderIds) {
    const order = orders?.find(o => o.id === orderId);

    if (!order) {
      results.push({
        orderId,
        success: false,
        errors: ['Order not found']
      });
      continue;
    }

    const errors: string[] = [];
    const required = order.requiredMeta ?? [];

    if (required.length === 0) {
      results.push({ orderId, success: true, errors: [] });
      continue;
    }

    // Check each required type using bulk-fetched metadata
    const meta = metaMap.get(orderId);
    for (const metaType of required) {
      const hasValue = checkMetadataValue(meta, metaType);

      if (!hasValue) {
        // Try to set from inventory
        try {
          await setMetadataFromInventory(orderId, metaType);
        } catch (err) {
          errors.push(`Failed to set ${metaType}: ${err}`);
        }
      }
    }

    results.push({
      orderId,
      success: errors.length === 0,
      errors
    });
  }

  return results;
}

function checkMetadataValue(orderMeta: any, type: string): boolean {
  switch (type) {
    case 'imei':
      return !!orderMeta?.imei;
    case 'sgtin':
      return (orderMeta?.sgtins?.length ?? 0) > 0;
    case 'uin':
      return !!orderMeta?.uin;
    case 'gtin':
      return !!orderMeta?.gtin;
    case 'customsDeclaration':
      return !!orderMeta?.customsDeclaration;
    default:
      return false;
  }
}

SGTIN Validation

typescript
function validateSgtin(sgtin: string): { valid: boolean; error?: string } {
  if (sgtin.length < 16 || sgtin.length > 135) {
    return {
      valid: false,
      error: `Invalid length: ${sgtin.length}. Must be 16-135 characters.`
    };
  }

  // Check format: starts with 01 (GTIN indicator)
  if (!sgtin.startsWith('01')) {
    return {
      valid: false,
      error: 'SGTIN should start with "01" (GTIN indicator)'
    };
  }

  return { valid: true };
}

async function setSgtinWithValidation(
  orders: Array<{ orderId: number; sgtins: string[] }>
): Promise<void> {
  // Validate all SGTINs for all orders first
  for (const order of orders) {
    const invalid = order.sgtins
      .map(s => ({ sgtin: s, ...validateSgtin(s) }))
      .filter(r => !r.valid);

    if (invalid.length > 0) {
      throw new Error(
        `Order ${order.orderId} invalid SGTINs: ${invalid.map(i => `${i.sgtin}: ${i.error}`).join(', ')}`
      );
    }

    if (order.sgtins.length > 24) {
      throw new Error(`Order ${order.orderId}: too many SGTINs (${order.sgtins.length}). Maximum is 24.`);
    }
  }

  // Set all SGTINs in one bulk call
  await sdk.ordersDBS.setSgtinBulk({ orders });
}

Status Transitions

Bulk Status Update

typescript
interface StatusUpdateResult {
  successful: number[];
  failed: Array<{ orderId: number; error: string }>;
}

async function bulkConfirm(orderIds: number[]): Promise<StatusUpdateResult> {
  const result = await sdk.ordersDBS.confirmBulk(orderIds);

  const successful: number[] = [];
  const failed: Array<{ orderId: number; error: string }> = [];

  for (const r of result.results ?? []) {
    if (r.isError) {
      failed.push({
        orderId: r.orderId!,
        error: r.errors?.[0]?.detail ?? 'Unknown error'
      });
    } else {
      successful.push(r.orderId!);
    }
  }

  return { successful, failed };
}

async function bulkDeliver(orderIds: number[]): Promise<StatusUpdateResult> {
  const result = await sdk.ordersDBS.deliverBulk(orderIds);

  const successful: number[] = [];
  const failed: Array<{ orderId: number; error: string }> = [];

  for (const r of result.results ?? []) {
    if (r.isError) {
      failed.push({
        orderId: r.orderId!,
        error: r.errors?.[0]?.detail ?? 'Unknown error'
      });
    } else {
      successful.push(r.orderId!);
    }
  }

  return { successful, failed };
}

async function completeHandover(
  orderCodes: Array<{ orderId: number; code: string }>
): Promise<StatusUpdateResult> {
  const result = await sdk.ordersDBS.receiveBulk(orderCodes);

  const successful: number[] = [];
  const failed: Array<{ orderId: number; error: string }> = [];

  for (const r of result.results ?? []) {
    if (r.isError) {
      failed.push({
        orderId: r.orderId!,
        error: r.errors?.[0]?.detail ?? 'Unknown error'
      });
    } else {
      successful.push(r.orderId!);
    }
  }

  return { successful, failed };
}

Status Monitoring

typescript
async function monitorOrderStatuses(orderIds: number[]): Promise<void> {
  const { orders } = await sdk.ordersDBS.getStatusesBulk(orderIds);

  const statusCounts = new Map<string, number>();

  for (const order of orders ?? []) {
    const status = order.supplierStatus ?? 'unknown';
    statusCounts.set(status, (statusCounts.get(status) ?? 0) + 1);

    // Log errors
    if (order.errors?.length) {
      console.error(`Order ${order.orderId} errors:`, order.errors);
    }
  }

  console.log('Status Summary:');
  for (const [status, count] of statusCounts) {
    console.log(`  ${status}: ${count}`);
  }
}

B2B Order Handling

B2B Detection and Processing

typescript
interface B2BOrder {
  orderId: number;
  organization: {
    name: string;
    inn: string;
    kpp?: string;
  };
}

async function getB2BOrders(orderIds: number[]): Promise<B2BOrder[]> {
  const { results } = await sdk.ordersDBS.getB2BInfo(orderIds);

  const b2bOrders: B2BOrder[] = [];

  for (const result of results ?? []) {
    if (!result.isError && result.data) {
      b2bOrders.push({
        orderId: result.orderId!,
        organization: {
          name: result.data.orgName!,
          inn: result.data.inn!,
          kpp: result.data.kpp
        }
      });
    }
  }

  return b2bOrders;
}

// Generate invoice data for B2B orders
interface InvoiceData {
  orderRef: string;
  buyer: string;
  inn: string;
  kpp: string;
  isIndividualEntrepreneur: boolean;
}

function generateInvoiceData(b2bOrder: B2BOrder): InvoiceData {
  return {
    orderRef: `WB-DBS-${b2bOrder.orderId}`,
    buyer: b2bOrder.organization.name,
    inn: b2bOrder.organization.inn,
    kpp: b2bOrder.organization.kpp ?? '',
    // Individual Entrepreneurs have 12-digit INN and no KPP
    isIndividualEntrepreneur:
      b2bOrder.organization.inn.length === 12 && !b2bOrder.organization.kpp
  };
}

Batch Processing

Process Orders in Batches

typescript
const BATCH_SIZE = 1000;

function chunkArray<T>(array: T[], size: number): T[][] {
  const chunks: T[][] = [];
  for (let i = 0; i < array.length; i += size) {
    chunks.push(array.slice(i, i + size));
  }
  return chunks;
}

async function batchConfirmOrders(orderIds: number[]): Promise<{
  totalSuccessful: number;
  totalFailed: number;
  errors: Array<{ orderId: number; error: string }>;
}> {
  const batches = chunkArray(orderIds, BATCH_SIZE);

  let totalSuccessful = 0;
  let totalFailed = 0;
  const errors: Array<{ orderId: number; error: string }> = [];

  for (const batch of batches) {
    const result = await sdk.ordersDBS.confirmBulk(batch);

    for (const r of result.results ?? []) {
      if (r.isError) {
        totalFailed++;
        errors.push({
          orderId: r.orderId!,
          error: r.errors?.[0]?.detail ?? 'Unknown error'
        });
      } else {
        totalSuccessful++;
      }
    }

    // Add delay between batches to avoid rate limiting
    if (batches.indexOf(batch) < batches.length - 1) {
      await new Promise(resolve => setTimeout(resolve, 200));
    }
  }

  return { totalSuccessful, totalFailed, errors };
}

Fetch All Completed Orders

typescript
async function fetchAllCompletedOrders(
  dateFrom: Date,
  dateTo: Date
): Promise<any[]> {
  const fromTs = Math.floor(dateFrom.getTime() / 1000);
  const toTs = Math.floor(dateTo.getTime() / 1000);

  // Validate date range (max 30 days)
  const MAX_DAYS = 30 * 24 * 60 * 60;
  if (toTs - fromTs > MAX_DAYS) {
    throw new Error('Date range cannot exceed 30 days');
  }

  const allOrders: any[] = [];
  let next = 0;

  do {
    const result = await sdk.ordersDBS.getOrders({
      limit: 1000,
      next,
      dateFrom: fromTs,
      dateTo: toTs
    });

    allOrders.push(...(result.orders ?? []));
    next = result.next ?? 0;

  } while (next > 0);

  return allOrders;
}

Rate Limit Considerations

Penalty Multiplier (409 Responses)

All DBS endpoints enforce a penalty multiplier of 10. If the API returns a 409 Conflict response (e.g., confirming an already-confirmed order or an invalid state transition), that single request counts as 10 requests toward your rate limit budget.

This is especially important for T2 (Status Write) operations, which only allow 60 requests/minute. A few 409 errors could consume your entire rate budget:

typescript
// Always check status before transitioning to avoid 409 penalties
const statuses = await sdk.ordersDBS.getStatusesBulk(orderIds);
const confirmable = (statuses.orders ?? [])
  .filter(o => o.supplierStatus === 'new')
  .map(o => o.orderId!);

// Only confirm orders that are actually in "new" status
if (confirmable.length > 0) {
  await sdk.ordersDBS.confirmBulk(confirmable);
}

Error Recovery

Retry Failed Operations

typescript
interface RetryConfig {
  maxAttempts: number;
  delayMs: number;
  backoffMultiplier: number;
}

async function retryOperation<T>(
  operation: () => Promise<T>,
  config: RetryConfig = { maxAttempts: 3, delayMs: 1000, backoffMultiplier: 2 }
): Promise<T> {
  let lastError: Error | undefined;
  let delay = config.delayMs;

  for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error as Error;

      console.warn(`Attempt ${attempt}/${config.maxAttempts} failed:`, error);

      if (attempt < config.maxAttempts) {
        await new Promise(resolve => setTimeout(resolve, delay));
        delay *= config.backoffMultiplier;
      }
    }
  }

  throw lastError;
}

// Example usage
async function confirmWithRetry(orderIds: number[]) {
  return retryOperation(
    () => sdk.ordersDBS.confirmBulk(orderIds),
    { maxAttempts: 3, delayMs: 1000, backoffMultiplier: 2 }
  );
}

Handle Partial Failures

typescript
async function processWithPartialFailureHandling(
  orderIds: number[]
): Promise<{
  processed: number[];
  retryLater: number[];
  permanentFailures: number[];
}> {
  const result = await sdk.ordersDBS.confirmBulk(orderIds);

  const processed: number[] = [];
  const retryLater: number[] = [];
  const permanentFailures: number[] = [];

  for (const r of result.results ?? []) {
    if (!r.isError) {
      processed.push(r.orderId!);
      continue;
    }

    const errorCode = r.errors?.[0]?.code;

    // Classify errors
    if (isTransientError(errorCode)) {
      retryLater.push(r.orderId!);
    } else {
      permanentFailures.push(r.orderId!);
    }
  }

  return { processed, retryLater, permanentFailures };
}

function isTransientError(code?: number): boolean {
  // Define which errors are transient (can be retried)
  const transientCodes = [
    429,  // Rate limit
    500,  // Server error
    503,  // Service unavailable
  ];
  return code ? transientCodes.includes(code) : false;
}

Migrating from Deprecated Methods

If your workflows still use the deprecated single-order methods (getMeta, deleteMeta, setSgtin, setImei, setUin, setGtin, setCustomsDeclaration), you need to migrate to bulk methods before April 13, 2026.

Quick Migration Summary

Old PatternNew Pattern
getMeta(orderId) one at a timegetMetaBulk({ orders: [id1, id2, ...] })
deleteMeta(orderId, key) one at a timedeleteMetaBulk({ orders: [id1, id2], key })
setImei(orderId, imei) per ordersetImeiBulk({ orders: [{orderId, imei}, ...] })
setSgtin(orderId, sgtins) per ordersetSgtinBulk({ orders: [{orderId, sgtins}, ...] })
setUin(orderId, uin) per ordersetUinBulk({ orders: [{orderId, uin}, ...] })
setGtin(orderId, gtin) per ordersetGtinBulk({ orders: [{orderId, gtin}, ...] })
setCustomsDeclaration(orderId, cd) per ordersetCustomsDeclarationBulk({ orders: [{orderId, customsDeclaration}, ...] })

Before and After Example

typescript
// BEFORE (deprecated -- will stop working April 13, 2026)
for (const orderId of orderIds) {
  const meta = await sdk.ordersDBS.getMeta(orderId);      // N API calls
  if (!meta.meta?.imei?.value) {
    await sdk.ordersDBS.setImei(orderId, getImei(orderId)); // N more calls
  }
}

// AFTER (recommended -- fewer API calls, better performance)
const meta = await sdk.ordersDBS.getMetaBulk({ orders: orderIds }); // 1 call
const needsImei = (meta.orders ?? [])
  .filter(m => !m.imei)
  .map(m => ({ orderId: m.orderId!, imei: getImei(m.orderId!) }));

if (needsImei.length > 0) {
  await sdk.ordersDBS.setImeiBulk({ orders: needsImei });           // 1 call
}

For the complete migration guide with all method mappings, error handling changes, and a step-by-step checklist, see the Migration Guide: Legacy to Bulk.

See Also

Made with ❤️ for the Wildberries developer community