Shop and Site Configuration APIs#
Customize merchant branding, layout, and preferences (Shop), and view platform-wide defaults (Site).
Overview#
- Base URL: markup
https://api.pay.ledger1.ai/portalpay - Authentication (Developer APIs – reads): 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 - Rate limiting headers (if enabled): markup,
X-RateLimit-Limitmarkup,X-RateLimit-Remainingmarkup.X-RateLimit-Reset - Identity: Wallet identity is resolved automatically at the gateway based on your subscription. Clients MUST NOT send wallet identity headers; APIM strips wallet headers and stamps the resolved identity.
- Admin/UI writes (updates) are performed via the PortalPay web app using JWT cookies (markup) with CSRF protections and role checks.
cb_auth_token
../auth.md../../public/openapi.yamlGET /portalpay/api/shop/config#
shop:readReturns the merchant shop configuration (branding for the shop, layout, links, etc.).
GET/portalpay/api/shop/configGet Shop Configuration
Fetch merchant shop configuration and branding settings
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/shop/config"Response Status—Response Headers—Response Body—
Resolution rules:
- Uses the wallet associated with your APIM subscription.
- Optionally, you may provide markupto resolve a merchant by public slug when a subscription-bound wallet context is not appropriate (e.g., public catalog viewing).
?slug=... - Client-supplied wallet headers are ignored by the gateway.
Authenticated not-found behavior:
- If an authenticated caller resolves a merchant but no shop config exists, the endpoint returns markupinstead of falling back to defaults.
404 { "error": "not_found" }
Request#
Headers:
markupOcp-Apim-Subscription-Key: {your-subscription-key}
Optional query parameter:
- markup(string): resolve merchant by public slug
slug
Example Requests:
curl -X GET "https://api.pay.ledger1.ai/portalpay/api/shop/config" \
-H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY"
# Resolve by public slug
curl -X GET "https://api.pay.ledger1.ai/portalpay/api/shop/config?slug=my-coffee-shop" \
-H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY"Response#
Success (200 OK):
Schema summary (OpenAPI ShopConfig):
- name (string)
- description (string)
- bio (string)
- theme (ShopTheme):
- primaryColor, secondaryColor, textColor, accentColor, brandLogoUrl, coverPhotoUrl, fontFamily, logoShape (square|circle), heroFontSize (microtext|small|medium|large|xlarge)
- arrangement (grid|featured_first|groups|carousel)
- xpPerDollar (number)
- slug (string, nullable)
- links (array of { label, url })
- industryPack (restaurant|retail|hotel|freelancer, nullable)
- industryPackActivatedAt (int64, nullable)
- setupComplete (boolean)
- createdAt (int64)
- updatedAt (int64)
Example:
json{ "config": { "name": "My Coffee Shop", "description": "Neighborhood espresso bar", "bio": "Small batch roastery and café", "theme": { "primaryColor": "#1f2937", "secondaryColor": "#F54029", "textColor": "#111827", "accentColor": "#10B981", "brandLogoUrl": "/cblogod.png", "coverPhotoUrl": "/hero.jpg", "fontFamily": "Inter, ui-sans-serif, system-ui", "logoShape": "square", "heroFontSize": "large" }, "arrangement": "grid", "xpPerDollar": 1, "slug": "my-coffee-shop", "links": [ { "label": "Instagram", "url": "https://instagram.com/mycoffeeshop" } ], "industryPack": "restaurant", "industryPackActivatedAt": 1698765400000, "setupComplete": true, "createdAt": 1698765300000, "updatedAt": 1698765400000 } }
Response Headers (when enabled at gateway):
- markup
X-RateLimit-Limit - markup
X-RateLimit-Remaining - markup
X-RateLimit-Reset
POST /portalpay/api/shop/config (Admin – JWT)#
Update shop configuration via the PortalPay admin UI. This route is not callable via APIM developer subscriptions and requires JWT + CSRF in the browser. Client-provided wallet headers are ignored; the authenticated wallet is used for writes.
Note:
- This admin write is not part of the public developer-facing OpenAPI. It is performed in-app by authenticated admins.
Request#
Headers:
markupContent-Type: application/json Cookie: cb_auth_token=...
Body Parameters (subset aligned to ShopConfig):
json{ "name": "My Coffee Shop", "description": "Neighborhood espresso bar", "bio": "Small batch roastery and café", "theme": { "primaryColor": "#2563eb", "secondaryColor": "#dc2626", "textColor": "#111827", "accentColor": "#10B981", "brandLogoUrl": "/cblogod.png", "coverPhotoUrl": "/hero.jpg", "fontFamily": "Inter, ui-sans-serif, system-ui", "logoShape": "square", "heroFontSize": "large" }, "arrangement": "grid", "xpPerDollar": 1, "slug": "my-coffee-shop", "links": [ { "label": "Instagram", "url": "https://instagram.com/mycoffeeshop" } ], "industryPack": "restaurant" }
Example (admin UI, fetch):
javascriptconst res = await fetch('/api/shop/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', // send cb_auth_token body: JSON.stringify({ name: 'My Coffee Shop', theme: { primaryColor: '#2563eb', secondaryColor: '#dc2626' }, arrangement: 'grid' }) }); const data = await res.json();
Response#
Success (200 OK):
json{ "ok": true, "config": { "...": "updated ShopConfig ..." } }
GET /portalpay/api/site/config#
Get site-level configuration (branding defaults and global payment defaults). Depending on environment, this may be public. In protected environments, APIM enforcement can be enabled; if so, include the subscription header.
GET/portalpay/api/site/configGet Site Configuration
Fetch site-level configuration and branding defaults
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/site/config"Response Status—Response Headers—Response Body—
Security (per OpenAPI): no default security (public/read-only), but may be APIM-protected by environment policy.
Request#
Examples:
<!-- CODE_TABS_START --> <!-- TAB:cURL (public) --><!-- TAB:JavaScript (with APIM, if enforced) -->bashcurl -X GET "https://api.pay.ledger1.ai/portalpay/api/site/config"
<!-- TAB:Python (public) -->javascriptconst headers = {}; if (process.env.APIM_SUBSCRIPTION_KEY) { headers['Ocp-Apim-Subscription-Key'] = process.env.APIM_SUBSCRIPTION_KEY; } const res = await fetch('https://api.pay.ledger1.ai/portalpay/api/site/config', { headers }); const data = await res.json();
<!-- CODE_TABS_END -->pythonimport requests r = requests.get('https://api.pay.ledger1.ai/portalpay/api/site/config') data = r.json()
Response#
Success (200 OK):
Schema summary (OpenAPI SiteConfig):
- theme (SiteTheme):
- primaryColor, secondaryColor, brandLogoUrl, brandFaviconUrl, brandName, fontFamily, receiptBackgroundUrl
- processingFeePct (number, nullable)
- defaultPaymentToken (ETH|USDC|USDT|cbBTC|cbXRP)
- reserveRatios (object<string, number>, nullable)
Example:
json{ "theme": { "primaryColor": "#1f2937", "secondaryColor": "#F54029", "brandLogoUrl": "/cblogod.png", "brandFaviconUrl": "/favicon-32x32.png", "brandName": "PortalPay", "fontFamily": "Inter, ui-sans-serif, system-ui", "receiptBackgroundUrl": "/manifest.webmanifest" }, "defaultPaymentToken": "ETH", "processingFeePct": 1.0 }
If APIM is enabled for this endpoint, rate limiting headers may be present:
- markup
X-RateLimit-Limit - markup
X-RateLimit-Remaining - markup
X-RateLimit-Reset
Branding Reference#
Shop (merchant) vs Site (platform) themes:
- Shop theme (ShopTheme)
- primaryColor, secondaryColor, textColor, accentColor, brandLogoUrl, coverPhotoUrl, fontFamily, logoShape, heroFontSize
- Site theme (SiteTheme)
- primaryColor, secondaryColor, brandLogoUrl, brandFaviconUrl, brandName, fontFamily, receiptBackgroundUrl
Code Examples#
JavaScript/TypeScript (developer read — Shop)#
typescriptconst APIM_SUBSCRIPTION_KEY = process.env.APIM_SUBSCRIPTION_KEY!; const BASE_URL = 'https://api.pay.ledger1.ai/portalpay'; export async function getShopConfig() { const res = await fetch(`${BASE_URL}/api/shop/config`, { headers: { 'Ocp-Apim-Subscription-Key': APIM_SUBSCRIPTION_KEY } }); return res.json(); // { config: ShopConfig } }
JavaScript/TypeScript (admin write — Shop, JWT in browser)#
typescriptexport async function updateShopBranding(theme: any) { const res = await fetch('/api/shop/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', // sends cb_auth_token cookie body: JSON.stringify({ theme }) }); return res.json(); // { ok: boolean, config?: ShopConfig } }
JavaScript/TypeScript (developer read — Site)#
typescriptexport async function getSiteConfig() { const headers: Record<string, string> = {}; if (process.env.APIM_SUBSCRIPTION_KEY) { headers['Ocp-Apim-Subscription-Key'] = process.env.APIM_SUBSCRIPTION_KEY; } const res = await fetch('https://api.pay.ledger1.ai/portalpay/api/site/config', { headers }); return res.json(); // SiteConfig }
Notes on Auth Models#
- Developer reads must use markupwhen APIM-enforced. 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 (configuration updates).
cb_auth_token - Client requests do not include wallet identity; APIM strips wallet headers and stamps the resolved identity.
