> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scanova.io/llms.txt
> Use this file to discover all available pages before exploring further.

# QR Code Analytics

> Retrieve in-depth QR Code performance analytics including scan counts, devices, operating systems, browsers, geographic insights, and time-based metrics. Supports filtering, grouping, and multiple analytic types in a single request. Authentication required.

## Description

This endpoint provides **comprehensive analytics** for one or more QR codes in your account.\
You can query by:

* **QR IDs** (specific QR codes), or
* **Tags** (group of QR codes with the same tag).

It supports a variety of **analytics types** — such as scans by date, device, browser, geography, and more — allowing you to build dashboards and reports programmatically.

## Query Parameters

| Parameter          | Type                  | Required | Description                                                                                                                                       |
| :----------------- | :-------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`             | `string`              | No       | Comma-separated list of analytics types to include in the response. Defaults to all.                                                              |
| `from`             | `string (YYYY-MM-DD)` | Yes      | Start date for analytics data (inclusive).                                                                                                        |
| `to`               | `string (YYYY-MM-DD)` | Yes      | End date for analytics data (inclusive). Defaults to current date.                                                                                |
| `exclude_bot_scan` | `boolean`             | No       | When `true`, bot scans are excluded from all aggregations and returned separately under the `bot_scans` key in the response. Defaults to `false`. |

### Supported `type` Values

| Type           | Description                                       |
| :------------- | :------------------------------------------------ |
| `count`        | Scan count overview (total, unique, trends)       |
| `qr`           | QR code-level summary                             |
| `device`       | Breakdown by device type (e.g., Mobile, PC)       |
| `os`           | Breakdown by operating system                     |
| `browser`      | Breakdown by browser type                         |
| `date`         | Time-series data by date                          |
| `handset`      | Breakdown by handset or device model              |
| `geography`    | Country/region-level distribution                 |
| `geo_location` | Detailed city-level location data                 |
| `day_time`     | Hourly and day-based scan distribution            |
| `age`          | Age group data (if enabled in analytics settings) |

## Request Body

| Field       | Type     | Required | Description                                                   |
| :---------- | :------- | :------- | :------------------------------------------------------------ |
| `filter_by` | `string` | Yes      | Specify filter type — either `qrid` or `tags` or `folder`.    |
| `q`         | `array`  | Yes      | List of QR IDs or tags or folder ids to filter analytics for. |

## Examples

### **Get QR Code Analytics by QR ID**

```bash theme={null}
curl -X POST "https://management.scanova.io/analytics/qr/?from=2025-02-01&to=2025-02-25&type=count,device,os" \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter_by": "qrid",
    "q": ["Qf94b25d768294148"]
  }'
```

### **Get Analytics by Tags**

```bash theme={null}
curl -X POST "https://management.scanova.io/analytics/qr/?from=2025-02-01&to=2025-02-25&type=count,geography" \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter_by": "tags",
    "q": ["marketing", "campaign"]
  }'
```

### **Get Multiple QR Codes Analytics**

```bash theme={null}
curl -X POST "https://management.scanova.io/analytics/qr/?from=2025-01-01&to=2025-01-31&type=count,device,os,browser,date" \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter_by": "qrid",
    "q": ["Qf94b25d768294148", "Qf94b25d768294149", "Qf94b25d768294150"]
  }'
```

### **Get All Analytics Types**

```bash theme={null}
curl -X POST "https://management.scanova.io/analytics/qr/?from=2025-02-01&to=2025-02-25&type=count,qr,device,os,browser,date,handset,geography,geo_location" \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter_by": "qrid",
    "q": ["Qf94b25d768294148"]
  }'
```

### **Exclude Bot Scans**

```bash theme={null}
curl -X POST "https://management.scanova.io/analytics/qr/?from=2025-02-01&to=2025-02-25&type=count,device&exclude_bot_scan=true" \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter_by": "qrid",
    "q": ["Qf94b25d768294148"]
  }'
```

## Response Structure

### **Success Response (200 OK)**

The response structure varies based on the `type` query parameter. Response keys depend on the requested analytics types.

#### **Complete Analytics Response (all types requested)**

```json theme={null}
{
  "qr": [
    ["QR Code", 1]
  ],
  "count": {
    "total": 1,
    "elevation": {
      "percent": 100,
      "previous": 2,
      "type": "-",
      "period": {
        "start": "2025-10-11T18:07:00Z",
        "end": "2025-10-18T18:07:00Z"
      }
    },
    "unique": 1,
    "unique_elevation": {
      "percent": 100,
      "previous": 2,
      "type": "-",
      "period": {
        "start": "2025-10-11T18:07:00Z",
        "end": "2025-10-18T18:07:00Z"
      }
    },
    "scan_qr": 0,
    "scan_url": 1
  },
  "device": [
    ["PC", 1]
  ],
  "os": [
    ["Mac OS X", 1]
  ],
  "date": {
    "all": [
      ["2025-10-18", 0],
      ["2025-10-19", 0],
      ["2025-10-20", 0],
      ["2025-10-21", 0],
      ["2025-10-22", 0],
      ["2025-10-23", 0],
      ["2025-10-24", 0],
      ["2025-10-25", 1],
      ["2025-10-26", 0]
    ],
    "qr": [
      ["2025-10-18", 0],
      ["2025-10-19", 0],
      ["2025-10-20", 0],
      ["2025-10-21", 0],
      ["2025-10-22", 0],
      ["2025-10-23", 0],
      ["2025-10-24", 0],
      ["2025-10-25", 0],
      ["2025-10-26", 0]
    ],
    "url": [
      ["2025-10-18", 0],
      ["2025-10-19", 0],
      ["2025-10-20", 0],
      ["2025-10-21", 0],
      ["2025-10-22", 0],
      ["2025-10-23", 0],
      ["2025-10-24", 0],
      ["2025-10-25", 1],
      ["2025-10-26", 0]
    ]
  },
  "handset": [
    ["Mac", 1]
  ],
  "browser": [
    ["Chrome", 1]
  ],
  "geography": [
    {
      "country_code": "Unknown",
      "country_name": "Unknown",
      "region": "Unknown",
      "city": "Unknown",
      "count": 1
    }
  ],
  "geo_location": [],
  "age": [],
  "day_time": [
    ["Sun", "00", 0],
    ["Sun", "01", 1],
    ["Sun", "02", 0],
    ["Mon", "09", 2],
    ["Mon", "14", 3],
    ["Tue", "18", 1],
    ["Wed", "12", 2],
    ["Thu", "16", 1],
    ["Fri", "11", 2],
    ["Sat", "20", 1]
  ]
}
```

#### **Count Analytics Only**

```json theme={null}
{
  "count": {
    "total": 1,
    "elevation": {
      "percent": 100,
      "previous": 2,
      "type": "-",
      "period": {
        "start": "2025-10-11T18:07:00Z",
        "end": "2025-10-18T18:07:00Z"
      }
    },
    "unique": 1,
    "unique_elevation": {
      "percent": 100,
      "previous": 2,
      "type": "-",
      "period": {
        "start": "2025-10-11T18:07:00Z",
        "end": "2025-10-18T18:07:00Z"
      }
    },
    "scan_qr": 0,
    "scan_url": 1
  }
}
```

#### **Device, OS, and Browser Analytics**

```json theme={null}
{
  "device": [
    ["PC", 1]
  ],
  "os": [
    ["Mac OS X", 1]
  ],
  "browser": [
    ["Chrome", 1]
  ]
}
```

#### **Analytics with Bot Scans Excluded (`exclude_bot_scan=true`)**

When `exclude_bot_scan=true` is passed, all aggregations reflect human traffic only and the response includes a `bot_scans` summary:

```json theme={null}
{
  "count": {
    "total": 85,
    "unique": 72
  },
  "device": [
    ["Mobile", 60],
    ["PC", 25]
  ],
  "bot_scans": {
    "total": 12,
    "unique": 5,
    "by_type": [
      ["Googlebot", 7],
      ["AhrefsBot", 5]
    ],
    "by_qr": [
      ["My Campaign QR", 10],
      ["Landing Page QR", 2]
    ]
  }
}
```

## Response Schema

Each analytics type returns a separate structure.

| Key            | Description                                                               | Example Format                                                 |
| :------------- | :------------------------------------------------------------------------ | :------------------------------------------------------------- |
| `count`        | Summary metrics for scans                                                 | `{ "total": 150, "unique": 120 }`                              |
| `device`       | Device type counts                                                        | `[["Mobile", 100], ["Desktop", 50]]`                           |
| `os`           | Operating systems                                                         | `[["Android", 80], ["iOS", 40]]`                               |
| `browser`      | Browser breakdown                                                         | `[["Chrome", 60], ["Safari", 30]]`                             |
| `date`         | Daily scan counts                                                         | `{ "all": [["2025-10-25", 5], ["2025-10-26", 2]] }`            |
| `geography`    | Country/region breakdown                                                  | `[{"country_name": "India", "count": 30}]`                     |
| `geo_location` | City-level data                                                           | `[{"city": "Delhi", "count": 10}]`                             |
| `day_time`     | Hourly scan distribution                                                  | `[["Mon", "09", 2], ["Tue", "14", 1]]`                         |
| `bot_scans`    | Bot scan summary — only present when `exclude_bot_scan=true` is requested | `{"total": 12, "unique": 5, "by_type": [...], "by_qr": [...]}` |

## Use Cases

* 📈 Build custom **QR code analytics dashboards**
* 🗺️ Analyze **user scan geography and device trends**
* ⏰ Study **time-based scan activity**
* 🧭 Track **campaign performance** by tags or QR IDs
* 🧮 Export **scan data summaries** for internal BI systems

## Integration Examples

### **JavaScript - Get QR Code Analytics**

```javascript theme={null}
async function getQRAnalytics(filterBy, q, fromDate, toDate, types = 'count,device,os') {
  try {
    const response = await fetch(
      `https://management.scanova.io/analytics/qr/?from=${fromDate}&to=${toDate}&type=${types}`,
      {
        method: 'POST',
        headers: {
          'Authorization': 'YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          filter_by: filterBy,
          q: q
        })
      }
    );
    
    if (response.ok) {
      const analytics = await response.json();
      return analytics;
    } else {
      throw new Error('Failed to fetch analytics');
    }
  } catch (error) {
    console.error('Error fetching analytics:', error);
    return null;
  }
}

// Usage - Get analytics by QR ID
const analytics = await getQRAnalytics('qrid', ['Qf94b25d768294148'], '2025-02-01', '2025-02-25');
console.log('Analytics data:', analytics);

// Usage - Get analytics by tags
const tagAnalytics = await getQRAnalytics('tags', ['marketing', 'campaign'], '2025-02-01', '2025-02-25', 'count,geography');

// Usage - Get multiple QR codes analytics
const multiAnalytics = await getQRAnalytics('qrid', ['Qf94b25d768294148', 'Qf94b25d768294149'], '2025-02-01', '2025-02-25', 'count,device,os,browser');
```

### **Python - Get QR Code Analytics**

```python theme={null}
import requests
from datetime import datetime, timedelta

def get_qr_analytics(filter_by, q, from_date, to_date, types='count,device,os'):
    url = "https://management.scanova.io/analytics/qr/"
    headers = {
        "Authorization": "YOUR_API_KEY",
        "Content-Type": "application/json"
    }
    
    params = {
        'from': from_date,
        'to': to_date,
        'type': types
    }
    
    data = {
        'filter_by': filter_by,
        'q': q
    }
    
    try:
        response = requests.post(url, headers=headers, params=params, json=data)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error fetching analytics: {e}")
        return None

def analyze_qr_performance(qr_ids, days=30, types='count,device,os,geography'):
    """Get analytics for the last N days"""
    to_date = datetime.now().strftime('%Y-%m-%d')
    from_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
    
    analytics = get_qr_analytics('qrid', qr_ids, from_date, to_date, types)
    if analytics:
        print(f"Analytics for QR Codes: {qr_ids}")
        print(f"Date Range: {from_date} to {to_date}")
        
        # Display analytics based on requested types
        if 'count' in types:
            print(f"\nScan Counts: {analytics.get('count', 'N/A')}")
        
        if 'device' in types:
            print(f"\nDevice Analytics: {analytics.get('device', 'N/A')}")
        
        if 'geography' in types:
            print(f"\nGeographic Analytics: {analytics.get('geography', 'N/A')}")
    
    return analytics

def get_analytics_by_tags(tags, from_date, to_date, types='count,geography'):
    """Get analytics for QR codes with specific tags"""
    return get_qr_analytics('tags', tags, from_date, to_date, types)

# Usage - Get analytics by QR ID
analytics = get_qr_analytics('qrid', ['Qf94b25d768294148'], '2025-02-01', '2025-02-25')
print("Analytics:", analytics)

# Usage - Get analytics by tags
tag_analytics = get_analytics_by_tags(['marketing', 'campaign'], '2025-02-01', '2025-02-25')

# Usage - Analyze performance for multiple QR codes
analyze_qr_performance(['Qf94b25d768294148', 'Qf94b25d768294149'], 30, 'count,device,os,browser,geography')
```

### **PHP - Get QR Code Analytics**

```php theme={null}
<?php
function getQRAnalytics($filterBy, $q, $fromDate, $toDate, $types = 'count,device,os') {
    $url = "https://management.scanova.io/analytics/qr/";
    $headers = [
        "Authorization: YOUR_API_KEY",
        "Content-Type: application/json"
    ];
    
    $params = http_build_query([
        'from' => $fromDate,
        'to' => $toDate,
        'type' => $types
    ]);
    
    $data = json_encode([
        'filter_by' => $filterBy,
        'q' => $q
    ]);
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url . '?' . $params);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($httpCode === 200) {
        return json_decode($response, true);
    } else {
        echo "Error fetching analytics: " . $response;
        return null;
    }
}

function analyzeQRPerformance($qrIds, $days = 30, $types = 'count,device,os,geography') {
    $toDate = date('Y-m-d');
    $fromDate = date('Y-m-d', strtotime("-{$days} days"));
    
    $analytics = getQRAnalytics('qrid', $qrIds, $fromDate, $toDate, $types);
    if ($analytics) {
        echo "Analytics for QR Codes: " . implode(', ', $qrIds) . "\n";
        echo "Date Range: {$fromDate} to {$toDate}\n";
        
        // Display analytics based on requested types
        if (strpos($types, 'count') !== false) {
            echo "\nScan Counts: " . json_encode($analytics['count'] ?? 'N/A') . "\n";
        }
        
        if (strpos($types, 'device') !== false) {
            echo "\nDevice Analytics: " . json_encode($analytics['device'] ?? 'N/A') . "\n";
        }
        
        if (strpos($types, 'geography') !== false) {
            echo "\nGeographic Analytics: " . json_encode($analytics['geography'] ?? 'N/A') . "\n";
        }
    }
    
    return $analytics;
}

function getAnalyticsByTags($tags, $fromDate, $toDate, $types = 'count,geography') {
    return getQRAnalytics('tags', $tags, $fromDate, $toDate, $types);
}

// Usage - Get analytics by QR ID
$analytics = getQRAnalytics('qrid', ['Qf94b25d768294148'], '2025-02-01', '2025-02-25');
echo "Analytics: " . json_encode($analytics) . "\n";

// Usage - Get analytics by tags
$tagAnalytics = getAnalyticsByTags(['marketing', 'campaign'], '2025-02-01', '2025-02-25');

// Usage - Analyze performance for multiple QR codes
analyzeQRPerformance(['Qf94b25d768294148', 'Qf94b25d768294149'], 30, 'count,device,os,browser,geography');
?>
```

<Note>
  Analytics data is available for the last 2 years. For longer historical data, use the export endpoint to download data for offline analysis.
</Note>

<Warning>
  Large date ranges may result in slower response times. Consider breaking down requests into smaller date ranges for better performance.
</Warning>


## OpenAPI

````yaml POST /analytics/qr/
openapi: 3.1.0
info:
  title: Scanova Management API (v2)
  description: >-
    The complete Scanova Management API — every endpoint available at
    management.scanova.io (QR codes, folders, tags, leads, forms, analytics,
    plans, shared users & roles), plus the token-creation and usage-stats
    endpoints used to authenticate against it. Every path and request/response
    shape below was verified live against a real API key and the actual running
    backend (Phase 7, 2026-08-16) — not guessed from reading urls.py alone.
  version: 2.0.0
servers:
  - url: https://management.scanova.io
    description: Management API — QR/folder/tag/lead/form/analytics/plans endpoints
security:
  - apiKeyAuth: []
paths:
  /analytics/qr/:
    post:
      summary: QR analytics
      description: >-
        Returns aggregated analytics data. Requires `from` and `to` as query
        parameters (dates) and a `type` query parameter (comma-separated metric
        types, e.g. `qr`). The request body selects which QR codes to include
        via `q` (a list of identifiers) and `filter_by` (which identifier type
        `q` contains — `qrid` is the default; `tags`, `id`, and `folder` are
        also supported). Requesting too many QR codes over too wide a date range
        returns a 400 under `q` — the same volume cap applies to
        /analytics/qr/export/ and /analytics/qr/raw/, which share this
        endpoint's request-validation code.
      operationId: getManagedQrAnalytics
      parameters:
        - name: from
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: to
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: type
          in: query
          required: true
          schema:
            type: string
          description: Comma-separated metric types, e.g. `qr`.
        - name: group
          in: query
          schema:
            type: string
            default: date
          description: Grouping for time-series metric types, e.g. date/week/month.
        - name: exclude_bot_scan
          in: query
          schema:
            type: boolean
            default: false
        - name: overview
          in: query
          schema:
            type: boolean
          description: Bypasses the volume cap; used by dashboard overview widgets.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                q:
                  type: array
                  items:
                    type: string
                  description: >-
                    Query data — a list of identifiers of the type named in
                    filter_by.
                filter_by:
                  type: string
                  enum:
                    - qrid
                    - tags
                    - id
                    - folder
                  default: qrid
              required:
                - q
            example:
              q:
                - Q07afe81aa0034c01
              filter_by: qrid
      responses:
        '200':
          description: >-
            Analytics data, grouped by the requested type(s). A `qr_meta`
            name->{qrid, category, category_slug} map is attached whenever `qr`
            is among the requested types.
          content:
            application/json:
              example:
                qr: []
                qr_meta:
                  docs-v2-first-qr-code:
                    qrid: Q07afe81aa0034c01
                    category: Website URL
                    category_slug: url
components:
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: >-
        Send your Management API key as the raw value of the Authorization
        header — no "Bearer " or "Token " prefix, and no other characters.
        Example: `Authorization: 401f7ac837da42b97f613d789819ff93537bee6a`. A
        header containing more than one space-separated part is rejected
        outright. Requests also require the request's Host header to be the
        management API host (e.g. management.scanova.io) — the same key sent to
        the regular API host will not authenticate.

````