Developer guide

BTAB Vendor Integration Guide

This guide shows you how to integrate BTAB's product catalog and order management into your own custom storefront.

Written with the code it describes, in the same pull request. Plain Markdown: /developers/vendor-integration-guide.md

Welcome to BTAB!

This guide shows you how to integrate BTAB's product catalog and order management into your own custom storefront.


Overview

What is BTAB? BTAB is a B2B2C platform that lets you sell products without holding inventory. You:

  1. Select products from our catalog
  2. Build your own storefront (any tech stack)
  3. Set your own pricing
  4. Sell to your customers
  5. We handle fulfillment

You Focus On: Marketing, branding, customer experience We Handle: Inventory, fulfillment, shipping


Quick Start (5 Steps)

1. Create Your BTAB Account

Visit https://dashboard.btab.app/register

Email: [email protected]
Password: [your secure password]
Company Name: Your Store Inc.

You'll receive an API key that looks like:

btab_live_abc123def456ghi789...

⚠️ KEEP THIS SECRET! Never expose it in browser code.


2. Select Products

  1. Log into https://dashboard.btab.app
  2. Go to "Products" tab
  3. Browse the catalog
  4. Click "Add to Store" on products you want to sell
  5. Optionally set custom pricing (your retail price)

Example:

  • Wholesale price: $49.99
  • Suggested retail: $89.99
  • Your custom price: $79.99 (your margin: $30)

3. Set Up Your Backend

CRITICAL: Your API key must ONLY be used from your backend server, NEVER in browser JavaScript.

Why? If you put it in browser code, anyone can:

  • Steal your API key using browser devtools
  • Make unauthorized requests
  • Impersonate your store
  • Create fake orders

Correct Architecture:

[Customer Browser]
    ↓ public requests
[Your Backend Server] ← API key stored here (env variable)
    ↓ authenticated requests
[BTAB API]

4. Integrate the API

Node.js Example

Install dependencies:

npm install express node-fetch dotenv

Create .env file:

BTAB_API_KEY=btab_live_abc123def456...
BTAB_API_URL=https://api.btab.app/api/v1
PORT=8000

Create server.js:

require('dotenv').config();
const express = require('express');
const app = express();

app.use(express.json());

const BTAB_API_KEY = process.env.BTAB_API_KEY;
const BTAB_API_URL = process.env.BTAB_API_URL;

// Get products for your store
app.get('/api/products', async (req, res) => {
  try {
    const response = await fetch(`${BTAB_API_URL}/my-products`, {
      headers: {
        'Authorization': `Bearer ${BTAB_API_KEY}`
      }
    });

    if (!response.ok) {
      throw new Error('Failed to fetch products');
    }

    const data = await response.json();
    res.json(data);
  } catch (error) {
    console.error('Error fetching products:', error);
    res.status(500).json({ error: 'Failed to load products' });
  }
});

// Handle customer checkout
app.post('/api/checkout', async (req, res) => {
  try {
    const { customer, items, shipping, payment } = req.body;

    // Create order in BTAB
    const response = await fetch(`${BTAB_API_URL}/orders`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${BTAB_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        customer,
        items,
        shipping,
        payment
      })
    });

    if (!response.ok) {
      throw new Error('Order creation failed');
    }

    const order = await response.json();
    res.json({
      success: true,
      order_id: order.id,
      message: 'Order placed successfully!'
    });
  } catch (error) {
    console.error('Error creating order:', error);
    res.status(500).json({ error: 'Checkout failed' });
  }
});

app.listen(process.env.PORT, () => {
  console.log(`Server running on port ${process.env.PORT}`);
});

Start your server:

node server.js

5. Build Your Frontend

Your frontend can be anything:

  • React, Vue, Angular
  • Next.js, Nuxt
  • Plain HTML/JS/CSS
  • WordPress
  • Mobile app

Example Frontend (React):

import { useState, useEffect } from 'react';

function ProductList() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Call YOUR backend, not BTAB directly
    fetch('/api/products')
      .then(res => res.json())
      .then(data => {
        setProducts(data.products);
        setLoading(false);
      })
      .catch(error => {
        console.error('Error loading products:', error);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Loading products...</div>;

  return (
    <div className="product-grid">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

function ProductCard({ product }) {
  return (
    <div className="product-card">
      <img src={product.image_url} alt={product.name} />
      <h3>{product.name}</h3>
      <p>{product.description}</p>
      <p className="price">
        ${(product.retail_price_cents / 100).toFixed(2)}
      </p>
      <button onClick={() => addToCart(product)}>
        Add to Cart
      </button>
    </div>
  );
}

async function checkout(cart, customer, shipping, payment) {
  const response = await fetch('/api/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      customer,
      items: cart.items.map(item => ({
        product_id: item.product.id,
        quantity: item.quantity,
        price_cents: item.product.retail_price_cents
      })),
      shipping,
      payment
    })
  });

  return response.json();
}

API Reference

Base URL

https://api.btab.app/api/v1

Authentication

All requests require API key in header:

Authorization: Bearer btab_live_abc123...

Get Your Selected Products

Endpoint: GET /my-products

Response:

{
  "products": [
    {
      "id": 1,
      "name": "Product Name",
      "description": "Product description...",
      "sku": "SKU-001",
      "wholesale_price_cents": 4999,
      "retail_price_cents": 8999,
      "custom_retail_price_cents": 7999,
      "image_url": "https://...",
      "in_stock": true,
      "stock_quantity": 50,
      "metadata": {
        "category": "electronics",
        "weight_oz": 16
      }
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total_count": 25,
    "total_pages": 2
  }
}

Get Full Product Catalog

Endpoint: GET /products

Shows ALL available products (not just yours). Useful for letting vendors browse and add new products via API.

Query Parameters:

  • page - Page number (default: 1)
  • limit - Items per page (default: 20, max: 100)
  • search - Search term
  • in_stock - true/false

Add Product to Your Store

Endpoint: POST /my-products

Request Body:

{
  "product_id": 1,
  "custom_retail_price_cents": 7999
}

Response:

{
  "message": "Product added to your store",
  "vendor_product": {
    "id": 123,
    "product_id": 1,
    "custom_retail_price_cents": 7999
  }
}

Remove Product from Your Store

Endpoint: DELETE /my-products/:id

Response:

{
  "message": "Product removed from your store"
}

Update Product Pricing

Endpoint: PUT /api/v1/my-products/{productId}/pricing

Request Body:

{
  "custom_retail_price_cents": 8999
}

Create Order

Endpoint: POST /orders

Request Body:

{
  "customer": {
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "+1234567890"
  },
  "items": [
    {
      "product_id": 1,
      "quantity": 2,
      "price_cents": 7999
    }
  ],
  "shipping": {
    "address": "123 Main St",
    "address2": "Apt 4B",
    "city": "San Francisco",
    "state": "CA",
    "zip": "94102",
    "country": "US"
  },
  "payment": {
    "method": "stripe",
    "transaction_id": "ch_abc123",
    "amount_cents": 15998
  }
}

Response:

{
  "order": {
    "id": 456,
    "order_number": "ORD-2025-001",
    "status": "pending",
    "total_cents": 15998,
    "created_at": "2025-10-16T12:00:00Z"
  }
}

Get Your Orders

Endpoint: GET /orders

Query Parameters:

  • page - Page number
  • limit - Items per page
  • status - Filter by status (pending, processing, shipped, delivered, cancelled)

Get Specific Order

Endpoint: GET /orders/:id

Response:

{
  "order": {
    "id": 456,
    "order_number": "ORD-2025-001",
    "status": "shipped",
    "customer": { ... },
    "items": [ ... ],
    "shipping": { ... },
    "tracking_number": "1Z999AA10123456784",
    "created_at": "2025-10-16T12:00:00Z",
    "updated_at": "2025-10-16T14:30:00Z"
  }
}

Get the Buyer's Tax Invoice

Endpoint: GET /orders/:id/invoice

Returns the buyer's tax invoice for this order as a PDF (Content-Type: application/pdf), not JSON.

BTAB is the merchant of record on every storefront, so BTAB issues the tax invoice and the document carries BTAB's legal name and ABN — your store appears on it as the channel the sale came through. The invoice is issued when the order ships, is emailed to the buyer automatically at that moment, and carries a permanent number in the form INV-YYYYMM-NNNNNN.

Before the order ships there is no invoice, and this endpoint returns 404 with a message saying so. The number and issue date are also returned on GET /orders/:id as invoice_number and invoice_issued_at — check those if you want to know whether a document exists before requesting it.

GET /orders/456/invoice
Authorization: Bearer <your-api-key>

200 OK
Content-Type: application/pdf
Content-Disposition: inline; filename="INV-202609-000042.pdf"

Error Handling

All errors follow this format:

{
  "error": "Error type",
  "message": "Human-readable error message"
}

Common HTTP Status Codes:

  • 200 - Success
  • 400 - Bad Request (invalid data)
  • 401 - Unauthorized (invalid API key)
  • 404 - Not Found
  • 500 - Server Error

Example Error Handling:

try {
  const response = await fetch(`${BTAB_API_URL}/my-products`, {
    headers: { 'Authorization': `Bearer ${BTAB_API_KEY}` }
  });

  if (!response.ok) {
    const error = await response.json();
    console.error('API Error:', error.message);

    if (response.status === 401) {
      // Invalid API key
      console.error('Check your API key in .env file');
    }
  }

  const data = await response.json();
  // Use data...

} catch (error) {
  console.error('Network error:', error);
}

Technology-Specific Examples

Next.js (App Router)

API Route: app/api/products/route.ts

import { NextRequest, NextResponse } from 'next/server';

const BTAB_API_KEY = process.env.BTAB_API_KEY!;
const BTAB_API_URL = process.env.BTAB_API_URL!;

export async function GET(request: NextRequest) {
  try {
    const response = await fetch(`${BTAB_API_URL}/my-products`, {
      headers: {
        'Authorization': `Bearer ${BTAB_API_KEY}`
      }
    });

    const data = await response.json();
    return NextResponse.json(data);
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch products' },
      { status: 500 }
    );
  }
}

PHP (WordPress Plugin)

<?php
// wp-content/plugins/btab-integration/btab-api.php

class BTAB_API {
    private $api_key;
    private $api_url = 'https://api.btab.app/api/v1';

    public function __construct() {
        $this->api_key = get_option('btab_api_key');
    }

    public function get_products() {
        $response = wp_remote_get($this->api_url . '/my-products', [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key
            ]
        ]);

        if (is_wp_error($response)) {
            return false;
        }

        $body = wp_remote_retrieve_body($response);
        return json_decode($body, true);
    }

    public function create_order($order_data) {
        $response = wp_remote_post($this->api_url . '/orders', [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json'
            ],
            'body' => json_encode($order_data)
        ]);

        if (is_wp_error($response)) {
            return false;
        }

        $body = wp_remote_retrieve_body($response);
        return json_decode($body, true);
    }
}

Python (Flask)

# app.py
import os
import requests
from flask import Flask, jsonify, request

app = Flask(__name__)

BTAB_API_KEY = os.getenv('BTAB_API_KEY')
BTAB_API_URL = os.getenv('BTAB_API_URL', 'https://api.btab.app/api/v1')

@app.route('/api/products')
def get_products():
    try:
        response = requests.get(
            f'{BTAB_API_URL}/my-products',
            headers={'Authorization': f'Bearer {BTAB_API_KEY}'}
        )
        response.raise_for_status()
        return jsonify(response.json())
    except requests.exceptions.RequestException as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/checkout', methods=['POST'])
def checkout():
    try:
        order_data = request.json

        response = requests.post(
            f'{BTAB_API_URL}/orders',
            json=order_data,
            headers={
                'Authorization': f'Bearer {BTAB_API_KEY}',
                'Content-Type': 'application/json'
            }
        )
        response.raise_for_status()
        return jsonify(response.json())
    except requests.exceptions.RequestException as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(port=8000)

Best Practices

Security

  1. Never expose API key in:

    • Browser JavaScript
    • Mobile app code
    • GitHub repository
    • Client-side config files
  2. Always store API key in:

    • Environment variables (.env file)
    • Server-side config
    • Secure secret management (AWS Secrets, Vault, etc.)
  3. Use HTTPS everywhere - Both your site and BTAB API use HTTPS

  4. Validate data before sending to BTAB API:

    // Validate customer email
    if (!isValidEmail(customer.email)) {
      return res.status(400).json({ error: 'Invalid email' });
    }
    
    // Validate pricing matches
    const product = await getProduct(item.product_id);
    if (item.price_cents !== product.retail_price_cents) {
      return res.status(400).json({ error: 'Price mismatch' });
    }
    

Performance

  1. Cache product data - Don't fetch on every page load:

    // Refresh products every 5 minutes
    let productsCache = null;
    let lastFetch = 0;
    
    async function getProducts() {
      const now = Date.now();
      if (productsCache && (now - lastFetch) < 5 * 60 * 1000) {
        return productsCache;
      }
    
      productsCache = await fetchFromBTAB();
      lastFetch = now;
      return productsCache;
    }
    
  2. Handle rate limits - Implement retry logic:

    async function fetchWithRetry(url, options, retries = 3) {
      for (let i = 0; i < retries; i++) {
        const response = await fetch(url, options);
        if (response.status === 429) {
          // Rate limited, wait and retry
          await sleep(1000 * (i + 1));
          continue;
        }
        return response;
      }
      throw new Error('Max retries exceeded');
    }
    

Testing

Test in Development: Use the test API key from dashboard for local testing.

Example Test:

// test-api.js
require('dotenv').config();

async function testAPI() {
  console.log('Testing BTAB API integration...\n');

  // Test 1: Get products
  console.log('1. Fetching products...');
  const response = await fetch(`${process.env.BTAB_API_URL}/my-products`, {
    headers: { 'Authorization': `Bearer ${process.env.BTAB_API_KEY}` }
  });

  if (!response.ok) {
    console.error('❌ Failed:', await response.text());
    return;
  }

  const data = await response.json();
  console.log(`✅ Success: Found ${data.products.length} products\n`);

  // Test 2: Create test order
  console.log('2. Creating test order...');
  // ... test order creation
}

testAPI();

Troubleshooting

"401 Unauthorized"

  • Check API key is correct in .env file
  • Verify key starts with btab_live_ or btab_test_
  • Make sure Authorization header is properly formatted

"No products returned"

  • Make sure you've added products in the dashboard
  • Check you're calling /my-products not /products

"CORS errors"

  • You should NOT get CORS errors if using backend proxy correctly
  • If you see CORS errors, you're calling BTAB API from browser (wrong!)

"Rate limit exceeded"

  • Implement caching (see Performance section)
  • Add retry logic with backoff

Support

Dashboard: https://dashboard.btab.app API Status: https://api.btab.app/health Documentation: https://btab.app/developers


Example code

A working webhook consumer — signature check, retries, idempotent handling — ships with these docs: examples/webhook-consumer.js. It is the one example BTAB publishes.


Happy Selling! 🚀