brazosd

Docs

Merchant Trade API Integration

This guide explains how merchants integrate with the Brazosd Trade API, using an Agent as the frontend entry point and the merchant SaaS as the backend fulfillment system.

1. Enable Merchant Features

Call this endpoint with a regular Brazosd Bearer token:

http
POST /api/v1/trade/merchant
Authorization: Bearer <access_token>

After it is enabled, the current org receives a merchant profile. The platform default commission rate is 1500 ppm, or 0.15%. Brazosd also creates an Ed25519 key for signing merchant callbacks. Merchants can call:

http
GET /api/v1/trade/merchant/callback-keys
Authorization: Bearer <access_token>

Use the returned public keys to verify Brazosd callback signatures.

2. Generate Trade API Keys

The merchant backend owns the Ed25519 private key. Brazosd only stores the public key. Keep the private key on the merchant server only; do not put it in browsers, Agent prompts, logs, or user-visible configuration.

python
from base64 import b64encode
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.hazmat.primitives import serialization

private_key = ed25519.Ed25519PrivateKey.generate()
public_key = private_key.public_key()

private_key_b64 = b64encode(private_key.private_bytes(
    encoding=serialization.Encoding.Raw,
    format=serialization.PrivateFormat.Raw,
    encryption_algorithm=serialization.NoEncryption(),
)).decode()

public_key_b64 = b64encode(public_key.public_bytes(
    encoding=serialization.Encoding.Raw,
    format=serialization.PublicFormat.Raw,
)).decode()

print("PRIVATE_KEY_B64=", private_key_b64)
print("PUBLIC_KEY_B64=", public_key_b64)

Register the public key:

http
POST /api/v1/trade/merchant/public-keys
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "name": "prod-2026-05",
  "publicKeyB64": "<PUBLIC_KEY_B64>"
}

Store the returned id. When creating orders, send it in x-brazosd-tradeapi-pubkey-id.

3. Configure Callback URL

http
PUT /api/v1/trade/merchant/callback-url
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "url": "https://merchant.example.com/brazosd/order-callback"
}

The callback URL must use https://, and the host must be a domain name, not an IP address or localhost.

4. Create Products

Products only store identity and display information that is independent of price. Prices are snapshotted into order line items when an order is created.

http
POST /api/v1/trade/products
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "name": "CRM Agent Seat",
  "description": "Monthly CRM agent seat"
}

description can contain at most 200 characters.

5. Sign and Create Orders

The signature canonical string is:

text
METHOD
REQUEST_URI
TIMESTAMP
BODY

For example, when calling POST /api/v1/tradeapi/orders, REQUEST_URI is /api/v1/tradeapi/orders. TIMESTAMP is Unix seconds, and Brazosd validates a 5-minute time window. The signature is the base64 encoding of the raw Ed25519 signature.

python
import json
import time
import requests
from base64 import b64decode, b64encode
from cryptography.hazmat.primitives.asymmetric import ed25519

base_url = "https://brazosd.example.com"
path = "/api/v1/tradeapi/orders"
private_key = ed25519.Ed25519PrivateKey.from_private_bytes(
    b64decode("<PRIVATE_KEY_B64>")
)

body = {
    "agentId": "<agent_uuid>",
    "buyerUserId": 123,
    "outTradeNo": "merchant-order-10001",
    "description": "CRM Agent monthly usage",
    "expiresInSeconds": 1800,
    "items": [
        {
            "productId": "<product_uuid>",
            "amountMicroyuans": 199000000,
            "note": "May subscription"
        }
    ]
}
body_bytes = json.dumps(body, separators=(",", ":"), ensure_ascii=False).encode()
timestamp = str(int(time.time()))
canonical = b"POST\n" + path.encode() + b"\n" + timestamp.encode() + b"\n" + body_bytes
signature = b64encode(private_key.sign(canonical)).decode()

resp = requests.post(
    base_url + path,
    data=body_bytes,
    headers={
        "content-type": "application/json",
        "x-brazosd-tradeapi-pubkey-id": "<registered_public_key_id>",
        "x-brazosd-tradeapi-timestamp": timestamp,
        "x-brazosd-tradeapi-signature": signature,
    },
    timeout=10,
)
resp.raise_for_status()
print(resp.json())

outTradeNo is idempotent within the same merchant. The same outTradeNo with the same request body returns the same order. The same outTradeNo with a different request body returns a conflict.

6. User Authorization and Allowlist

After an order is created, Brazosd creates an authorization form for buyerUserId. The user is charged only after confirmation. When the user selects "Do not ask again for this type of order", every productId in the order is added to the allowlist for that org and agent. Later payments for the same product can be approved automatically.

Gift credits can only be used for platform consumption and cannot be used for merchant orders. Trade API orders only deduct recharge credits from the buyer org.

7. Handle Callbacks

When an order is paid successfully or cancelled by the user, Brazosd POSTs JSON to the merchant callback URL. Brazosd signs callbacks with Ed25519.

Headers:

text
X-Brazosd-Callback-Key-Id: <callback_key_uuid>
X-Brazosd-Callback-Timestamp: <unix_seconds>
X-Brazosd-Callback-Signature: <base64_signature>

The callback signature uses the same canonical string:

text
METHOD
REQUEST_URI
TIMESTAMP
BODY

Verification example:

python
from base64 import b64decode
from cryptography.hazmat.primitives.asymmetric import ed25519

def verify_brazosd_callback(method, request_uri, body_bytes, timestamp, signature_b64, public_key_b64):
    public_key = ed25519.Ed25519PublicKey.from_public_bytes(b64decode(public_key_b64))
    canonical = (
        method.upper().encode()
        + b"\n"
        + request_uri.encode()
        + b"\n"
        + timestamp.encode()
        + b"\n"
        + body_bytes
    )
    public_key.verify(b64decode(signature_b64), canonical)

Merchant services should handle callbacks idempotently by eventId or by order.id + order.status.

If a callback fails, Brazosd retries the task once every 30 minutes. Each task attempts delivery up to 3 times with a 5-second interval. Brazosd keeps the latest failure information for each callback URL. A later success does not delete the failure record; it only updates the latest success time.