npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@maxime-ns/back-market

v0.5.0

Published

A back market component for Convex.

Downloads

25

Readme

@maxime-ns/back-market

A Convex component for integrating Back Market's Marketplace Orders API into your Convex application.

npm version

Features

  • Connection Management - Connect, disconnect, and manage Back Market API credentials per organization
  • Order Syncing - Automatic order sync every 5 minutes via cron job
  • Orderlines - Query and update individual orderlines with IMEI support
  • Listings Management - Sync, update, and bulk create/update listings via CSV
  • BackShip Integration - Full support for deliveries and returns with label storage
  • Real-time Data - Query synced data from your Convex database in real-time
  • Multi-tenant - Support multiple organizations with separate API keys
  • Environment Support - Switch between sandbox and production environments

Quick Start

1. Install the Component

npm install @maxime-ns/back-market

2. Add to Your Convex App

Create or update convex/convex.config.ts:

import { defineApp } from "convex/server";
import backMarket from "@maxime-ns/back-market/convex.config.js";

const app = defineApp();
app.use(backMarket);

export default app;

3. Get Your Back Market API Key

  1. Go to Back Market Developer Portal
  2. Create an application or use an existing one
  3. Copy your API key (for sandbox testing, use the sandbox API key)

4. Understand Multi-Tenancy

This component is designed to support multiple organizations, each with their own Back Market API key. The organizationId parameter is a string that you provide from your host application.

Single Organization (Simple Setup)

If your app only needs one Back Market connection, you can use any constant string as the organization ID:

// Use a constant for single-tenant apps
const ORGANIZATION_ID = "default";

// Or use the authenticated user's ID
const organizationId = identity.subject;

Multiple Organizations (Multi-Tenant Setup)

For multi-tenant applications where each organization has their own Back Market account, pass the organization ID from your auth system. The component will:

  • Store separate API keys per organization
  • Sync orders independently for each organization
  • Keep data isolated between organizations

5. Use the Component

Create convex/backmarket.ts:

import { action, mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
import { BackMarket } from "@maxime-ns/back-market";
import { v } from "convex/values";

const backMarket = new BackMarket(components.backMarket);

// Connect an organization to Back Market
export const connect = mutation({
  args: {
    organizationId: v.string(),
    apiKey: v.string(),
  },
  handler: async (ctx, args) => {
    return await backMarket.upsertConnection(ctx, {
      organizationId: args.organizationId,
      apiKey: args.apiKey,
    });
  },
});

// Get connection status
export const getConnectionStatus = query({
  args: { organizationId: v.string() },
  handler: async (ctx, args) => {
    return await backMarket.getConnectionStatus(ctx, {
      organizationId: args.organizationId,
    });
  },
});

// Get orders from local database
export const getOrders = query({
  args: {
    organizationId: v.string(),
    state: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    return await backMarket.getOrders(ctx, {
      organizationId: args.organizationId,
      state: args.state,
    });
  },
});

// Manually sync orders from Back Market API
export const syncOrders = action({
  args: { organizationId: v.string() },
  handler: async (ctx, args) => {
    return await backMarket.syncOrders(ctx, {
      organizationId: args.organizationId,
      environment: "production",
    });
  },
});

Order States

Back Market uses numeric states for orders:

| State | Description | | ----- | ---------------------------------------------- | | 0 | New Order - Payment validation pending | | 1 | Paid - Payment validated, process orderlines | | 3 | Shipping pending - Ready to ship | | 8 | Not paid - Payment failed | | 9 | Processed - Package shipped | | 10 | Pending - Customer ordered but hasn't paid yet |

Orderline States

Individual orderlines within an order have their own states:

| State | Description | | ----- | ------------------------------ | | 0 | New | | 1 | Paid (Pending validation) | | 2 | Validated (Accepted by seller) | | 3 | Shipped | | 4 | Completed (Delivered) | | 5 | Cancellation requested | | 6 | Cancelled/Refunded | | 7 | Rejected by buyer | | 8 | Not paid | | 9 | Waiting for action |

Listing Publication States

| State | Description | | ----- | ---------------------- | | 0 | Not online | | 1 | Online | | 2 | Waiting for validation | | 3 | Rejected | | 4 | Pending |

API Reference

Using the BackMarket Client

The component provides a BackMarket client class for a more ergonomic API:

import { BackMarket } from "@maxime-ns/back-market";
import { components } from "./_generated/api";

const backMarket = new BackMarket(components.backMarket);

Connection Methods

| Method | Context | Description | | ----------------------- | -------- | ------------------------------------------ | | getConnectionStatus() | Query | Get connection status (no secrets exposed) | | upsertConnection() | Mutation | Create or update a connection | | disconnect() | Mutation | Disable a connection | | reconnect() | Mutation | Re-enable a previously disabled connection | | removeConnection() | Mutation | Permanently delete a connection |

Order Methods

| Method | Context | Description | | ---------------- | ------- | -------------------------------------- | | getOrders() | Query | Get orders from local database | | getOrderById() | Query | Get a single order by its ID | | syncOrders() | Action | Manually sync orders to local database | | updateOrder() | Action | Update order state, tracking, etc. |

Shipping an Order

// Ship an order with tracking info
const result = await backMarket.updateOrder(ctx, {
  organizationId: "org_123",
  orderId: 5978,
  body: {
    new_state: 3, // Shipped
    tracking_number: "1Z999AA10123456784",
    tracking_url: "https://tracking.example.com/1Z999AA10123456784",
    shipper: "UPS",
  },
  environment: "production",
});

Cancelling/Refunding an Order

const result = await backMarket.updateOrder(ctx, {
  organizationId: "org_123",
  orderId: 5978,
  body: {
    new_state: 6, // Cancelled/Refunded
    return_reason: 0, // Stock mistake
    return_message: "Item out of stock",
  },
  environment: "production",
});

Orderline Methods

| Method | Context | Description | | -------------------------- | ------- | ----------------------------- | | getOrderlines() | Query | Get orderlines for an order | | getOrderlineById() | Query | Get a single orderline by ID | | getOrderlinesByListing() | Query | Get orderlines by SKU/listing | | getOrderlinesByState() | Query | Get orderlines by state | | updateOrderline() | Action | Update orderline (e.g., IMEI) |

Updating IMEI on an Orderline

// Update IMEI for a smartphone orderline
const result = await backMarket.updateOrderline(ctx, {
  organizationId: "org_123",
  orderlineId: 12345,
  orderId: 5978,
  body: {
    imei: "153124587625348",
  },
  environment: "production",
});

if (result.success) {
  console.log(`IMEI updated to: ${result.imei}`);
}

Invoice Methods

| Method | Context | Description | | ---------------------- | ------- | ---------------------------------- | | uploadOrderInvoice() | Action | Upload custom invoice PDF to order |

Uploading an Invoice

// Get a signed URL for the invoice from your app's storage
const invoiceUrl = await ctx.storage.getUrl(invoiceStorageId);

// Upload to Back Market
const result = await backMarket.uploadOrderInvoice(ctx, {
  organizationId: "org_123",
  orderId: 5978,
  invoiceUrl,
  filename: "invoice-5978.pdf",
  environment: "production",
});

Listing Methods

| Method | Context | Description | | --------------------------------- | ------- | ------------------------------------------ | | getListings() | Query | Get listings from local database | | getListingById() | Query | Get a single listing by UUID | | getListingBySku() | Query | Get a single listing by SKU | | syncListings() | Action | Sync listings to local database | | updateListing() | Action | Update a single listing (price, qty, etc.) | | createOrUpdateListings() | Action | Bulk create/update via CSV | | getTaskStatus() | Action | Check status of bulk operation | | createOrUpdateListingsAndSync() | Action | Bulk update + wait + sync (all-in-one) |

Updating a Single Listing

// Update stock quantity
const result = await backMarket.updateListing(ctx, {
  organizationId: "org_123",
  listingId: "7a97a1b6-547e-4718-bc72-5b84aca91b09",
  body: {
    quantity: 50,
    price: "199.99",
    currency: "EUR",
  },
  environment: "production",
});

// Take listing offline (set quantity to 0)
await backMarket.updateListing(ctx, {
  organizationId: "org_123",
  listingId: "7a97a1b6-547e-4718-bc72-5b84aca91b09",
  body: { quantity: 0 },
  environment: "production",
});

Bulk Update Listings via CSV

// Create CSV content
const csvContent = `sku,quantity,price
MY_SKU_1,50,123.45
MY_SKU_2,25,99.99`;

// All-in-one: submit, wait for completion, sync
const result = await backMarket.createOrUpdateListingsAndSync(ctx, {
  organizationId: "org_123",
  body: {
    catalog: csvContent,
    quotechar: '"',
    delimiter: ",",
    encoding: "utf-8",
  },
  maxWaitMs: 10 * 60 * 1000, // 10 minutes
  pollIntervalMs: 15 * 1000, // 15 seconds
  environment: "production",
});

if (result.success) {
  console.log(`Synced ${result.syncResult?.totalListings} listings`);
}

BackShip Delivery Methods

| Method | Context | Description | | -------------------------- | ------- | -------------------------------------- | | getDeliveries() | Query | Get deliveries from local database | | getDeliveryById() | Query | Get a single delivery by shipment ID | | getDeliveriesByOrderId() | Query | Get all deliveries for an order | | getLabelUrl() | Query | Get signed URL for shipping label | | syncDeliveries() | Action | Sync deliveries + download labels | | downloadLabel() | Action | Download a specific shipping label | | fetchDelivery() | Action | Fetch single delivery from API + label |

Syncing Deliveries

const result = await backMarket.syncDeliveries(ctx, {
  organizationId: "org_123",
  downloadLabels: true, // default: true
  environment: "production",
});

console.log(`Synced ${result.totalDeliveries} deliveries`);
console.log(`Downloaded ${result.labelsDownloaded} labels`);

Getting a Shipping Label URL

const result = await backMarket.getLabelUrl(ctx, {
  organizationId: "org_123",
  shipmentId: 12345,
});

if (result.success) {
  // Open or download the label PDF
  window.open(result.url);
}

BackShip Return Methods

| Method | Context | Description | | --------------------------- | ------- | ------------------------------------ | | getReturns() | Query | Get returns from local database | | getReturnById() | Query | Get a single return by shipment ID | | getReturnsByOrderId() | Query | Get all returns for an order | | getReturnsByOrderlineId() | Query | Get all returns for an orderline | | getReturnLabelUrl() | Query | Get signed URL for return label | | syncReturns() | Action | Sync returns + download labels | | downloadReturnLabel() | Action | Download a specific return label | | fetchReturn() | Action | Fetch single return from API + label |

Syncing Returns

const result = await backMarket.syncReturns(ctx, {
  organizationId: "org_123",
  downloadLabels: true,
  environment: "production",
});

console.log(`Synced ${result.totalReturns} returns`);
console.log(`Downloaded ${result.labelsDownloaded} labels`);

Automatic Data Sync

The component includes cron jobs that automatically sync data every 5 minutes for all enabled connections:

  • Orders & Orderlines - Synced together automatically
  • Listings - Synced automatically

You can still trigger manual syncs with syncOrders, syncListings, syncDeliveries, and syncReturns if needed.

Database Schema

The component creates these tables in its namespace:

connections

| Field | Type | Description | | ---------------------- | --------- | ------------------------------------------ | | organizationId | string | Unique identifier for the organization | | enabled | boolean | Whether the connection is active | | apiKey | string? | Back Market API key (stored securely) | | lastOrdersSyncAt | number? | Timestamp of last successful order sync | | lastListingsSyncAt | number? | Timestamp of last successful listings sync | | lastDeliveriesSyncAt | number? | Timestamp of last deliveries sync | | lastReturnsSyncAt | number? | Timestamp of last returns sync |

orders

| Field | Type | Description | | ------------------ | ---------- | --------------------------------- | | organizationId | string | Organization that owns this order | | orderId | number? | Back Market order ID | | state | number? | Order state (0, 1, 3, 8, 9, 10) | | countryCode | string? | Country code (e.g., "fr-fr") | | dateCreation | string? | Order creation date | | dateModification | string? | Last modification date | | dateShipping | string? | Shipping date | | trackingNumber | string? | Package tracking number | | trackingUrl | string? | Package tracking URL | | shipperDisplay | string? | Shipper name | | isBackship | boolean? | Whether using BackShip | | shippingAddress | object? | Customer shipping address | | billingAddress | object? | Customer billing address | | lastSyncedAt | number? | When this order was last synced |

orderlines

| Field | Type | Description | | ---------------- | --------- | ---------------------------------- | | organizationId | string | Organization that owns this record | | orderId | number | Parent order ID | | orderlineId | number | Back Market orderline ID | | state | number? | Orderline state (0-9) | | listing | string? | SKU/listing identifier | | product | string? | Product name | | quantity | number? | Quantity ordered | | price | string? | Price per item | | currency | string? | Currency code | | imei | string? | IMEI (for smartphones) | | serialNumber | string? | Serial number | | lastSyncedAt | number? | When this was last synced |

listings

| Field | Type | Description | | ------------------ | --------- | ----------------------------------- | | organizationId | string | Organization that owns this listing | | listingId | string? | Back Market listing UUID | | sku | string? | Merchant SKU | | title | string? | Product title | | price | string? | Current price | | currency | string? | Currency code | | quantity | number? | Available stock | | publicationState | number? | Publication state (0-4) | | state | number? | Condition state | | grade | string? | Grade (e.g., "A", "B") | | lastSyncedAt | number? | When this was last synced |

deliveries

| Field | Type | Description | | ---------------- | ---------- | ------------------------------- | | organizationId | string | Organization that owns this | | shipmentId | number | Back Market shipment ID | | orderId | number | Related order ID | | orderlines | number[] | Related orderline IDs | | carrierName | string? | Carrier name | | trackingNumber | string? | Tracking number | | trackingUrl | string? | Tracking URL | | labelUrl | string? | Label URL from Back Market | | labelStorageId | string? | Convex storage ID for label PDF | | lastSyncedAt | number? | When this was last synced |

returns

| Field | Type | Description | | ---------------- | --------- | ------------------------------- | | organizationId | string | Organization that owns this | | shipmentId | number | Back Market shipment ID | | orderId | number | Related order ID | | orderlineId | number | Related orderline ID | | carrierName | string? | Carrier name | | trackingNumber | string? | Tracking number | | trackingUrl | string? | Tracking URL | | label | string? | Return label URL | | labelStorageId | string? | Convex storage ID for label PDF | | lastSyncedAt | number? | When this was last synced |

Connection Lifecycle

The component supports a full connection lifecycle:

┌─────────────────┐
│   No Connection │
└────────┬────────┘
         │ upsertConnection()
         ▼
┌─────────────────┐
│    Connected    │◄───────────────┐
│   (enabled)     │                │
└────────┬────────┘                │
         │ disconnect()            │ reconnect()
         ▼                         │
┌─────────────────┐                │
│  Disconnected   │────────────────┘
│  (disabled)     │
└────────┬────────┘
         │ removeConnection()
         ▼
┌─────────────────┐
│    Deleted      │
└─────────────────┘
  • upsertConnection: Create a new connection or update an existing one
  • disconnect: Disable the connection (optionally clear the API key)
  • reconnect: Re-enable a disabled connection (if API key is still stored)
  • removeConnection: Permanently delete the connection and all its data

Environment Configuration

The component supports both sandbox and production environments:

// Use sandbox for testing
await backMarket.syncOrders(ctx, {
  organizationId: "org_123",
  environment: "sandbox",
});

// Use production for real data
await backMarket.syncOrders(ctx, {
  organizationId: "org_123",
  environment: "production",
});

Typed API Client

The component also exports a typed API client for direct Back Market API access:

import { createBackmarketClient } from "@maxime-ns/back-market";

// Create a client with your API key
const client = createBackmarketClient({
  apiKey: "your-api-key",
  environment: "sandbox",
  locale: "en-US",
});

// Make typed API calls
const { data, error } = await client.GET("/ws/orders", {
  params: {
    query: { state: 1, page: 1 },
  },
});

This client is generated from the Back Market OpenAPI spec and provides full type safety.

Example App

Check out the full example app in the example/ directory:

git clone https://github.com/Maxime-NS/backmarket-component
cd backmarket-component
npm install
npm run dev

The example includes:

  • Connection management UI
  • Order listing with filters
  • Manual sync functionality
  • Environment switching

Troubleshooting

Orders not syncing

  1. Verify the connection is enabled:

    const status = await backMarket.getConnectionStatus(ctx, {
      organizationId: "your-org-id",
    });
    console.log(status); // Check enabled: true
  2. Check if the API key is valid by trying a manual sync:

    const result = await backMarket.syncOrders(ctx, {
      organizationId: "your-org-id",
      environment: "sandbox",
    });
    console.log(result); // Check for errors

"No connection found" errors

Make sure you've created a connection first:

await backMarket.upsertConnection(ctx, {
  organizationId: "your-org-id",
  apiKey: "your-api-key",
});

"Connection is disabled" errors

Re-enable the connection:

await backMarket.reconnect(ctx, {
  organizationId: "your-org-id",
});

BackShip labels not downloading

  1. Ensure downloadLabels is not set to false in sync calls
  2. Check that the delivery/return has a labelUrl from Back Market
  3. Use downloadLabel() or downloadReturnLabel() to manually download

Contributing

Found a bug? Feature request? File it here.

See CONTRIBUTING.md for development setup.

License

Apache-2.0