--- url: /docs/api-reference.md description: Complete reference for the BannerOS REST API. --- # API Reference Complete reference for the BannerOS REST API. ## Base URL Set `BANNEROS_API_BASE_URL` to one of: * **If running BannerOS locally:** `http://localhost:3001/api` * **If using hosted version:** `https://banner-os.alwaysdata.net/api` All endpoints below are relative to your `BANNEROS_API_BASE_URL`. ## Banners ### GET /api/banners List all banners for a tenant. * **Parameters:** Query: `tenant_id`, `status`, `type` * **Response:** `{ banners: Banner[] }` ### GET /api/banners/:id Get a single banner by ID. * **Parameters:** Path: `id` * **Response:** `{ banner: Banner }` ### POST /api/banners Create a new banner. * **Parameters:** Body: `title`\*, `body`, `type`, `status`, `priority`, `targeting_rules`, `style`, `cta_text`, `cta_url`, `start_date`, `end_date`, `tenant_id` * **Response:** `{ banner: Banner }` ### PUT /api/banners/:id Update an existing banner. * **Parameters:** Path: `id`. Body: any banner fields * **Response:** `{ banner: Banner }` ### DELETE /api/banners/:id Delete a banner and its impressions. * **Parameters:** Path: `id` * **Response:** `{ success: true }` ## Evaluate ### POST /api/evaluate Evaluate which banners to show for a user/context. Filters by targeting rules, date range, dismissed banners, and tenant limits. * **Parameters:** Body: `tenant_id`, `user_id`, `context` (`platform`, `country`, `segment`, `page_path`, `app_version`, `is_authenticated`) * **Response:** `{ banners: Banner[], count: number }` **Example request:** ```json { "tenant_id": "default", "user_id": "user-123", "context": { "platform": "web", "country": "US", "segment": "pro", "page_path": "/dashboard", "is_authenticated": true } } ``` ## Impressions ### POST /api/impressions Record an impression (view, click, or dismiss). * **Parameters:** Body: `banner_id`\*, `tenant_id`, `user_id`, `context`, `action` (`view`|`click`|`dismiss`) * **Response:** `{ success: true }` ### POST /api/impressions/dismiss Dismiss a banner for a specific user. * **Parameters:** Body: `banner_id`\*, `user_id`\*, `tenant_id` * **Response:** `{ success: true, message: string }` ### GET /api/impressions/stats/:banner\_id Get impression stats for a specific banner. * **Parameters:** Path: `banner_id` * **Response:** `{ banner_id, stats: { views, clicks, dismissals, unique_users, ctr }, daily: [...] }` ### GET /api/impressions/stats Get aggregate stats for all banners in a tenant. * **Parameters:** Query: `tenant_id` * **Response:** `{ tenant_id, stats: [...] }` ## Tenants ### GET /api/tenants/:id Get tenant configuration. * **Parameters:** Path: `id` * **Response:** `{ tenant: Tenant }` ### PUT /api/tenants/:id Update tenant configuration. * **Parameters:** Path: `id`. Body: `name`, `config` * **Response:** `{ tenant: Tenant }` ### POST /api/tenants Create a new tenant. * **Parameters:** Body: `name`\*, `id`, `config` * **Response:** `{ tenant: Tenant }` ## Validation ### GET /api/validate Validate all banners for configuration issues. * **Parameters:** Query: `tenant_id` * **Response:** `{ total_banners, issues_count, errors, warnings, issues: [...] }` ## Health ### GET /api/health Health check endpoint. * **Parameters:** None * **Response:** `{ status: "ok", service: "BannerOS API", version: "1.0.0" }` ## Data Models ### Banner ```typescript interface Banner { id: string; tenant_id: string; title: string; body: string; type: 'promotional' | 'support' | 'informational'; status: 'active' | 'inactive' | 'archived'; priority: number; targeting_rules: TargetingRules; style: Record; cta_text: string | null; cta_url: string | null; start_date: string | null; // ISO date end_date: string | null; // ISO date created_at: string; updated_at: string; } ``` ### TargetingRules ```typescript interface TargetingRules { platforms?: string[]; // ['web', 'mobile', 'desktop'] countries?: string[]; // ['US', 'GB', 'IN'] user_segments?: string[]; // ['free', 'pro', 'enterprise'] page_paths?: string[]; // ['/dashboard', '/settings/*'] user_ids?: string[]; // ['user-123'] min_app_version?: string; // '2.1.0' is_authenticated?: boolean; // true } ``` ### Tenant ```typescript interface Tenant { id: string; name: string; config: { maxBannersPerPage: number; defaultDismissDuration: number; allowPromotional: boolean; allowSupport: boolean; allowInformational: boolean; }; created_at: string; } ``` --- --- url: /docs/banner-types.md description: 'BannerOS supports three banner types, each suited for different use cases.' --- # Banner Types BannerOS supports three banner types, each suited for different use cases. ## Promotional **Type value:** `promotional` Used for marketing campaigns, sales announcements, new feature launches, and other promotional content. ### Best Practices * Always set targeting rules to avoid showing irrelevant promotions * Set an end date to auto-expire time-sensitive offers * Use a clear CTA (call-to-action) with a link * Keep priority high for important campaigns ### Styling Guidance * **Color scheme:** Warm (amber/yellow/orange) * **Background:** `bg-amber-50` or gradient * **Border:** `border-amber-200` * **Text:** `text-amber-900` * **CTA:** Always include a CTA button ### Priority Range 80–200 ### Typical Placements `home_top`, `cart_top`, `product_top`, `home_inline` ### Example ```json { "title": "Black Friday Sale — 40% off all plans!", "body": "Upgrade now and save big. Offer ends Nov 30.", "type": "promotional", "priority": 100, "cta_text": "Upgrade Now", "cta_url": "/pricing", "targeting_rules": { "platforms": ["web"], "countries": ["US", "GB"] }, "start_date": "2025-11-25", "end_date": "2025-11-30" } ``` ## Support **Type value:** `support` Used for support-related messages like maintenance notices, outage alerts, known issues, and migration guides. ### Best Practices * Use high priority for critical outage notices * Set a short end date for maintenance windows * Target affected user segments specifically * Avoid a sales-style CTA — keep it neutral and helpful ### Styling Guidance * **Color scheme:** Cool (blue) * **Background:** `bg-blue-50` or gradient * **Border:** `border-blue-200` * **Text:** `text-blue-900` * **CTA:** Optional — link to status page or details ### Priority Range 150–250 ### Typical Placements `home_top`, `checkout_top`, `account_top` ### Example ```json { "title": "Scheduled Maintenance — Dec 15", "body": "Our platform will be briefly unavailable from 2–4 AM UTC.", "type": "support", "priority": 200, "targeting_rules": {}, "start_date": "2025-12-14", "end_date": "2025-12-16" } ``` ## Informational **Type value:** `informational` Used for general announcements, tips, onboarding guidance, and non-urgent information. ### Best Practices * Good for onboarding users with helpful tips * Can run without targeting for broad announcements * Lower priority than support and promotional banners * Great for feature discovery and education ### Styling Guidance * **Color scheme:** Green (emerald) * **Background:** `bg-emerald-50` or gradient * **Border:** `border-emerald-200` * **Text:** `text-emerald-900` * **CTA:** Optional — link to docs or feature page ### Priority Range 10–100 ### Typical Placements `home_top`, `account_top` ### Example ```json { "title": "Did you know? You can set up keyboard shortcuts", "body": "Go to Settings > Shortcuts to configure your custom keybindings.", "type": "informational", "priority": 10, "targeting_rules": { "user_segments": ["new"] } } ``` ## Type Restrictions Tenant configuration can disable specific banner types. If a type is disabled, banners of that type will still be stored but won't appear in evaluation results. Configure allowed types in the **Dashboard > Config** page or via the tenant API: ```json { "allowPromotional": true, "allowSupport": true, "allowInformational": true } ``` --- --- url: /docs/index.md description: >- BannerOS is a banner management platform that lets you create, target, and measure banners across your application. --- # Getting Started with BannerOS BannerOS is a banner management platform that lets you create, target, and measure banners across your application. *** ### API Base URL ``` https://banner-os.alwaysdata.net/api ``` Use this URL in your integrations, client scripts, and environment variables: ```bash export BANNEROS_API_BASE_URL=https://banner-os.alwaysdata.net/api ``` ### Access the Dashboard Open the dashboard to manage banners visually: ``` https://banner-os.alwaysdata.net ``` ### Create Your First Banner Using the REST API: ```bash curl -X POST $BANNEROS_API_BASE_URL/banners \ -H "Content-Type: application/json" \ -d '{ "title": "Welcome to our platform!", "body": "Check out our new features.", "type": "informational", "status": "active", "priority": 10 }' ``` Or use the Dashboard and click **Create Banner**. ### Evaluate Banners for a User ```bash curl -X POST $BANNEROS_API_BASE_URL/evaluate \ -H "Content-Type: application/json" \ -d '{ "user_id": "user-123", "context": { "platform": "web", "country": "US", "page_path": "/dashboard" } }' ``` ### Record an Impression ```bash curl -X POST $BANNEROS_API_BASE_URL/impressions \ -H "Content-Type: application/json" \ -d '{ "banner_id": "", "user_id": "user-123", "action": "view" }' ``` Supported actions: `view`, `click`, `dismiss` *** ## Default Configuration * **Tenant ID:** `default` (pre-seeded when the API starts) * **Max banners per page:** 3 * **Default dismiss duration:** 24 hours * **Allowed banner types:** promotional, support, informational * All API calls require `Content-Type: application/json` * `tenant_id` is required in every evaluate and impression call ## Next Steps * Learn about [banner types](./banner-types) and when to use each * Configure [targeting rules](./targeting-rules) to reach the right users * See the full [API reference](./api-reference) * Read the [Integration Guide](./integration-guide) for framework-specific examples --- --- url: /docs/integration-guide.md description: >- Integrate BannerOS into your application using direct REST API calls. No SDK required. --- # Integration Guide Integrate BannerOS into your application using direct REST API calls. No SDK required. ## Quick Start (REST API) ### Evaluate Banners ```bash POST /api/evaluate Content-Type: application/json { "tenant_id": "default", "user_id": "user-123", "context": { "platform": "web", "country": "US", "page_path": "/dashboard" } } ``` ### Record Impression ```bash POST /api/impressions Content-Type: application/json { "banner_id": "banner-uuid", "user_id": "user-123", "action": "view" } ``` Actions: `view`, `click`, `dismiss` ### Dismiss Banner ```bash POST /api/impressions/dismiss Content-Type: application/json { "banner_id": "banner-uuid", "user_id": "user-123" } ``` ## Environment Setup Before integrating, set your API base URL: ```bash # If running BannerOS locally export BANNEROS_API_BASE_URL=http://localhost:3001/api # If using hosted version export BANNEROS_API_BASE_URL=https://banner-os.alwaysdata.net/api ``` ## React Integration Use a custom hook with `useEffect` + `fetch` to call `POST /api/evaluate` on mount, store banners in state, render conditionally. Best for: Single-page React apps, Vite, Create React App. ```jsx import { useState, useEffect, useRef, useCallback } from 'react'; // Read from environment variable (Vite: import.meta.env.VITE_BANNEROS_API_BASE_URL) // or from your app config const API_BASE = import.meta.env.VITE_BANNEROS_API_BASE_URL; export function useBanners(tenantId, userId, context) { const [banners, setBanners] = useState([]); const [loading, setLoading] = useState(true); const trackedRef = useRef(new Set()); const load = useCallback(async () => { setLoading(true); try { const res = await fetch(`${API_BASE}/evaluate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tenant_id: tenantId, user_id: userId, context }), }); const data = await res.json(); setBanners(data.banners || []); // Auto-track views for (const b of data.banners || []) { if (!trackedRef.current.has(b.id)) { trackedRef.current.add(b.id); fetch(`${API_BASE}/impressions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ banner_id: b.id, tenant_id: tenantId, user_id: userId, action: 'view' }), }).catch(() => {}); } } } catch { setBanners([]); } setLoading(false); }, [tenantId, userId, JSON.stringify(context)]); useEffect(() => { load(); }, [load]); const dismiss = (id) => { setBanners(prev => prev.filter(b => b.id !== id)); fetch(`${API_BASE}/impressions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ banner_id: id, tenant_id: tenantId, user_id: userId, action: 'dismiss' }), }).catch(() => {}); }; const trackClick = (id) => { fetch(`${API_BASE}/impressions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ banner_id: id, tenant_id: tenantId, user_id: userId, action: 'click' }), }).catch(() => {}); }; return { banners, loading, dismiss, trackClick, refresh: load }; } ``` ## Next.js Integration Use server-side fetch in `getServerSideProps` or route handler, hydrate client with banner data to avoid flicker. Best for: Next.js apps that need SSR banner rendering. ```tsx // app/home/page.tsx — Server Component banner fetch const API_BASE = process.env.BANNEROS_API_BASE_URL; async function getBanners(userId, context) { const res = await fetch(`${API_BASE}/evaluate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tenant_id: 'default', user_id: userId, context }), next: { revalidate: 300 }, // Cache for 5 minutes }); return res.json(); } export default async function HomePage() { const { banners } = await getBanners('user-123', { platform: 'web', page_path: '/home' }); return (
{/* rest of page */}
); } ``` ## Vue 3 Integration Use a composable with `ref` + `onMounted` to fetch banners from `POST /api/evaluate`. Best for: Vue 3 apps with Composition API. ```js import { ref, onMounted } from 'vue'; export function useBanners(tenantId, userId, context) { const banners = ref([]); const loading = ref(true); onMounted(async () => { const res = await fetch('/api/evaluate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tenant_id: tenantId, user_id: userId, context }), }); const data = await res.json(); banners.value = data.banners || []; loading.value = false; }); return { banners, loading }; } ``` ## Vanilla JavaScript Integration Use `fetch` directly, inject banner HTML into a container element. Best for: Static sites, WordPress, non-framework apps. ```js async function loadBanners(containerId, tenantId, userId, context) { const container = document.getElementById(containerId); if (!container) return; const res = await fetch('/api/evaluate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tenant_id: tenantId, user_id: userId, context }), }); const { banners } = await res.json(); container.innerHTML = banners.map(b => `
${b.title}

${b.body || ''}

${b.cta_text ? `${b.cta_text}` : ''}
`).join(''); } ``` ## Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `apiUrl` | string | Required | Base URL of the BannerOS API | | `tenantId` | string | `"default"` | Tenant identifier | | `userId` | string | undefined | User ID for dismiss tracking and personalization | ## Context Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `tenant_id` | string | Yes | Tenant identifier | | `user_id` | string | Recommended | User ID for personalization and dismiss persistence | | `platform` | string | Recommended | `web`, `mobile`, `desktop`, `ios`, `android` | | `page_path` | string | Recommended | Current page path, e.g. `/dashboard` | | `country` | string | Optional | ISO 3166-1 alpha-2 country code | | `segment` | string | Optional | User segment, e.g. `free`, `pro`, `enterprise` | | `app_version` | string | Optional | Semver app version for version targeting | | `is_authenticated` | boolean | Optional | Whether the user is authenticated | ## Telemetry Track banner impressions, clicks, and dismissals. All telemetry calls should be **fire-and-forget** — never `await` them or let failures break the page. ### Required events * **view** — Fire once per banner per page load. Deduplicate with a `Set` or `ref` — do not re-fire on re-renders. * **click** — Fire when the user clicks the CTA button. ### Optional events * **dismiss** — Fire when the user closes a banner. Requires `user_id` for persistence. ### Impression payload ```bash POST /api/impressions Content-Type: application/json { "banner_id": "string (required)", "tenant_id": "string (required)", "user_id": "string (optional, required for dismiss)", "action": "view" | "click" | "dismiss" } ``` ## Caching * **Default TTL:** 5 minutes, in-memory cache keyed by `tenant_id` + `user_id` + context hash * **Stale on error:** Yes — serve cached banners if the API is unreachable * **Home page:** 5 min TTL, refresh on focus/visibility change, stale-on-error OK * **Cart page:** 2 min TTL, invalidate when cart contents change, stale-on-error OK * **Checkout page:** No cache — always fetch fresh. No stale-on-error. * Invalidate on user segment change, page navigation, or explicit refresh * Do not cache dismiss actions — always send to API immediately ## Fallback Behavior What to show when the API is unavailable or no banners match: * **Default:** Show nothing — render empty container with reserved height, collapse after 2s * **Home:** Static welcome message ("Free shipping on orders over $75.") * **Cart:** Static free-shipping banner * **Checkout:** Show nothing — never interrupt checkout with fallback banners * Always use `try/catch` around fetch calls. Never let a banner error break the page. * **Retry:** Default 2 attempts / 3000ms. Cart: 1 attempt / 2000ms. Checkout: no retry. --- --- url: /docs/targeting-rules.md description: Control which users see each banner by specifying targeting rules. --- # Targeting Rules Reference Control which users see each banner by specifying targeting rules. All rules must match (AND logic) for a banner to be shown. ## How Targeting Works * Rules are stored as a JSON object in the `targeting_rules` field of a banner * All specified rules must match (AND logic) — if any rule fails, the banner is excluded * Rules that are **not specified** are treated as "match all" — an empty rules object shows to everyone * The evaluate endpoint compares rules against the provided `context` object ## Rules ### platforms **Type:** `string[]` Target specific platforms. Banner only shows if the user's platform matches one of the listed values. **Accepted values:** `web`, `mobile`, `desktop`, `ios`, `android` ```json { "targeting_rules": { "platforms": ["web", "mobile"] } } ``` ### countries **Type:** `string[]` Target by country code (ISO 3166-1 alpha-2). Banner shows only to users in the listed countries. **Accepted values:** `US`, `GB`, `IN`, `DE`, `FR`, etc. ```json { "targeting_rules": { "countries": ["US", "GB", "DE"] } } ``` ### user\_segments **Type:** `string[]` Target users by segment. The user's segment must match one of the listed values. **Accepted values:** `free`, `pro`, `enterprise`, `new`, `churned`, etc. ```json { "targeting_rules": { "user_segments": ["free", "new"] } } ``` ### page\_paths **Type:** `string[]` Target specific page paths. Supports exact match and wildcard suffix (`*`). Banner shows only when the user is on a matching page. **Accepted values:** `/dashboard`, `/settings/*`, `/pricing` ```json { "targeting_rules": { "page_paths": ["/dashboard", "/settings/*"] } } ``` ### user\_ids **Type:** `string[]` Target specific users by ID. Useful for beta features or individual support banners. **Accepted values:** Any user ID strings ```json { "targeting_rules": { "user_ids": ["user-123", "user-456"] } } ``` ### min\_app\_version **Type:** `string` Target users on a minimum app version. Uses semantic version comparison (e.g., 2.1.0 > 1.9.9). **Accepted values:** Semver string, e.g., `2.1.0` ```json { "targeting_rules": { "min_app_version": "3.0.0" } } ``` ### is\_authenticated **Type:** `boolean` Target authenticated or unauthenticated users only. **Accepted values:** `true` or `false` ```json { "targeting_rules": { "is_authenticated": true } } ``` ## Full Example A banner targeting authenticated web users in the US and GB, on the dashboard page, running app version 2.0.0 or later: ```json { "title": "New analytics dashboard available", "type": "informational", "targeting_rules": { "platforms": ["web"], "countries": ["US", "GB"], "page_paths": ["/dashboard", "/dashboard/*"], "is_authenticated": true, "min_app_version": "2.0.0" } } ``` ## Evaluate Context When calling the evaluate endpoint, pass the user's context for rule matching: ```json { "tenant_id": "default", "user_id": "user-123", "context": { "platform": "web", "country": "US", "segment": "pro", "page_path": "/dashboard", "app_version": "2.5.0", "is_authenticated": true } } ```