Split Contract APIs#
Configure and inspect payment split configuration and indexed split transactions.
Overview#
Split configuration controls how payments are distributed among recipients. Before accepting payments, merchants should configure a split for their wallet.
- Base URL: markup
https://api.pay.ledger1.ai/portalpay - Authentication (Developer APIs – reads): All developer reads require an Azure APIM subscription key header:
- markup
Ocp-Apim-Subscription-Key: {your-subscription-key}
- Gateway posture: APIM custom domain is the primary endpoint. Azure Front Door (AFD) may be configured as an optional/fallback edge; if enabled, APIM accepts an internal markupper policy.
x-edge-secret - Identity: Wallet identity is resolved automatically at the gateway based on your subscription; clients MUST NOT send wallet identity headers.
- Admin/UI writes (configuration) are performed via the PortalPay web app using JWT cookies (markup) with CSRF protections and role checks. Public developer subscriptions cannot perform admin writes.
cb_auth_token
../auth.md../../public/openapi.yamlGET /portalpay/api/split/deploy#
split:readGet split configuration for the merchant associated with your subscription.
GET/portalpay/api/split/deployGet Split Configuration
Retrieve the split configuration for your merchant account
Default is the APIM custom domain. For AFD, enter only the AFD endpoint host (e.g., https://afd-endpoint-pay-...) without any path; the /portalpay prefix is added automatically for /api/* and /healthz.
The key is kept only in memory while this page is open. Do not paste secrets on shared machines.
For public reads (GET /api/inventory, GET /api/shop/config), include the merchant wallet (0x-prefixed 40-hex). Non-GET requests should use JWT and will ignore this header.
Using server-side proxy to avoid CORS. Requests go through /api/tryit-proxy to AFD/APIM.cURLcurl -X GET "https://api.pay.ledger1.ai/portalpay/api/split/deploy"Response Status—Response Headers—Response Body—
Request#
Headers:
markupOcp-Apim-Subscription-Key: {your-subscription-key}
Example Requests:
curl -X GET "https://api.pay.ledger1.ai/portalpay/api/split/deploy" \
-H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY"Response#
Success (200 OK):
json{ "split": { "address": "0x...", "recipients": [ { "address": "0xMerchantWallet", "sharesBps": 9950 }, { "address": "0xPlatformAddress", "sharesBps": 50 } ] } }
Not Configured (200 OK):
json{ "split": { "address": null, "recipients": [] } }
POST /portalpay/api/split/deploy (Admin – JWT; APIM writes not permitted)#
Idempotently persist split address and recipients for a merchant. This is an admin-only operation performed inside the PortalPay web app and is not callable via developer APIM subscriptions.
Request#
Headers:
markupContent-Type: application/json Cookie: cb_auth_token=...
Body Parameters (subset aligned to OpenAPI SplitConfigUpsertRequest):
| Parameter | Type | Required | Description |
|---|---|---|---|
markup | string | No | Optional pre-deployed split contract address |
markup | array | No | Recipients array of markup |
Recipient item:
- markup(string, required): Recipient wallet
address - markup(integer, required): Basis points of split shares (e.g., 9950 = 99.5%)
sharesBps
Example (admin UI, fetch):
javascriptconst res = await fetch('/api/split/deploy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', // send cb_auth_token body: JSON.stringify({ splitAddress: '0xOptionalExistingSplit', recipients: [ { address: '0xMerchantWallet', sharesBps: 9950 }, { address: '0xPlatformAddress', sharesBps: 50 } ] }) }); const data = await res.json();
Response#
Success (200 OK):
json{ "ok": true, "split": { "address": "0x...", "recipients": [ { "address": "0xMerchantWallet", "sharesBps": 9950 }, { "address": "0xPlatformAddress", "sharesBps": 50 } ] } }
Idempotent (already configured):
json{ "ok": true, "split": { "address": "0x...", "recipients": [ "..." ] }, "idempotent": true }
GET /portalpay/api/split/transactions#
split:readList split transactions indexed by the split indexer. Availability varies by deployment.
GET/portalpay/api/split/transactionsList Split Transactions
Retrieve indexed split transactions
Default is the APIM custom domain. For AFD, enter only the AFD endpoint host (e.g., https://afd-endpoint-pay-...) without any path; the /portalpay prefix is added automatically for /api/* and /healthz.
The key is kept only in memory while this page is open. Do not paste secrets on shared machines.
For public reads (GET /api/inventory, GET /api/shop/config), include the merchant wallet (0x-prefixed 40-hex). Non-GET requests should use JWT and will ignore this header.
Query ParametersUsing server-side proxy to avoid CORS. Requests go through /api/tryit-proxy to AFD/APIM.cURLcurl -X GET "https://api.pay.ledger1.ai/portalpay/api/split/transactions"Response Status—Response Headers—Response Body—
Request#
Headers:
markupOcp-Apim-Subscription-Key: {your-subscription-key}
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
markup | string | No | Optional filter by recipient/split address |
Example:
bashcurl -X GET "https://api.pay.ledger1.ai/portalpay/api/split/transactions" \ -H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY"
Response#
Success (200 OK):
json[ { "id": "txn_abc", "receiptId": "rcpt_12345", "amount": 27.0, "currency": "USDC", "status": "completed", "timestamp": "2025-01-01T12:00:00Z", "transactionHash": "0x...", "fees": 0.2 } ]
Response Headers (when enabled at gateway):
- markup
X-RateLimit-Limit - markup
X-RateLimit-Remaining - markup
X-RateLimit-Reset
POST /portalpay/api/split/withdraw (Deprecated)#
PaymentSplitter.releaseBehavior:
- Returns HTTP 410 Gone with a deprecation payload; some deployments may return 204 No Content.
Request#
Headers:
markupContent-Type: application/json
Responses#
204 No Content (deprecated endpoint)
markupHTTP/1.1 204 No Content x-correlation-id: ...
410 Gone (preferred deprecation response)
json{ "error": "deprecated", "reason": "use_connected_wallet", "message": "Deprecated in favor of client-side PaymentSplitter.release" }
Migration:
- Use your wallet to call the split contract’s markupfunction directly.
release
Error Responses#
401 Unauthorized
json{ "error": "unauthorized", "message": "Missing or invalid subscription key" }
403 Forbidden
json{ "error": "forbidden", "message": "Insufficient scope or origin enforcement failed" }
429 Too Many Requests
json{ "error": "rate_limited", "resetAt": 1698765432000 }
400 Bad Request
json{ "error": "invalid_input", "message": "Invalid request parameters" }
500 Internal Server Error
json{ "error": "failed", "message": "Error message details" }
Important Notes#
Split Contract Requirement#
A split configuration should be in place before:
- Creating orders
- Accepting payments
split_requiredBasis Points (BPS)#
Split shares are specified in basis points:
- 10000 bps = 100%
- 9950 bps = 99.50%
- 50 bps = 0.50%
Idempotency#
The admin split configuration route is idempotent:
- First call: Creates configuration
- Subsequent calls: Returns existing configuration with markup
idempotent: true
Code Examples#
JavaScript/TypeScript (developer reads)#
typescriptconst APIM_SUBSCRIPTION_KEY = process.env.APIM_SUBSCRIPTION_KEY!; const BASE_URL = 'https://api.pay.ledger1.ai/portalpay'; export async function getSplitConfig() { const res = await fetch(`${BASE_URL}/api/split/deploy`, { headers: { 'Ocp-Apim-Subscription-Key': APIM_SUBSCRIPTION_KEY } }); return res.json(); // { split: SplitConfig } } export async function listSplitTransactions(address?: string) { const params = new URLSearchParams(); if (address) params.set('address', address); const res = await fetch(`${BASE_URL}/api/split/transactions?${params}`, { headers: { 'Ocp-Apim-Subscription-Key': APIM_SUBSCRIPTION_KEY } }); return res.json() as Promise<{ id: string; receiptId: string; amount: number; currency: string; status: string; timestamp: string; transactionHash: string; fees: number; }[]>; }
JavaScript/TypeScript (admin write – JWT, in browser)#
typescriptexport async function configureSplitAdmin(input: { splitAddress?: string; recipients?: { address: string; sharesBps: number }[]; }) { const res = await fetch('/api/split/deploy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', // sends cb_auth_token body: JSON.stringify(input) }); return res.json(); }
Python (developer reads)#
pythonimport os, requests KEY = os.environ['APIM_SUBSCRIPTION_KEY'] BASE_URL = 'https://api.pay.ledger1.ai/portalpay' def get_split_config(): r = requests.get(f'{BASE_URL}/api/split/deploy', headers={'Ocp-Apim-Subscription-Key': KEY}) return r.json().get('split') def list_split_transactions(address=None): params = {} if address: params['address'] = address r = requests.get(f'{BASE_URL}/api/split/transactions', headers={'Ocp-Apim-Subscription-Key': KEY}, params=params) return r.json() # array
Notes on Auth Models#
- Developer reads must use markup. Wallet identity is resolved automatically at the gateway based on your subscription; the backend trusts the resolved identity.
Ocp-Apim-Subscription-Key - Admin/UI operations use JWT cookies (markup) with CSRF and role checks for sensitive actions (configure split).
cb_auth_token - Client requests do not include wallet identity; APIM strips wallet headers and stamps the resolved identity.
