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:
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:
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.
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:
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
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.
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:
METHOD
REQUEST_URI
TIMESTAMP
BODYFor 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.
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:
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:
METHOD
REQUEST_URI
TIMESTAMP
BODYVerification example:
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.