{"openapi":"3.0.3","info":{"title":"qrius.io API","description":"## Dynamic QR Code Management API\n\nCreate, manage, and track dynamic QR codes with real-time analytics.\n\n### Authentication\nAll protected endpoints require a valid session. Authenticate via `POST /auth/login` — the server sets an `access_token` HttpOnly cookie automatically.\n\nAlternatively, pass an API key in the `Authorization` header:\n```\nAuthorization: Bearer qrius_YOUR_API_KEY\n```\n\n### Rate Limiting\nRate limits are enforced per user and plan:\n- **FREE**: 10 requests/minute\n- **PRO**: 60 requests/minute\n- **BUSINESS**: 300 requests/minute\n\n### Base URL\n```\nhttps://qrius.io/api\n```","version":"1.0.0","contact":{"name":"qrius.io Support","email":"support@qrius.io","url":"https://qrius.io"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://qrius.io/api","description":"Production"},{"url":"http://localhost:3000","description":"Local development"}],"tags":[{"name":"Authentication","description":"Register, login, logout, password reset"},{"name":"QR Codes","description":"Create and manage dynamic QR codes"},{"name":"Bulk Operations","description":"Batch operations on multiple QR codes"},{"name":"Analytics","description":"Scan analytics and usage statistics"},{"name":"API Keys","description":"Manage API keys for programmatic access"},{"name":"Billing","description":"Subscription and billing management"},{"name":"Account","description":"User profile and settings"}],"components":{"securitySchemes":{"cookieAuth":{"type":"apiKey","in":"cookie","name":"access_token","description":"HttpOnly cookie set by POST /auth/login"},"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"API key prefixed with `qrius_`. Pass as Bearer token."}},"schemas":{"Error":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string","example":"User not authenticated"}}}}},"QRCode":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"clx1abc..."},"name":{"type":"string","example":"Summer Campaign"},"slug":{"type":"string","example":"summer-2025"},"destinationUrl":{"type":"string","format":"uri","example":"https://example.com/landing"},"description":{"type":"string","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"isPaused":{"type":"boolean"},"isBlocked":{"type":"boolean"},"redirectUrl":{"type":"string","format":"uri","example":"https://qrius.io/summer-2025"},"totalScans":{"type":"integer","example":1234},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}},"security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"paths":{"/auth/register":{"post":{"tags":["Authentication"],"summary":"Register a new user","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","example":"user@example.com"},"password":{"type":"string","minLength":8,"example":"securepassword"},"firstName":{"type":"string","example":"Jane"},"lastName":{"type":"string","example":"Doe"}}}}}},"responses":{"201":{"description":"User created. Verification email sent."},"409":{"description":"Email already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/login":{"post":{"tags":["Authentication"],"summary":"Login and obtain session cookie","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","example":"user@example.com"},"password":{"type":"string","example":"securepassword"}}}}}},"responses":{"200":{"description":"Login successful. Sets `access_token` and `refresh_token` cookies.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"plan":{"type":"string"}}}}}}}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/logout":{"post":{"tags":["Authentication"],"summary":"Logout and clear session cookies","responses":{"200":{"description":"Logged out successfully"}}}},"/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Refresh access token","description":"Uses the `refresh_token` cookie (or body field) to issue a new `access_token`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string","description":"Optional if using cookie-based auth"}}}}}},"responses":{"200":{"description":"New access_token cookie set"},"401":{"description":"Refresh token invalid or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/forgot-password":{"post":{"tags":["Authentication"],"summary":"Request a password reset email","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","example":"user@example.com"}}}}}},"responses":{"200":{"description":"Reset email sent if account exists (always returns 200 to prevent enumeration)"}}}},"/auth/reset-password":{"post":{"tags":["Authentication"],"summary":"Reset password using token from email","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","password"],"properties":{"token":{"type":"string","description":"Token from reset email"},"password":{"type":"string","minLength":8,"example":"newsecurepassword"}}}}}},"responses":{"200":{"description":"Password reset successfully"},"400":{"description":"Token invalid or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/qr":{"post":{"tags":["QR Codes"],"summary":"Create a dynamic QR code","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","destinationUrl"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"Summer Campaign"},"slug":{"type":"string","minLength":3,"maxLength":50,"pattern":"^[a-z0-9-]+$","example":"summer-2025","description":"Custom URL slug. Auto-generated if omitted."},"destinationUrl":{"type":"string","format":"uri","example":"https://example.com/landing"},"description":{"type":"string","maxLength":500,"example":"QR for summer poster campaign"},"tags":{"type":"array","items":{"type":"string"},"maxItems":10,"example":["marketing","print"]}}}}}},"responses":{"201":{"description":"QR code created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/QRCode"}}}}}},"403":{"description":"Plan limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["QR Codes"],"summary":"List all QR codes for the authenticated user","parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"search","in":"query","schema":{"type":"string"},"description":"Filter by name or slug"},{"name":"tags","in":"query","schema":{"type":"string"},"description":"Comma-separated tag filter"}],"responses":{"200":{"description":"Paginated list of QR codes","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"qrCodes":{"type":"array","items":{"$ref":"#/components/schemas/QRCode"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}}}}}}}}}}}}},"/qr/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"QR code ID"}],"get":{"tags":["QR Codes"],"summary":"Get a single QR code","responses":{"200":{"description":"QR code details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/QRCode"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["QR Codes"],"summary":"Update a QR code","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"destinationUrl":{"type":"string","format":"uri"},"description":{"type":"string","maxLength":500},"tags":{"type":"array","items":{"type":"string"},"maxItems":10},"isPaused":{"type":"boolean","description":"Pause or resume the QR code redirect"}}}}}},"responses":{"200":{"description":"QR code updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/QRCode"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["QR Codes"],"summary":"Delete a QR code","responses":{"200":{"description":"QR code deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/qr/bulk/pause":{"post":{"tags":["Bulk Operations"],"summary":"Pause multiple QR codes","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["qrCodeIds"],"properties":{"qrCodeIds":{"type":"array","items":{"type":"string"},"maxItems":100,"example":["id1","id2"]}}}}}},"responses":{"200":{"description":"Bulk pause result with success/failure counts"}}}},"/qr/bulk/resume":{"post":{"tags":["Bulk Operations"],"summary":"Resume multiple QR codes","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["qrCodeIds"],"properties":{"qrCodeIds":{"type":"array","items":{"type":"string"},"maxItems":100}}}}}},"responses":{"200":{"description":"Bulk resume result"}}}},"/qr/bulk/delete":{"post":{"tags":["Bulk Operations"],"summary":"Delete multiple QR codes","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["qrCodeIds"],"properties":{"qrCodeIds":{"type":"array","items":{"type":"string"},"maxItems":100}}}}}},"responses":{"200":{"description":"Bulk delete result"}}}},"/qr/bulk/update-destination":{"post":{"tags":["Bulk Operations"],"summary":"Update destination URL for multiple QR codes","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["qrCodeIds","destinationUrl"],"properties":{"qrCodeIds":{"type":"array","items":{"type":"string"},"maxItems":100},"destinationUrl":{"type":"string","format":"uri","example":"https://new-destination.com/page"}}}}}},"responses":{"200":{"description":"Bulk update result"}}}},"/qr/bulk/tags":{"post":{"tags":["Bulk Operations"],"summary":"Add, remove, or replace tags on multiple QR codes","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["qrCodeIds","tags","operation"],"properties":{"qrCodeIds":{"type":"array","items":{"type":"string"},"maxItems":100},"tags":{"type":"array","items":{"type":"string"},"example":["campaign-2025"]},"operation":{"type":"string","enum":["add","remove","replace"]}}}}}},"responses":{"200":{"description":"Bulk tag update result"}}}},"/qr/{id}/stats":{"get":{"tags":["Analytics"],"summary":"Get scan analytics for a QR code","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"from","in":"query","schema":{"type":"string","format":"date"},"example":"2025-01-01","description":"Start date (YYYY-MM-DD)"},{"name":"to","in":"query","schema":{"type":"string","format":"date"},"example":"2025-01-31","description":"End date (YYYY-MM-DD)"}],"responses":{"200":{"description":"Scan analytics","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"summary":{"type":"object","properties":{"totalScans":{"type":"integer","example":4821},"uniqueScans":{"type":"integer","example":3104}}},"timeSeries":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date","example":"2025-01-15"},"scans":{"type":"integer","example":142}}}},"breakdown":{"type":"object","properties":{"byCountry":{"type":"array","items":{"type":"object","properties":{"country":{"type":"string","example":"SE"},"scans":{"type":"integer","example":2100}}}},"byDevice":{"type":"array","items":{"type":"object","properties":{"device":{"type":"string","enum":["mobile","desktop","tablet"],"example":"mobile"},"scans":{"type":"integer","example":3850}}}},"byCity":{"type":"array","items":{"type":"object","properties":{"city":{"type":"string","example":"Stockholm"},"scans":{"type":"integer","example":450}}}}}}}}}}}}}}}},"/stats/overview":{"get":{"tags":["Analytics"],"summary":"Get dashboard overview statistics","responses":{"200":{"description":"Overview stats","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"totalQRCodes":{"type":"integer"},"activeQRCodes":{"type":"integer"},"totalScansAllTime":{"type":"integer"},"scansLast30Days":{"type":"integer"},"scansToday":{"type":"integer"},"topPerformers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"totalScans":{"type":"integer"}}}}}}}}}}}}}},"/api-keys":{"post":{"tags":["API Keys"],"summary":"Create a new API key","description":"The full key is only returned once at creation time. Store it securely.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"My App Integration"}}}}}},"responses":{"201":{"description":"API key created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"key":{"type":"string","example":"qrius_AbCdEfGhIj...","description":"Full key — shown only once"},"createdAt":{"type":"string","format":"date-time"}}}}}}}}}},"get":{"tags":["API Keys"],"summary":"List API keys","description":"Returns keys without the secret portion.","responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"keyPreview":{"type":"string","example":"qrius_AbCd...XyZ"},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":"string","format":"date-time","nullable":true}}}}}}}}}}}},"/api-keys/{id}/revoke":{"post":{"tags":["API Keys"],"summary":"Revoke an API key","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"API key revoked"},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys/{id}":{"delete":{"tags":["API Keys"],"summary":"Delete an API key","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"API key deleted"},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/billing/prices":{"get":{"tags":["Billing"],"summary":"Get available plans and prices","security":[],"responses":{"200":{"description":"Available pricing plans","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"plan":{"type":"string","enum":["PRO","BUSINESS"]},"monthly":{"type":"object","properties":{"priceId":{"type":"string"},"amount":{"type":"integer"},"currency":{"type":"string"}}},"yearly":{"type":"object","properties":{"priceId":{"type":"string"},"amount":{"type":"integer"},"currency":{"type":"string"}}}}}}}}}}}}}},"/billing/subscription":{"get":{"tags":["Billing"],"summary":"Get current subscription status","responses":{"200":{"description":"Subscription details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"plan":{"type":"string","enum":["FREE","PRO","BUSINESS"]},"status":{"type":"string","example":"active"},"currentPeriodEnd":{"type":"string","format":"date-time","nullable":true},"cancelAtPeriodEnd":{"type":"boolean"}}}}}}}}}}},"/billing/checkout":{"post":{"tags":["Billing"],"summary":"Create a Stripe Checkout session","description":"Returns a Stripe Checkout URL. Redirect the user to this URL to complete payment.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["priceId"],"properties":{"priceId":{"type":"string","example":"price_1T7MolJuS9hmvDprNLQXQi7C","description":"Stripe price ID from /billing/prices"}}}}}},"responses":{"200":{"description":"Checkout URL","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Stripe Checkout URL — redirect user here"}}}}}}}}}}},"/billing/portal":{"post":{"tags":["Billing"],"summary":"Create a Stripe Billing Portal session","description":"Returns a URL to the Stripe customer portal where the user can manage their subscription.","responses":{"200":{"description":"Billing portal URL","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"url":{"type":"string","format":"uri"}}}}}}}}}}},"/billing/cancel":{"post":{"tags":["Billing"],"summary":"Cancel subscription at end of billing period","responses":{"200":{"description":"Subscription scheduled for cancellation"}}}},"/users/me":{"get":{"tags":["Account"],"summary":"Get current user profile","responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string","nullable":true},"lastName":{"type":"string","nullable":true},"plan":{"type":"string","enum":["FREE","PRO","BUSINESS","ADMIN"]},"emailVerified":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}}}}}}}}},"patch":{"tags":["Account"],"summary":"Update user profile","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"firstName":{"type":"string","example":"Jane"},"lastName":{"type":"string","example":"Doe"},"company":{"type":"string","example":"Acme Corp"}}}}}},"responses":{"200":{"description":"Profile updated"}}},"delete":{"tags":["Account"],"summary":"Delete account permanently","responses":{"200":{"description":"Account deleted"}}}},"/users/me/usage":{"get":{"tags":["Account"],"summary":"Get plan usage statistics","responses":{"200":{"description":"Plan usage","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"plan":{"type":"string"},"limits":{"type":"object","properties":{"qrCodes":{"type":"integer","description":"-1 means unlimited"},"scansPerMonth":{"type":"integer","description":"-1 means unlimited"},"apiAccess":{"type":"boolean"}}},"usage":{"type":"object","properties":{"qrCodes":{"type":"integer"},"scansThisMonth":{"type":"integer"}}},"percentUsed":{"type":"object","properties":{"qrCodes":{"type":"number"},"scans":{"type":"number"}}}}}}}}}}}}}}}