Skip to content

Site Integration Open API

Last Updated: 2026-04-01

This document covers the Dujiao-Next site-to-site integration API endpoints under /api/v1/upstream/*.


1. Basics

1.1 Base URL

https://<upstream-site-domain>/api/v1/upstream

Example: https://api.example.com/api/v1/upstream

1.2 Unified Response Envelope

Success:

json
{
  "ok": true,
  ...
}

Failure:

json
{
  "ok": false,
  "error_code": "error_code_here",
  "error_message": "human readable message"
}

1.3 Runtime Constraints (Important)

  • Procurement after payment is asynchronous and must not block end-user checkout.
  • Mapped upstream items must wait for upstream callbacks; local manual/auto fulfillment is not allowed.
  • Local cancellation is upstream-first: if upstream cancel fails or is non-cancelable, local cancel/refund must be denied.
  • When upstream procurement fails, downstream should mark the order as an exception for admin handling, instead of auto local cancel/refund.

2. Authentication & Signature

2.1 Required Headers

HeaderDescription
Dujiao-Next-Api-KeyAPI Key (generated by the upstream site user under "API Permissions")
Dujiao-Next-TimestampUnix timestamp (seconds)
Dujiao-Next-SignatureHMAC-SHA256 signature (lowercase hex)

2.2 Signature Algorithm

Sign string format:

{METHOD}\n{PATH}\n{TIMESTAMP}\n{BODY_MD5}

Component details:

PlaceholderDescription
{METHOD}HTTP method in uppercase, e.g. GET, POST
{PATH}Request path (no domain, no query string), e.g. /api/v1/upstream/products
{TIMESTAMP}Must match the Dujiao-Next-Timestamp header
{BODY_MD5}MD5 hash of the request body (lowercase hex). For empty body, use MD5 of empty string: d41d8cd98f00b204e9800998ecf8427e

Final signature:

signature = hex_lower(hmac_sha256(signString, api_secret))

2.3 Signature Pseudocode

python
import hashlib, hmac, time

api_key = "your_api_key"
api_secret = "your_api_secret"
method = "POST"
path = "/api/v1/upstream/orders"
body = b'{"sku_id":1,"quantity":1}'
timestamp = str(int(time.time()))

body_md5 = hashlib.md5(body).hexdigest()
sign_string = f"{method}\n{path}\n{timestamp}\n{body_md5}"
signature = hmac.new(
    api_secret.encode(), sign_string.encode(), hashlib.sha256
).hexdigest()

headers = {
    "Dujiao-Next-Api-Key": api_key,
    "Dujiao-Next-Timestamp": timestamp,
    "Dujiao-Next-Signature": signature,
    "Content-Type": "application/json",
}

2.4 Validation Rules

  • Timestamp skew tolerance: ±60 seconds.
  • API Key must be in "approved and active" status.
  • The user account associated with the API Key must be active.

3. Endpoint List

MethodPathDescription
POST/pingConnectivity check
GET/categoriesFetch category list
GET/productsFetch product list
GET/products/:idFetch product details
POST/ordersCreate procurement order (auto wallet payment)
GET/orders/:idQuery procurement order
POST/orders/:id/cancelCancel procurement order

Callback endpoint (Site B → Site A):

MethodPathDescription
POST/upstream/callbackSite B pushes order status changes and delivery content to Site A

4. Core Endpoints

4.1 POST /ping

Connectivity check. Also returns wallet balance, currency, and member level for the API user.

Request: No body required.

Response fields:

FieldTypeDescription
okbooleanConnectivity status
site_namestringSite name
protocol_versionstringProtocol version, currently "1.0"
user_idnumberAPI user ID
balancestringAvailable wallet balance
currencystringSite currency (e.g. CNY, USD)
member_levelobject|nullUser's member level info (null if no member level)

member_level sub-object:

FieldTypeDescription
idnumberMember level ID
nameobjectMultilingual level name, e.g. {"zh-CN":"黄金会员","en":"Gold"}
slugstringLevel identifier
iconstringLevel icon URL

Response example:

json
{
  "ok": true,
  "site_name": "My Shop",
  "protocol_version": "1.0",
  "user_id": 42,
  "balance": "1000.00",
  "currency": "CNY",
  "member_level": {
    "id": 1,
    "name": { "zh-CN": "黄金会员", "en": "Gold" },
    "slug": "gold",
    "icon": "https://example.com/gold.png"
  }
}

4.2 GET /categories

Fetch the upstream category list.

Request: No body or query parameters required.

Response fields:

FieldTypeDescription
okbooleanWhether successful
categoriesarrayCategory array (see Category structure below)

Category Structure

FieldTypeDescription
idnumberCategory ID
parent_idnumberParent category ID (0 for top-level categories)
slugstringUnique category identifier
nameobjectLocalized name, e.g. {"zh-CN":"Game Top-up","en":"Game Top-up"}
iconstringCategory icon URL
sort_ordernumberSort weight (descending)

Response example:

json
{
  "ok": true,
  "categories": [
    {
      "id": 1,
      "parent_id": 0,
      "slug": "game-topup",
      "name": { "zh-CN": "Game Top-up", "en": "Game Top-up" },
      "icon": "",
      "sort_order": 10
    },
    {
      "id": 2,
      "parent_id": 1,
      "slug": "steam",
      "name": { "zh-CN": "Steam", "en": "Steam" },
      "icon": "",
      "sort_order": 5
    }
  ]
}

Category Hierarchy

Categories support up to two levels: top-level categories (parent_id = 0) and sub-categories (parent_id points to a top-level category). Products can only be associated with leaf categories.


4.3 GET /products

Fetch the upstream product list (only active products are returned).

Query parameters:

ParamTypeRequiredDefaultDescription
pagenumberNo1Page number
page_sizenumberNo20Items per page (1–100)

Response fields:

FieldTypeDescription
okbooleanSuccess flag
itemsarrayProduct array (see Product structure below)
totalnumberTotal count
pagenumberCurrent page
page_sizenumberItems per page

Product Structure

FieldTypeDescription
idnumberProduct ID (orders reference SKU ID, not this)
slugstringURL slug
titleobjectMultilingual title, e.g. {"zh-CN":"标题","en":"Title"}
descriptionobjectMultilingual description
contentobjectMultilingual detail content
seo_metaobjectSEO metadata
imagesstring[]Image URL list
tagsstring[]Tag list
price_amountstringActual selling price (member price if applicable)
original_pricestringOriginal price (only returned when member discount exists, omitempty)
member_pricestringMember price (only returned when member discount exists, omitempty)
fulfillment_typestringFulfillment type: auto / manual
manual_form_schemaobjectManual fulfillment form schema (required buyer input for manual type products)
is_activebooleanWhether the product is active
category_idnumberCategory ID
skusarraySKU list (see SKU structure below)
created_atstringCreated time (ISO 8601)
updated_atstringUpdated time (ISO 8601)

SKU Structure

FieldTypeDescription
idnumberSKU ID (use this ID when placing orders)
sku_codestringSKU code
spec_valuesobjectSpec values, e.g. {"Color":"Red","Size":"128GB"}
price_amountstringActual selling price (member price if applicable)
original_pricestringOriginal price (only returned when member discount exists, omitempty)
member_pricestringMember price (only returned when member discount exists, omitempty)
stock_statusstringStock status: unlimited / in_stock / low_stock / out_of_stock
stock_quantitynumberStock quantity (-1 = unlimited)
is_activebooleanWhether the SKU is active

Stock Status Reference

  • unlimited: Unlimited stock (stock_quantity = -1)
  • in_stock: Sufficient (> 20)
  • low_stock: Low stock (1–20)
  • out_of_stock: Sold out (0)

Response example:

json
{
  "ok": true,
  "items": [
    {
      "id": 1,
      "slug": "example-product",
      "title": { "zh-CN": "示例商品", "en": "Example Product" },
      "description": { "zh-CN": "这是一个示例" },
      "content": {},
      "seo_meta": {},
      "images": ["https://example.com/img1.jpg"],
      "tags": ["hot"],
      "price_amount": "7.90",
      "original_price": "9.90",
      "member_price": "7.90",
      "fulfillment_type": "auto",
      "manual_form_schema": null,
      "is_active": true,
      "category_id": 1,
      "skus": [
        {
          "id": 1,
          "sku_code": "DEFAULT",
          "spec_values": {},
          "price_amount": "7.90",
          "original_price": "9.90",
          "member_price": "7.90",
          "stock_status": "in_stock",
          "stock_quantity": 100,
          "is_active": true
        }
      ],
      "created_at": "2026-03-01T12:00:00Z",
      "updated_at": "2026-03-01T12:00:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 20
}

4.4 GET /products/:id

Fetch product details by ID.

Path parameter:

ParamTypeDescription
idnumberProduct ID

Response fields:

FieldTypeDescription
okbooleanSuccess flag
productobjectProduct structure (same as section 4.3)

Error codes:

error_codeHTTP StatusDescription
product_not_found404Product not found
product_unavailable404Product is inactive

4.5 POST /orders

Create a procurement order. The system automatically deducts from the API user's wallet balance — no separate payment step required.

Request body:

FieldTypeRequiredDescription
sku_idnumberYesSKU ID (from skus[].id in the product list)
quantitynumberYesQuantity, must be ≥ 1
manual_form_dataobjectNoManual fulfillment form data (required when product is manual type)
downstream_order_nostringNoDownstream order number (recommended globally unique, for idempotency and callback matching)
trace_idstringNoCaller trace ID
callback_urlstringNoCallback URL (must be a publicly accessible HTTP/HTTPS address)

Idempotency

The same API Key + downstream_order_no combination cannot create duplicate orders. Repeat submissions return the existing order info.

Success response (ok: true):

FieldTypeDescription
okbooleantrue
order_idnumberUpstream order ID
order_nostringUpstream order number
statusstringOrder status (typically paid)
amountstringOrder amount
currencystringCurrency

Success response example:

json
{
  "ok": true,
  "order_id": 101,
  "order_no": "DJ20260301120000ABCD",
  "status": "paid",
  "amount": "9.90",
  "currency": "CNY"
}

Business failure (HTTP 200, ok: false):

json
{
  "ok": false,
  "order_id": 101,
  "order_no": "DJ20260301120000ABCD",
  "status": "canceled",
  "error_code": "payment_failed",
  "error_message": "wallet payment failed: insufficient balance"
}

When wallet balance is insufficient or payment fails, the order is auto-canceled and ok: false is returned.

Error codes:

error_codeHTTP StatusDescription
bad_request400Invalid request parameters
invalid_callback_url400Callback URL is illegal (private network/localhost, etc.)
sku_unavailable400SKU not found or inactive
product_unavailable400Product not available
insufficient_balance402Insufficient wallet balance
insufficient_stock409Insufficient stock
payment_failed200Wallet payment failed (ok: false)

4.6 GET /orders/:id

Query procurement order details.

Path parameter:

ParamTypeDescription
idnumberOrder ID (from order_id returned when creating the order)

Response fields:

FieldTypeDescription
okbooleanSuccess flag
order_idnumberOrder ID
order_nostringOrder number
statusstringOrder status
amountstringOrder amount
currencystringCurrency
itemsarrayOrder items list (see below)
fulfillmentobjectFulfillment info (only present when delivered, see below)

items[] fields:

FieldTypeDescription
product_idnumberProduct ID
sku_idnumberSKU ID
titleobjectMultilingual title
quantitynumberQuantity
unit_pricestringUnit price
total_pricestringSubtotal
fulfillment_typestringFulfillment type

fulfillment fields (only when status is delivered):

FieldTypeDescription
typestringFulfillment type (auto / manual)
statusstringFulfillment status (delivered)
payloadstringDelivery content (card keys, text, etc.)
delivery_dataobjectStructured delivery data
delivered_atstringDelivery time (ISO 8601)

Response example:

json
{
  "ok": true,
  "order_id": 101,
  "order_no": "DJ20260301120000ABCD",
  "status": "completed",
  "amount": "9.90",
  "currency": "CNY",
  "items": [
    {
      "product_id": 1,
      "sku_id": 1,
      "title": { "zh-CN": "示例商品" },
      "quantity": 1,
      "unit_price": "9.90",
      "total_price": "9.90",
      "fulfillment_type": "auto"
    }
  ],
  "fulfillment": {
    "type": "auto",
    "status": "delivered",
    "payload": "ABCD-EFGH-1234-5678",
    "delivery_data": null,
    "delivered_at": "2026-03-01T12:01:00Z"
  }
}

4.7 POST /orders/:id/cancel

Cancel a procurement order.

Path parameter:

ParamTypeDescription
idnumberOrder ID

Request: No body required.

Success response:

FieldTypeDescription
okbooleantrue
order_idnumberOrder ID
order_nostringOrder number
statusstringStatus after cancellation (canceled)

Error codes:

error_codeHTTP StatusDescription
order_not_found404Order not found
cancel_not_allowed409Order cannot be canceled in current status

5. Callback Endpoint

5.1 POST /api/v1/upstream/callback

Site B proactively pushes order status changes and delivery content to Site A.

Callback triggers:

  • Order status changes (paid → completed, canceled, etc.)
  • Delivery content generated (auto card delivery, manual fulfillment complete)

Callback authentication:

Site B signs with the api_key / api_secret configured in Site A's connection settings. Site A verifies using the same algorithm.

Request headers:

HeaderDescription
Dujiao-Next-Api-KeyAPI Key configured in Site A's "Site Connection"
Dujiao-Next-TimestampUnix timestamp (seconds)
Dujiao-Next-SignatureHMAC-SHA256 signature

Request body:

FieldTypeRequiredDescription
eventstringNoEvent type
order_idnumberYesSite B order ID
order_nostringNoSite B order number
downstream_order_nostringYesSite A local order number (for matching procurement orders)
statusstringYesOrder status
fulfillmentobjectNoFulfillment info (see below)
timestampnumberNoEvent timestamp

fulfillment sub-object:

FieldTypeDescription
typestringFulfillment type (auto / manual)
statusstringFulfillment status (delivered)
payloadstringDelivery content (card keys, text, etc.)
delivery_dataobjectStructured delivery data
delivered_atstringDelivery time (ISO 8601)

Callback status mapping:

Upstream statusSite A handles as
delivered / completeddelivered (triggers local fulfillment)
canceledcanceled (triggers local cancellation)
OthersPassed through as-is

Callback success response:

json
{ "ok": true, "message": "received" }

Callback failure response:

json
{ "ok": false, "message": "reason" }

6. Order Status Reference

StatusDescription
pending_paymentPending payment
paidPaid (awaiting fulfillment)
fulfillingFulfilling (delivery in progress)
partially_deliveredPartially delivered
deliveredDelivered
completedCompleted
canceledCanceled

7. Error Code Reference

7.1 Authentication Errors (HTTP 401 / 403)

error_codeDescription
missing_auth_headersMissing authentication headers
invalid_timestampInvalid timestamp format
timestamp_expiredTimestamp expired (beyond ±60 seconds)
invalid_signatureSignature verification failed
invalid_api_keyAPI Key is invalid or disabled
user_disabledUser account associated with API Key is banned

7.2 Business Errors

error_codeHTTP StatusDescription
bad_request400Invalid request parameters
invalid_callback_url400Callback URL is illegal
sku_unavailable400SKU not found or inactive
product_unavailable400Product not found or inactive
product_not_found404Product not found
order_not_found404Order not found
insufficient_balance402Insufficient wallet balance
insufficient_stock409Insufficient stock
cancel_not_allowed409Order cannot be canceled
payment_failed200Wallet payment failed (ok: false)
internal_error500Internal server error

8. Integration Workflow & Best Practices

8.1 Standard Integration Flow

1. Site B user enables API access and generates API Key / Secret
2. Site A admin creates a "Site Connection" with Site B's URL and credentials
3. Site A calls POST /ping to test connectivity
4. Site A calls GET /products to fetch product list
5. Site A creates product mappings in admin panel (local product → upstream product/SKU)
6. End user places order on Site A → Site A asynchronously calls POST /orders to procure from Site B
7. Site B processes order and fulfills → pushes callback to Site A
8. Site A updates local order status based on callback

8.2 Idempotency & Retry

  • downstream_order_no serves as idempotency key: same API Key + same downstream_order_no creates only one order.
  • Implement exponential backoff retry for all write operations (e.g. 1s → 2s → 4s).
  • Query endpoints can be polled but should be rate-limited.

8.3 Callback Reliability

  • Site B pushes callbacks to the callback_url provided during order creation when delivery/status changes occur.
  • Site A should also implement polling fallback: periodically call GET /orders/:id to check order status.
  • Callbacks may fail due to network issues; Site A should handle idempotently.

8.4 Security Requirements

  • callback_url must not point to private networks (localhost, 127.0.0.1, private IP ranges, etc.).
  • Signatures use HMAC-SHA256; the Secret is only shown once at creation time — store it securely.
  • All communications should use HTTPS.

9. Signature Implementation Examples

Go

go
package main

import (
    "crypto/hmac"
    "crypto/md5"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "time"
)

func sign(secret, method, path string, timestamp int64, body []byte) string {
    h := md5.New()
    h.Write(body)
    bodyMD5 := hex.EncodeToString(h.Sum(nil))
    signString := fmt.Sprintf("%s\n%s\n%d\n%s", method, path, timestamp, bodyMD5)
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(signString))
    return hex.EncodeToString(mac.Sum(nil))
}

func main() {
    secret := "your_api_secret"
    method := "POST"
    path := "/api/v1/upstream/orders"
    body := []byte(`{"sku_id":1,"quantity":1}`)
    ts := time.Now().Unix()

    sig := sign(secret, method, path, ts, body)
    fmt.Println("Signature:", sig)
    fmt.Println("Timestamp:", ts)
}

Python

python
import hashlib, hmac, time, json, requests

api_key = "your_api_key"
api_secret = "your_api_secret"
base_url = "https://api.example.com"

def sign(secret: str, method: str, path: str, timestamp: int, body: bytes) -> str:
    body_md5 = hashlib.md5(body).hexdigest()
    sign_string = f"{method}\n{path}\n{timestamp}\n{body_md5}"
    return hmac.new(
        secret.encode(), sign_string.encode(), hashlib.sha256
    ).hexdigest()

def api_request(method: str, path: str, body: dict = None):
    ts = int(time.time())
    body_bytes = json.dumps(body).encode() if body else b""
    sig = sign(api_secret, method, path, ts, body_bytes)
    headers = {
        "Dujiao-Next-Api-Key": api_key,
        "Dujiao-Next-Timestamp": str(ts),
        "Dujiao-Next-Signature": sig,
        "Content-Type": "application/json",
    }
    resp = requests.request(method, base_url + path, headers=headers, data=body_bytes)
    return resp.json()

# Test connectivity
print(api_request("POST", "/api/v1/upstream/ping"))

# Fetch products
print(api_request("GET", "/api/v1/upstream/products"))

# Create order
print(api_request("POST", "/api/v1/upstream/orders", {
    "sku_id": 1,
    "quantity": 1,
    "downstream_order_no": "A-20260301-001",
    "callback_url": "https://a-site.example.com/api/v1/upstream/callback",
}))

PHP

php
<?php
function dujiaoSign(string $secret, string $method, string $path, int $timestamp, string $body): string {
    $bodyMD5 = md5($body);
    $signString = "{$method}\n{$path}\n{$timestamp}\n{$bodyMD5}";
    return hash_hmac('sha256', $signString, $secret);
}

$apiKey = 'your_api_key';
$apiSecret = 'your_api_secret';
$baseUrl = 'https://api.example.com';

$method = 'POST';
$path = '/api/v1/upstream/orders';
$body = json_encode(['sku_id' => 1, 'quantity' => 1, 'downstream_order_no' => 'A-001']);
$timestamp = time();
$signature = dujiaoSign($apiSecret, $method, $path, $timestamp, $body);

$ch = curl_init($baseUrl . $path);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $body,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        "Dujiao-Next-Api-Key: {$apiKey}",
        "Dujiao-Next-Timestamp: {$timestamp}",
        "Dujiao-Next-Signature: {$signature}",
    ],
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

Released under the MIT License.