# Hawas TMS - REST API Documentation & Integration Guide
**Version:** `1.0.0 (Enterprise & SaaS Edition)`  
**Base URL:** `http://localhost:5050` *(Development)* | `https://tms.hawas-qr.com` *(Cloud)*  
**Protocol:** `HTTPS / JSON`  
**Authentication:** `API Key (X-API-Key)` or `Bearer Token (JWT)`  

---

## 1. Overview & Architecture

The **Hawas Tag Management System (TMS)** API provides programmatic control over dynamic QR codes, NFC smart cards, RFID chips, and real-time visitor scan telemetry. 

### Dynamic Redirection Engine
When a visitor scans a physical QR code or taps an NFC badge:
```
[ User scans QR or taps NFC chip ]
                  │
                  ▼
[ Hawas Edge Router: https://tms.hawas-qr.com/tms/m-06001 ]
                  │
                  ├─► 1. Asynchronously records scan event (IP, Geo, Device, OS, Browser, Timestamp)
                  │
                  └─► 2. Returns instantaneous HTTP 302 redirect to current destination:
                          https://example.com/exhibits/item-123
```

> [!TIP]
> **Zero Re-printing Advantage**: Destination URLs (`MapURL`) can be updated dynamically via API in milliseconds without ever re-printing physical materials or re-writing NFC chips.

---

## 2. Authentication Schemes

Every API request requires authentication using one of two methods:

### Method A: Live API Key (Recommended for Backend, IoT, & Scripts)
Pass your organization's API key in the `X-API-Key` request header:
```http
X-API-Key: hw_live_your_api_key_here
Content-Type: application/json
```

### Method B: JWT Bearer Token (For User Sessions & Mobile Apps)
Authenticate via `/api/v1/auth/login` to obtain a JWT bearer token, then pass it in the `Authorization` header:
```http
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
```

---

## 3. Standard Response Format

All responses follow a predictable JSON envelope:

### Success Response (`HTTP 200 / 201`)
```json
{
  "success": true,
  "data": { ... }
}
```

### Paginated Response (`HTTP 200`)
```json
{
  "success": true,
  "data": {
    "tags": [ ... ],
    "total": 5332,
    "page": 1,
    "pageSize": 25,
    "totalPages": 214
  }
}
```

### Error Response (`HTTP 4xx / 5xx`)
```json
{
  "success": false,
  "message": "Human-readable error explanation",
  "error": "Error details or validation code"
}
```

---

## 4. Endpoints Reference

### 4.1 Authentication & Profile

#### `POST /api/v1/auth/login`
Authenticate with username/email and password.

* **Request Body:**
```json
{
  "usernameOrEmail": "adamhenein",
  "password": "AdamHenein@Museum2026!"
}
```
* **Response (`200 OK`):**
```json
{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsIn...",
    "user": {
      "Id": 2,
      "Username": "adamhenein",
      "Email": "museum@adamhenein.org",
      "Name": "متحف آدم حنين (Adam Henein Museum)",
      "Role": "vip",
      "api_key": "hw_live_adam_henein_museum_2026_vip"
    }
  }
}
```

---

#### `GET /api/v1/auth/me`
Retrieve currently authenticated user profile, active subscription package, quotas, and feature flags.

* **Headers:** `Authorization: Bearer <TOKEN>` or `X-API-Key: <KEY>`
* **Response (`200 OK`):**
```json
{
  "success": true,
  "data": {
    "user": {
      "Id": 2,
      "Username": "adamhenein",
      "Email": "museum@adamhenein.org",
      "company": "مؤسسة متحف آدم حنين للفنون التشكيلية",
      "role": "vip",
      "api_key": "hw_live_adam_henein_museum_2026_vip"
    },
    "package": {
      "name": "Museums & Heritage (VIP)",
      "name_ar": "باقة المتاحف والتراث (VIP)",
      "max_tags": -1,
      "max_scans_per_tag": -1,
      "has_nfc_support": true,
      "has_api_access": true
    },
    "quota": {
      "tagsCount": 5253,
      "totalScans": 1961
    }
  }
}
```

---

#### `POST /api/v1/auth/regenerate-api-key`
Instantly invalidates old API key and generates a new secure key.

* **Headers:** `Authorization: Bearer <TOKEN>`
* **Response (`200 OK`):**
```json
{
  "success": true,
  "apiKey": "hw_live_new_generated_key_abc123"
}
```

---

### 4.2 Dynamic Tags & NFC Management

#### `GET /api/v1/tags`
Retrieve paginated list of dynamic tags with search and category filtering.

* **Query Parameters:**
  * `page` *(number, optional, default: `1`)*: Page number.
  * `pageSize` *(number, optional, default: `25`)*: Results per page (max `100`).
  * `search` *(string, optional)*: Search term matching tag title, Virtual ID, or URL.
  * `categoryId` *(number, optional)*: Filter by category ID.
* **Headers:** `X-API-Key: <KEY>` or `Authorization: Bearer <TOKEN>`
* **Example Request:**
```bash
curl -X GET "http://localhost:5050/api/v1/tags?page=1&pageSize=10&search=falcon" \
  -H "X-API-Key: hw_live_your_api_key_here"
```
* **Response (`200 OK`):**
```json
{
  "success": true,
  "data": {
    "tags": [
      {
        "Id": 80,
        "VirtualId": "m-00080",
        "Title": "تمثال الصقر البرونزي",
        "MapURL": "https://adamheneinmuseum.com/exhibits/falcon",
        "HitsCount": 142,
        "CategoryId": 2,
        "IsPublished": true,
        "CreatedOnUtc": "2026-03-10T12:00:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 10,
    "totalPages": 1
  }
}
```

---

#### `GET /api/v1/tags/:virtualId`
Get full details of a specific tag by its Virtual ID.

* **Path Parameter:** `virtualId` (e.g. `m-00080`, `w-01050`)
* **Headers:** `X-API-Key: <KEY>` or `Authorization: Bearer <TOKEN>`
* **Response (`200 OK`):**
```json
{
  "success": true,
  "data": {
    "Id": 80,
    "VirtualId": "m-00080",
    "Title": "تمثال الصقر البرونزي",
    "MapURL": "https://adamheneinmuseum.com/exhibits/falcon",
    "HitsCount": 142,
    "CategoryId": 2,
    "Description": "عمل فني من البرونز المصبوب 1994",
    "IsPublished": true,
    "CreatedOnUtc": "2026-03-10T12:00:00.000Z",
    "UpdatedOnUtc": "2026-09-23T11:45:00.000Z"
  }
}
```

---

#### `POST /api/v1/tags`
Create a new dynamic tag with optional custom Virtual ID and category.

* **Request Body:**
```json
{
  "Title": "تمثال عصفور الصباح",
  "MapURL": "https://adamheneinmuseum.com/exhibits/morning-bird",
  "CategoryId": 2,
  "VirtualId": "m-06001",
  "Description": "قطعة فوتوغرافية ومجسم نحتي",
  "Domain": "https://hawas-qr.com",
  "PathFormat": "/tms/",
  "IsPublished": true
}
```
* **Notes:**
  * If `VirtualId` is omitted, the system generates one automatically using the category leading letter (e.g., `m-00081`).
* **Response (`201 Created`):**
```json
{
  "success": true,
  "data": {
    "Id": 5333,
    "VirtualId": "m-06001",
    "Title": "تمثال عصفور الصباح",
    "MapURL": "https://adamheneinmuseum.com/exhibits/morning-bird",
    "DirectUrl": "https://hawas-qr.com/tms/m-06001",
    "HitsCount": 0,
    "IsPublished": true
  }
}
```

---

#### `PUT /api/v1/tags/:virtualId`
Update the destination URL or metadata of an existing tag.

* **Request Body (all fields optional):**
```json
{
  "MapURL": "https://adamheneinmuseum.com/exhibits/morning-bird-new-gallery",
  "Title": "تمثال عصفور الصباح - القاعة 4",
  "IsPublished": true,
  "Description": "تم النقل إلى المعرض الدائم"
}
```
* **Response (`200 OK`):**
```json
{
  "success": true,
  "message": "Tag updated successfully",
  "data": {
    "VirtualId": "m-06001",
    "MapURL": "https://adamheneinmuseum.com/exhibits/morning-bird-new-gallery",
    "IsPublished": true
  }
}
```

---

#### `DELETE /api/v1/tags/:virtualId`
Permanently remove or deactivate a dynamic tag.

* **Response (`200 OK`):**
```json
{
  "success": true,
  "message": "Tag deleted successfully"
}
```

---

### 4.3 Real-Time Scan Telemetry & Analytics

#### `GET /api/v1/tags/:virtualId/analytics`
Retrieve visitor engagement statistics and scan history.

* **Query Parameters:**
  * `days` *(number, optional, default: `30`)*: Lookback window in days.
* **Response (`200 OK`):**
```json
{
  "success": true,
  "data": {
    "totalScans": 142,
    "last30Days": 38,
    "deviceBreakdown": {
      "iOS": 68,
      "Android": 62,
      "Desktop": 12
    },
    "recentLogs": [
      {
        "CreatedOnUtc": "2026-09-23T14:12:00.000Z",
        "IpAddress": "197.34.120.45",
        "UserAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X)",
        "BrowserInfo": "Mobile Safari 17.4"
      }
    ]
  }
}
```

---

### 4.4 Bulk Domain & URL Migration

#### `POST /api/v1/tags/bulk-domain-preview`
Simulate domain or host replacement across all or filtered tags before execution.

* **Request Body:**
```json
{
  "oldDomain": "adamhenien.com",
  "newDomain": "adamheneinmuseum.com",
  "categoryId": 2
}
```
* **Response (`200 OK`):**
```json
{
  "success": true,
  "data": {
    "matchCount": 5253,
    "samples": [
      {
        "virtualId": "m-00001",
        "currentUrl": "https://adamhenien.com/sculpture-01",
        "newUrl": "https://adamheneinmuseum.com/sculpture-01"
      }
    ]
  }
}
```

---

#### `POST /api/v1/tags/bulk-domain-replace`
Execute bulk domain update across thousands of tags atomically in a single database transaction.

* **Request Body:**
```json
{
  "oldDomain": "adamhenien.com",
  "newDomain": "adamheneinmuseum.com",
  "categoryId": 2
}
```
* **Response (`200 OK`):**
```json
{
  "success": true,
  "data": {
    "updatedCount": 5253,
    "message": "Successfully migrated 5,253 tags to the new domain."
  }
}
```

---

### 4.5 Categories & Prefixes

#### `GET /api/v1/categories`
Retrieve all categories with leading letter prefixes and tag quotas.

* **Response (`200 OK`):**
```json
{
  "success": true,
  "data": [
    {
      "Id": 2,
      "Name": "Adam Henein Museum",
      "NameAr": "متحف آدم حنين",
      "Letter": "m",
      "tagCount": 5253
    },
    {
      "Id": 1,
      "Name": "Art & Cinema Pioneers",
      "NameAr": "رواد الفن والسينما",
      "Letter": "w",
      "tagCount": 79
    }
  ]
}
```

---

### 4.6 Public System Stats

#### `GET /api/v1/stats`
Public platform metrics (suitable for status pages and homepage counters).

* **No authentication required.**
* **Response (`200 OK`):**
```json
{
  "success": true,
  "data": {
    "totalTags": 5332,
    "totalScans": 1961,
    "totalCategories": 5,
    "totalUsers": 5
  }
}
```

---

## 5. Integration Code Samples

### 5.1 cURL
```bash
# 1. Create a new tag
curl -X POST "http://localhost:5050/api/v1/tags" \
  -H "X-API-Key: hw_live_adam_henein_museum_2026_vip" \
  -H "Content-Type: application/json" \
  -d '{
    "Title": "Sculpture 104 - Bronze Hawk",
    "MapURL": "https://adamheneinmuseum.com/hawk",
    "CategoryId": 2
  }'

# 2. Update destination URL
curl -X PUT "http://localhost:5050/api/v1/tags/m-06001" \
  -H "X-API-Key: hw_live_adam_henein_museum_2026_vip" \
  -H "Content-Type: application/json" \
  -d '{
    "MapURL": "https://adamheneinmuseum.com/exhibits/hawk-v2"
  }'

# 3. Query telemetry analytics
curl -X GET "http://localhost:5050/api/v1/tags/m-06001/analytics?days=7" \
  -H "X-API-Key: hw_live_adam_henein_museum_2026_vip"
```

---

### 5.2 JavaScript / Node.js
```javascript
import fetch from 'node-fetch';

const API_BASE = 'http://localhost:5050/api/v1';
const API_KEY = 'hw_live_adam_henein_museum_2026_vip';

async function updateTagTarget(virtualId, newTargetUrl) {
  const response = await fetch(`${API_BASE}/tags/${virtualId}`, {
    method: 'PUT',
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ MapURL: newTargetUrl })
  });

  const result = await response.json();
  if (!response.ok || !result.success) {
    throw new Error(result.message || 'Failed to update tag destination');
  }

  console.log(`✓ Tag [${virtualId}] redirect updated to:`, newTargetUrl);
  return result.data;
}

// Usage Example:
updateTagTarget('m-00080', 'https://adamheneinmuseum.com/new-gallery/item-80');
```

---

### 5.3 Python 3
```python
import requests

API_BASE = "http://localhost:5050/api/v1"
API_KEY = "hw_live_adam_henein_museum_2026_vip"

headers = {
    "X-API-Key": API_KEY,
    "Content-Type": "application/json"
}

def create_museum_tag(title: str, target_url: str, category_id: int = 2) -> dict:
    payload = {
        "Title": title,
        "MapURL": target_url,
        "CategoryId": category_id
    }
    
    response = requests.post(f"{API_BASE}/tags", json=payload, headers=headers)
    response.raise_for_status()
    
    data = response.json()
    if not data.get("success"):
        raise ValueError(data.get("message", "API request failed"))
        
    return data["data"]

# Example:
new_tag = create_museum_tag("Granite Vessel", "https://adamheneinmuseum.com/vessel-01")
print(f"Created Tag: {new_tag['VirtualId']} -> {new_tag['DirectUrl']}")
```

---

### 5.4 PHP
```php
<?php
$apiBase = 'http://localhost:5050/api/v1';
$apiKey = 'hw_live_adam_henein_museum_2026_vip';

$payload = json_encode([
    'MapURL' => 'https://adamheneinmuseum.com/catalog/sculpture-55'
]);

$ch = curl_init("$apiBase/tags/m-00055");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "X-API-Key: $apiKey",
    "Content-Type: application/json"
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$result = json_decode($response, true);
if ($httpCode === 200 && $result['success']) {
    echo "✓ Tag updated successfully!\n";
} else {
    echo "Error: " . ($result['message'] ?? 'HTTP ' . $httpCode) . "\n";
}
?>
```

---

## 6. HTTP Error Codes & Troubleshooting

| HTTP Code | Error Key | Cause & Remediation |
|---|---|---|
| `400 Bad Request` | `INVALID_INPUT` | Missing required fields (e.g. `MapURL` or `Title`), or malformed JSON payload. |
| `401 Unauthorized` | `INVALID_KEY` | Missing or invalid `X-API-Key` or expired Bearer JWT token. |
| `403 Forbidden` | `INSUFFICIENT_ROLE` | The action requires `admin` privileges (e.g. modifying system packages or CMS). |
| `404 Not Found` | `TAG_NOT_FOUND` | The specified `virtualId` does not exist in the database. |
| `429 Too Many Requests` | `RATE_LIMIT_EXCEEDED` | Request threshold exceeded. Implement exponential backoff. |
| `500 Internal Error` | `SERVER_ERROR` | Database connectivity issue or unhandled exception. |

---

## 7. Best Practices

1. **Keep Secrets Secure**: Never commit API keys to client-side code (browsers or native mobile app bundles). Proxy requests via your server when accessing protected TMS endpoints.
2. **Bulk Migration for Rebrands**: Use `/api/v1/tags/bulk-domain-replace` rather than looping individual `PUT` requests when migrating domains or CDN prefixes.
3. **Connection Pooling**: When executing high-volume API requests from Node.js, Python, or Go, maintain persistent HTTP keep-alive sockets to optimize response latency under 15ms.
