# Databuddy Documentation (Full)

> Privacy-first web analytics. 65x faster than Google Analytics, GDPR compliant, no cookies required.
> This file contains the complete documentation corpus for long-context agents.

## Developer Resources
- [Databuddy Developer Resources](https://www.databuddy.cc/developers): Canonical index of Databuddy API docs, OpenAPI, MCP, SDK, auth, and webhook resources.
- [Databuddy Developer Docs](https://www.databuddy.cc/docs): SDK setup, REST API guides, feature flags, web vitals, privacy, and integrations.
- [Databuddy API Docs](https://www.databuddy.cc/docs/api): Authentication, rate limits, analytics queries, events, and links.
- [Databuddy OpenAPI Spec](https://www.databuddy.cc/openapi.json): Machine-readable OpenAPI 3.1 schema for Databuddy's REST API.
- [Databuddy API Catalog](https://www.databuddy.cc/.well-known/api-catalog): RFC 9727 linkset catalog for Databuddy API and machine-readable specs.
- [Databuddy API Reference](https://api.databuddy.cc): Interactive API reference generated from the OpenAPI schema.
- [Databuddy API Authentication](https://www.databuddy.cc/auth.md): API key headers, bearer tokens, scopes, and access levels.
- [Databuddy Agent Discovery](https://www.databuddy.cc/.well-known/agent.json): Machine-readable agent discovery file with capabilities, endpoints, auth, and when-to-use guidance.
- [Databuddy A2A Agent Card](https://www.databuddy.cc/.well-known/agent-card.json): Agent-to-Agent card describing Databuddy analytics capabilities and skills.
- [Databuddy MCP Server](https://www.databuddy.cc/docs/api/mcp): Model Context Protocol setup for Claude, Cursor, Windsurf, and other agents.
- [Databuddy MCP Manifest](https://www.databuddy.cc/.well-known/mcp.json): Machine-readable MCP discovery manifest pointing to the Streamable HTTP server.
- [Databuddy MCP Server Card](https://www.databuddy.cc/.well-known/mcp/server-card.json): MCP server card with tools, UI resources, authentication, and transport details.
- [Databuddy SDK Docs](https://www.databuddy.cc/docs/sdk): React, Vue, Node.js, Nuxt, vanilla JS, and tracker SDK guides.
- [Databuddy Webhooks Docs](https://www.databuddy.cc/docs/api/events): Server-side event tracking and webhook-style ingestion examples.
- [Databuddy llms.txt](https://www.databuddy.cc/llms.txt): Compact LLM-readable index of Databuddy documentation and developer resources.
- [Databuddy skills.sh Skill](https://www.databuddy.cc/skill.md): Official SKILL.md source for agents that integrate Databuddy SDK, API, and MCP workflows.

---

## Core

# API Keys

> Create, use, and manage API keys with scopes and resource access

import { CodeBlock } from "@/components/docs";

## TL;DR

- Generate keys in [Organization Settings → API Keys](https://app.databuddy.cc/organizations/settings#api-keys)
- Use either `Authorization: Bearer <key>` or `x-api-key: <key>`
- Scopes: `read:data`, `track:events`, `read:links`, `write:links`, `manage:websites`, `manage:flags`, `manage:config`
- Access can be global or scoped to a resource like a specific `website`
- For private websites, include `website_id` and ensure the key has `read:data`
- Rotate and revoke keys in [Organization Settings → API Keys](https://app.databuddy.cc/organizations/settings#api-keys); only the prefix/start should be shared/logged

### What is an API key?

An API key authenticates server-to-server calls to Databuddy. It supports fine-grained scopes and optional resource scoping to enforce least-privilege access.

### Create a key

1. Open [Organization Settings → API Keys](https://app.databuddy.cc/organizations/settings#api-keys)
2. Click “Create API Key”
3. Choose a name, optional organization, scopes, and resource access
4. Copy the secret immediately (it’s only shown once)

We only display the `prefix` and first characters (`start`) later for identification. Never share the full secret.

### Use your key

You can authenticate with either header:

<CodeBlock 
  language="bash"
  code={`curl -H "Authorization: Bearer YOUR_API_KEY" \\
  "https://api.databuddy.cc/v1/websites/{website_id}/analytics"`}
/>

<CodeBlock 
  language="bash"
  code={`curl -H "x-api-key: YOUR_API_KEY" \\
  "https://api.databuddy.cc/v1/websites/{website_id}/analytics"`}
/>

Notes:

- For private websites, include the `website_id` query or path parameter and ensure the key has `read:data` for that site.
- For public websites, `website_id` may be optional depending on the endpoint semantics.
- For link shortener analytics, use `link_id` instead of `website_id` to query analytics for specific shortened links.

### Scopes

- **read:data**: Read analytics data. Covers query endpoints (`POST /v1/query`, `POST /v1/query/compile`, `POST /v1/query/custom`) and listing accessible websites (`GET /v1/query/websites`). Required for any dashboard-style data retrieval.
- **track:events**: Send custom events via the SDK or `/track` endpoint
- **read:links**: Read short links and their analytics
- **write:links**: Create, update, and delete short links
- **manage:websites**: Create, update, and delete websites and their settings
- **manage:flags**: Create, update, and delete feature flags and targeting rules
- **manage:config**: Integration config and organization settings

Grant only what you need. Prefer resource-scoped access where possible.

#### What each scope unlocks

| Scope | Endpoints / actions |
|-------|---------------------|
| `read:data` | `POST /v1/query`, `POST /v1/query/compile`, `POST /v1/query/custom`, `GET /v1/query/websites`, `websites.list` RPC |
| `track:events` | `POST /track` (custom events, pageviews, sessions) |
| `read:links` | `GET /v1/links/*` read endpoints, link analytics queries |
| `write:links` | `POST /v1/links`, `PATCH /v1/links/:id`, `DELETE /v1/links/:id` |
| `manage:websites` | Create / update / delete websites, rotate tracking config |
| `manage:flags` | Feature flag CRUD, targeting rule edits, flag evaluation admin |
| `manage:config` | Integration config and organization-level settings |

Listing websites is intentionally gated by `read:data` (not a separate `read:websites` scope) because the list is only useful for picking a site to query — any key that can list sites can already read their analytics.

### Resource access

Access can be scoped to:

- **global**: Applies to all websites in the organization
- **website**: Applies to a specific website only

Example: a key with `read:data` scoped to a single website can read analytics only for that website, not others in the organization.

### Errors

Authentication/authorization failures return structured errors:

<CodeBlock 
  language="json"
  code={`{
    "success": false,
    "error": "Authentication required",
    "code": "AUTH_REQUIRED"
  }`}
/>

<CodeBlock 
  language="json"
  code={`{
    "success": false,
    "error": "Insufficient permissions",
    "code": "FORBIDDEN"
  }`}
/>

### Rotation and revocation

- **Rotate** to generate a new secret for the same key (update your servers immediately)
- **Revoke** to immediately disable a key (cannot be undone; create a new one if needed)

Actions are available in [Organization Settings → API Keys](https://app.databuddy.cc/organizations/settings#api-keys) under each key’s detail view.

### Rate limits

All API endpoints are rate-limited. See the Rate Limits section in the API Reference. Responses include standard `X-RateLimit-*` headers where applicable.

### Audit Logging

All API key usage is logged for security and compliance:

- **Authentication events**: Successful and failed key usage
- **Scope resolution**: What permissions were checked and granted
- **Resource access**: Which data resources were accessed
- **Administrative actions**: Key creation, rotation, and revocation

Logs include IP addresses, user agents, and timestamps for security monitoring.

### Best practices

- Treat API keys like passwords; don't commit them to source control
- Use environment variables or secret managers
- Share only the prefix and `start` snippet for identification
- Use least-privilege scopes and resource scoping
- Rotate keys periodically and revoke unused keys
- Monitor audit logs for suspicious activity
- Use resource-scoped keys for better security

---

# Dashboard Guide

> Complete guide to using your Databuddy analytics dashboard

import { Tab, Tabs } from "@/components/docs";
import { Callout } from "@/components/docs";
import { Card, Cards } from "@/components/docs";
import { Accordion, Accordions } from "@/components/docs";
import { CodeBlock } from "@/components/docs";

Your Databuddy dashboard provides comprehensive analytics insights with real-time data, detailed reports, and powerful filtering capabilities.

<Callout type="info">
  **Access**: Visit [app.databuddy.cc](https://app.databuddy.cc) to view your
  analytics dashboard
</Callout>

## Overview Tab

The overview tab gives you a high-level view of your website's performance with key metrics and trends.

### Key Metrics

<Cards>
  <Card title="Core Analytics">
    - **Pageviews** - Total page views across your site
    - **Visitors** - Unique visitors (deduplicated by anonymous ID)
    - **Sessions** - User sessions with 30-minute timeout
    - **Bounce Rate** - Percentage of single-page sessions
    - **Session Duration** - Average time spent per session
    - **Pages per Session** - Average pages viewed per session
  </Card>

  <Card title="Performance Metrics">
    - **Load Time** - Average page load time
    - **TTFB** - Time to First Byte
    - **Core Web Vitals** - LCP, FCP, CLS when available
    - **Performance Score** - Overall site performance rating
  </Card>
</Cards>

### Time Range Selection

<Tabs items={['Quick Ranges', 'Custom Range']}>
<Tab value="Quick Ranges">

Choose from predefined time periods:

- **Last 24 hours** - Real-time insights
- **Last 7 days** - Weekly trends
- **Last 30 days** - Monthly overview
- **Last 90 days** - Quarterly analysis

</Tab>
<Tab value="Custom Range">

Select any date range for specific analysis:

- Pick start and end dates
- Compare different periods
- Analyze specific campaigns or events
- Export data for custom ranges

</Tab>
</Tabs>

<Callout type="info">
  **Comparison Mode**: Use the comparison toggle to compare your selected period
  with the previous period of the same length.
</Callout>

### Data Granularity

View your metrics at different granularities based on your time range:

<Accordions>
<Accordion title="Hourly Granularity">

**Best for**: Last 24-48 hours

- Detailed hour-by-hour breakdown
- Perfect for monitoring recent changes
- Real-time performance tracking

</Accordion>

<Accordion title="Daily Granularity">

**Best for**: Weekly/monthly views

- Day-by-day analysis
- Identify weekly patterns
- Track daily performance trends

</Accordion>

<Accordion title="Weekly Granularity">

**Best for**: Quarterly views

- Week-over-week comparisons
- Seasonal trend analysis
- Long-term pattern recognition

</Accordion>

<Accordion title="Monthly Granularity">

**Best for**: Yearly views

- Month-by-month growth tracking
- Annual performance reviews
- Strategic planning insights

</Accordion>
</Accordions>

### Charts and Visualizations

**Interactive Metrics Chart:**

- Line charts showing trends over time
- Toggle different metrics on/off
- Interactive hover for detailed values
- Responsive design for mobile viewing

**Performance Indicators:**

- Green indicators for positive changes
- Red indicators for areas needing attention
- Percentage changes compared to previous period
- Automatic calculation based on selected time range

## Top Pages

Discover your most popular content and page performance:

### Page Analytics

<Cards>
  <Card title="Page Metrics">
    - **Page path and title** - URL and page title
    - **Total pageviews** - Number of views
    - **Unique visitors** - Distinct users
    - **Average time on page** - Engagement duration
    - **Bounce rate** - Single-page session rate
  </Card>

  <Card title="Interactive Features">
    - **Click to visit** - Click page paths to open them
    - **Sort by metrics** - Order by any column
    - **Pagination** - Handle large datasets efficiently
    - **Search & filter** - Find specific pages quickly
  </Card>
</Cards>

### Page Insights

<Callout type="info">
  **Performance Tip**: Pages with high bounce rates but long time-on-page often
  indicate quality content that doesn't require further navigation.
</Callout>

Use this data to:

- Identify your most valuable content
- Find pages that need optimization
- Track content performance over time
- Optimize user journeys

## Traffic Sources

Understand where your visitors come from and optimize your marketing efforts:

### Referrer Categories

<Accordions>
<Accordion title="Direct Traffic">

**Source**: Users typing your URL directly or from bookmarks

**Characteristics:**

- No referrer information
- Often returning visitors
- High-intent traffic
- Brand awareness indicator

**Optimization**: Focus on brand building and memorable URLs

</Accordion>

<Accordion title="Search Engines">

**Sources**: Google, Bing, DuckDuckGo, and other search engines

**Data Shown:**

- Search engine names
- Organic traffic volume
- Search performance trends

**Optimization**: SEO improvements and content strategy

</Accordion>

<Accordion title="Social Media">

**Sources**: Facebook, Twitter, LinkedIn, Instagram, etc.

**Insights:**

- Social platform performance
- Content sharing effectiveness
- Social engagement tracking

**Optimization**: Social media strategy and content tailoring

</Accordion>

<Accordion title="External Websites">

**Sources**: Other websites linking to you

**Benefits:**

- Referral traffic analysis
- Partnership effectiveness
- Backlink impact measurement

**Optimization**: Relationship building and link acquisition

</Accordion>
</Accordions>

### Traffic Analysis

For each source, you'll see:

- **Visitor counts** - Number of users from each source
- **Percentage breakdown** - Share of total traffic
- **Source classification** - Automatic categorization with icons
- **Trend analysis** - Growth or decline over time

## Geographic Analytics

Discover your global audience and optimize for different regions:

### Geographic Breakdown

<Tabs items={['Country View', 'Regional View']}>
<Tab value="Country View">

**Country-Level Analytics:**

- Country names with flag icons
- Visitor counts and percentages
- Interactive sortable data table
- Geographic distribution insights

**Use Cases:**

- Identify primary markets
- Plan localization efforts
- Understand global reach
- Optimize content for regions

</Tab>
<Tab value="Regional View">

**Detailed Location Data:**

- State/province information
- City-level analytics (when available)
- Time zone considerations
- Geographic clustering insights

**Benefits:**

- Local market analysis
- Regional campaign targeting
- Time zone optimization
- Location-based content strategy

</Tab>
</Tabs>

<Callout type="info">
  **Privacy Note**: Location data is derived from IP addresses and anonymized.
  No precise location tracking is performed.
</Callout>

<Callout type="tip" title="Powering the Map View">
  The geographic data displayed on maps and in regional breakdowns is typically
  derived from the user's IP address at the beginning of a session. This
  functionality is an integral part of session tracking. Ensure `trackSessions=
  {true}` (which is enabled by default) in your [SDK
  configuration](/docs/sdk/configuration) to collect this data.
</Callout>

## Technology Stack Analysis

Understand your audience's technical environment:

### Device Analytics

<Cards>
  <Card title="Device Types">
    **Categories:**
    - **Desktop** - Traditional computers
    - **Mobile** - Smartphones and mobile devices
    - **Tablet** - Tablets and large mobile devices
    **Metrics:**
    - Visitor distribution
    - Device-specific performance
    - User behavior patterns
  </Card>

  <Card title="Browser Information">
    **Data Included:**
    - Browser names and versions
    - Market share among your visitors
    - Browser-specific performance data
    - Compatibility insights
  </Card>
</Cards>

### Operating System Insights

**Platform Distribution:**

- **macOS** - Apple computers
- **Windows** - Microsoft Windows
- **Linux** - Linux distributions
- **iOS** - Apple mobile devices
- **Android** - Android devices

**Strategic Benefits:**

- Optimize for popular platforms
- Debug platform-specific issues
- Understand technical demographics
- Plan feature compatibility

<Callout type="warn">
  **Cross-Platform Considerations**: Ensure your website works well across all
  popular platforms used by your audience.
</Callout>

## Custom Events Analytics

Track and analyze your business-specific events with detailed property breakdowns:

<Callout type="info" title="Populating This Section">
  The analytics, breakdowns, and examples shown here are populated by the custom
  events you actively send from your application using the
  `db.track('your_event_name', {your_properties})` method. The more relevant and
  well-structured custom events you track, the more insightful this section will
  become. For detailed guidance on how to implement custom event tracking,
  please refer to the [Tracker Helpers](/docs/sdk/tracker) guide.
</Callout>

### Enhanced Event Interface

<Tabs items={['Hierarchical View', 'Property Breakdowns', 'Interactive Features']}>
<Tab value="Hierarchical View">

**Tree-Structure Analytics:**

- **Event Level** - Overall event metrics and performance
- **Property Categories** - Organized property groupings with counts
- **Property Values** - Individual values with percentage distributions
- **Visual Hierarchy** - Color-coded dots and professional typography

**Event Display Features:**

- **Event badges** - Blue dots for main events
- **Count displays** - Total events and unique users
- **Share percentages** - Relative event frequency
- **Occurrence tracking** - First and last event timestamps

</Tab>
<Tab value="Property Breakdowns">

**Automatic Property Analysis:**

- **Property extraction** - All custom properties automatically categorized
- **Value distribution** - Percentage breakdown of property values
- **Count aggregation** - Total occurrences per property value
- **Category sorting** - Properties sorted by frequency

**Property Category Features:**

- **Category headers** - Property names with aggregate counts
- **Value lists** - Individual property values with metrics
- **Usage indicators** - Visual badges showing value counts
- **Percentage pills** - Clean percentage displays

**Example Property Structure:**

```
Event: purchase_completed (156 events, 89 users)
├── product_category (156 values)
│   ├── software: 98 (63%)
│   ├── hardware: 45 (29%)
│   └── services: 13 (8%)
├── payment_method (156 values)
│   ├── credit_card: 124 (79%)
│   ├── paypal: 22 (14%)
│   └── bank_transfer: 10 (6%)
```

</Tab>
<Tab value="Interactive Features">

**User Experience Enhancements:**

- **Individual toggles** - Expand/collapse each property category independently
- **Scrollable content** - Handle large property lists with smooth scrolling
- **Visual feedback** - Hover effects and transition animations
- **Mobile responsive** - Optimized for all device sizes

**Navigation Features:**

- **Expand arrows** - Rotating chevrons indicate expansion state
- **Value count badges** - Show number of unique values per property
- **Scroll indicators** - Visual cues when content is scrollable
- **Professional styling** - Subtle gradients and clean borders

**Performance Optimizations:**

- **Lazy rendering** - Only render visible content
- **Efficient scrolling** - Max height limits with overflow handling
- **State management** - Persistent expand/collapse states
- **Touch optimization** - Mobile-friendly interaction areas

</Tab>
</Tabs>

### Event Examples

<Cards>
  <Card title="E-commerce Events">
    <CodeBlock
      language="json"
      code={`{
    "event": "purchase_completed",
    "properties": {
      "product_id": "prod_123",
      "revenue": 29.99,
      "currency": "USD",
      "category": "software"
    }
  }`}
    />
  </Card>

  <Card title="Engagement Events">
    <CodeBlock
      language="json"
      code={`{
    "event": "feature_used",
    "properties": {
      "feature": "export_data",
      "plan": "pro",
      "usage_count": 5
    }
  }`}
    />
  </Card>
</Cards>

## Advanced Filtering & Segmentation

Slice and dice your data for specific insights:

### Available Filters

<Accordions>
<Accordion title="Time-based Filters">

- **Date range selection** - Custom time periods
- **Time of day filtering** - Specific hours
- **Day of week analysis** - Weekday vs weekend patterns
- **Month/quarter comparisons** - Seasonal trends

</Accordion>

<Accordion title="Audience Filters">

- **Device type filtering** - Mobile, desktop, tablet
- **Geographic filtering** - Country, region, city
- **Traffic source filtering** - Direct, search, social, referral
- **Visitor type** - New vs returning (when available)

</Accordion>

<Accordion title="Content Filters">

- **Page path filtering** - Specific pages or sections
- **Event filtering** - Specific custom events
- **Property filtering** - Event property values
- **Performance filtering** - Fast vs slow pages

</Accordion>
</Accordions>

### Segmentation Strategies

<Cards>
  <Card title="User Behavior Segments">
    - **High-engagement users** - Multiple page visits
    - **Quick browsers** - Short session durations
    - **Deep readers** - Long time on page
    - **Mobile-first users** - Primarily mobile traffic
  </Card>

  <Card title="Technical Segments">
    - **Performance impact** - Fast vs slow connections
    - **Browser compatibility** - Modern vs legacy browsers
    - **Device capabilities** - Screen size and features
    - **Platform preferences** - Operating system usage
  </Card>
</Cards>

## Performance Monitoring

Monitor your website's technical health and user experience:

<Callout type="tip" title="Enabling Performance Data">
  The performance metrics, including Core Web Vitals, are populated when you
  enable `trackPerformance={true}` (enabled by default) and `trackWebVitals=
  {true}` (disabled by default, but recommended) in your [SDK
  configuration](/docs/sdk/configuration). Enabling these options
  allows the SDK to collect detailed timing and performance data from the user's
  browser.
</Callout>

### Core Web Vitals

<Tabs items={['Understanding Metrics', 'Performance Optimization']}>
<Tab value="Understanding Metrics">

**Essential Performance Metrics:**

<Cards>
  <Card title="LCP - Largest Contentful Paint">
    **Good**: ≤ 2.5 seconds Measures loading performance. Time until the largest
    content element is rendered.
  </Card>

<Card title="FID - First Input Delay">
  **Good**: ≤ 100 milliseconds Measures interactivity. Time from first user
  interaction to browser response.
</Card>

<Card title="CLS - Cumulative Layout Shift">
  **Good**: ≤ 0.1 Measures visual stability. Sum of all unexpected layout shift
  scores.
</Card>

  <Card title="FCP - First Contentful Paint">
    **Good**: ≤ 1.8 seconds Time until first content element is rendered on
    screen.
  </Card>
</Cards>

</Tab>
<Tab value="Performance Optimization">

**Optimization Strategies:**

**For LCP (Loading):**

- Optimize images and videos
- Use a fast CDN
- Minimize resource load times
- Implement lazy loading

**For FID (Interactivity):**

- Reduce JavaScript bundle size
- Optimize long-running tasks
- Use web workers for heavy computation
- Improve mobile responsiveness

**For CLS (Visual Stability):**

- Set dimensions for images/videos
- Avoid inserting content above existing content
- Use transform animations over layout changes
- Preload web fonts

</Tab>
</Tabs>

### Performance Analytics

**Detailed Performance Data:**

- **Load time distribution** - Histogram of page load times
- **Performance by page** - Page-specific metrics
- **Performance trends** - Changes over time
- **Mobile vs desktop** - Device-specific performance

<Callout type="info">
  **Performance Tip**: Use the performance data to identify slow pages and
  prioritize optimization efforts where they'll have the biggest impact.
</Callout>

## Error Tracking & Debugging

Monitor and resolve issues affecting your users:

<Callout type="tip" title="Enabling Error Data Collection">
  To populate the error tracking sections of the dashboard, ensure you have
  `trackErrors={true}` enabled in your [SDK
  configuration](/docs/sdk/configuration). This will automatically
  capture unhandled JavaScript errors and promise rejections. For more detailed
  context on specific handled errors, you can manually send error events using
  `db.track('error', {/* custom properties */})` as described in the [Tracker
  Helpers](/docs/sdk/tracker) guide.
</Callout>

### JavaScript Error Analytics

<Accordions>
<Accordion title="Error Details">

**Comprehensive Error Information:**

- **Error messages** - Descriptive error text
- **Error locations** - File, line, and column numbers
- **Stack traces** - Detailed execution path
- **Error frequency** - How often errors occur
- **Affected users** - Number of users experiencing errors

</Accordion>

<Accordion title="Error Categorization">

**Error Types:**

- **JavaScript errors** - Runtime exceptions
- **Network errors** - Failed API calls
- **Browser compatibility** - Feature support issues
- **Resource errors** - Missing images, stylesheets

</Accordion>

<Accordion title="Debugging Tools">

**Resolution Features:**

- **Error grouping** - Similar errors grouped together
- **Error trends** - Error frequency over time
- **Error by location** - Geographic error distribution
- **Error by device** - Device-specific error patterns

</Accordion>
</Accordions>

### Error Resolution Workflow

1. **Identify** - Spot errors in the dashboard
2. **Analyze** - Review stack traces and context
3. **Prioritize** - Focus on high-frequency errors
4. **Fix** - Implement solutions
5. **Verify** - Monitor error reduction

<Callout type="warn">
  **Error Impact**: JavaScript errors can significantly impact user experience.
  Regular monitoring helps maintain site reliability.
</Callout>

## Mobile Dashboard Experience

Access your analytics on any device:

### Mobile Features

**Optimized Mobile Interface:**

- Touch-friendly charts and graphs
- Responsive table layouts
- Mobile-optimized filtering
- Fast loading on mobile networks

**Mobile-Specific Insights:**

- Mobile vs desktop performance comparison
- Mobile user behavior patterns
- Mobile-specific error tracking
- Mobile Core Web Vitals monitoring

## Data Export & Integration

Get your data where you need it:

### Export Options

<Cards>
  <Card title="CSV Export">
    Download raw data for analysis in spreadsheets or other tools.
  </Card>

<Card title="API Access">
  Programmatic access to your analytics data via REST API.
</Card>

<Card title="Scheduled Reports">
  Automated email reports with key metrics and insights.
</Card>

  <Card title="Webhooks">
    Real-time data streaming to your applications.
  </Card>
</Cards>

## Getting the Most from Your Dashboard

### Best Practices

<Callout type="info">
  **Regular Monitoring**: Check your dashboard weekly to spot trends and issues
  early.
</Callout>

**Daily Tasks:**

- Check overall visitor trends
- Monitor error reports
- Review performance metrics
- Track custom events

**Weekly Analysis:**

- Compare week-over-week growth
- Analyze geographic trends
- Review device usage patterns
- Evaluate traffic sources

**Monthly Reviews:**

- Assess monthly growth trends
- Review goal achievement
- Analyze top-performing content
- Plan optimization strategies

## Dashboard Troubleshooting

### Common Issues

<Accordions>
<Accordion title="No Data Showing">

**Possible Causes:**

- SDK not properly installed
- Tracking disabled in development
- Domain not verified
- Ad blockers preventing data collection

**Solutions:**

- Verify SDK installation
- Check browser network tab for requests
- Ensure domain verification is complete
- Test with ad blockers disabled

</Accordion>

<Accordion title="Delayed Data Updates">

**Expected Behavior:**

- Data typically updates within 1-2 minutes
- Some metrics may take up to 5 minutes
- Refresh the page to see latest data

**If data is still delayed:**

- Check your internet connection
- Verify dashboard status page
- Contact support if issues persist

</Accordion>

<Accordion title="Missing Features">

**Feature Availability:**

- Some features require specific tracking to be enabled
- Custom events need manual implementation
- Performance metrics require opt-in tracking

**Enable Missing Features:**

- Review your SDK configuration
- Enable additional tracking options
- Implement custom event tracking

</Accordion>
</Accordions>

## Related Resources

<Cards>
  <Card title="SDK Reference" href="/docs/sdk">
    Complete SDK configuration and tracking options
  </Card>
  <Card title="Custom Events" href="/docs/sdk">
    Implement custom event tracking for business insights
  </Card>
  <Card title="Performance Tracking" href="/docs/sdk">
    Optimize your website's performance metrics
  </Card>
  <Card title="Security & Privacy Settings" href="/docs/security">
    Configure access control, allowed origins, IP restrictions, etc.
  </Card>
</Cards>

<Callout type="info">
  **Need Help?** Join our [Discord community](https://discord.gg/JTk7a38tCZ) or
  [contact support](mailto:support@databuddy.cc) for dashboard assistance.
</Callout>

---

# Getting Started

> Install and configure Databuddy for your application

import { Step, Steps } from "@/components/docs";
import { Tab, Tabs } from "@/components/docs";
import { Callout } from "@/components/docs";
import { Card, Cards } from "@/components/docs";
import { CodeBlock } from "@/components/docs";
import { LeapComponent } from "@/components/docs";

This guide takes you from a new Databuddy account to your first tracked page view.

<LeapComponent/>

<Steps>
<Step>

## Create Your Account

1. Go to [app.databuddy.cc](https://app.databuddy.cc)
2. Sign up for a free account
3. Copy your **Client ID** from the dashboard

</Step>
<Step>

## Install the SDK

Choose the install method for your app:

<Tabs items={['React/Next.js', 'Script Tag']}>
<Tab value="React/Next.js">

Install the official Databuddy SDK:

<CodeBlock
  language="bash"
  code={`# Using bun (recommended)
bun add @databuddy/sdk

# Using npm
npm install @databuddy/sdk

# Using yarn
yarn add @databuddy/sdk`}
/>

</Tab>
<Tab value="Script Tag">

For any website, use the script tag method:

<CodeBlock
  language="html"
  code={`<script
    src="https://cdn.databuddy.cc/databuddy.js"
    data-client-id="your-client-id"
    data-track-performance
    async
  ></script>`}
/>

</Tab>
</Tabs>

</Step>
<Step>

## Add Tracking

<Tabs items={['React/Next.js', 'Script Tag']}>
<Tab value="React/Next.js">

Add the `<Databuddy />` component once in your root layout:

<CodeBlock
  language="tsx"
  filename="app/layout.tsx"
  code={`import { Databuddy } from "@databuddy/sdk/react";

  export default function RootLayout({
    children,
  }: {
    children: React.ReactNode;
  }) {
    return (
      <html lang="en">
        <head />
        <body>
          <Databuddy
            clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
            trackPerformance
            trackWebVitals
            trackErrors
          />
          {children}
        </body>
      </html>
    );
  }`}
/>

**Environment Variables:**

<CodeBlock
  language="bash"
  filename=".env.local"
  code="NEXT_PUBLIC_DATABUDDY_CLIENT_ID=your-client-id"
/>

</Tab>
<Tab value="Script Tag">

Add the script to your HTML before the closing `</body>` tag:

<CodeBlock
  language="html"
  filename="index.html"
  code={`<!DOCTYPE html>
  <html>
    <head>
      <title>My Website</title>
    </head>
    <body>
      <!-- Your content -->

      <!-- Databuddy Analytics -->
      <script
        src="https://cdn.databuddy.cc/databuddy.js"
        data-client-id="your-client-id"
        data-track-performance
        data-track-web-vitals
        data-track-errors
        async
      ></script>
    </body>
  </html>`}
/>

</Tab>
</Tabs>

</Step>
<Step>

## Verify Installation

Check that page tracking works:

1. **Visit your website** in a new browser tab
2. **Open your Databuddy dashboard** at [app.databuddy.cc](https://app.databuddy.cc)
3. **Check Real-time data** - you should see yourself as an active visitor
4. **Navigate between pages** - Databuddy records route changes automatically

<Callout type="success">
  If you see real-time data in your dashboard, congratulations! Databuddy is now
  tracking your website analytics.
</Callout>

</Step>
</Steps>

## Track Your First Custom Event

<Tabs items={['React/Next.js', 'Script Tag']}>
<Tab value="React/Next.js">

<CodeBlock
  language="tsx"
  filename="components/signup-button.tsx"
  code={`"use client";

import { track } from "@databuddy/sdk";

export function SignupButton() {
  return (
    <button
      onClick={() =>
        track("signup_clicked", {
          location: "header",
          plan: "free",
        })
      }
      type="button"
    >
      Sign up
    </button>
  );
}`}
/>

</Tab>
<Tab value="Script Tag">

<CodeBlock
  language="html"
  filename="index.html"
  code={`<script>
    function trackSignupClick() {
      db.track("signup_clicked", {
        location: "header",
        plan: "free",
      });
    }
  </script>

<button onclick="trackSignupClick()">Sign up</button>`}
/>

</Tab>
</Tabs>

<Callout type="info">
  Keep custom event names stable and use properties for details like location,
  plan, status, or source.
</Callout>

## What's Next?

<Cards>
  <Card title="API Playground" href="/api">
    Test endpoints interactively and explore query types with real data
  </Card>
  <Card title="SDK Configuration" href="/docs/sdk/configuration">
    Explore all SDK configuration props and options
  </Card>
  <Card title="Dashboard Guide" href="/docs/dashboard">
    Learn to navigate your analytics dashboard and its features
  </Card>
  <Card title="Tracker Helpers" href="/docs/sdk/tracker">
    Use helper methods like track, flush, and getAnonymousId
  </Card>
  <Card title="Security & Privacy" href="/docs/security">
    Configure privacy settings and GDPR compliance
  </Card>
</Cards>

## Troubleshooting

**Not seeing data in your dashboard?**

1. **Check your Client ID** - Make sure it matches this website
2. **Test manually** - Try `db.track('test', {})` in your browser console
3. **Check Network tab** - Look for requests to `basket.databuddy.cc`
4. **Review environment** - Make sure tracking isn't disabled in production

<Callout type="info">
  Need help? Join our [Discord community](https://discord.gg/JTk7a38tCZ) or
  [contact support](mailto:support@databuddy.cc).
</Callout>

---

# Tracking Recipes

> Copyable recipes for tracking custom events and user interactions

import { CodeBlock } from "@/components/docs";
import { Callout } from "@/components/docs";
import { Card, Cards } from "@/components/docs";

<Callout type="info">
  <strong>TL;DR</strong> — **Tracking recipes** you can copy and customize for custom events in your application. **Track only what you need** and **avoid duplicate events**.
</Callout>

These recipes show common ways to track user interactions and application events. Copy and customize them for your needs.

## Recipes

<Cards>
  <Card title="Toast Tracking" href="/docs/hooks/toast-tracking">
    Track toast notifications automatically to understand user feedback patterns and error rates
  </Card>
  <Card title="Form Tracking" href="/docs/hooks/form-tracking">
    Track form submissions with validation state and error patterns
  </Card>
  <Card title="Modal Tracking" href="/docs/hooks/modal-tracking">
    Track when modals and dialogs are opened and closed
  </Card>
  <Card title="Feature Usage" href="/docs/hooks/feature-usage">
    Track when users interact with specific features
  </Card>
  <Card title="Feedback Tracking" href="/docs/hooks/feedback-tracking">
    Track user feedback with server actions and client hooks
  </Card>
</Cards>

## Best Practices

### 1. Prevent Duplicate Events

Use refs or Sets to track what's already been tracked:

<CodeBlock 
  language="tsx"
  code={`const tracked = useRef(new Set<string>());

if (!tracked.current.has(eventId)) {
  tracked.current.add(eventId);
  track("event_name", { id: eventId });
}`}
/>

### 2. Track Only Essential Data

Avoid tracking sensitive information or excessive data:

<CodeBlock 
  language="tsx"
  code={`// ✅ Good - Only essential properties
track("purchase_completed", {
  order_id: "ORD-123",
  revenue: 99.99,
  currency: "USD",
});

// ❌ Avoid - Too much data, includes PII
track("purchase_completed", {
  order_id: "ORD-123",
  revenue: 99.99,
  currency: "USD",
  email: "user@example.com", // PII
  full_address: "...", // Sensitive
  credit_card_last_4: "1234", // Sensitive
});`}
/>

### 3. Use Consistent Event Names

Follow a naming convention (e.g., `snake_case`):

<CodeBlock 
  language="tsx"
  code={`// ✅ Good - Consistent naming
track("button_clicked");
track("form_submitted");
track("modal_opened");

// ❌ Avoid - Inconsistent naming
track("buttonClick");
track("form-submitted");
track("ModalOpened");`}
/>

### 4. Handle Errors Gracefully

Don't let tracking errors break your app:

<CodeBlock 
  language="tsx"
  code={`const trackSafely = useCallback((eventName: string, properties?: Record<string, unknown>) => {
  try {
    track(eventName, properties);
  } catch (error) {
    console.error("Tracking error:", error);
    // Don't throw - tracking failures shouldn't break the app
  }
}, []);`}
/>

### 5. Conditional Tracking

Only track in production or when explicitly enabled:

<CodeBlock 
  language="tsx"
  code={`export function useToastTracking() {
  const { toasts } = useSonner();
  const tracked = useRef(new Set<string | number>());
  const isEnabled = process.env.NODE_ENV === "production";

  useEffect(() => {
    if (!isEnabled) return;

    for (const toast of toasts) {
      if (!tracked.current.has(toast.id)) {
        tracked.current.add(toast.id);
        track("toast_shown", {
          type: toast.type,
          message: toast.title,
        });
      }
    }
  }, [toasts, isEnabled]);
}`}
/>

## Related Documentation

<Cards>
  <Card title="SDK Reference" href="/docs/sdk">
    Complete SDK API reference and configuration options
  </Card>
  <Card title="React Integration" href="/docs/sdk/react">
    React-specific integration guide and examples
  </Card>
  <Card title="Tracker Helpers" href="/docs/sdk/tracker">
    Helper functions for tracking custom events
  </Card>
</Cards>

---

# What is Databuddy

> Lightweight analytics platform for developers. One script for analytics, error tracking, web vitals, feature flags, and AI insights. Under 30 KB, no cookies, GDPR compliant.

import { Card, Cards } from "@/components/docs";
import { Callout } from "@/components/docs";

Databuddy is a lightweight analytics platform for developers. One script gives you analytics, error tracking, web vitals, feature flags, short links, and an AI agent, under 30 KB, with no cookies.

<Callout type="info">
  <strong>TL;DR</strong> — **65x faster**, **no cookies**, **GDPR by default**,
  anonymous & aggregated only.
</Callout>

## Why Choose Databuddy?

<Cards>
  <Card title="One Script, Six Tools" href="/docs/getting-started">
    Analytics, errors, vitals, flags, links, and AI in a single install under 30 KB.
  </Card>
  <Card title="No Cookies, No Banners" href="/docs/security">
    Cookieless tracking. GDPR, CCPA, and ePrivacy compliant by default.
  </Card>
  <Card title="AI-Powered Insights" href="/docs/dashboard">
    Ask questions in plain English. Get charts and answers, not more dashboards.
  </Card>
  <Card title="Works With Your Stack" href="/docs/Integrations">
    Next.js, React, Vue, WordPress, Shopify. Install in under 5 minutes.
  </Card>
</Cards>

## Analytics Features

**Essential Tracking**

- Page views with automatic SPA routing
- Anonymous user sessions
- Traffic sources and referrers
- Real-time visitor monitoring

**Performance Monitoring**

- Core Web Vitals (LCP, FID, CLS)
- Page load timing metrics
- JavaScript error tracking
- Custom business events

**E-commerce Analytics**

- Purchase tracking and revenue
- Shopping cart behavior
- Product performance metrics
- Conversion funnel analysis

## Quick Start

<Cards>
  <Card title="Create Account" href="https://app.databuddy.cc">
    Sign up free and verify your domain.
  </Card>
  <Card title="Install SDK" href="/docs/getting-started">
    Add a script tag or install the React package.
  </Card>
  <Card title="Try API Playground" href="/api">
    Explore endpoints interactively with real data.
  </Card>
  <Card title="View Analytics" href="/docs/dashboard">
    Watch real-time data in your dashboard.
  </Card>
</Cards>

## Platform Integrations

<Cards>
  <Card title="React" href="/docs/Integrations/react">
    TypeScript support with React hooks and components.
  </Card>
  <Card title="Next.js" href="/docs/Integrations/nextjs">
    App Router and Pages Router with automatic route tracking.
  </Card>
  <Card title="WordPress" href="/docs/Integrations/wordpress">
    Plugin installation and manual setup options.
  </Card>
  <Card title="Shopify" href="/docs/Integrations/shopify">
    E‑commerce tracking with purchase analytics.
  </Card>
</Cards>

## Key Benefits

**No Cookies Required**
Anonymous session tracking only. No cookie banners needed.

**GDPR Compliant by Default**
Anonymous collection and immediate IP anonymization. No consent management.

**Developer‑Friendly**
TypeScript, React components, comprehensive API with interactive playground, and detailed docs.

**Cross‑Platform**
Websites, web apps, mobile apps—any JavaScript environment.

## Pricing

<Callout type="success">
  **Free tier: 10,000 monthly events** — perfect for personal projects and small
  websites.
</Callout>

Looking for plan details? See the full breakdown on the [pricing page](/pricing).

## Get Support

- [Documentation](/docs/getting-started) — guides and API reference
- [API Playground](/api) — test endpoints interactively
- [Email support](mailto:help@databuddy.cc) — direct help from our team
- [GitHub issues](https://github.com/databuddy-analytics/databuddy/issues) — bug reports and features

---

# Security & Privacy

> How Databuddy protects your data and respects user privacy

import { CodeBlock } from "@/components/docs";

Databuddy is built with privacy-first principles and enterprise-grade security. This guide covers what we do to protect your data and how to configure privacy settings for your users.

## Privacy-First Design

### What We Don't Collect

- **No Personal Information** - Names, emails, phone numbers, addresses
- **No Cookies for Tracking** - Only local storage for anonymous IDs
- **No Cross-Site Tracking** - Each site is isolated
- **No Fingerprinting** - Respects browser privacy settings
- **No Raw IP Addresses** - Only used for location (country/region) then discarded

### What We Do Collect

- **Anonymous Usage Data** - Page views, clicks, performance metrics
- **Session Information** - Anonymous session IDs with 30-minute timeouts
- **Technical Data** - Browser type, screen size, performance metrics
- **Location Data** - Country and region only (from IP, then IP discarded)

## Data Protection

### Anonymous by Default

<CodeBlock 
  language="javascript"
  code={`// All users get anonymous IDs - no personal data
  {
    anonymousId: "anon_abc123...",      // Random UUID
    sessionId: "sess_xyz789...",        // Session identifier
    event: "page_view",                 // What happened
    path: "/dashboard",                 // Where it happened
    // No names, emails, or personal data
  }`}
/>

### Local Data Storage

- **Anonymous ID** - Stored in localStorage, never sent to servers
- **Session Data** - Temporary sessionStorage, clears on browser close
- **No Cookies** - We don't use tracking cookies
- **User Control** - Easy to clear all data

## Privacy Compliance

### GDPR Compliance by Design

**Lawful Basis:** Legitimate interest for website analytics

Databuddy is **compliant by default** - no consent banners or cookie notices required because:

- **No Personal Data Collected** - Only anonymous usage statistics
- **No Cookies Used** - Uses localStorage for anonymous IDs only
- **No User Identification** - Cannot identify individual users
- **Automatic Data Anonymization** - All data is anonymous from collection

### No Consent Required

<CodeBlock 
  language="tsx"
  code={`import { Databuddy } from "@databuddy/sdk/react";

  function App() {
    return (
      <>
        {/* No consent needed - privacy-first by design */}
        <Databuddy
          clientId="your-client-id"
          trackPerformance
        />

        {/* No cookie banner needed! */}
      </>
    );
  }`}
/>

**Why No Consent Needed:**

- Anonymous data only
- No cross-site tracking
- No personal information
- No behavioral profiling
- Legitimate interest applies

## Privacy Controls

### Minimal Tracking Setup

<CodeBlock 
  language="tsx"
  filename="Minimal Tracking Setup"
  code={`// Essential analytics only
  <Databuddy
    clientId="your-client-id"
    // Basic page tracking (automatic)
    // Disable everything else
    trackPerformance={false}
    trackWebVitals={false}
    trackOutgoingLinks={false}
    trackErrors={false}
  />`}
/>

### Development vs Production

<CodeBlock 
  language="tsx"
  filename="Environment Configuration"
  code={`const isProd = process.env.NODE_ENV === "production";

  <Databuddy
    clientId="your-client-id"
    disabled={!isProd} // No tracking in development
    // Production-only features
    trackPerformance={isProd}
    trackWebVitals={isProd}
  />;`}
/>

## 🔐 Security Features

### Domain Protection

- **Domain Verification Required** - Only verified domains can send data
- **Origin Validation** - Requests validated against registered domains
- **HTTPS Required** - All communications encrypted in transit

### Access Control Settings

Configure allowed origins and IP addresses to control who can send analytics data to your website.

#### Allowed Origins

By default, Databuddy only accepts requests from your website's registered domain. To track analytics from third-party services (like [Cal.com](/docs/Integrations/cal), embedded widgets, or other integrations), you need to add those domains to your allowed origins.

**Access**: Open [Websites](https://app.databuddy.cc/websites), select your site, then go to **Settings → Security**.

**Default Behavior:**

- Only requests from your registered domain are accepted
- Requests from other origins are blocked with a 403 error

**Supported Formats:**

- **Exact domains**: `cal.com`, `example.com`
- **Wildcard subdomains**: `*.cal.com` (matches `app.cal.com`, `api.cal.com`, etc.)
- **Localhost**: `localhost` (for development)
- **Allow all**: `*` (allows any origin - use with caution)

**How It Works:**

1. By default, only your registered domain can send analytics data
2. Add additional origins to allow third-party integrations
3. Requests from unauthorized origins are blocked with a 403 error

**Example Configuration:**

```
Allowed Origins:
- cal.com
- *.cal.com
- localhost
```

#### Allowed IP Addresses

Restrict analytics requests to specific IP addresses or IP ranges using CIDR notation.

**Supported Formats:**

- **Single IPv4**: `192.168.1.1`
- **Single IPv6**: `2001:0db8:85a3:0000:0000:8a2e:0370:7334`
- **CIDR ranges**: `192.168.1.0/24`, `10.0.0.0/8`

**How It Works:**

1. When `allowedIps` is configured, only requests from those IPs/ranges are accepted
2. If not configured, all IPs are allowed (standard behavior)
3. IP addresses are extracted from request headers (`cf-connecting-ip`, `x-forwarded-for`, `x-real-ip`)
4. Requests from unauthorized IPs are blocked with a 403 error

**Example Configuration:**

```
Allowed IPs:
- 192.168.1.1
- 10.0.0.0/8
- 172.16.0.0/12
```

**Use Cases:**

- **Internal Tools**: Restrict analytics to your office IP range
- **API Integrations**: Allow only specific server IPs
- **Development**: Limit to your development environment IPs
- **Security**: Block suspicious IP ranges

<Callout type="warning">
  **Important**: If you configure allowed IPs, make sure to include your production servers and any legitimate sources. Blocking legitimate traffic will prevent analytics collection.
</Callout>

<Callout type="info">
  **Third-Party Integrations**: If you use services like Cal.com, embedded widgets, or other integrations that need to send analytics from their domains, add those domains to your allowed origins.
</Callout>

### Data Validation

- **Input Sanitization** - All user data cleaned and validated
- **Size Limits** - Prevents large payloads and spam
- **Rate Limiting** - Protects against abuse

### Infrastructure Security

- **Enterprise-Grade Hosting** - SOC 2 compliant infrastructure
- **DDoS Protection** - Automatic attack mitigation
- **Regular Security Audits** - Professional penetration testing
- **Encrypted Storage** - All data encrypted at rest

## 🛠️ User Privacy Controls (Optional)

### Anonymous Data Only

Since Databuddy only collects anonymous data, users don't need to request data deletion - there's no personal data to delete! However, you can still provide opt-out controls if desired.

### Optional Opt-Out Implementation

<CodeBlock 
  language="tsx"
  filename="Optional Opt-Out Component"
  code={`function PrivacyControls() {
    const [trackingEnabled, setTrackingEnabled] = useState(true);

    const handleOptOut = () => {
      // Clear local anonymous ID
      localStorage.removeItem("databuddy_anon_id");

      // Disable tracking
      setTrackingEnabled(false);

      // Store preference
      localStorage.setItem("databuddy_opt_out", "true");
    };

    return (
      <div>
        <p>
          Analytics helps us improve our website. No personal data is collected.
        </p>

        <label>
          <input
            type="checkbox"
            checked={trackingEnabled}
            onChange={(e) => setTrackingEnabled(e.target.checked)}
          />
          Enable anonymous analytics
        </label>

        <button onClick={handleOptOut}>Disable analytics</button>
      </div>
    );
  }`}
/>

### Why Data Deletion Isn't Needed

- **No Personal Data** - Nothing to identify individual users
- **Anonymous by Design** - All data is aggregated and anonymous
- **No User Profiles** - Cannot build profiles of individual users
- **Automatic Expiry** - Data expires automatically over time

## 🌐 Global Privacy Settings

### Respect Browser Preferences

<CodeBlock 
  language="javascript"
  filename="Respect Browser Privacy Settings"
  code={`// Check Do Not Track setting
  const respectDNT = navigator.doNotTrack === "1";

  <Databuddy
    clientId="your-client-id"
    disabled={respectDNT} // Respect browser privacy setting
  />;`}
/>

## 📋 Privacy Best Practices

### 1. Be Transparent

<CodeBlock 
  language="tsx"
  filename="Privacy Notice Component"
  code={`// Clear privacy notice
  function PrivacyNotice() {
    return (
      <div className="privacy-notice">
        <h3>We respect your privacy</h3>
        <p>
          We collect anonymous usage data to improve our website. No personal
          information is collected. You can opt out anytime.
        </p>
        <a href="/privacy-policy">Read our privacy policy</a>
      </div>
    );
  }`}
/>

### 2. Provide Controls (Optional)

<CodeBlock 
  language="tsx"
  filename="Privacy Dashboard Component"
  code={`// Optional privacy dashboard (not required since data is anonymous)
  function PrivacyDashboard() {
    return (
      <div>
        <h2>Analytics Preferences</h2>

        <div>
          <h3>Anonymous Data Collection</h3>
          <p>Help us improve our website with anonymous usage statistics.</p>
          <Toggle label="Page views" />
          <Toggle label="Performance metrics" />
          <Toggle label="Error tracking" />
        </div>

        <div>
          <h3>Your Privacy</h3>
          <p>✅ No personal data is collected</p>
          <p>✅ No cookies are used for tracking</p>
          <p>✅ Cannot identify individual users</p>
          <button onClick={clearLocalData}>Clear local preferences</button>
        </div>
      </div>
    );
  }`}
/>

### 3. Honor Preferences

<CodeBlock 
  language="tsx"
  filename="Respect User Preferences"
  code={`// Respect user choices
  const privacySettings = getUserPrivacySettings();

  <Databuddy
    clientId="your-client-id"
    // Screen views are tracked automatically
    trackPerformance={privacySettings.allowPerformance}
    trackErrors={privacySettings.allowErrors}
  />`}
/>

## ⚖️ Legal Compliance

### Recommended Disclosures

**Privacy Policy Should Include:**

- Anonymous analytics are collected via Databuddy
- No personal information or cookies are used
- Data is used only for website improvement
- Optional: How users can opt-out

**Cookie Notice:**

- **Not required for Databuddy** (no cookies used)
- Only needed if you use other tracking tools

### Sample Privacy Policy Text

```
Analytics: We use Databuddy to collect anonymous website usage statistics
to help us improve our site. No personal information, cookies, or tracking
is used. All data is completely anonymous and cannot identify individual
visitors. Data is processed securely by Databuddy and used only for
understanding website performance and usage patterns.
```

### Minimal Privacy Notice

```
We collect anonymous usage statistics to improve our website.
No personal data or cookies are used.
```

## 🛡️ Implementation Security

### Content Security Policy

<CodeBlock 
  language="html"
  filename="Content Security Policy"
  code={`<!-- Add Databuddy to your CSP -->
<meta
  http-equiv="Content-Security-Policy"
  content="script-src 'self' https://app.databuddy.cc; 
    connect-src 'self' https://basket.databuddy.cc;"
/>`}
/>

### Secure Configuration

<CodeBlock 
  language="tsx"
  filename="Secure Environment Configuration"
  code={`// Environment-specific settings
  const config = {
    development: {
      clientId: process.env.NEXT_PUBLIC_DATABUDDY_DEV_ID,
      disabled: true, // No tracking in development
    },
    production: {
      clientId: process.env.NEXT_PUBLIC_DATABUDDY_PROD_ID,
      disabled: false,
    },
  }[process.env.NODE_ENV];

  <Databuddy {...config} />;`}
/>

## 🆘 Support & Questions

### Privacy Questions

If you have questions about privacy or data handling:

- 📧 [privacy@databuddy.cc](mailto:privacy@databuddy.cc)
- 📚 [Privacy Policy](/privacy)
- 📊 [Data Policy](/data-policy)
- 🛡️ [Security Overview](/docs/security)

### Data Requests

**No data requests needed** - Databuddy doesn't collect personal data that can identify users. If you have questions about our data handling:

- 📧 [privacy@databuddy.cc](mailto:privacy@databuddy.cc)

---

## What's Next?

- **[SDK Reference](/docs/sdk)** - Configure tracking options
- **[Getting Started](/docs/getting-started)** - Set up Databuddy on your site
- **[Dashboard Guide](/docs/dashboard)** - Analyze your anonymous data

---

## SDK

# Configuration Reference

> Complete configuration options for all Databuddy SDK platforms

import { Callout, CodeBlock, Card, Cards } from "@/components/docs";

This page documents all configuration options across the Databuddy SDK platforms: React, Vue, vanilla JavaScript, and Node.js.

## React / Next.js

<CodeBlock language="tsx">
  {`import { Databuddy } from "@databuddy/sdk/react";

<Databuddy
  // Required
  clientId="your-client-id"
  
  // API endpoints
  apiUrl="https://basket.databuddy.cc"
  scriptUrl="https://cdn.databuddy.cc/databuddy.js"
  
  // Core tracking
  trackPerformance        // default: true
  trackWebVitals          // default: false
  trackErrors             // default: false
  trackOutgoingLinks      // default: false
  trackInteractions       // default: false
  trackAttributes         // default: false
  trackHashChanges        // default: false
  
  // Batching
  enableBatching          // default: true
  batchSize={10}          // 1-50
  batchTimeout={5000}     // ms
  
  // Retries
  enableRetries           // default: true
  maxRetries={3}
  initialRetryDelay={500} // ms
  
  // Sampling
  samplingRate={1.0}      // 0.0-1.0
  
  // Privacy
  skipPatterns={["/admin/**", "/private/*"]}
  maskPatterns={["/users/*", "/orders/**"]}
  
  // Control
  disabled={false}
  debug={false}
  waitForProfile={false}
/>`}
</CodeBlock>

### React Props Reference

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `clientId` | `string` | Auto-detect | Client ID (auto-detects from `NEXT_PUBLIC_DATABUDDY_CLIENT_ID`) |
| `apiUrl` | `string` | `https://basket.databuddy.cc` | API endpoint |
| `scriptUrl` | `string` | `https://cdn.databuddy.cc/databuddy.js` | Script URL |
| `disabled` | `boolean` | `false` | Disable all tracking |
| `debug` | `boolean` | `false` | Enable debug logging |

#### Tracking Features

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `trackPerformance` | `boolean` | `true` | Page load times and navigation |
| `trackWebVitals` | `boolean` | `false` | Core Web Vitals (LCP, FID, CLS, TTFB) |
| `trackErrors` | `boolean` | `false` | JavaScript errors |
| `trackOutgoingLinks` | `boolean` | `false` | External link clicks |
| `trackInteractions` | `boolean` | `false` | Button clicks and form submissions |
| `trackAttributes` | `boolean` | `false` | Elements with `data-track` attributes |
| `trackHashChanges` | `boolean` | `false` | URL hash changes |

#### Batching & Performance

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `enableBatching` | `boolean` | `true` | Group events into batches |
| `batchSize` | `number` | `10` | Events per batch (1-50) |
| `batchTimeout` | `number` | `5000` | Batch timeout in ms |
| `enableRetries` | `boolean` | `true` | Retry failed requests |
| `maxRetries` | `number` | `3` | Maximum retry attempts |
| `initialRetryDelay` | `number` | `500` | Initial retry delay in ms |
| `samplingRate` | `number` | `1.0` | Event sampling rate (0.0-1.0) |

#### Privacy

| Prop | Type | Description |
|------|------|-------------|
| `skipPatterns` | `string[]` | Paths to skip tracking entirely |
| `maskPatterns` | `string[]` | Paths to mask in analytics |

## Vue

<CodeBlock language="html">
  {`<script setup>
import { Databuddy } from "@databuddy/sdk/vue";
</script>

<template>
  <Databuddy
    :client-id="clientId"
    :track-web-vitals="true"
    :track-errors="true"
    :enable-batching="true"
    :batch-size="20"
    :disabled="isDevelopment"
  />
</template>`}
</CodeBlock>

Vue uses the same props as React but with kebab-case naming:
- `clientId` → `:client-id`
- `trackWebVitals` → `:track-web-vitals`
- `enableBatching` → `:enable-batching`

## Vanilla JavaScript

<CodeBlock language="html">
  {`<script
  src="https://cdn.databuddy.cc/databuddy.js"
  data-client-id="your-client-id"
  data-track-web-vitals
  data-track-errors
  data-track-outgoing-links
  data-enable-batching
  data-batch-size="20"
  data-batch-timeout="5000"
  data-sampling-rate="1.0"
  data-skip-patterns='["/admin/**"]'
  data-mask-patterns='["/users/*"]'
  async
></script>`}
</CodeBlock>

### Data Attributes Reference

| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `data-client-id` | `string` | Required | Your client ID |
| `data-track-performance` | flag | `true` | Track page performance |
| `data-track-web-vitals` | flag | `false` | Track Core Web Vitals |
| `data-track-errors` | flag | `false` | Track JavaScript errors |
| `data-track-outgoing-links` | flag | `false` | Track external links |
| `data-track-interactions` | flag | `false` | Track interactions |
| `data-track-attributes` | flag | `false` | Track data-track elements |
| `data-track-hash-changes` | flag | `false` | Track hash changes |
| `data-enable-batching` | flag | `true` | Enable event batching |
| `data-batch-size` | `number` | `10` | Batch size |
| `data-batch-timeout` | `number` | `5000` | Batch timeout (ms) |
| `data-sampling-rate` | `number` | `1.0` | Sampling rate |
| `data-skip-patterns` | `JSON` | `[]` | Paths to skip |
| `data-mask-patterns` | `JSON` | `[]` | Paths to mask |
| `data-disabled` | flag | `false` | Disable tracking |

<Callout type="info">
  **Boolean options**: Vue templates use kebab-case props such as `track-web-vitals`; vanilla CDN installs use `data-*` attributes such as `data-track-web-vitals`.
</Callout>

## Node.js

<CodeBlock language="tsx">
  {`import { Databuddy } from "@databuddy/sdk/node";

const client = new Databuddy({
  // Required
  apiKey: process.env.DATABUDDY_API_KEY!,
  
  // Optional: Scope events to a specific website
  websiteId: process.env.DATABUDDY_WEBSITE_ID,
  
  // Optional: Logical grouping (e.g., 'auth', 'api', 'jobs')
  namespace: "api",
  
  // Optional: Source identifier (e.g., 'backend', 'webhook', 'cli')
  source: "backend",
  
  // API
  apiUrl: "https://basket.databuddy.cc",
  
  // Batching
  enableBatching: true,       // default: true
  batchSize: 10,              // max: 100
  batchTimeout: 2000,         // ms
  maxQueueSize: 1000,
  
  // Deduplication
  enableDeduplication: true,  // default: true
  maxDeduplicationCacheSize: 10_000,
  
  // Middleware
  middleware: [
    (event) => {
      event.properties = { ...event.properties, server: true };
      return event;
    }
  ],
  
  // Logging
  debug: false,
  logger: customLogger
});`}
</CodeBlock>

### Node.js Options Reference

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `apiKey` | `string` | Required | Your API key for authentication |
| `websiteId` | `string` | - | Optional default Client ID to scope events |
| `namespace` | `string` | - | Optional default namespace for logical grouping |
| `source` | `string` | - | Optional default source identifier for events |
| `apiUrl` | `string` | `https://basket.databuddy.cc` | API endpoint |
| `enableBatching` | `boolean` | `true` | Enable event batching |
| `batchSize` | `number` | `10` | Batch size (max 100) |
| `batchTimeout` | `number` | `2000` | Batch timeout in ms |
| `maxQueueSize` | `number` | `1000` | Maximum queue size |
| `enableDeduplication` | `boolean` | `true` | Deduplicate events by ID |
| `maxDeduplicationCacheSize` | `number` | `10000` | Dedup cache size |
| `middleware` | `Middleware[]` | `[]` | Event transform functions |
| `debug` | `boolean` | `false` | Enable debug logging |
| `logger` | `Logger` | - | Custom logger instance |

## Feature Flags (Client)

<CodeBlock language="tsx">
  {`import { FlagsProvider } from "@databuddy/sdk/react";

<FlagsProvider
  clientId="your-client-id"
  apiUrl="https://api.databuddy.cc"
  user={{
    userId: "user-123",
    email: "user@example.com",
    organizationId: "org-456",
    teamId: "team-789",
    properties: {
      plan: "premium",
      role: "admin"
    }
  }}
  isPending={isLoadingSession}
  disabled={false}
  debug={false}
  skipStorage={false}
  autoFetch={true}
  environment="production"
  cacheTtl={60_000}
  staleTime={30_000}
  maxCacheSize={5000}
>
  <App />
</FlagsProvider>`}
</CodeBlock>

### Feature Flags Options Reference

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `clientId` | `string` | Required | Your client ID |
| `apiUrl` | `string` | `https://api.databuddy.cc` | API endpoint |
| `user` | `UserContext` | - | User context for targeting |
| `isPending` | `boolean` | `false` | Defer evaluation |
| `disabled` | `boolean` | `false` | Disable flag evaluation |
| `debug` | `boolean` | `false` | Enable debug logging |
| `skipStorage` | `boolean` | `false` | Skip localStorage caching |
| `autoFetch` | `boolean` | `true` | Auto-fetch on mount |
| `environment` | `string` | - | Environment name |
| `cacheTtl` | `number` | `60000` | Cache TTL (ms) |
| `staleTime` | `number` | `30000` | Revalidate after (ms) |
| `maxCacheSize` | `number` | `5000` | Maximum in-memory cache entries |

### User Context

| Option | Type | Description |
|--------|------|-------------|
| `userId` | `string` | Unique user identifier |
| `email` | `string` | User email |
| `organizationId` | `string` | Organization ID for group rollouts |
| `teamId` | `string` | Team ID for group rollouts |
| `properties` | `object` | Custom targeting properties |

## Feature Flags (Server)

<CodeBlock language="tsx">
  {`import { createServerFlagsManager } from "@databuddy/sdk/node";

const flags = createServerFlagsManager({
  clientId: process.env.DATABUDDY_CLIENT_ID!,
  apiUrl: "https://api.databuddy.cc",
  user: {
    userId: "user-123"
  },
  environment: "production",
  cacheTtl: 60_000,
  staleTime: 30_000,
  maxCacheSize: 5000,
  autoFetch: false,
  debug: false,
  disabled: false
});`}
</CodeBlock>

Server flags use the same options as client flags, but `skipStorage` is always `true`, `autoFetch` defaults to `false`, and the in-memory cache is capped by `maxCacheSize`.

## Environment Variables

### Next.js

<CodeBlock language="bash" filename=".env.local">
  {`NEXT_PUBLIC_DATABUDDY_CLIENT_ID=your-client-id
DATABUDDY_CLIENT_ID=your-client-id
DATABUDDY_API_KEY=your-api-key`}
</CodeBlock>

### Vue / Vite

<CodeBlock language="bash" filename=".env">
  {`VITE_DATABUDDY_CLIENT_ID=your-client-id`}
</CodeBlock>

### Node.js / Server

<CodeBlock language="bash" filename=".env">
  {`DATABUDDY_API_KEY=your-api-key
DATABUDDY_WEBSITE_ID=your-client-id  # Optional default Client ID
DATABUDDY_API_URL=https://basket.databuddy.cc  # Optional`}
</CodeBlock>

## Related

<Cards>
  <Card title="React SDK" href="/docs/sdk/react">
    React/Next.js integration
  </Card>
  <Card title="Node SDK" href="/docs/sdk/node">
    Server-side tracking
  </Card>
  <Card title="Feature Flags" href="/docs/sdk/feature-flags">
    Client-side feature flags
  </Card>
</Cards>

---

# Databuddy DevTools

> Inspect Databuddy events, identity, queues, diagnostics, and feature flags during local development

import { Callout, CodeBlock, Card, Cards } from "@/components/docs";

Databuddy DevTools is a moveable browser overlay for local development, QA, and previews. It observes the Databuddy browser SDK already running on the page.

<Callout type="info">
  **Package**: `@databuddy/devtools` | **Shortcut**: Cmd/Ctrl + Shift + D
</Callout>

## Installation

<CodeBlock language="bash">
  {`bun add -d @databuddy/devtools`}
</CodeBlock>

## React

<CodeBlock language="tsx">
  {`import { DatabuddyDevtools } from "@databuddy/devtools/react";

export function AppShell({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <DatabuddyDevtools enabled={process.env.NODE_ENV !== "production"} />
    </>
  );
}`}
</CodeBlock>

Manual mount:

<CodeBlock language="tsx">
  {`import { mountDevtools } from "@databuddy/devtools/react";

const unmount = mountDevtools();`}
</CodeBlock>

## Vue

<CodeBlock language="vue">
  {`<script setup>
import { DatabuddyDevtools } from "@databuddy/devtools/vue";
</script>

<template>
  <RouterView />
  <DatabuddyDevtools :enabled="import.meta.env.DEV" />
</template>`}
</CodeBlock>

Manual mount:

<CodeBlock language="ts">
  {`import { mountDevtools } from "@databuddy/devtools/vue";

const unmount = mountDevtools();`}
</CodeBlock>

## What It Shows

- Runtime status and diagnostics
- Client, anonymous, and session IDs
- URL attribution params and storage keys
- Global properties
- Event calls: `track`, `screenView`, `flush`, and `clear`
- Queue lengths and flush state
- Feature flag readiness, config, values, variants, reasons, and sources
- Local feature flag overrides

## Feature Flags

DevTools reads the browser flag manager created by `FlagsProvider` or Vue `createFlagsPlugin`. If no flag manager is mounted, the flag panel will show flags as unavailable.

Flag sources:

| Source | Meaning |
| --- | --- |
| `server` | Result came from the flags API |
| `cache` | Cached result |
| `default` | Local fallback |
| `error` | Evaluation failed |
| `override` | Local DevTools override |

Overrides are local only. Clear overrides before validating real flag behavior.

### Flag Debugging Workflow

1. Open DevTools with Cmd/Ctrl + Shift + D.
2. Confirm the flag manager is ready and using the expected `clientId`, API host, and environment.
3. Confirm the user context includes the IDs your rollout needs: `userId`, `organizationId`, or `teamId`.
4. Inspect the flag source. `server` means a real API evaluation, `cache` means a cached value, and `override` means DevTools is forcing the result locally.
5. Test each UI branch with a local override, then clear the override and refresh to validate real behavior.
6. Trigger the outcome event and verify it includes `flag_key`, `variant`, and any useful status field.

## Managing Flags

DevTools can create, update, and delete flag definitions when you paste an API key with `manage:flags` scope at runtime.

It uses:

- `GET /public/v1/flags/definitions`
- `POST /public/v1/flags`
- `PATCH /public/v1/flags/{id}`
- `DELETE /public/v1/flags/{id}`

The default API host is the active flag manager API URL, falling back to `https://api.databuddy.cc`. You can override it in the overlay for local or preview environments.

<Callout type="warning">
  Do not hard-code flag management API keys in source. Paste scoped keys into DevTools only when you need runtime flag management. DevTools keeps the key in session storage and clears any older local-storage copy when you disconnect.
</Callout>

## Related

<Cards>
  <Card title="Feature Flags" href="/docs/sdk/feature-flags">
    React and Vue feature flag hooks
  </Card>
  <Card title="Server Flags" href="/docs/sdk/server-flags">
    Server-side feature flag evaluation
  </Card>
  <Card title="Tracker Helpers" href="/docs/sdk/tracker">
    Event helpers, IDs, and attribution utilities
  </Card>
</Cards>

---

# Feature Flags (Client)

> Control feature rollouts and A/B testing with React and Vue hooks

import { Tab, Tabs } from "@/components/docs";
import { Callout } from "@/components/docs";
import { CodeBlock } from "@/components/docs";
import { Card, Cards } from "@/components/docs";

Feature flags allow you to control feature rollouts and conduct A/B testing without deploying new code. Enable or disable features instantly from your dashboard.

<Callout type="info">
  **Dashboard-controlled Rollouts** | **Client-side Caching** | **Stale-While-Revalidate**
</Callout>

## Quick Start

<Tabs items={['React with User Context', 'Vue', 'Setup']}>

<Tab>
<CodeBlock language="tsx" filename="app.tsx">
  {`'use client';

  import { FlagsProvider, useFlag } from '@databuddy/sdk/react';
  // Replace with your app's auth/session hook.
  import { useCurrentUser } from '@/lib/auth-client';

  function App() {
    const { user, isLoading } = useCurrentUser();

    return (
      <FlagsProvider
        clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
        apiUrl="https://api.databuddy.cc"
        isPending={isLoading}
        user={user ? {
          userId: user.id,
          organizationId: user.organizationId,
          teamId: user.teamId,
          properties: {
            role: user.role,
            workspace_type: user.workspaceType,
            region: user.region,
          }
        } : undefined}
      >
        <MyComponent />
      </FlagsProvider>
    );
  }

  function MyComponent() {
    const { on: isDarkMode, loading } = useFlag('dark-mode');
    const { on: showNewDashboard } = useFlag('new-dashboard');

    if (loading) return <Skeleton />;

    return (
      <div className={isDarkMode ? 'dark' : ''}>
        {showNewDashboard ? <NewDashboard /> : <OldDashboard />}
      </div>
    );
  }`}
</CodeBlock>
</Tab>

<Tab>
<CodeBlock language="ts" filename="main.ts">
  {`import { createApp } from 'vue';
  import { createFlagsPlugin } from '@databuddy/sdk/vue';
  import App from './App.vue';

  // currentUser should come from your app's auth/session store.
  createApp(App)
    .use(createFlagsPlugin({
      clientId: import.meta.env.VITE_DATABUDDY_CLIENT_ID,
      apiUrl: 'https://api.databuddy.cc',
      user: {
        userId: currentUser.id,
        organizationId: currentUser.organizationId,
        teamId: currentUser.teamId,
        properties: {
          role: currentUser.role,
          workspace_type: currentUser.workspaceType,
        },
      },
    }))
    .mount('#app');`}
</CodeBlock>

<CodeBlock language="vue" filename="FeatureGate.vue">
  {`<script setup lang="ts">
  import { useFlag } from '@databuddy/sdk/vue';

  const newNav = useFlag('new-nav');
  </script>

  <template>
    <Skeleton v-if="newNav.loading.value" />
    <NewNav v-else-if="newNav.on.value" />
    <OldNav v-else />
  </template>`}
</CodeBlock>
</Tab>

<Tab>
<CodeBlock language="bash" filename="terminal">
  {`# Install the SDK
  bun add @databuddy/sdk`}
</CodeBlock>
</Tab>

</Tabs>

## Hooks API

### `useFlag(key)` - Flag State

The primary hook for checking feature flags. Returns `{ on, loading, status, value, variant }`.

<CodeBlock language="tsx">
  {`import { useFlag } from '@databuddy/sdk/react';

  function MyComponent() {
    const { on, loading } = useFlag('new-dashboard');

    if (loading) return <Skeleton />;
    return on ? <NewDashboard /> : <OldDashboard />;
  }`}
</CodeBlock>

You can also use the `status` field for more granular control:

<CodeBlock language="tsx">
  {`import { useFlag } from '@databuddy/sdk/react';

  function MyComponent() {
    const flag = useFlag('experiment');

    switch (flag.status) {
      case 'loading':
        return <Skeleton />;
      case 'error':
        return <ErrorFallback />;
      case 'ready':
        return flag.on ? <NewFeature /> : <OldFeature />;
      case 'pending':
        return <LoadingState />;
    }
  }`}
</CodeBlock>

### `useFlags()` - Context API

Access the full flags context for advanced use cases like checking multiple flags, fetching typed values, or updating user context.

<CodeBlock language="tsx">
  {`import { useFlags } from '@databuddy/sdk/react';

  function MyComponent() {
    const {
      isOn,           // Simple boolean check
      getFlag,        // Get full flag state
      getValue,       // Get typed value
      fetchFlag,      // Async fetch single flag
      fetchAllFlags,  // Async fetch all flags
      updateUser,     // Update user context
      refresh,        // Refresh all flags
      isReady         // SDK ready state
    } = useFlags();

    // Simple boolean check
    if (isOn('advanced-feature')) {
      // Show the advanced experience
    }

    // Typed values (string, number, boolean)
    const maxItems = getValue<number>('max-items', 10);
    const theme = getValue<'light' | 'dark'>('theme', 'light');

    // Get full state
    const flag = getFlag('experiment');
    console.log(flag.on, flag.loading, flag.status);

    // A/B test variants
    const variant = getFlag('export-flow').variant;
  }`}
</CodeBlock>

## Flag Types

### Boolean Flags
Simple on/off switches for features.

<CodeBlock language="tsx">
  {`const { on } = useFlag('my-feature');`}
</CodeBlock>

### String/Number Flags
For configuration values and multivariate testing. Use `getValue` from the context:

<CodeBlock language="tsx">
  {`const { getValue } = useFlags();
  const maxUsers = getValue<number>('max-users', 100);
  const theme = getValue<'light' | 'dark'>('theme', 'light');`}
</CodeBlock>

### Rollout Flags
Gradually roll out features to a percentage of users.

<CodeBlock language="tsx">
  {`// Set rollout percentage in dashboard (e.g., 25%)
  const { on } = useFlag('new-ui-rollout');`}
</CodeBlock>

#### Rollout Units

Control how users are grouped for rollouts. In the dashboard, choose the **Rollout Unit**:

| Unit | Description | Use Case |
|------|-------------|----------|
| **User** | Each user individually | Standard per-user rollouts |
| **Organization** | All org members together | Organization-wide launches |
| **Team** | All team members together | Team-specific features |

<CodeBlock language="tsx" filename="org-rollout.tsx">
  {`// Pass organizationId for org-level rollouts
  <FlagsProvider
    clientId="your-client-id"
    user={{
      userId: "user_123",
      organizationId: "org_abc",  // All org members get same result
      teamId: "team_456",         // All team members get same result
    }}
  >
    <App />
  </FlagsProvider>`}
</CodeBlock>

<Callout type="info">
  **Consistent Group Rollouts**: When using organization or team rollouts, all members with the same ID get the same flag result. If `org_abc` falls within the 25% rollout, all its members see the feature.
</Callout>

### Multivariant Flags (A/B/n Testing)

Run experiments with multiple variants. Each user is consistently assigned to a variant based on their identifier. Use the `variant` field from `useFlag`:

<CodeBlock language="tsx" filename="export-flow.tsx">
  {`import { useFlag } from '@databuddy/sdk/react';

  function ExportPanel() {
    const { variant, loading } = useFlag('export-flow');

    if (loading) return <Skeleton />;

    switch (variant) {
      case 'control':
        return <DefaultExportPanel />;
      case 'compact':
        return <CompactExportPanel />;
      case 'guided':
        return <GuidedExportPanel />;
      default:
        return <DefaultExportPanel />;
    }
  }`}
</CodeBlock>

**Creating multivariant flags in the dashboard:**

1. Select "multivariant" as the flag type
2. Add your variants with keys and values (string, number, or JSON)
3. Add traffic weights when you need a weighted split, or omit weights for even assignment
4. Avoid using force targeting rules to assign variants; rules are gates or overrides, not variant selectors

<Callout type="info">
  **Consistent Assignment**: Users are deterministically assigned to variants based on their userId or email, ensuring they always see the same variant across sessions.
</Callout>

<Callout type="info">
  **Variant weights**: If no variant weights are configured, users are split evenly. If you add weights, keep the totals easy to reason about, commonly 100.
</Callout>

**Use cases:**
- **A/B Testing**: Compare two versions of a feature
- **UI variations**: Test layouts, colors, copy
- **Onboarding flows**: Compare guided vs. compact setup paths
- **Feature configuration**: Ship string, number, or JSON values without a deploy
- **A/B/n experiments**: Run experiments with 3+ variants

## Configuration

<CodeBlock language="tsx" filename="provider.tsx">
  {`<FlagsProvider
    clientId="your-client-id"
    apiUrl="https://api.databuddy.cc"
    user={{
      userId: currentUser.id,
      organizationId: currentUser.organizationId,  // For org-level rollouts
      teamId: currentUser.teamId,                  // For team-level rollouts
      properties: {
        role: currentUser.role || 'user',
        workspace_type: currentUser.workspaceType || 'personal',
        region: currentUser.region || 'us-east',
        created_at: currentUser.createdAt,
      }
    }}
    isPending={isLoadingSession}
    debug={process.env.NODE_ENV === 'development'}
    autoFetch={true}
    cacheTtl={60000}        // Cache for 1 minute
    staleTime={30000}       // Revalidate after 30s
  >
    <App />
  </FlagsProvider>`}
</CodeBlock>

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `clientId` | string | Required | Your Databuddy Client ID from the dashboard |
| `apiUrl` | string | `https://api.databuddy.cc` | API endpoint |
| `user` | object | `undefined` | User context for targeting |
| `isPending` | boolean | `false` | Defer evaluation while session loads |
| `disabled` | boolean | `false` | Disable all flag evaluation |
| `debug` | boolean | `false` | Enable debug logging |
| `environment` | string | `undefined` | Environment name |
| `cacheTtl` | number | `60000` | Cache TTL in ms (1 minute) |
| `staleTime` | number | `30000` | Revalidate after (30 seconds) |
| `autoFetch` | boolean | `true` | Auto-fetch flags on mount |
| `skipStorage` | boolean | `false` | Skip localStorage caching |

### User Context Options

| Option | Type | Description |
|--------|------|-------------|
| `userId` | string | Unique user identifier for per-user rollouts |
| `email` | string | User email (alternative identifier) |
| `organizationId` | string | Organization ID for org-level rollouts |
| `teamId` | string | Team ID for team-level rollouts |
| `properties` | object | Custom properties for targeting rules |

### Environments

Pass `environment` when you maintain separate flag definitions for production, staging, or previews. If `environment` is omitted, the SDK fetches definitions that do not have an environment assigned.

<CodeBlock language="tsx">
  {`<FlagsProvider
    clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
    environment={process.env.NEXT_PUBLIC_VERCEL_ENV ?? "production"}
  >
    <App />
  </FlagsProvider>`}
</CodeBlock>

## Flag States

The `FlagState` object returned by `useFlag`:

<CodeBlock language="tsx">
  {`interface FlagState {
    on: boolean;              // Whether the flag is enabled
    loading: boolean;         // Whether the flag is loading
    status: FlagStatus;       // 'loading' | 'ready' | 'error' | 'pending'
    value?: boolean | string | number;  // The flag's value
    variant?: string;         // Variant for A/B tests
  }`}
</CodeBlock>

## Performance Features

### Stale-While-Revalidate
Flags return cached values immediately while revalidating in the background.

<CodeBlock language="tsx">
  {`// Returns cached value instantly, revalidates if stale
  const { on } = useFlag('my-feature');  // Instant!`}
</CodeBlock>

### Request Batching
Multiple flag requests within 10ms are batched into a single API call.

<CodeBlock language="tsx">
  {`// These 3 calls become 1 API request
  const feature1 = useFlag('feature-1');
  const feature2 = useFlag('feature-2');
  const feature3 = useFlag('feature-3');`}
</CodeBlock>

### Visibility API
Pauses fetching when tab is hidden to save bandwidth and battery.

### Request Deduplication
Identical requests are deduplicated automatically.

## Why isPending Matters

The `isPending` prop prevents race conditions during authentication:

<CodeBlock language="tsx">
  {`// Bad: Flags evaluate with wrong user context
  <FlagsProvider user={undefined}>
    <App /> // Shows anonymous features, then switches
  </FlagsProvider>

  // Good: Waits for session before evaluating
  <FlagsProvider
    isPending={isPending}
    user={session?.user ? {...} : undefined}
  >
    <App /> // Shows correct features immediately
  </FlagsProvider>`}
</CodeBlock>

**Benefits:**
- Prevents flash of incorrect content
- Avoids unnecessary API calls
- Consistent user experience
- Better performance

## User Targeting

Target specific users or groups from your dashboard:

- **User ID**: Target specific users by their unique identifier
- **Email**: Target by email address or domain (e.g., `@company.com`)
- **Organization**: Roll out to entire organizations at once
- **Team**: Roll out to specific teams
- **Custom Properties**: Target by user attributes

### Organization & Team Rollouts

For launches that should apply to all members of an organization:

<CodeBlock language="tsx">
  {`<FlagsProvider
    user={{
      userId: "user-123",
      organizationId: "org_abc",  // All org members get same result
      teamId: "team_456",         // All team members get same result
    }}
  >`}
</CodeBlock>

Then in the dashboard, set the **Rollout Unit** to "Organization" or "Team" when configuring your rollout flag.

### Target Groups

Target groups are reusable audiences you can attach to flags. They can match:

- `userId`
- `email`
- custom `properties` fields

Supported rule operators include `equals`, `contains`, `starts_with`, `ends_with`, `in`, and `not_in`. Property rules also support `exists` and `not_exists`.

Evaluation order matters:

1. Direct user targeting rules
2. Attached target group rules
3. Multivariant assignment
4. Rollout percentage
5. Boolean default value

Direct rules and target group matches short-circuit evaluation as force on/off rules. For A/B/n experiments, use them to control eligibility carefully, but do not rely on them to choose a specific variant. If you need both strict eligibility and variant measurement, use a boolean eligibility flag plus a separate multivariant assignment flag.

### Advanced Targeting with Custom Properties

<CodeBlock language="tsx">
  {`<FlagsProvider
    user={{
      userId: "user-123",
      organizationId: "org_abc",
      teamId: "team_456",
      properties: {
        role: 'admin',             // Role-based rollouts
        workspace_type: 'team',    // Workspace targeting
        region: 'us-east',         // Geographic targeting
        created_at: '2024-01-01',  // Time-based targeting
        cohort: 'activation-a',    // Experiment or onboarding cohort
        featureUsage: {
          reportsViewed: 25        // Behavioral targeting
        }
      }
    }}
  >`}
</CodeBlock>

**Targeting Examples:**
- **Role-based**: `role: 'admin'` → Show admin workflows
- **Workspace-based**: `workspace_type: 'team'` → Group-specific experience
- **Geographic**: `region: 'us-east'` → Test in specific regions
- **Behavioral**: `reportsViewed > 10` → Target power users
- **Time-based**: `created_at > '2024-01-01'` → New vs. existing users

## Best Practices

### Measure Flag Outcomes

Treat flag metrics as two separate questions: who was exposed, and what changed afterward. The browser SDK emits `$flag_evaluated` for single-flag fetches when the tracker is available, but bulk-prefetched or cached reads may not emit a fresh exposure event. For experiments where exposure accounting must be complete, track one explicit exposure event when the meaningful flagged view appears.

<CodeBlock language="tsx">
  {`import { track } from "@databuddy/sdk";
import { useEffect, useRef } from "react";
import { useFlag } from "@databuddy/sdk/react";

function ExportPanel() {
  const exportFlow = useFlag("export-flow");
  const trackedExposure = useRef(false);

  useEffect(() => {
    if (exportFlow.loading || trackedExposure.current) {
      return;
    }

    trackedExposure.current = true;
    track("feature_exposed", {
      flag_key: "export-flow",
      variant: exportFlow.variant ?? "control",
      feature: "report_export",
    });
  }, [exportFlow.loading, exportFlow.variant]);

  return exportFlow.variant === "compact" ? (
    <CompactExportPanel />
  ) : (
    <DefaultExportPanel />
  );
}`}
</CodeBlock>

Then track the downstream outcome and include the same flag key and variant.

<CodeBlock language="tsx">
  {`import { track } from "@databuddy/sdk";
import { useFlag } from "@databuddy/sdk/react";

function ExportButton() {
  const exportFlow = useFlag("export-flow");

  async function handleExport() {
    const result = await runExport();

    track("report_exported", {
      flag_key: "export-flow",
      variant: exportFlow.variant ?? "control",
      status: result.ok ? "success" : "failed",
      format: result.format,
    });
  }

  return exportFlow.variant === "compact" ? (
    <CompactExportButton onClick={handleExport} />
  ) : (
    <DefaultExportButton onClick={handleExport} />
  );
}`}
</CodeBlock>

Use one meaningful exposure event and one meaningful outcome event. Avoid tracking every render of a flagged component.

### Flag Planning Checklist

Before adding a flag, decide:

- **Owner**: who removes or expands the flag later
- **Default**: what users see if evaluation fails
- **Rollout unit**: user, organization, or team
- **Targeting inputs**: which IDs and low-cardinality properties are required
- **Success metric**: the one outcome event that proves the rollout helped
- **Cleanup date**: when the flag should be removed or made permanent

### Use the Right API

<CodeBlock language="tsx">
  {`// Simple boolean check — most common
  const { on, loading } = useFlag('dark-mode');

  // Full control with status
  const flag = useFlag('experiment');
  if (flag.status === 'ready') { ... }

  // A/B test variants
  const { variant } = useFlag('export-flow');

  // Typed values via context
  const { getValue } = useFlags();
  const maxItems = getValue<number>('max-items', 10);

  // Quick boolean via context (no loading state)
  const { isOn } = useFlags();
  if (isOn('advanced-search')) { ... }`}
</CodeBlock>

### Handle Loading States

<CodeBlock language="tsx">
  {`function FeatureComponent() {
    const { on, loading } = useFlag('new-feature');

    if (loading) return <Skeleton />;
    return on ? <NewFeature /> : <OldFeature />;
  }`}
</CodeBlock>

### Multiple Flags

<CodeBlock language="tsx">
  {`function Dashboard() {
    const { on: darkMode } = useFlag('dark-mode');
    const { on: newLayout } = useFlag('new-layout');
    const { getValue } = useFlags();
    const maxItems = getValue<number>('max-items', 10);

    return (
      <div className={darkMode ? 'dark' : ''}>
        {newLayout ? <NewLayout items={maxItems} /> : <OldLayout />}
      </div>
    );
  }`}
</CodeBlock>

### Update User Context

<CodeBlock language="tsx">
  {`function WorkspaceSwitcher() {
    const { updateUser, refresh } = useFlags();

    const handleWorkspaceChange = async (workspace: Workspace) => {
      // Update user context to refresh flags
      updateUser({
        userId: user.id,
        organizationId: workspace.organizationId,
        teamId: workspace.teamId,
        properties: {
          role: workspace.role,
          workspace_type: workspace.type
        }
      });

      // Clear stored flag values when switching between unrelated workspaces.
      await refresh(true);
    };

    return <WorkspaceMenu onChange={handleWorkspaceChange} />;
  }`}
</CodeBlock>

## Debug With DevTools

Install DevTools in local development to inspect the flag manager that `FlagsProvider` or Vue `createFlagsPlugin` mounted on the page.

<CodeBlock language="bash">
  {`bun add -d @databuddy/devtools`}
</CodeBlock>

<CodeBlock language="tsx">
  {`import { DatabuddyDevtools } from "@databuddy/devtools/react";

export function AppShell({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <DatabuddyDevtools enabled={process.env.NODE_ENV !== "production"} />
    </>
  );
}`}
</CodeBlock>

Use the flag panel to verify:

- `clientId`, API host, environment, and readiness
- user context used for targeting
- evaluated values, variants, reasons, and sources
- whether a value came from API, cache, default, error, or local override
- local overrides before you ship a UI branch

When you paste an API key with `manage:flags` scope at runtime, DevTools can also create, edit, and delete flag definitions. Do not hard-code management keys in source, and clear local overrides before validating production-like behavior.

## Debug Mode

Enable debug mode to see flag evaluation in the console:

<CodeBlock language="tsx">
  {`<FlagsProvider
    debug={true}
    // ... other props
  >`}
</CodeBlock>

## Related

<Cards>
  <Card title="Server-Side Flags" href="/docs/sdk/server-flags">
    Feature flags for Node.js and API routes
  </Card>
  <Card title="React SDK" href="/docs/sdk/react">
    React component and integration
  </Card>
  <Card title="Vue SDK" href="/docs/sdk/vue">
    Vue component and composables
  </Card>
  <Card title="Configuration" href="/docs/sdk/configuration">
    All configuration options
  </Card>
  <Card title="DevTools" href="/docs/sdk/devtools">
    Inspect flag values, variants, and local overrides
  </Card>
</Cards>

---

Ready to get started? [Create your first feature flag in the dashboard →](https://app.databuddy.cc/register)

---

# Identify Users

> Link tracked activity to your own user IDs and see real names in your dashboard

import { Callout, CodeBlock, Tab, Tabs } from "@/components/docs";

By default, Databuddy tracks visitors with an anonymous device ID. Calling `identify()` links that activity to a user ID from your own system, so the same person is recognized across sessions and devices — and your dashboard shows real names and emails instead of anonymous visitors.

<Callout type="warn">
  User identification processes personal data. Make sure you have a lawful
  basis (typically consent), disclose it in your privacy policy, and only send
  traits you are allowed to process.
</Callout>

## Quick Start

<Tabs items={['React / Next.js', 'Script Tag', 'Node.js']}>
<Tab value="React / Next.js">
Call `identify()` when your auth state resolves — on every page load is fine, repeat calls are deduplicated to one request per session.

<CodeBlock language="tsx">
  {`import { clearProfile, identify } from "@databuddy/sdk";

function IdentifyUser() {
  const { data: session, isPending } = authClient.useSession();

  useEffect(() => {
    if (isPending) {
      return;
    }
    if (session?.user) {
      identify(session.user.id, {
        email: session.user.email,
        name: session.user.name,
      });
    } else {
      clearProfile();
    }
  }, [session, isPending]);

  return null;
}`}
</CodeBlock>
</Tab>
<Tab value="Script Tag">
The same methods live on the global object once the script loads.

<CodeBlock language="js">
  {`// On login or signup
window.databuddy.identify("user_12345", {
  email: "jo@acme.com",
  name: "Jo Doe",
  plan: "pro",
});

// On logout
window.databuddy.clearProfile();`}
</CodeBlock>
</Tab>
<Tab value="Node.js">
Backends can identify users with an API key that has the `track:events` scope for the target website. Pass the client's anonymous ID (readable in the browser via `getAnonymousId()`) to link the device's activity.

<CodeBlock language="ts">
  {`import { Databuddy } from "@databuddy/sdk/node";

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY,
  websiteId: "your-website-id",
});

await client.identify({
  profileId: user.id,
  anonymousId: cookies.get("did"),
  traits: { email: user.email, plan: "pro" },
});`}
</CodeBlock>
</Tab>
</Tabs>

<Callout type="info">
  Pass an opaque, stable ID from your database (max 128 characters, stored
  verbatim) — not an email address. Emails belong in traits.
</Callout>

## How It Works

- Every visitor has an anonymous ID stored on their device.
- `identify(profileId, traits)` links that ID to your user and attaches the user ID to every subsequent event, including earlier pageviews from the same session.
- The profile ID persists in localStorage across reloads. Call `clearProfile()` on logout to return to anonymous tracking.
- Identified users appear on the **Users** page with their name and email, and collapse into one person across all their devices.

## Traits

Traits are flat key/value metadata about the user. Values must be strings, numbers, booleans, or `null` — nested objects are rejected. Limits: 50 keys, 2KB serialized.

| Trait | Behavior |
|-------|----------|
| `username` | Primary display name in the dashboard |
| `name` | Fallback display name when `username` is absent |
| `email` | Shown under the user's name; lowercased |
| anything else | Stored on the profile and shown in the user detail view |

Display names and emails are encrypted at rest (AES-256-GCM). Custom traits are stored as regular values so you can filter and segment by them.

Update traits later without re-identifying, and remove one by setting it to `null`:

<CodeBlock language="ts">
  {`import { setTraits } from "@databuddy/sdk";

setTraits({ plan: "enterprise", trial_ends_at: null });`}
</CodeBlock>

## Revenue Attribution

If you use the Stripe or Paddle integration, include the user ID in your payment metadata and transactions are attributed to the identified user:

<Tabs items={['Stripe', 'Paddle']}>
<Tab value="Stripe">
<CodeBlock language="ts">
  {`await stripe.paymentIntents.create({
  amount,
  currency,
  metadata: {
    databuddy_profile_id: user.id,
  },
});`}
</CodeBlock>
</Tab>
<Tab value="Paddle">
<CodeBlock language="ts">
  {`{ custom_data: { profile_id: user.id } }`}
</CodeBlock>
</Tab>
</Tabs>

## API Reference

### `identify(profileId, traits?)`

Links the browser to a user ID. Persists across sessions, attaches to every subsequent event. Safe to call on every page load — repeat calls with the same ID and no traits send one request per session.

### `setTraits(traits)`

Merges traits into the identified user's profile. Requires a prior `identify()` — otherwise a no-op that warns in debug builds.

### `clearProfile()`

Forgets the identified user. The anonymous ID is kept, so subsequent activity is tracked anonymously again.

### `getProfileId()`

Returns the currently identified user ID, or `null` when anonymous.

---

# SDK Overview

> SDK documentation for tracking analytics across web and server runtimes

import { Card, Cards, Callout, CodeBlock } from "@/components/docs";

The Databuddy SDK provides framework-specific components, helper functions, server-side tracking, and a vanilla JavaScript library for tracking analytics events. Choose your preferred integration method.

<Callout type="info">
  **SDK Version**: 2.0.0+ | **Package**: `@databuddy/sdk` | **CDN**: cdn.databuddy.cc
</Callout>

## Installation

<CodeBlock language="bash">
  {`# Using bun (recommended)
bun add @databuddy/sdk

# Using npm
npm install @databuddy/sdk`}
</CodeBlock>

For vanilla JavaScript, no installation needed - load directly from CDN.

## Quick Start

<Cards>
  <Card title="React / Next.js" href="/docs/sdk/react">
    Component and hooks for React applications
  </Card>
  <Card title="Nuxt" href="/docs/sdk/nuxt">
    First-class Nuxt module for script injection, SPA navigation, and optional error tracking
  </Card>
  <Card title="Vue" href="/docs/sdk/vue">
    Component and composables for Vue applications
  </Card>
  <Card title="Vanilla JavaScript" href="/docs/sdk/vanilla-js">
    CDN script for any website without dependencies
  </Card>
  <Card title="Node.js / Server" href="/docs/sdk/node">
    Server-side tracking for APIs, webhooks, and background jobs
  </Card>
</Cards>

## Core Features

<Cards>
  <Card title="Tracker Helpers" href="/docs/sdk/tracker">
    Helper functions: track, flush, getAnonymousId, getTrackingParams
  </Card>
  <Card title="Feature Flags" href="/docs/sdk/feature-flags">
    Control feature rollouts and A/B testing in React and Vue
  </Card>
  <Card title="Server Flags" href="/docs/sdk/server-flags">
    Server-side feature flag evaluation for Node.js
  </Card>
  <Card title="DevTools" href="/docs/sdk/devtools">
    Local overlay for events, identity, queues, diagnostics, and flags
  </Card>
  <Card title="Configuration" href="/docs/sdk/configuration">
    All configuration options across platforms
  </Card>
</Cards>

## Import Paths

The SDK is organized into submodules for tree-shaking:

| Import | Description |
|--------|-------------|
| `@databuddy/sdk` | Core tracker helpers (track, flush, etc.) |
| `@databuddy/sdk/react` | React component and hooks |
| `@databuddy/sdk/vue` | Vue component and composables |
| `@databuddy/nuxt` | Nuxt module for Databuddy tracking |
| `@databuddy/sdk/node` | Node.js server-side SDK |

## Example: React/Next.js

<CodeBlock language="tsx">
  {`import { Databuddy } from "@databuddy/sdk/react";
import { track } from "@databuddy/sdk";

// In your root layout
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Databuddy
          clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
          trackWebVitals
          trackErrors
        />
        {children}
      </body>
    </html>
  );
}

// In any component
function SignupButton() {
  return (
    <button onClick={() => track("signup_clicked", { source: "header" })}>
      Sign Up
    </button>
  );
}`}
</CodeBlock>

## Example: Node.js

<CodeBlock language="tsx">
  {`import { Databuddy } from "@databuddy/sdk/node";

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  websiteId: process.env.DATABUDDY_WEBSITE_ID,
  enableBatching: true
});

await client.track({
  name: "api_call",
  properties: { endpoint: "/api/users", method: "GET" }
});

await client.flush(); // Important in serverless!`}
</CodeBlock>

## TypeScript Support

The SDK is fully typed with excellent TypeScript support:

<CodeBlock language="tsx">
  {`import { track, type DatabuddyTracker } from "@databuddy/sdk";
import type { DatabuddyConfig } from "@databuddy/sdk/react";
import type { CustomEventInput } from "@databuddy/sdk/node";`}
</CodeBlock>

## Related Documentation

<Cards>
  <Card title="Getting Started" href="/docs/getting-started">
    Quick setup guide and basic configuration
  </Card>
  <Card title="API Reference" href="/docs/api">
    Direct HTTP API for custom implementations
  </Card>
  <Card title="Dashboard Guide" href="/docs/dashboard">
    Navigate your analytics dashboard
  </Card>
</Cards>

---

# Node SDK Reference

> Complete reference for the Databuddy Node.js SDK - Server-side analytics tracking

import { Tab, Tabs, Callout, Card, Cards, CodeBlock } from "@/components/docs";

The Databuddy Node SDK provides a lightweight, type-safe way to track server-side analytics events from Node.js, Bun, Deno, and serverless environments.

<Callout type="info">
  **SDK Version**: 2.0.0+ | **Package**: `@databuddy/sdk` | **API**: basket.databuddy.cc
</Callout>

## TL;DR

The Node SDK is perfect for tracking server-side events like API calls, background jobs, webhooks, and more. It supports batching and explicit flushes for serverless environments.

<CodeBlock language="tsx">
  {`import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  enableBatching: true
});

await client.track({
  name: 'api_call',
  properties: { endpoint: '/api/users', method: 'GET' }
});

await client.flush(); // Important in serverless!`}
</CodeBlock>

## Installation

<CodeBlock language="bash">
  {`bun add @databuddy/sdk`}
</CodeBlock>

## Quick Start

### Basic Usage

<CodeBlock language="tsx">
  {`import { Databuddy } from '@databuddy/sdk/node';

// Initialize the client
const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!
});

// Track an event
await client.track({
  name: 'user_signup',
  properties: {
    plan: 'pro',
    source: 'api'
  }
});`}
</CodeBlock>

### Serverless Usage

In serverless environments, always flush before the function exits:

<CodeBlock language="tsx">
  {`import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  enableBatching: true
});

export async function handler(event) {
  await client.track({
    name: 'lambda_invocation',
    properties: { source: event.source }
  });
  
  // Ensure events are sent before function exits
  const flushResult = await client.flush();
  if (!flushResult.success) {
    console.error("Failed to flush analytics:", flushResult.error);
  }
  
  return { statusCode: 200 };
}`}
</CodeBlock>

## Configuration

<CodeBlock language="tsx">
  {`interface DatabuddyConfig {
    apiKey: string;               // Required: Your API key for authentication
    websiteId?: string;           // Optional: Default Client ID to scope events
    namespace?: string;           // Optional: Default namespace for logical grouping
    source?: string;              // Optional: Default source identifier for events
    apiUrl?: string;              // Default: 'https://basket.databuddy.cc'
    debug?: boolean;              // Enable debug logging
    logger?: Logger;              // Custom logger instance
    enableBatching?: boolean;     // Default: true
    batchSize?: number;           // Default: 10, Max: 100
    batchTimeout?: number;        // Default: 2000ms
    maxQueueSize?: number;        // Default: 1000
    enableDeduplication?: boolean; // Default: true
    maxDeduplicationCacheSize?: number; // Default: 10000
    middleware?: Middleware[];     // Transform or drop events before send
  }`}
</CodeBlock>

<CodeBlock language="tsx">
  {`import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  enableBatching: true,
  batchSize: 20,
  batchTimeout: 5000
});`}
</CodeBlock>

## Core Methods

### `track(event)`

Track custom events with optional properties, session IDs, and anonymous IDs:

<CodeBlock language="tsx">
  {`await client.track({
  name: 'purchase_completed',
  eventId: 'purchase-ORD-789',
  anonymousId: 'anon_123',
  sessionId: 'sess_456',
  properties: {
    order_id: 'ORD-789',
    revenue: 299.99,
    currency: 'USD',
    items: 3
  }
});`}
</CodeBlock>

**Response:**

<CodeBlock language="tsx">
  {`interface EventResponse {
  success: boolean;
  eventId?: string;
  error?: string;
}`}
</CodeBlock>

With `enableBatching: true`, `track()` usually confirms that the event was queued. It returns a delivery failure only when the call immediately sends the event, such as `enableBatching: false` or when the queue reaches `batchSize` or `maxQueueSize`. Call `flush()` when you need the final delivery result.

Use `eventId` when your code may retry the same logical event. The SDK uses it as a local deduplication key for queued or already delivered events.

### `batch(events)`

Send multiple events in a single batch (max 100 events):

<CodeBlock language="tsx">
  {`await client.batch([
  {
    type: 'custom',
    name: 'event1',
    properties: { foo: 'bar' }
  },
  {
    type: 'custom',
    name: 'event2',
    anonymousId: 'anon_123',
    sessionId: 'sess_456',
    properties: { baz: 'qux' }
  }
]);`}
</CodeBlock>

### `flush()`

Manually flush all queued events. **Critical for serverless environments:**

<CodeBlock language="tsx">
  {`await client.track({ name: 'event1' });
await client.track({ name: 'event2' });
await client.track({ name: 'event3' });

// Flush all queued events before function exits
const flushResult = await client.flush();
if (!flushResult.success) {
  console.error("Failed to flush analytics:", flushResult.error);
}`}
</CodeBlock>

## Event Tracking Patterns

### Per-Event Options

You can override the default `websiteId`, `namespace`, and `source` for individual events:

<CodeBlock language="tsx">
  {`// Set defaults in config
const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  websiteId: "default-website",
  namespace: "api",
  source: "backend"
});

// Override per-event
await client.track({
  name: 'payment_processed',
  websiteId: 'billing-service',  // Override default websiteId
  namespace: 'payments',          // Override default namespace
  source: 'stripe-webhook',       // Override default source
  properties: {
    amount: 99.99,
    currency: 'USD'
  }
});

// Mix defaults and overrides
await client.track({
  name: 'user_login',
  namespace: 'auth',  // Only override namespace
  properties: {
    method: 'oauth',
    provider: 'google'
  }
});`}
</CodeBlock>

**Use Cases:**

- **websiteId**: Scope events to different websites within your organization
- **namespace**: Group events logically (e.g., `billing`, `auth`, `api`, `webhook`)
- **source**: Identify where the event originated (e.g., `backend`, `webhook`, `cli`, `cron-job`)

### E-commerce Events

<CodeBlock language="tsx">
  {`// Product viewed
await client.track({
  name: 'product_viewed',
  anonymousId: userId,
  sessionId: sessionId,
  properties: {
    product_id: 'P12345',
    category: 'Electronics',
    price: 99.99,
    currency: 'USD'
  }
});

// Purchase completed
await client.track({
  name: 'purchase_completed',
  anonymousId: userId,
  sessionId: sessionId,
  properties: {
    order_id: 'ORD-789',
    revenue: 299.99,
    currency: 'USD',
    items: 3,
    payment_method: 'credit_card'
  }
});`}
</CodeBlock>

### API & Backend Events

<CodeBlock language="tsx">
  {`// API call tracking
await client.track({
  name: 'api_call',
  properties: {
    endpoint: '/api/users',
    method: 'GET',
    status_code: 200,
    duration_ms: 45
  }
});

// Background job tracking
await client.track({
  name: 'job_completed',
  properties: {
    job_name: 'email_digest',
    duration_ms: 5400,
    emails_sent: 1250,
    status: 'success'
  }
});`}
</CodeBlock>

### Webhook Events

<CodeBlock language="tsx">
  {`// Stripe webhook
await client.track({
  name: 'stripe_webhook_received',
  properties: {
    event_type: 'payment_intent.succeeded',
    amount: 2999,
    currency: 'usd'
  }
});

// Generic webhook
await client.track({
  name: 'webhook_received',
  properties: {
    source: 'github',
    event_type: 'push',
    repository: 'my-repo'
  }
});`}
</CodeBlock>

### Authentication Events

<CodeBlock language="tsx">
  {`// User signup
await client.track({
  name: 'user_signup',
  anonymousId: userId,
  properties: {
    method: 'email',
    plan: 'free',
    source: 'landing_page'
  }
});

// User login
await client.track({
  name: 'user_login',
  anonymousId: userId,
  sessionId: sessionId,
  properties: {
    method: 'oauth',
    provider: 'google',
    success: true
  }
});`}
</CodeBlock>

## Session & Anonymous IDs

The Node SDK supports optional `anonymousId` and `sessionId` fields to track user sessions across requests. **These IDs should match the IDs generated by the web SDK** to enable unified tracking across client and server.

<Callout type="warn">
**Important**: When tracking server-side events for a user session, you must use the **same anonymousId and sessionId** that the web SDK generated on the client. This ensures events are properly attributed to the same user and session.
</Callout>

### Getting IDs from the Web SDK

The web SDK automatically generates and manages these IDs. You need to pass them to your backend to use in server-side tracking.

**Client-Side (React/Next.js):**

<CodeBlock language="tsx" filename="app/components/checkout-button.tsx">
  {`import { getTrackingIds } from '@databuddy/sdk';

export function CheckoutButton() {
  const handleCheckout = async () => {
    const { anonId: anonymousId, sessionId } = getTrackingIds();
    
    if (anonymousId || sessionId) {
      await fetch('/api/checkout', {
        method: 'POST',
        body: JSON.stringify({ anonymousId, sessionId, cart: [...] })
      });
    }
  };
  
  return <button onClick={handleCheckout}>Checkout</button>;
}`}
</CodeBlock>

**Server-Side (API Route):**

<CodeBlock language="tsx" filename="app/api/checkout/route.ts">
  {`import { Databuddy } from '@databuddy/sdk/node';
import { NextResponse } from 'next/server';

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!
});

export async function POST(request: Request) {
  const { anonymousId, sessionId, cart } = await request.json();
  
  await client.track({
    name: 'checkout_started',
    anonymousId,
    sessionId,
    properties: {
      cart_value: calculateTotal(cart),
      items_count: cart.length
    }
  });
  
  return NextResponse.json({ success: true });
}`}
</CodeBlock>

### Web SDK ID Access Methods

<CodeBlock language="tsx">
  {`import { getAnonymousId, getSessionId, getTrackingIds } from '@databuddy/sdk';

// Method 1: Via SDK helpers (React/Next.js)
const { anonId: anonymousId, sessionId } = getTrackingIds();

// Method 2: Individual helpers
const currentAnonymousId = getAnonymousId();
const currentSessionId = getSessionId();`}
</CodeBlock>

<Callout type="info">
**ID Format**: 
- Anonymous IDs: `anon_xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`
- Session IDs: `sess_xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`

These are UUID v4 strings with a prefix. The web SDK stores them in localStorage (anonymousId) and sessionStorage (sessionId).
</Callout>

## Environment-Specific Usage

### Next.js API Routes

<CodeBlock language="tsx" filename="app/api/track/route.ts">
  {`import { Databuddy } from '@databuddy/sdk/node';
import { NextResponse } from 'next/server';

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  enableBatching: false
});

export async function POST(request: Request) {
  const body = await request.json();
  
  await client.track({
    name: 'api_endpoint_called',
    properties: {
      endpoint: '/api/track',
      payload_size: JSON.stringify(body).length
    }
  });
  
  return NextResponse.json({ success: true });
}`}
</CodeBlock>

### Express.js Middleware

<CodeBlock language="tsx">
  {`import express from 'express';
import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!
});

const app = express();

app.use(async (req, res, next) => {
  const start = Date.now();
  
  res.on('finish', async () => {
    await client.track({
      name: 'http_request',
      properties: {
        method: req.method,
        path: req.path,
        status_code: res.statusCode,
        duration_ms: Date.now() - start
      }
    });
  });
  
  next();
});

process.on('SIGTERM', async () => {
  await client.flush();
  process.exit(0);
});`}
</CodeBlock>

### AWS Lambda

<CodeBlock language="tsx">
  {`import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  enableBatching: true
});

export const handler = async (event: any) => {
  try {
    await client.track({
      name: 'lambda_invoked',
      properties: {
        function_name: process.env.AWS_LAMBDA_FUNCTION_NAME,
        event_source: event.source
      }
    });
    
    const result = await processEvent(event);
    
    await client.track({
      name: 'lambda_success',
      properties: { result_count: result.length }
    });
    
    return { statusCode: 200, body: JSON.stringify(result) };
  } catch (error) {
    await client.track({
      name: 'lambda_error',
      properties: { error: error.message, stack: error.stack }
    });
    throw error;
  } finally {
    await client.flush();
  }
};`}
</CodeBlock>

### Cloudflare Workers

<CodeBlock language="tsx">
  {`import { Databuddy } from '@databuddy/sdk/node';

export default {
  async fetch(request: Request, env: Env) {
    const client = new Databuddy({
      apiKey: env.DATABUDDY_API_KEY,
      enableBatching: false
    });
    
    await client.track({
      name: 'worker_request',
      properties: { url: request.url, method: request.method }
    });
    
    return new Response('Hello World');
  }
};`}
</CodeBlock>

### Bun Server

<CodeBlock language="tsx">
  {`import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!
});

Bun.serve({
  port: 3000,
  async fetch(req) {
    await client.track({
      name: 'bun_request',
      properties: { url: req.url, method: req.method }
    });
    
    return new Response('Hello from Bun!');
  }
});`}
</CodeBlock>

## Batching

Events are automatically batched when batch size or timeout is reached:

<CodeBlock language="tsx">
  {`const client = new Databuddy({
    apiKey: process.env.DATABUDDY_API_KEY!,
    websiteId: process.env.DATABUDDY_WEBSITE_ID,
    namespace: "api",
    source: "backend",
    enableBatching: true,
    batchSize: 20,
    batchTimeout: 5000
  });

// These will be batched automatically
await client.track({ name: 'event1' });
await client.track({ name: 'event2' });
await client.track({ name: 'event3' });

const flushResult = await client.flush();
if (!flushResult.success) {
  console.error("Failed to deliver analytics:", flushResult.error);
}`}
</CodeBlock>

## Deduplication

When `enableDeduplication` is enabled, events with the same `eventId` are dropped if they are already queued or were delivered successfully. Failed sends do not poison the deduplication cache, so you can retry the same `eventId` after a temporary network or API failure.

<CodeBlock language="tsx">
  {`const response = await client.track({
  name: "invoice_paid",
  eventId: \`stripe-\${stripeEvent.id}\`,
  properties: { invoice_id: invoice.id }
});

if (!response.success) {
  // Safe to retry later with the same eventId
  await retryAnalyticsEvent();
}`}
</CodeBlock>

## Debugging

Enable debug logging:

<CodeBlock language="tsx">
  {`const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  debug: true
});`}
</CodeBlock>

## Error Handling

The SDK returns success/failure status for delivery errors instead of throwing:

<CodeBlock language="tsx">
  {`const response = await client.track({
  name: 'test_event',
  properties: { test: true }
});

if (!response.success) {
  console.error('Failed to track event:', response.error);
}`}
</CodeBlock>

The constructor still throws for invalid configuration, such as a missing or blank `apiKey`.

## TypeScript Support

The SDK is fully typed:

<CodeBlock language="tsx">
  {`import { Databuddy, type CustomEventInput } from '@databuddy/sdk/node';

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  enableBatching: true
});

const event: CustomEventInput = {
  name: 'user_signup',
  anonymousId: 'anon_123',
  sessionId: 'sess_456',
  properties: { plan: 'pro', source: 'api' }
};

await client.track(event);`}
</CodeBlock>

## Best Practices

### Always Flush in Serverless

<CodeBlock language="tsx">
  {`// ✅ Good
export async function handler(event: any) {
  await client.track({ name: 'event' });
  await client.flush();
  return { statusCode: 200 };
}

// ❌ Bad - events may be lost
export async function handler(event: any) {
  await client.track({ name: 'event' });
  return { statusCode: 200 };
}`}
</CodeBlock>

### Use Consistent Property Names

<CodeBlock language="tsx">
  {`// ✅ Good - consistent naming
await client.track({
  name: 'api_call',
  properties: {
    endpoint: '/api/users',
    method: 'GET',
    status_code: 200
  }
});

// ❌ Bad - inconsistent naming
await client.track({
  name: 'API_CALL',
  properties: {
    EndPoint: '/api/users',
    METHOD: 'GET',
    statusCode: 200
  }
});`}
</CodeBlock>

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Events not appearing | Verify `apiKey`, check network in logs with `debug: true` |
| Authentication errors | Ensure `apiKey` is valid and has proper permissions |
| TypeScript errors | Ensure TypeScript 4.5+, check `@databuddy/sdk` version |
| Serverless events missing | Always call `await client.flush()` before function exits |
| High latency | Enable batching: `enableBatching: true, batchSize: 50` |
| Temporary network failures | Check `response.success` and retry from your application when the event is safe to send again |
| Memory issues | Reduce `maxQueueSize`, flush more frequently |
| Wrong website/namespace | Check per-event `websiteId`, `namespace`, and `source` values |

## Related

<Cards>
  <Card title="React / Next.js" href="/docs/sdk/react">
    Client-side tracking with React component
  </Card>
  <Card title="Tracker Helpers" href="/docs/sdk/tracker">
    Helper functions for tracking
  </Card>
  <Card title="Server Flags" href="/docs/sdk/server-flags">
    Server-side feature flag evaluation
  </Card>
  <Card title="API Reference" href="/docs/api">
    Direct HTTP API for custom implementations
  </Card>
</Cards>

---

# Nuxt

> Zero-config Nuxt module with auto-imports, SPA tracking, and TypeScript support

import { Callout, CodeBlock, Card, Cards } from "@/components/docs";

`@databuddy/nuxt` is a first-class Nuxt module. Add it to `nuxt.config.ts` once and you're done — pageviews and SPA navigation work automatically with no `app.vue` changes. Enable `trackErrors` to also capture JavaScript and Vue component errors.

## Install

Run this in your project root. It installs the package **and** adds it to your `nuxt.config.ts` automatically:

<CodeBlock language="bash">
  {`npx nuxi module add @databuddy/nuxt`}
</CodeBlock>

Or install manually:

<CodeBlock language="bash">
  {`bun add @databuddy/nuxt
# npm install @databuddy/nuxt
# yarn add @databuddy/nuxt
# pnpm add @databuddy/nuxt`}
</CodeBlock>

## Setup

Add your client ID to `nuxt.config.ts`:

<CodeBlock language="ts" filename="nuxt.config.ts">
  {`export default defineNuxtConfig({
  modules: ["@databuddy/nuxt"],
  databuddy: {
    clientId: process.env.NUXT_PUBLIC_DATABUDDY_CLIENT_ID,
  },
})`}
</CodeBlock>

<CodeBlock language="bash" filename=".env">
  {`NUXT_PUBLIC_DATABUDDY_CLIENT_ID=your-client-id`}
</CodeBlock>

That's it. Pageviews and SPA route changes are tracked automatically. No `app.vue` edits, no plugins to register, no components to place.

<Callout type="info">
  Nuxt automatically maps the `NUXT_PUBLIC_DATABUDDY_CLIENT_ID` environment variable to `runtimeConfig.public.databuddy.clientId` — no extra configuration needed.
</Callout>

## Tracking Events

`useDatabuddy` is auto-imported in every component:

<CodeBlock language="html" filename="pages/index.vue">
  {`<script setup>
const { track } = useDatabuddy()

function handleSignup() {
  track("signup_clicked", {
    source: "hero",
    plan: "pro",
  })
}
</script>

<template>
  <button @click="handleSignup">Get started</button>
</template>`}
</CodeBlock>

### Full composable API

All functions are available from `useDatabuddy()`:

| Function | Description |
|----------|-------------|
| `track(name, properties?)` | Track a custom event |
| `trackError(message, properties?)` | Track an error |
| `screenView(properties?)` | Manually trigger a pageview |
| `setGlobalProperties(properties)` | Attach properties to all future events |
| `clear()` | Reset anonymous and session IDs (call after logout) |
| `flush()` | Force-send all queued events immediately |
| `getAnonymousId()` | Get the current anonymous user ID |
| `getSessionId()` | Get the current session ID |
| `getTrackingIds()` | Get both IDs at once |
| `getTrackingParams()` | Get IDs as a URL query string for cross-domain tracking |

## Options API and templates

`$databuddy` is available directly in templates and Options API components — no import needed:

<CodeBlock language="html">
  {`<template>
  <button @click="$databuddy.track('cta_clicked')">Sign up</button>
</template>`}
</CodeBlock>

<CodeBlock language="ts">
  {`export default defineComponent({
  methods: {
    handleClick() {
      this.$databuddy.track("cta_clicked")
    },
  },
})`}
</CodeBlock>

## Feature Flags

Add the `flags` config to enable the flag composables:

<CodeBlock language="ts" filename="nuxt.config.ts">
  {`export default defineNuxtConfig({
  modules: ["@databuddy/nuxt"],
  databuddy: {
    clientId: process.env.NUXT_PUBLIC_DATABUDDY_CLIENT_ID,
    flags: {}, // clientId is inherited automatically
  },
})`}
</CodeBlock>

Then use `useFlag` and `useFlags` — auto-imported everywhere:

<CodeBlock language="html">
  {`<script setup>
const { on: isNewCheckout, loading } = useFlag("new-checkout")
</script>

<template>
  <template v-if="loading">
    <CheckoutSkeleton />
  </template>
  <template v-else-if="isNewCheckout">
    <NewCheckout />
  </template>
  <template v-else>
    <LegacyCheckout />
  </template>
</template>`}
</CodeBlock>

## Module options

All options are passed under the `databuddy` key in `nuxt.config.ts`:

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `clientId` | `string` | — | Your Databuddy client ID |
| `disabled` | `boolean` | `false` | Disable all tracking |
| `debug` | `boolean` | `false` | Enable verbose logging |
| `trackWebVitals` | `boolean` | `false` | Core Web Vitals (LCP, CLS, TTFB) |
| `trackErrors` | `boolean` | `false` | JS errors + Vue component errors |
| `trackOutgoingLinks` | `boolean` | `false` | Clicks on external links |
| `trackInteractions` | `boolean` | `false` | Button clicks and form submissions |
| `trackAttributes` | `boolean` | `false` | Elements with `data-track` attribute |
| `trackPerformance` | `boolean` | `true` | Page load timing |
| `maskPatterns` | `string[]` | `[]` | Glob patterns to anonymise paths |
| `skipPatterns` | `string[]` | `[]` | Glob patterns to skip entirely |
| `samplingRate` | `number` | `1.0` | Event sampling rate (0.0–1.0) |
| `flags` | `FlagsConfig` | — | Feature flag configuration |

## Privacy

<CodeBlock language="ts" filename="nuxt.config.ts">
  {`export default defineNuxtConfig({
  modules: ["@databuddy/nuxt"],
  databuddy: {
    clientId: process.env.NUXT_PUBLIC_DATABUDDY_CLIENT_ID,
    // Never track admin or internal routes
    skipPatterns: ["/admin/**", "/internal/*"],
    // Anonymise user IDs in paths
    maskPatterns: ["/users/*", "/orders/**"],
  },
})`}
</CodeBlock>

## Disable in development

<CodeBlock language="ts" filename="nuxt.config.ts">
  {`export default defineNuxtConfig({
  modules: ["@databuddy/nuxt"],
  databuddy: {
    clientId: process.env.NUXT_PUBLIC_DATABUDDY_CLIENT_ID,
    disabled: process.env.NODE_ENV !== "production",
  },
})`}
</CodeBlock>

## Related

<Cards>
  <Card title="Vue SDK" href="/docs/sdk/vue">
    Component-based integration for plain Vue 3 apps
  </Card>
  <Card title="Feature Flags" href="/docs/sdk/feature-flags">
    Complete feature flag documentation
  </Card>
  <Card title="Tracker Helpers" href="/docs/sdk/tracker">
    track, flush, getAnonymousId, and more
  </Card>
  <Card title="Configuration" href="/docs/sdk/configuration">
    Full configuration reference
  </Card>
</Cards>

---

# React / Next.js

> React component and hooks for tracking analytics in React and Next.js applications

import { Callout, CodeBlock, Card, Cards } from "@/components/docs";

The Databuddy React SDK provides a component for script injection and hooks for custom event tracking. Works with React 18+, Next.js 13+, and other React-based frameworks.

<Callout type="info">
  **Package**: `@databuddy/sdk` | **Import**: `@databuddy/sdk/react`
</Callout>

## Installation

<CodeBlock language="bash">
  {`bun add @databuddy/sdk`}
</CodeBlock>

## Quick Start

Add the `Databuddy` component to your root layout:

<CodeBlock language="tsx" filename="app/layout.tsx">
  {`import { Databuddy } from "@databuddy/sdk/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Databuddy
          clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
          trackWebVitals
          trackErrors
        />
        {children}
      </body>
    </html>
  );
}`}
</CodeBlock>

## Databuddy Component

The `Databuddy` component injects the tracking script and configures analytics. It renders nothing to the DOM.

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `clientId` | `string` | Auto-detected | Your client ID (auto-detects from `NEXT_PUBLIC_DATABUDDY_CLIENT_ID`) |
| `disabled` | `boolean` | `false` | Disable all tracking |
| `debug` | `boolean` | `false` | Enable debug logging |

### Tracking Features

| Prop | Default | Description |
|------|---------|-------------|
| `trackPerformance` | `true` | Page load times and navigation timing |
| `trackWebVitals` | `false` | Core Web Vitals (LCP, FID, CLS, TTFB) |
| `trackErrors` | `false` | JavaScript errors and unhandled rejections |
| `trackOutgoingLinks` | `false` | Clicks on external links |
| `trackInteractions` | `false` | Button clicks and form submissions |
| `trackAttributes` | `false` | Elements with `data-track` attributes |
| `trackHashChanges` | `false` | Hash changes in the URL |

### Batching & Performance

| Prop | Default | Description |
|------|---------|-------------|
| `enableBatching` | `true` | Group events into batches |
| `batchSize` | `10` | Events per batch (1-50) |
| `batchTimeout` | `5000` | Batch timeout in ms |
| `samplingRate` | `1.0` | Event sampling rate (0.0-1.0) |

### Privacy

| Prop | Type | Description |
|------|------|-------------|
| `skipPatterns` | `string[]` | Skip tracking on matching paths |
| `maskPatterns` | `string[]` | Mask sensitive URL segments |

### Full Example

<CodeBlock language="tsx" filename="app/layout.tsx">
  {`import { Databuddy } from "@databuddy/sdk/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Databuddy
          clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
          trackWebVitals
          trackErrors
          trackOutgoingLinks
          enableBatching
          batchSize={20}
          disabled={process.env.NODE_ENV === "development"}
          skipPatterns={["/admin/**", "/internal/*"]}
          maskPatterns={["/users/*", "/orders/**"]}
        />
        {children}
      </body>
    </html>
  );
}`}
</CodeBlock>

## Tracking Events

Import tracker helpers to track custom events:

<CodeBlock language="tsx">
  {`import { track } from "@databuddy/sdk";

function SignupButton() {
  const handleClick = () => {
    track("signup_clicked", {
      source: "header",
      plan: "pro"
    });
  };

  return <button onClick={handleClick}>Sign Up</button>;
}`}
</CodeBlock>

### Common Patterns

<CodeBlock language="tsx">
  {`import { track, trackError } from "@databuddy/sdk";

// E-commerce
track("product_viewed", {
  product_id: "P12345",
  category: "Electronics",
  price: 299.99
});

track("purchase_completed", {
  order_id: "ORD-789",
  revenue: 299.99,
  currency: "USD"
});

// Feature usage
track("feature_used", {
  feature: "export_data",
  user_tier: "premium"
});

// Error tracking
try {
  await riskyOperation();
} catch (error) {
  trackError(error.message, {
    stack: error.stack,
    context: "checkout_flow"
  });
}`}
</CodeBlock>

## Environment Setup

<CodeBlock language="bash" filename=".env.local">
  {`NEXT_PUBLIC_DATABUDDY_CLIENT_ID=your-client-id-here`}
</CodeBlock>

The component auto-detects the client ID from this environment variable.

## Pages Router

For Next.js Pages Router, add to `_app.tsx`:

<CodeBlock language="tsx" filename="pages/_app.tsx">
  {`import { Databuddy } from "@databuddy/sdk/react";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Databuddy
        clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
        trackWebVitals
      />
      <Component {...pageProps} />
    </>
  );
}`}
</CodeBlock>

## Development Mode

Disable tracking during development:

<CodeBlock language="tsx">
  {`<Databuddy
  clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
  disabled={process.env.NODE_ENV === "development"}
/>`}
</CodeBlock>

## Data Attributes

Enable automatic tracking of elements with `data-track` attributes:

<CodeBlock language="tsx">
  {`<Databuddy
  clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
  trackAttributes
/>

{/* Automatically tracks button_click event */}
<button data-track="signup_clicked" data-plan="premium">
  Sign Up
</button>`}
</CodeBlock>

## Related

<Cards>
  <Card title="Tracker Helpers" href="/docs/sdk/tracker">
    track, flush, getAnonymousId, and more helper functions
  </Card>
  <Card title="Feature Flags" href="/docs/sdk/feature-flags">
    Control feature rollouts with FlagsProvider and hooks
  </Card>
  <Card title="Configuration" href="/docs/sdk/configuration">
    Complete configuration reference
  </Card>
</Cards>

---

# Server-Side Feature Flags

> Evaluate feature flags on the server in Node.js, API routes, and serverless functions

import { Callout, CodeBlock, Card, Cards } from "@/components/docs";

The Databuddy Node SDK includes a server-side feature flags manager optimized for server environments with request deduplication, batching, and stale-while-revalidate caching.

<Callout type="info">
  **Package**: `@databuddy/sdk` | **Import**: `@databuddy/sdk/node`
</Callout>

## Installation

<CodeBlock language="bash">
  {`bun add @databuddy/sdk`}
</CodeBlock>

## Quick Start

<CodeBlock language="tsx">
  {`import { createServerFlagsManager } from "@databuddy/sdk/node";

const flags = createServerFlagsManager({
  clientId: process.env.DATABUDDY_CLIENT_ID!,
  user: {
    userId: "user-123",
    organizationId: "org-456"
  }
});

// Wait for initialization. Server managers only preload flags here when
// autoFetch is true; getFlag() below fetches this flag on demand.
await flags.waitForInit();

// Check a flag
const result = await flags.getFlag("new-feature");
if (result.enabled) {
  // Show new feature
}`}
</CodeBlock>

## Creating a Manager

### createServerFlagsManager(config)

Creates a new server-side flags manager instance:

<CodeBlock language="tsx">
  {`import { createServerFlagsManager } from "@databuddy/sdk/node";

const flags = createServerFlagsManager({
  clientId: process.env.DATABUDDY_CLIENT_ID!,
  apiUrl: "https://api.databuddy.cc",
  user: {
    userId: "user-123",
    organizationId: "org-456",
    properties: {
      role: "admin",
      workspace_type: "team"
    }
  },
  environment: "production",
  cacheTtl: 60_000,      // 1 minute cache
  staleTime: 30_000,     // Revalidate after 30s
  maxCacheSize: 5000,    // Cap in-memory cache entries
  debug: false
});`}
</CodeBlock>

### Configuration

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `clientId` | `string` | Required | Your client ID |
| `apiUrl` | `string` | `https://api.databuddy.cc` | API endpoint |
| `user` | `UserContext` | - | User context for targeting |
| `environment` | `string` | - | Environment name |
| `cacheTtl` | `number` | `60000` | Cache TTL in ms |
| `staleTime` | `number` | `30000` | Revalidate after (ms) |
| `maxCacheSize` | `number` | `5000` | Maximum in-memory cache entries |
| `autoFetch` | `boolean` | `false` | Fetch all flags on init |
| `debug` | `boolean` | `false` | Enable debug logging |
| `disabled` | `boolean` | `false` | Disable flag evaluation |

### User Context

| Option | Type | Description |
|--------|------|-------------|
| `userId` | `string` | User identifier for targeting |
| `email` | `string` | Email for targeting |
| `organizationId` | `string` | Organization for group rollouts |
| `teamId` | `string` | Team for group rollouts |
| `properties` | `object` | Custom properties for targeting |

## Fetching Flags

### getFlag(key, user?)

Fetch a single flag with caching and deduplication:

<CodeBlock language="tsx">
  {`const result = await flags.getFlag("my-feature");

console.log({
  enabled: result.enabled,    // boolean
  value: result.value,        // boolean | string | number
  variant: result.variant,    // string (for A/B tests)
  reason: result.reason       // evaluation reason
});`}
</CodeBlock>

Override user context for a specific check:

<CodeBlock language="tsx">
  {`const result = await flags.getFlag("advanced-feature", {
  userId: "different-user",
  properties: { role: "admin" }
});`}
</CodeBlock>

Per-call user context is sent to the flags API for that evaluation and overrides the manager's default user. Cache entries are isolated by flag key, environment, and the full targeting context (`userId`, `email`, `organizationId`, `teamId`, and `properties`).

### fetchAllFlags(user?)

Pre-fetch all flags for a user before using synchronous reads:

<CodeBlock language="tsx">
  {`// Fetch all flags upfront
await flags.fetchAllFlags();

// Now synchronous checks are fast
const state = flags.isEnabled("feature-1");
const value = flags.getValue("max-items", 10);`}
</CodeBlock>

### isEnabled(key)

Synchronous read from cache (call after `fetchAllFlags` or `getFlag`). Returns `FlagState`:

<CodeBlock language="tsx">
  {`const state = flags.isEnabled("my-feature");

if (state.status === "ready" && state.on) {
  // Feature is enabled
}`}
</CodeBlock>

<CodeBlock language="tsx">
  {`interface FlagState {
  on: boolean;
  status: "ready" | "loading" | "error" | "pending";
  loading: boolean;
  value?: boolean | string | number;
  variant?: string;
}`}
</CodeBlock>

### getValue(key, defaultValue)

Get a typed value from cache:

<CodeBlock language="tsx">
  {`const maxItems = flags.getValue("max-items", 10);
const theme = flags.getValue<"light" | "dark">("theme", "light");`}
</CodeBlock>

## Usage Patterns

### Next.js API Routes

<CodeBlock language="tsx" filename="app/api/data/route.ts">
  {`import { createServerFlagsManager } from "@databuddy/sdk/node";
import { NextResponse } from "next/server";

const flags = createServerFlagsManager({
  clientId: process.env.DATABUDDY_CLIENT_ID!
});

export async function GET(request: Request) {
  const userId = request.headers.get("x-user-id");
  
  const result = await flags.getFlag("new-api-version", {
    userId: userId ?? undefined
  });
  
  if (result.enabled) {
    return NextResponse.json({ version: "v2", data: await getNewData() });
  }
  
  return NextResponse.json({ version: "v1", data: await getLegacyData() });
}`}
</CodeBlock>

### Next.js Server Components

<CodeBlock language="tsx" filename="app/dashboard/page.tsx">
  {`import { createServerFlagsManager } from "@databuddy/sdk/node";
import { auth } from "@/lib/auth";

export default async function DashboardPage() {
  const session = await auth();
  
  const flags = createServerFlagsManager({
    clientId: process.env.DATABUDDY_CLIENT_ID!,
    user: session?.user ? {
      userId: session.user.id,
      organizationId: session.user.organizationId,
      properties: {
        role: session.user.role,
        workspace_type: session.user.workspaceType
      }
    } : undefined
  });
  
  const newDashboard = await flags.getFlag("new-dashboard");
  
  if (newDashboard.enabled) {
    return <NewDashboard />;
  }
  
  return <LegacyDashboard />;
}`}
</CodeBlock>

### Express Middleware

<CodeBlock language="tsx">
  {`import express from "express";
import { createServerFlagsManager } from "@databuddy/sdk/node";

const app = express();

// Create a shared manager
const flags = createServerFlagsManager({
  clientId: process.env.DATABUDDY_CLIENT_ID!,
  autoFetch: true
});

// Wait for init
await flags.waitForInit();

// Middleware to attach flags to request
app.use(async (req, res, next) => {
  const userId = req.headers["x-user-id"] as string;
  
  req.flags = {
    isEnabled: async (key: string) => {
      const result = await flags.getFlag(key, { userId });
      return result.enabled;
    }
  };
  
  next();
});

app.get("/api/feature", async (req, res) => {
  const enabled = await req.flags.isEnabled("my-feature");
  res.json({ enabled });
});`}
</CodeBlock>

### Serverless Functions

<CodeBlock language="tsx">
  {`import { createServerFlagsManager } from "@databuddy/sdk/node";

// Create manager outside handler for reuse
const flags = createServerFlagsManager({
  clientId: process.env.DATABUDDY_CLIENT_ID!
});

export async function handler(event: any) {
  const userId = event.headers["x-user-id"];
  
  const result = await flags.getFlag("feature", { userId });
  
  return {
    statusCode: 200,
    body: JSON.stringify({
      enabled: result.enabled,
      variant: result.variant
    })
  };
}`}
</CodeBlock>

## Measure Server-Side Outcomes

Server-side flag evaluation does not emit browser exposure events. When a server action, API route, webhook, or job is the source of truth, track the durable outcome on the server and include the flag key and variant. If the outcome belongs to a browser session, send `anonId` and `sessionId` from the client using `getTrackingIds()` and map `anonId` to `anonymousId`.

<CodeBlock language="tsx" filename="client.tsx">
  {`import { getTrackingIds } from "@databuddy/sdk";

async function startExport(format: string) {
  const { anonId, sessionId } = getTrackingIds();

  await fetch("/api/export", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ anonId, sessionId, format })
  });
}`}
</CodeBlock>

<CodeBlock language="tsx" filename="app/api/export/route.ts">
  {`import { Databuddy, createServerFlagsManager } from "@databuddy/sdk/node";

const events = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  websiteId: process.env.DATABUDDY_WEBSITE_ID!,
  source: "server"
});

const flags = createServerFlagsManager({
  clientId: process.env.DATABUDDY_CLIENT_ID!
});

export async function POST(request: Request) {
  const { anonId, sessionId, format } = await request.json();
  const userId = request.headers.get("x-user-id") ?? undefined;

  const exportFlow = await flags.getFlag("export-flow", { userId });
  const variant = exportFlow.variant ?? "control";
  const result = await runExport({ format, variant });

  await events.track({
    name: "report_exported",
    anonymousId: anonId,
    sessionId,
    properties: {
      flag_key: "export-flow",
      variant,
      status: result.ok ? "success" : "failed",
      format: result.format
    }
  });

  await events.flush();
  return Response.json({ ok: result.ok });
}`}
</CodeBlock>

## Caching Behavior

The server manager uses stale-while-revalidate caching:

1. **Fresh cache**: Returns immediately
2. **Stale cache**: Returns immediately, revalidates in background
3. **No cache**: Fetches from API

<CodeBlock language="tsx">
  {`const flags = createServerFlagsManager({
  clientId: process.env.DATABUDDY_CLIENT_ID!,
  cacheTtl: 60_000,      // Cache valid for 1 minute
  staleTime: 30_000,     // Revalidate after 30 seconds
  maxCacheSize: 5000     // Evict old entries above this size
});

// First call: fetches from API
const result1 = await flags.getFlag("my-feature");

// Within 30s: returns cached value (fresh)
const result2 = await flags.getFlag("my-feature");

// After 30s but within 60s: returns cached, revalidates in background
const result3 = await flags.getFlag("my-feature");

// After 60s: fetches from API
const result4 = await flags.getFlag("my-feature");`}
</CodeBlock>

Expired entries are pruned during cache writes and reads. Long-lived shared managers should keep the default `maxCacheSize` or set a limit that matches their traffic shape, especially when passing per-request users.

## Request Batching

Multiple concurrent flag requests with the same API URL and targeting context are batched automatically:

<CodeBlock language="tsx">
  {`// These 3 concurrent requests become 1 API call
const [flag1, flag2, flag3] = await Promise.all([
  flags.getFlag("feature-1"),
  flags.getFlag("feature-2"),
  flags.getFlag("feature-3")
]);`}
</CodeBlock>

Requests for different users, organizations, teams, properties, or environments are intentionally sent through separate batches so evaluations cannot mix targeting contexts.

## Request Deduplication

Identical concurrent requests are deduplicated:

<CodeBlock language="tsx">
  {`// Only 1 API call is made
const [result1, result2] = await Promise.all([
  flags.getFlag("same-feature"),
  flags.getFlag("same-feature")
]);`}
</CodeBlock>

## Updating User Context

<CodeBlock language="tsx">
  {`// Update user for subsequent calls
flags.updateUser({
  userId: "new-user",
  organizationId: "org-456",
  properties: { role: "admin" }
});

// Refresh flags for new user
await flags.refresh();`}
</CodeBlock>

## Manager Lifecycle

### waitForInit()

Wait for the manager to initialize:

<CodeBlock language="tsx">
  {`const flags = createServerFlagsManager({
  clientId: process.env.DATABUDDY_CLIENT_ID!,
  autoFetch: true
});

await flags.waitForInit();
// Now all flags are cached`}
</CodeBlock>

### isReady()

Check if the manager is ready:

<CodeBlock language="tsx">
  {`if (flags.isReady()) {
  // Safe to use synchronous reads
  const state = flags.isEnabled("my-feature");
}`}
</CodeBlock>

### destroy()

Clean up resources:

<CodeBlock language="tsx">
  {`flags.destroy();`}
</CodeBlock>

## Singleton Pattern

For most applications, create a single manager instance:

<CodeBlock language="tsx" filename="lib/flags.ts">
  {`import { createServerFlagsManager } from "@databuddy/sdk/node";

let flagsManager: ReturnType<typeof createServerFlagsManager> | null = null;

export function getFlags() {
  if (!flagsManager) {
    flagsManager = createServerFlagsManager({
      clientId: process.env.DATABUDDY_CLIENT_ID!,
      autoFetch: true,
      maxCacheSize: 5000
    });
  }
  return flagsManager;
}`}
</CodeBlock>

<CodeBlock language="tsx" filename="In your routes">
  {`import { getFlags } from "@/lib/flags";

const flags = getFlags();
const result = await flags.getFlag("my-feature", { userId });`}
</CodeBlock>

## TypeScript Types

<CodeBlock language="tsx">
  {`import {
  createServerFlagsManager,
  ServerFlagsManager,
  type FlagsConfig,
  type FlagResult,
  type FlagState,
  type UserContext
} from "@databuddy/sdk/node";`}
</CodeBlock>

## Related

<Cards>
  <Card title="Feature Flags (Client)" href="/docs/sdk/feature-flags">
    React and Vue feature flag hooks
  </Card>
  <Card title="Node SDK" href="/docs/sdk/node">
    Server-side event tracking
  </Card>
  <Card title="Configuration" href="/docs/sdk/configuration">
    All configuration options
  </Card>
</Cards>

---

# Tracker Helpers

> Helper functions for tracking events, managing sessions, and cross-domain attribution

import { Callout, CodeBlock, Card, Cards } from "@/components/docs";

The Databuddy SDK exports helper functions for tracking events, managing sessions, and cross-domain attribution. These work in any JavaScript environment where the Databuddy script is loaded.

<Callout type="info">
  **Import**: `import { track, flush, getAnonymousId, ... } from "@databuddy/sdk"`
</Callout>

## Event Tracking

### track(name, properties)

Track custom events with optional properties. Safe to call on server (no-op) or before tracker loads.

<CodeBlock language="tsx">
  {`import { track } from "@databuddy/sdk";

// Simple event
track("signup_started");

// Event with properties
track("item_purchased", {
  itemId: "sku-123",
  price: 29.99,
  currency: "USD"
});

// In a React component
function CheckoutButton() {
  return (
    <button onClick={() => track("checkout_clicked", { cartSize: 3 })}>
      Checkout
    </button>
  );
}`}
</CodeBlock>

### trackError(message, properties)

Track error events. Convenience wrapper around `track("error", ...)`.

<CodeBlock language="tsx">
  {`import { trackError } from "@databuddy/sdk";

try {
  await riskyOperation();
} catch (error) {
  trackError(error.message, {
    stack: error.stack,
    error_type: error.name,
    context: "checkout_flow"
  });
}`}
</CodeBlock>

<Callout type="info">
  **Note:** `trackError()` is for *manual* error tracking. To automatically capture all JavaScript errors, use the `trackErrors` prop on the `<Databuddy />` component.
</Callout>

## Session Management

### clear()

Clears the current user session and generates new anonymous/session IDs. Use after logout to ensure the next user gets a fresh identity.

<CodeBlock language="tsx">
  {`import { clear } from "@databuddy/sdk";

async function handleLogout() {
  await signOut();
  clear(); // Reset tracking identity
  router.push("/login");
}`}
</CodeBlock>

### flush()

Forces all queued events to be sent immediately. Useful before navigation or when you need to ensure events are captured.

<CodeBlock language="tsx">
  {`import { track, flush } from "@databuddy/sdk";

function handleExternalLink(url: string) {
  track("external_link_clicked", { url });
  flush(); // Ensure event is sent before leaving
  window.location.href = url;
}`}
</CodeBlock>

## Identity & Attribution

### getAnonymousId(urlParams?)

Gets the anonymous user ID. Persists across sessions via localStorage. Useful for server-side identification or cross-domain tracking.

**Priority**: URL params → localStorage

<CodeBlock language="tsx">
  {`import { getAnonymousId } from "@databuddy/sdk";

// Get from storage
const anonId = getAnonymousId();

// Check URL params first (for cross-domain tracking)
const params = new URLSearchParams(window.location.search);
const anonId = getAnonymousId(params);

// Pass to server
await fetch("/api/identify", {
  body: JSON.stringify({ anonId })
});`}
</CodeBlock>

### getSessionId(urlParams?)

Gets the current session ID. Resets after 30 minutes of inactivity. Useful for correlating events within a single browsing session.

**Priority**: URL params → sessionStorage

<CodeBlock language="tsx">
  {`import { getSessionId } from "@databuddy/sdk";

const sessionId = getSessionId();
console.log("Current session:", sessionId);`}
</CodeBlock>

### getTrackingIds(urlParams?)

Gets both anonymous ID and session ID in a single call.

<CodeBlock language="tsx">
  {`import { getTrackingIds } from "@databuddy/sdk";

const { anonId, sessionId } = getTrackingIds();

// Send to your backend
await api.identify({ anonId, sessionId, userId: user.id });`}
</CodeBlock>

### getTrackingParams(urlParams?)

Returns tracking IDs as a URL query string for cross-domain tracking. Append to URLs when linking to other domains you own.

<CodeBlock language="tsx">
  {`import { getTrackingParams } from "@databuddy/sdk";

// Link to subdomain with tracking continuity
const params = getTrackingParams();
const url = \`https://app.example.com/dashboard\${params ? \`?\${params}\` : ""}\`;

// In a component
<a href={\`https://shop.example.com?\${getTrackingParams()}\`}>
  Visit Shop
</a>`}
</CodeBlock>

**Returns**: `"anonId=xxx&sessionId=yyy"` or empty string if unavailable.

## Tracker Instance

### isTrackerAvailable()

Checks if the Databuddy tracker script has loaded and is available. Use this before calling tracking functions in conditional scenarios.

<CodeBlock language="tsx">
  {`import { isTrackerAvailable, track } from "@databuddy/sdk";

if (isTrackerAvailable()) {
  track("feature_used", { feature: "export" });
}`}
</CodeBlock>

### getTracker()

Returns the raw Databuddy tracker instance for advanced use cases. Prefer using the exported functions instead.

<CodeBlock language="tsx">
  {`import { getTracker } from "@databuddy/sdk";

const tracker = getTracker();
if (tracker) {
  // Access tracker methods directly
  tracker.track("event", { prop: "value" });
  tracker.setGlobalProperties({ plan: "premium" });
  tracker.screenView({ section: "dashboard" });
}`}
</CodeBlock>

## Cross-Domain Tracking

When users navigate between domains you own, use tracking params to maintain identity:

<CodeBlock language="tsx">
  {`import { getTrackingParams, getAnonymousId, getSessionId } from "@databuddy/sdk";

// Method 1: URL params (recommended)
function redirectToApp() {
  const params = getTrackingParams();
  window.location.href = \`https://app.mysite.com/signup?\${params}\`;
}

// Method 2: Manual construction
function redirectWithIds() {
  const anonId = getAnonymousId();
  const sessionId = getSessionId();
  
  const url = new URL("https://app.mysite.com/signup");
  if (anonId) url.searchParams.set("anonId", anonId);
  if (sessionId) url.searchParams.set("sessionId", sessionId);
  
  window.location.href = url.toString();
}`}
</CodeBlock>

On the destination page, the tracker automatically reads `anonId` and `sessionId` from URL params.

## Server-Side Integration

Pass tracking IDs to your backend for server-side event attribution:

<CodeBlock language="tsx" filename="Client-side">
  {`import { getTrackingIds } from "@databuddy/sdk";

const { anonId, sessionId } = getTrackingIds();

await fetch("/api/projects", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    anonId,
    sessionId,
    projectType: "template"
  })
});`}
</CodeBlock>

<CodeBlock language="tsx" filename="Server-side (Node SDK)">
  {`import { Databuddy } from "@databuddy/sdk/node";

const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  websiteId: process.env.DATABUDDY_WEBSITE_ID!
});

export async function POST(request: Request) {
  const { anonId, sessionId, projectType } = await request.json();
  
  await client.track({
    name: "project_created",
    anonymousId: anonId,
    sessionId: sessionId,
    properties: { project_type: projectType },
    source: "server"
  });

  await client.flush();
  
  return Response.json({ success: true });
}`}
</CodeBlock>

## Best Practices

### Use snake_case for Event Names

<CodeBlock language="tsx">
  {`// ✅ Good
track("signup_completed");
track("product_viewed");
track("purchase_completed");

// ❌ Avoid
track("signupCompleted");
track("ProductViewed");
track("PURCHASE_COMPLETED");`}
</CodeBlock>

### Avoid PII in Properties

<CodeBlock language="tsx">
  {`// ✅ Good
track("purchase_completed", {
  order_id: "ORD-123",
  total: 99.99,
  currency: "USD"
});

// ❌ Avoid PII
track("purchase_completed", {
  email: "user@example.com",  // PII
  credit_card: "4242..."      // Sensitive
});`}
</CodeBlock>

### Always Flush Before Navigation

<CodeBlock language="tsx">
  {`function handleExternalRedirect(url: string) {
  track("redirect_clicked", { destination: url });
  flush();  // Ensure event is sent
  window.location.href = url;
}`}
</CodeBlock>

## TypeScript Types

<CodeBlock language="tsx">
  {`import { 
  track, 
  getTracker,
  type DatabuddyTracker 
} from "@databuddy/sdk";

// Tracker instance type
const tracker: DatabuddyTracker | null = getTracker();`}
</CodeBlock>

## Related

<Cards>
  <Card title="React SDK" href="/docs/sdk/react">
    React component and integration
  </Card>
  <Card title="Node SDK" href="/docs/sdk/node">
    Server-side tracking for APIs and webhooks
  </Card>
  <Card title="Configuration" href="/docs/sdk/configuration">
    All configuration options
  </Card>
</Cards>

---

# Vanilla JavaScript

> Use the Databuddy script directly in vanilla JavaScript without any framework

import { Callout, Card, Cards, CodeBlock } from "@/components/docs";

The Databuddy script can be used directly in vanilla JavaScript projects without any framework dependencies. This is perfect for static sites, traditional HTML pages, or any environment where you want minimal overhead.

<Callout type="info">
  **CDN**: `https://cdn.databuddy.cc/databuddy.js` | **Size**: under 30 KB minified
</Callout>

## Quick Setup

Add the script tag to your HTML before the closing `</body>` tag:

<CodeBlock language="html">
  {`<script
  src="https://cdn.databuddy.cc/databuddy.js"
  data-client-id="your-client-id"
  async
></script>`}
</CodeBlock>

The script automatically initializes and starts tracking page views.

## Configuration Options

Configure the tracker using data attributes on the script tag:

<CodeBlock language="html">
  {`<script
  src="https://cdn.databuddy.cc/databuddy.js"
  data-client-id="your-client-id"
  data-track-performance
  data-track-errors
  data-enable-batching
  data-batch-size="20"
  async
></script>`}
</CodeBlock>

## Available Options

| Option | Default | Description |
|--------|---------|-------------|
| `data-track-hash-changes` | `false` | Track hash changes in URL |
| `data-track-attributes` | `false` | Track data-track attributes |
| `data-track-outgoing-links` | `false` | Track clicks on external links |
| `data-track-interactions` | `false` | Track button clicks and form submissions |
| `data-track-performance` | `true` | Include performance metrics |
| `data-track-web-vitals` | `false` | Track Core Web Vitals |
| `data-track-errors` | `false` | Track JavaScript errors |
| `data-enable-batching` | `true` | Batch events before sending |
| `data-batch-size` | `10` | Events per batch when batching enabled |
| `data-batch-timeout` | `5000` | Batch timeout in ms |
| `data-sampling-rate` | `1.0` | Sample rate 0.0-1.0 |

## Tracking Events

### Manual Tracking

Use the global `window.databuddy` object to track custom events:

<CodeBlock language="jsx">
  {`// Track a custom event
window.databuddy.track('button_click', {
  button_text: 'Sign Up',
  button_id: 'header-cta'
});

// Track with additional properties
window.databuddy.track('feature_used', {
  feature: 'export_data',
  user_tier: 'premium'
});`}
</CodeBlock>

### Screen Views

Manually track page views (useful for SPAs):

<CodeBlock language="jsx">
  {`window.databuddy.screenView({
  section: 'dashboard'
});

// With properties
window.databuddy.screenView({
  section: 'analytics'
});`}
</CodeBlock>

### Using Shorthand

For convenience, use the shorter `window.db` alias:

<CodeBlock language="jsx">
  {`window.db.track('button_click', { button_id: 'cta' });
window.db.screenView({ section: 'dashboard' });`}
</CodeBlock>

## Getting Tracking IDs

The tracker provides access to anonymous and session IDs for attribution tracking:

<CodeBlock language="jsx">
  {`// Get anonymous ID
const anonId = localStorage.getItem("did");
console.log('Anonymous ID:', anonId);

// Get session ID
const sessionId = sessionStorage.getItem("did_session");
console.log('Session ID:', sessionId);

// Use for redirect attribution
const signupUrl = \`https://app.databuddy.cc/register?anonId=\${anonId}&sessionId=\${sessionId}\`;
window.location.href = signupUrl;`}
</CodeBlock>

### Reading from Storage

IDs are also stored in browser storage for persistence:

<CodeBlock language="jsx">
  {`// Get from localStorage
const anonId = localStorage.getItem('did');

// Get from sessionStorage
const sessionId = sessionStorage.getItem('did_session');`}
</CodeBlock>

## Global Properties

Set properties that attach to all future events:

<CodeBlock language="jsx">
  {`window.databuddy.setGlobalProperties({
  user_id: 'user-123',
  plan: 'premium',
  version: '2.0'
});`}
</CodeBlock>

## Session Management

### Clear Session

Clear current session and generate new IDs:

<CodeBlock language="jsx">
  {`window.databuddy.clear();`}
</CodeBlock>

### Flush Events

Force immediate sending of queued events:

<CodeBlock language="jsx">
  {`window.databuddy.flush();`}
</CodeBlock>

## Error Tracking

Automatically track JavaScript errors:

<CodeBlock language="html">
  {`<script
  src="https://cdn.databuddy.cc/databuddy.js"
  data-client-id="your-client-id"
  data-track-errors
  async
></script>`}
</CodeBlock>

Or track manually:

<CodeBlock language="jsx">
  {`try {
  // Your code
} catch (error) {
  window.databuddy.track('error', {
    message: error.message,
    filename: error.filename,
    lineno: error.lineno,
    error_type: error.name
  });
}`}
</CodeBlock>

## Data Attributes Tracking

Track clicks on elements with `data-track` attributes:

<CodeBlock language="html">
  {`<!-- Enable tracking -->
<script
  src="https://cdn.databuddy.cc/databuddy.js"
  data-client-id="your-client-id"
  data-track-attributes
  async
></script>

<!-- Elements to track -->
<button data-track="button_click" data-button-text="Sign Up" data-section="header">
  Sign Up
</button>

<a href="/pricing" data-track="link_click" data-link-type="pricing">
  View Pricing
</a>`}
</CodeBlock>

## Outgoing Links

Track clicks on external links:

<CodeBlock language="html">
  {`<script
  src="https://cdn.databuddy.cc/databuddy.js"
  data-client-id="your-client-id"
  data-track-outgoing-links
  async
></script>

<!-- External links are automatically tracked -->
<a href="https://github.com/databuddy-analytics/Databuddy">GitHub</a>`}
</CodeBlock>

## Batch Events

Enable batching to reduce network requests:

<CodeBlock language="html">
  {`<script
  src="https://cdn.databuddy.cc/databuddy.js"
  data-client-id="your-client-id"
  data-enable-batching
  data-batch-size="20"
  data-batch-timeout="5000"
  async
></script>`}
</CodeBlock>

## Sampling

Reduce event volume with sampling:

<CodeBlock language="html">
  {`<script
  src="https://cdn.databuddy.cc/databuddy.js"
  data-client-id="your-client-id"
  data-sampling-rate="0.5"
  async
></script>`}
</CodeBlock>

Set to `0.5` to track 50% of events, `0.1` for 10%, etc.

## Skip Patterns

Skip tracking on specific paths:

<CodeBlock language="html">
  {`<script
  src="https://cdn.databuddy.cc/databuddy.js"
  data-client-id="your-client-id"
  data-skip-patterns='["/admin/**", "/internal/*"]'
  async
></script>`}
</CodeBlock>

## Opt Out

Allow users to opt out of tracking:

<CodeBlock language="jsx">
  {`// User opts out
window.databuddyOptOut();

// User opts back in
window.databuddyOptIn();`}
</CodeBlock>

Opt-out is stored in localStorage and persists across sessions.

## Complete Example

<CodeBlock language="html" filename="index.html">
  {`<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Website</title>
</head>
<body>
  <h1>Welcome</h1>
  
  <button onclick="trackSignup()">Sign Up</button>
  
  <!-- Databuddy Script -->
  <script
    src="https://cdn.databuddy.cc/databuddy.js"
    data-client-id="your-client-id"
    data-track-performance
    data-track-errors
    data-enable-batching
    async
  ></script>
  
  <script>
    function trackSignup() {
      window.databuddy.track('signup_clicked', {
        source: 'header_button',
        timestamp: Date.now()
      });
      
      // Redirect with attribution
      const anonId = localStorage.getItem("did");
      const sessionId = sessionStorage.getItem("did_session");
      window.location.href = \`https://app.databuddy.cc/register?anonId=\${anonId}&sessionId=\${sessionId}\`;
    }
  </script>
</body>
</html>`}
</CodeBlock>

## Browser Compatibility

The script works in all modern browsers:
- Chrome, Edge, Firefox, Safari (latest 2 versions)
- Mobile browsers (iOS Safari, Chrome Mobile)
- No Internet Explorer support

## CDN URL

Use the official CDN URL for best performance:

<CodeBlock language="text">
  {`https://cdn.databuddy.cc/databuddy.js`}
</CodeBlock>

The script is automatically minified and gzipped for optimal performance (under 30 KB minified).

## Related

<Cards>
  <Card title="Tracker Helpers" href="/docs/sdk/tracker">
    Helper functions for the SDK
  </Card>
  <Card title="Configuration" href="/docs/sdk/configuration">
    All configuration options
  </Card>
  <Card title="React SDK" href="/docs/sdk/react">
    React component integration
  </Card>
</Cards>

---

# Vue

> Vue component and composables for tracking analytics in Vue applications

import { Callout, CodeBlock, Card, Cards } from "@/components/docs";

The Databuddy Vue SDK provides a component for script injection and composables for feature flags. Works with Vue 3.

<Callout type="info">
  Using Nuxt? The dedicated **[`@databuddy/nuxt`](/docs/sdk/nuxt)** module is a better fit — zero config, auto-imports, and no `app.vue` changes needed.
</Callout>

<Callout type="info">
  **Package**: `@databuddy/sdk` | **Import**: `@databuddy/sdk/vue`
</Callout>

## Installation

<CodeBlock language="bash">
  {`bun add @databuddy/sdk`}
</CodeBlock>

## Quick Start

Add the `Databuddy` component to your App.vue:

<CodeBlock language="html" filename="App.vue">
  {`<script setup>
import { Databuddy } from "@databuddy/sdk/vue";

const clientId = import.meta.env.VITE_DATABUDDY_CLIENT_ID;
</script>

<template>
  <Databuddy
    :client-id="clientId"
    track-web-vitals
    track-errors
  />
  <RouterView />
</template>`}
</CodeBlock>

## Databuddy Component

The `Databuddy` component injects the tracking script and configures analytics.

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `client-id` | `string` | Required | Your client ID |
| `disabled` | `boolean` | `false` | Disable all tracking |
| `debug` | `boolean` | `false` | Enable debug logging |

### Tracking Features

| Prop | Default | Description |
|------|---------|-------------|
| `track-performance` | `true` | Page load times and navigation timing |
| `track-web-vitals` | `false` | Core Web Vitals (LCP, FID, CLS, TTFB) |
| `track-errors` | `false` | JavaScript errors and unhandled rejections |
| `track-outgoing-links` | `false` | Clicks on external links |
| `track-interactions` | `false` | Button clicks and form submissions |
| `track-attributes` | `false` | Elements with `data-track` attributes |

### Batching & Performance

| Prop | Default | Description |
|------|---------|-------------|
| `enable-batching` | `true` | Group events into batches |
| `batch-size` | `10` | Events per batch (1-50) |
| `batch-timeout` | `5000` | Batch timeout in ms |
| `sampling-rate` | `1.0` | Event sampling rate (0.0-1.0) |

### Full Example

<CodeBlock language="html" filename="App.vue">
  {`<script setup>
import { Databuddy } from "@databuddy/sdk/vue";

const clientId = import.meta.env.VITE_DATABUDDY_CLIENT_ID;
const isDevelopment = import.meta.env.DEV;
</script>

<template>
  <Databuddy
    :client-id="clientId"
    :track-web-vitals="true"
    :track-errors="true"
    :track-outgoing-links="true"
    :enable-batching="true"
    :batch-size="20"
    :disabled="isDevelopment"
  />
  <RouterView />
</template>`}
</CodeBlock>

## Tracking Events

Use the global tracker or import helpers:

<CodeBlock language="html">
  {`<script setup>
import { track } from "@databuddy/sdk";

function handleSignup() {
  track("signup_clicked", {
    source: "header",
    plan: "pro"
  });
}
</script>

<template>
  <button @click="handleSignup">Sign Up</button>
</template>`}
</CodeBlock>

### Using window.databuddy

You can also use the global tracker directly:

<CodeBlock language="html">
  {`<script setup>
function handleClick() {
  window.databuddy?.track("button_clicked", {
    button_id: "cta"
  });
}
</script>`}
</CodeBlock>

## Environment Setup

<CodeBlock language="bash" filename=".env">
  {`VITE_DATABUDDY_CLIENT_ID=your-client-id-here`}
</CodeBlock>

## Feature Flags

Vue has built-in feature flag support:

<CodeBlock language="html">
  {`<script setup>
import { useFlag } from "@databuddy/sdk/vue";

const darkMode = useFlag("dark-mode");
</script>

<template>
  <div :class="{ dark: darkMode.on }">
    <template v-if="darkMode.loading">
      <Skeleton />
    </template>
    <template v-else>
      <MainContent />
    </template>
  </div>
</template>`}
</CodeBlock>

See [Feature Flags](/docs/sdk/feature-flags) for complete Vue flag documentation.

## Privacy Patterns

### Skip Patterns

<CodeBlock language="html">
  {`<Databuddy
  :client-id="clientId"
  :skip-patterns="['/admin/**', '/internal/*']"
/>`}
</CodeBlock>

### Mask Patterns

<CodeBlock language="html">
  {`<Databuddy
  :client-id="clientId"
  :mask-patterns="['/users/*', '/orders/**']"
/>`}
</CodeBlock>

## Related

<Cards>
  <Card title="Nuxt Module" href="/docs/sdk/nuxt">
    Zero-config Nuxt integration with auto-imports and no app.vue changes
  </Card>
  <Card title="Tracker Helpers" href="/docs/sdk/tracker">
    track, flush, getAnonymousId, and more helper functions
  </Card>
  <Card title="Feature Flags" href="/docs/sdk/feature-flags">
    Feature flag composables for Vue
  </Card>
  <Card title="Configuration" href="/docs/sdk/configuration">
    Complete configuration reference
  </Card>
</Cards>

---

## API Reference

# Authentication

> API keys, scopes, and authentication methods for the Databuddy API

import { Callout, CodeBlock } from "@/components/docs";

All API requests require authentication. Databuddy supports two authentication methods: API keys for server-side integrations and session cookies for browser-based apps.

## API Key Authentication

Use your API key in the `x-api-key` header:

<CodeBlock 
  language="bash"
  code={`curl -H "x-api-key: dbdy_your_api_key_here" \\
  https://api.databuddy.cc/v1/query/websites`}
/>

Alternatively, use Bearer token format:

<CodeBlock 
  language="bash"
  code={`curl -H "Authorization: Bearer dbdy_your_api_key_here" \\
  https://api.databuddy.cc/v1/query/websites`}
/>

## Getting an API Key

1. Go to **[Dashboard → Organization Settings → API Keys](https://app.databuddy.cc/organizations/settings#api-keys)**
2. Click **Create API Key**
3. Enter a descriptive name (e.g., "Production Server", "CI Pipeline")
4. Select the required scopes
5. Optionally restrict access to specific websites
6. Copy and securely store your key — it won't be shown again

<Callout type="warning">
  **Security Note**: Store API keys securely. Never commit them to version control or expose them in client-side code.
</Callout>

## Agent Auth Discovery

AI agents can discover Databuddy authentication without scraping this page:

| Resource | URL |
|----------|-----|
| auth.md walkthrough | `https://www.databuddy.cc/auth.md` |
| OAuth Protected Resource Metadata | `https://api.databuddy.cc/.well-known/oauth-protected-resource` |
| Authorization Server Metadata | `https://api.databuddy.cc/.well-known/oauth-authorization-server` |
| API catalog | `https://api.databuddy.cc/.well-known/api-catalog` |

Unauthenticated protected API probes return a `WWW-Authenticate` header with `Bearer resource_metadata="https://api.databuddy.cc/.well-known/oauth-protected-resource"`. Agents should fetch that metadata, choose the smallest required scope, and then retry with `x-api-key` or `Authorization: Bearer`.

## API Key Scopes

Scopes control what actions an API key can perform:

| Scope | Permission |
|-------|------------|
| `read:data` | Query analytics data **and list accessible websites** — covers `POST /v1/query`, `POST /v1/query/compile`, `POST /v1/query/custom`, and `GET /v1/query/websites` |
| `track:events` | Send custom events via `POST /track` |
| `read:links` | Read short links and their analytics |
| `write:links` | Create, update, and delete short links |
| `manage:websites` | Create, update, and delete websites |
| `manage:flags` | Manage feature flags and targeting rules |
| `manage:config` | Integration config and organization settings |

<Callout type="info">
  Most integrations only need `read:data`. Add `track:events` if you also send events server-side.
</Callout>

## Access Levels

API keys can have two access levels:

### Global Access

Access all websites in your account or organization. Best for:
- Internal dashboards
- Automated reporting
- Organization-wide analytics

### Website-Specific Access

Access only specified websites. Best for:
- Third-party integrations
- Client-specific keys
- Least-privilege security

## Session Cookie Authentication

Browser-based applications using the Databuddy dashboard session can authenticate automatically via cookies. This works when:

- Users are logged into the Databuddy dashboard
- Requests include `credentials: 'include'`
- Requests originate from `*.databuddy.cc` domains

<CodeBlock 
  language="typescript"
  code={`fetch('https://api.databuddy.cc/v1/query/websites', {
  credentials: 'include'
})`}
/>

## Choosing an Authentication Method

| Use Case | Recommended Method |
|----------|-------------------|
| Server-to-server integration | API Key (`x-api-key`) |
| CI/CD pipelines | API Key (`x-api-key`) |
| Custom dashboards (server-side) | API Key (`x-api-key`) |
| Browser apps on your domain | Session Cookie |
| Third-party applications | API Key with limited scope |

## Authentication Errors

| Error Code | Meaning |
|------------|---------|
| `AUTH_REQUIRED` | No API key or session provided |
| `ACCESS_DENIED` | Valid auth but no access to requested resource |
| `INVALID_API_KEY` | API key is invalid, expired, or revoked |
| `INSUFFICIENT_SCOPE` | API key lacks required scope |

Example error response:

<CodeBlock 
  language="json"
  code={`{
  "success": false,
  "error": "Authentication required",
  "code": "AUTH_REQUIRED"
}`}
/>

## Best Practices

1. **Use environment variables** for API keys in code
2. **Rotate keys regularly** — especially if team members leave
3. **Use minimal scopes** — only request permissions you need
4. **Set expiration dates** for temporary integrations
5. **Monitor usage** — check API key activity in the dashboard

---

# Custom Queries

> Build advanced analytics queries with custom aggregations and filters

import { Callout, CodeBlock } from "@/components/docs";

<Callout type="warning">
  **Advanced Feature**: Custom queries give you direct access to the analytics database. Use standard query types when possible for better performance and caching.
</Callout>

The Custom Query API lets you build queries with custom aggregations, filters, and groupings when the standard query types don't meet your needs.

## Endpoint

<CodeBlock 
  language="http"
  code="POST /v1/query/custom?website_id={id}"
/>

## Request Format

<CodeBlock 
  language="json"
  code={`{
  "query": {
    "table": "events",
    "selects": [
      {
        "field": "path",
        "aggregate": "count",
        "alias": "pageviews"
      },
      {
        "field": "anonymous_id",
        "aggregate": "uniq",
        "alias": "unique_visitors"
      }
    ],
    "filters": [
      {
        "field": "country",
        "operator": "eq",
        "value": "US"
      }
    ],
    "groupBy": ["path"]
  },
  "startDate": "2024-01-01",
  "endDate": "2024-01-31",
  "timezone": "America/New_York",
  "limit": 100
}`}
/>

## Request Fields

### Root Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `query` | object | Yes | Query configuration |
| `startDate` | string | Yes | Start date (YYYY-MM-DD) |
| `endDate` | string | Yes | End date (YYYY-MM-DD) |
| `timezone` | string | No | Timezone (default: UTC) |
| `granularity` | string | No | `"hourly"` or `"daily"` |
| `limit` | number | No | Max rows (default: 1000, max: 10000) |

### Query Object

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `table` | string | Yes | Table to query |
| `selects` | array | Yes | Aggregations to compute (1-10) |
| `filters` | array | No | Filter conditions (max 20) |
| `groupBy` | array | No | Group by columns (max 5) |

### Select Object

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `field` | string | Yes | Column name or `"*"` for count |
| `aggregate` | string | Yes | Aggregation function |
| `alias` | string | No | Output column name |

### Aggregate Functions

| Function | Description | Field Requirements |
|----------|-------------|-------------------|
| `count` | Count rows | Any field or `"*"` |
| `uniq` | Count unique values | Any field |
| `sum` | Sum values | Numeric fields only |
| `avg` | Average value | Numeric fields only |
| `max` | Maximum value | Numeric fields only |
| `min` | Minimum value | Numeric fields only |

### Filter Operators

| Operator | Description |
|----------|-------------|
| `eq` | Equals |
| `ne` | Not equals |
| `gt` | Greater than |
| `lt` | Less than |
| `gte` | Greater than or equal |
| `lte` | Less than or equal |
| `contains` | Contains substring |
| `not_contains` | Does not contain |
| `starts_with` | Starts with prefix |
| `in` | Value in array |
| `not_in` | Value not in array |

## Available Tables

| Table | Description |
|-------|-------------|
| `events` | Page views and custom events |
| `sessions` | Session-level data |
| `profiles` | User profile data |
| `errors` | JavaScript errors |
| `performance` | Web vitals and performance metrics |

## Example Queries

### Top Pages by Unique Visitors

<CodeBlock 
  language="json"
  code={`{
  "query": {
    "table": "events",
    "selects": [
      {"field": "path", "aggregate": "count", "alias": "views"},
      {"field": "anonymous_id", "aggregate": "uniq", "alias": "visitors"}
    ],
    "groupBy": ["path"]
  },
  "startDate": "2024-01-01",
  "endDate": "2024-01-31",
  "limit": 20
}`}
/>

### Average Session Duration by Country

<CodeBlock 
  language="json"
  code={`{
  "query": {
    "table": "sessions",
    "selects": [
      {"field": "duration", "aggregate": "avg", "alias": "avg_duration"},
      {"field": "*", "aggregate": "count", "alias": "session_count"}
    ],
    "groupBy": ["country"]
  },
  "startDate": "2024-01-01",
  "endDate": "2024-01-31",
  "limit": 50
}`}
/>

### Error Count by Browser

<CodeBlock 
  language="json"
  code={`{
  "query": {
    "table": "errors",
    "selects": [
      {"field": "*", "aggregate": "count", "alias": "error_count"},
      {"field": "message", "aggregate": "uniq", "alias": "unique_errors"}
    ],
    "filters": [
      {"field": "level", "operator": "eq", "value": "error"}
    ],
    "groupBy": ["browser_name"]
  },
  "startDate": "2024-01-01",
  "endDate": "2024-01-31"
}`}
/>

## Response Format

<CodeBlock 
  language="json"
  code={`{
  "success": true,
  "data": [
    {
      "path": "/",
      "views": 1250,
      "visitors": 890
    },
    {
      "path": "/pricing",
      "views": 450,
      "visitors": 380
    }
  ],
  "meta": {
    "rowCount": 2,
    "executionTime": 45
  }
}`}
/>

## Validation Errors

Custom queries are validated against the schema. Common errors:

<CodeBlock 
  language="json"
  code={`{
  "success": false,
  "error": "Invalid column \\"invalid_field\\" for table \\"events\\""
}`}
/>

| Error | Cause |
|-------|-------|
| Invalid table | Table name not in allowed list |
| Invalid column | Column doesn't exist on table |
| Column not filterable | Column can't be used in filters |
| Column not aggregatable | Column can't use that aggregate function |
| Too many selects | More than 10 select expressions |
| Too many filters | More than 20 filters |
| Too many groupBy | More than 5 group by fields |

## Rate Limits

Custom queries have stricter rate limits due to their computational cost:

- **30 requests per minute** (vs 200+ for standard queries)

Use standard query types when possible for better performance and higher rate limits.

---

# Error Handling

> API error codes, formats, and troubleshooting

import { CodeBlock } from "@/components/docs";

All API errors follow a consistent format with clear error codes, messages, and request IDs for debugging.

## Error Response Format

<CodeBlock
  language="json"
  code={`{
  "success": false,
  "error": "Human-readable error message",
  "code": "ERROR_CODE",
  "requestId": "req_abc123def456"
}`}
/>

## Validation Errors

Validation errors include detailed field-level information:

<CodeBlock
  language="json"
  code={`{
  "success": false,
  "error": "Unknown query type: summry. Did you mean 'summary'?",
  "code": "VALIDATION_ERROR",
  "requestId": "req_abc123def456",
  "details": [
    {
      "field": "parameters[0]",
      "message": "Unknown query type: summry",
      "suggestion": "Did you mean 'summary'?"
    }
  ]
}`}
/>

The `details` array contains all validation issues found, with suggestions when available.

## Error Codes

### Authentication Errors (4xx)

| Code | HTTP Status | Description |
|------|-------------|-------------|
| `AUTH_REQUIRED` | 401 | No authentication provided |
| `INVALID_API_KEY` | 401 | API key is invalid, expired, or revoked |
| `ACCESS_DENIED` | 403 | Authenticated but lacks permission |
| `INSUFFICIENT_SCOPE` | 403 | API key missing required scope |

### Request Errors (4xx)

| Code | HTTP Status | Description |
|------|-------------|-------------|
| `MISSING_PROJECT_ID` | 400 | No `website_id`, `link_id`, or `schedule_id` provided |
| `MISSING_WEBSITE_ID` | 400 | Endpoint requires `website_id` parameter |
| `VALIDATION_ERROR` | 400 | Request body failed validation |
| `INVALID_QUERY_TYPE` | 400 | Unknown query type in `parameters` |
| `INVALID_DATE_RANGE` | 400 | Invalid or missing date range |
| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests |

### Resource Errors (4xx)

| Code | HTTP Status | Description |
|------|-------------|-------------|
| `WEBSITE_NOT_FOUND` | 404 | Website doesn't exist or no access |
| `LINK_NOT_FOUND` | 404 | Link doesn't exist or no access |
| `SCHEDULE_NOT_FOUND` | 404 | Uptime schedule doesn't exist or no access |

### Server Errors (5xx)

| Code | HTTP Status | Description |
|------|-------------|-------------|
| `INTERNAL_SERVER_ERROR` | 500 | Unexpected server error |
| `DATABASE_ERROR` | 500 | Database query failed |
| `TIMEOUT` | 504 | Query took too long |

## Error Examples

### Missing Authentication

401 responses include a discovery hint for agents:

<CodeBlock
  language="http"
  code={`WWW-Authenticate: Bearer resource_metadata="https://api.databuddy.cc/.well-known/oauth-protected-resource"`}
/>

<CodeBlock 
  language="json"
  code={`{
  "success": false,
  "error": "Authentication required",
  "code": "AUTH_REQUIRED"
}`}
/>

### Missing Resource Identifier

<CodeBlock 
  language="json"
  code={`{
  "success": false,
  "error": "Missing resource identifier (website_id, schedule_id, link_id, or organization_id)",
  "code": "MISSING_PROJECT_ID"
}`}
/>

### Invalid Query Type

<CodeBlock 
  language="json"
  code={`{
  "success": false,
  "error": "Unknown query type: summry",
  "code": "INVALID_QUERY_TYPE"
}`}
/>

### Rate Limit Exceeded

<CodeBlock 
  language="json"
  code={`{
  "success": false,
  "error": "Rate limit exceeded. Please try again later.",
  "code": "RATE_LIMIT_EXCEEDED",
  "limit": 200,
  "remaining": 0,
  "reset": "2024-01-01T12:00:10.000Z",
  "retryAfter": 8
}`}
/>

## Partial Success

When querying multiple parameters, individual queries can fail while others succeed:

<CodeBlock 
  language="json"
  code={`{
  "success": true,
  "data": [
    {
      "parameter": "summary",
      "success": true,
      "data": [...]
    },
    {
      "parameter": "invalid_type",
      "success": false,
      "error": "Unknown query type: invalid_type",
      "data": []
    }
  ]
}`}
/>

The top-level `success: true` indicates the request was processed. Check each parameter's `success` field for individual results.

## Troubleshooting

### "Authentication required"

- Ensure you're sending the API key in headers
- Check header format: `x-api-key: dbdy_xxx` or `Authorization: Bearer dbdy_xxx`
- Verify the API key hasn't been revoked

### "Access denied"

- Check that your API key has access to this website
- Verify the API key has the required scope (`read:data`)
- For website-specific keys, ensure the website is in the allowed list

### "Rate limit exceeded"

- Check the `retryAfter` field for when to retry
- Consider batching multiple queries into one request
- Upgrade your plan for higher limits

### "Unknown query type"

- Check spelling of the query type
- Use `GET /v1/query/types` to see available types
- Some types require specific resource IDs (e.g., `link_*` needs `link_id`)

### "Hourly granularity only supports up to 30 days"

- Reduce your date range to 30 days or less
- Use `"granularity": "daily"` for longer ranges

## Best Practices

1. **Check `success` at both levels** — Request success and parameter success

2. **Handle rate limits gracefully** — Implement exponential backoff

3. **Log error codes** — Use codes for programmatic handling, messages for debugging

4. **Validate before sending** — Check required fields client-side to avoid validation errors

---

# Event Tracking

> Send custom events programmatically via the Basket API

import { Callout, CodeBlock } from "@/components/docs";

Track custom events programmatically using the Basket API. This is useful for server-side tracking, mobile apps, or custom integrations.

## Base URL

<CodeBlock 
  language="text"
  code="https://basket.databuddy.cc"
/>

## Authentication

You can authenticate requests using either:

1. **API Key** (recommended for server-side) — Pass in the `Authorization` header
2. **Client ID** — Pass as the `website_id` query parameter or as `websiteId` in the event body

<CodeBlock 
  language="http"
  code={`# With API Key
POST /track
Authorization: Bearer your_api_key

# With Client ID
POST /track?website_id={website_id}`}
/>

<Callout type="info">
  API keys require the `track:events` scope to send events.
</Callout>

---

## Track Events

The primary endpoint for sending custom events.

<CodeBlock 
  language="http"
  code="POST /track"
/>

### Single Event

<CodeBlock 
  language="json"
  code={`{
  "name": "purchase",
  "properties": {
    "value": 99.99,
    "currency": "USD",
    "product_id": "prod_123"
  },
  "anonymousId": "anon_user_123",
  "sessionId": "session_456",
  "timestamp": 1704067200000
}`}
/>

### Batch Events

Send an array of events in a single request:

<CodeBlock 
  language="json"
  code={`[
  {
    "name": "page_view",
    "properties": { "page": "/pricing" },
    "timestamp": 1704067200000
  },
  {
    "name": "purchase",
    "properties": { "value": 99.99 },
    "timestamp": 1704067260000
  }
]`}
/>

### Response

<CodeBlock 
  language="json"
  code={`{
  "status": "success",
  "type": "custom_event",
  "count": 1
}`}
/>

### Event Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Event name (1-256 characters) |
| `properties` | object | No | Custom event properties |
| `anonymousId` | string | No | Anonymous user identifier (max 256 chars) |
| `sessionId` | string | No | Session identifier (max 256 chars) |
| `timestamp` | number \| string \| Date | No | Event timestamp (defaults to now) |
| `namespace` | string | No | Event namespace for grouping (max 64 chars) |
| `source` | string | No | Event source identifier (max 64 chars) |
| `websiteId` | string | No | Public Databuddy Client ID (same value as `data-client-id` in the tracker; string id from the dashboard, not necessarily a UUID). Alternative to `?website_id=` |

---

## Server-Side Examples

### Node.js / TypeScript

<CodeBlock 
  language="typescript"
  code={`async function trackEvent(
  name: string,
  properties?: Record<string, unknown>
) {
  const response = await fetch('https://basket.databuddy.cc/track', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer your_api_key'
    },
    body: JSON.stringify({
      name,
      properties,
      timestamp: Date.now()
    })
  });
  return response.json();
}

// Usage
await trackEvent('purchase', {
  value: 99.99,
  currency: 'USD',
  product_id: 'prod_123'
});`}
/>

### Python

<CodeBlock 
  language="python"
  code={`import requests
import time

def track_event(name: str, properties: dict = None):
    response = requests.post(
        "https://basket.databuddy.cc/track",
        headers={
            "Content-Type": "application/json",
            "Authorization": "Bearer your_api_key"
        },
        json={
            "name": name,
            "properties": properties or {},
            "timestamp": int(time.time() * 1000)
        }
    )
    return response.json()

# Usage
track_event("purchase", {
    "value": 99.99,
    "currency": "USD",
    "product_id": "prod_123"
})`}
/>

### cURL

<CodeBlock 
  language="bash"
  code={`curl -X POST https://basket.databuddy.cc/track \\
  -H "Content-Type: application/json" \\
  -H "Authorization: Bearer your_api_key" \\
  -d '{
    "name": "purchase",
    "properties": {
      "value": 99.99,
      "currency": "USD"
    }
  }'`}
/>

---

## Common Event Examples

### E-commerce Purchase

<CodeBlock 
  language="json"
  code={`{
  "name": "purchase",
  "properties": {
    "order_id": "order_123",
    "value": 149.99,
    "currency": "USD",
    "items": [
      {"sku": "SKU-001", "name": "Product A", "quantity": 2, "price": 49.99},
      {"sku": "SKU-002", "name": "Product B", "quantity": 1, "price": 50.01}
    ]
  }
}`}
/>

### User Signup

<CodeBlock 
  language="json"
  code={`{
  "name": "signup",
  "properties": {
    "method": "email",
    "plan": "free",
    "referrer": "google"
  }
}`}
/>

### Feature Usage

<CodeBlock 
  language="json"
  code={`{
  "name": "feature_used",
  "namespace": "dashboard",
  "properties": {
    "feature": "export_csv",
    "format": "xlsx"
  }
}`}
/>

### Subscription Event

<CodeBlock 
  language="json"
  code={`{
  "name": "subscription_started",
  "properties": {
    "plan": "pro",
    "billing_cycle": "annual",
    "mrr": 99
  }
}`}
/>

---

## Additional Endpoints

These endpoints are used by the JavaScript tracker SDK and are documented here for completeness. The `client_id` query value is your Databuddy Client ID.

### Web Vitals

Track Core Web Vitals metrics.

<CodeBlock 
  language="http"
  code="POST /vitals?client_id={client_id}"
/>

<CodeBlock 
  language="json"
  code={`[
  {
    "timestamp": 1704067200000,
    "path": "https://example.com/page",
    "metricName": "LCP",
    "metricValue": 2500
  }
]`}
/>

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `timestamp` | number | Yes | Unix timestamp in milliseconds |
| `path` | string | Yes | Page URL |
| `metricName` | string | Yes | One of: `FCP`, `LCP`, `CLS`, `INP`, `TTFB`, `FPS` |
| `metricValue` | number | Yes | Metric value |
| `anonymousId` | string | No | Anonymous user identifier |
| `sessionId` | string | No | Session identifier |

### Error Tracking

Track JavaScript errors.

<CodeBlock 
  language="http"
  code="POST /errors?client_id={client_id}"
/>

<CodeBlock 
  language="json"
  code={`[
  {
    "timestamp": 1704067200000,
    "path": "https://example.com/app",
    "message": "Cannot read property 'id' of undefined",
    "filename": "https://example.com/app.js",
    "lineno": 42,
    "colno": 15,
    "stack": "TypeError: ...",
    "errorType": "TypeError"
  }
]`}
/>

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `timestamp` | number | Yes | Unix timestamp in milliseconds |
| `path` | string | Yes | Page URL |
| `message` | string | Yes | Error message |
| `filename` | string | No |

---

## Additional Documentation

This single-file agent corpus is capped below 200,000 characters for one-request ingestion. Continue with the scoped indexes at https://www.databuddy.cc/docs/llms.txt, https://www.databuddy.cc/api/llms.txt, and https://www.databuddy.cc/developers/llms.txt.
