API Reference
Complete REST API documentation for Open Short URL.
Overview
Open Short URL provides a comprehensive REST API for programmatic access to all features.
Base URL: https://your-domain.com/api
Interactive Documentation: Access Swagger UI at /api when running the backend.
Authentication
JWT Authentication (Recommended for Web Apps)
- Login via
POST /api/auth/login- token stored in httpOnly cookie - JWT token is automatically sent via cookie for subsequent requests
API Key Authentication (Recommended for Server-to-Server)
- Create API Key via
POST /api/api-keys - Add to request headers:
X-API-Key: <your-api-key>
Authentication
Login
POST /api/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123",
"twoFactorCode": "123456" // Optional, required if 2FA enabled
}Response:
{
"user": {
"id": "user_id",
"email": "user@example.com",
"role": "USER"
},
"accessToken": "jwt_token"
}Logout
POST /api/auth/logoutGet Current User
GET /api/auth/meChange Password
POST /api/auth/password
Content-Type: application/json
{
"currentPassword": "old_password",
"newPassword": "new_password"
}Two-Factor Authentication
# Setup 2FA
POST /api/auth/2fa/setup
# Enable 2FA
POST /api/auth/2fa/enable
{
"code": "123456"
}
# Disable 2FA
POST /api/auth/2fa/disable
{
"code": "123456",
"password": "your_password"
}URLs
Create Short URL
POST /api/urls
Content-Type: application/json
{
"originalUrl": "https://example.com/very-long-url",
"customSlug": "my-link", // Optional, 3-50 chars
"title": "My Link", // Optional
"password": "secret", // Optional, min 4 chars
"expiresAt": "2025-12-31T23:59:59Z", // Optional, ISO 8601
"utmSource": "newsletter", // Optional
"utmMedium": "email", // Optional
"utmCampaign": "summer_sale", // Optional
"utmTerm": "discount", // Optional
"utmContent": "banner_top", // Optional
"utmId": "abc123", // Optional, GA4 recommended
"utmSourcePlatform": "google" // Optional, GA4 recommended
}List URLs
GET /api/urls?page=1&limit=10&search=keyword&status=ACTIVEQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 10) |
search | string | Search in title and URL |
status | string | Filter by status: ACTIVE, INACTIVE |
sortBy | string | Sort field |
sortOrder | string | asc or desc |
Get UTM Suggestions
GET /api/urls/utm-suggestions?field=utmSource&q=newsQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
field | string | Required. UTM field name: utmSource, utmMedium, utmCampaign, utmTerm, utmContent |
q | string | Optional prefix filter (max 100 chars) |
Response:
{
"field": "utmSource",
"suggestions": [
{ "value": "facebook", "count": 12 },
{ "value": "google", "count": 8 },
{ "value": "newsletter", "count": 5 }
]
}Returns up to 20 distinct values across all users, sorted by usage count (most popular first).
Get URL Details
GET /api/urls/{id}Update URL
PUT /api/urls/{id}
Content-Type: application/json
{
"title": "Updated Title",
"status": "ACTIVE",
"password": "new_password",
"expiresAt": "2026-01-01T00:00:00Z"
}Delete URL
DELETE /api/urls/{id}Get URL Stats
GET /api/urls/statsGenerate QR Code
GET /api/urls/{id}/qrcode?width=300&color=%23000000Bulk Operations
# Bulk Create
POST /api/urls/bulk
Content-Type: application/json
{
"urls": [
{ "originalUrl": "https://example1.com" },
{ "originalUrl": "https://example2.com", "customSlug": "ex2" }
]
}
# Bulk Update Status
PATCH /api/urls/bulk
{
"urlIds": ["id1", "id2"],
"status": "INACTIVE"
}
# Bulk Delete
DELETE /api/urls/bulk
{
"urlIds": ["id1", "id2"]
}A/B Testing (Variants)
Create Variant
POST /api/urls/{id}/variants
Content-Type: application/json
{
"name": "Variant A",
"targetUrl": "https://example.com/page-v1",
"weight": 50,
"isActive": true
}List Variants
GET /api/urls/{id}/variantsUpdate Variant
PUT /api/urls/{id}/variants/{variantId}
Content-Type: application/json
{
"name": "Updated Variant",
"weight": 30,
"isActive": false
}Delete Variant
DELETE /api/urls/{id}/variants/{variantId}OG Images
Upload OG Image
Upload and optimize an image for social media preview.
POST /api/og-images/upload/{urlId}
Content-Type: multipart/form-data
Authorization: Bearer <token>
file: (binary)Supported formats: JPEG, PNG, WebP, GIF (max 10MB)
Response:
{
"statusCode": 201,
"data": {
"key": "og-images/clxxx123/1711234567890.webp",
"proxyUrl": "/api/og-images/og-images%2Fclxxx123%2F1711234567890.webp"
}
}Images are automatically:
- Resized to max 1200×630px
- Converted to WebP (GIFs keep original format)
- Compressed at quality 80
- Stripped of EXIF metadata
Serve OG Image
Public endpoint — no authentication required. Used by social media crawlers.
GET /api/og-images/{encoded-key}Returns the image binary with Cache-Control: public, max-age=86400, immutable.
Analytics
Get URL Analytics
GET /api/analytics/urls/{id}?startDate=2025-01-01&endDate=2025-01-31&timezone=Asia/TaipeiQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
startDate | string | Start date (ISO 8601) |
endDate | string | End date (ISO 8601) |
timezone | string | Timezone (e.g., Asia/Taipei) |
Get Overview Analytics
GET /api/analytics/overview?startDate=2025-01-01&endDate=2025-01-31Get Top URLs
GET /api/analytics/top-urls?limit=10Get Recent Clicks
GET /api/analytics/urls/{id}/recent-clicks?limit=20&includeBots=falseGet Bot Analytics
GET /api/analytics/urls/{id}/bots
GET /api/analytics/bots # Overall bot analyticsGet A/B Test Analytics
GET /api/analytics/ab-testsExport Analytics
GET /api/analytics/urls/{id}/export?format=csv&startDate=2025-01-01&endDate=2025-01-31
GET /api/analytics/export?format=json # Export allExport Formats: csv, json
Bundles
Create Bundle
POST /api/bundles
Content-Type: application/json
{
"name": "Marketing Campaign",
"description": "Q1 2025 campaign links",
"color": "#FF5733",
"icon": "🎯",
"urlIds": ["url_id_1", "url_id_2"]
}List Bundles
GET /api/bundles?page=1&limit=10&search=campaign&status=ACTIVEGet Bundle Details
GET /api/bundles/{id}Update Bundle
PUT /api/bundles/{id}
Content-Type: application/json
{
"name": "Updated Name",
"color": "#3B82F6"
}Delete Bundle
DELETE /api/bundles/{id}Archive / Restore Bundle
POST /api/bundles/{id}/archive
POST /api/bundles/{id}/restoreGet Bundle Stats
GET /api/bundles/{id}/statsManage Bundle URLs
# Add single URL
POST /api/bundles/{id}/urls
{ "urlId": "url_id" }
# Add multiple URLs
POST /api/bundles/{id}/urls/batch
{ "urlIds": ["url_id_1", "url_id_2"] }
# Remove URL
DELETE /api/bundles/{id}/urls/{urlId}
# Update URL order
PATCH /api/bundles/{id}/urls/{urlId}/order
{ "order": 2 }API Keys
Create API Key
POST /api/api-keys
Content-Type: application/json
{
"name": "Production Server",
"expiresAt": "2026-01-01T00:00:00Z"
}Response includes the key only once:
{
"id": "key_id",
"name": "Production Server",
"key": "osu_xxxxxxxxxxxxxxxx",
"createdAt": "2025-01-01T00:00:00Z"
}List API Keys
GET /api/api-keysDelete API Key
DELETE /api/api-keys/{id}Webhooks
Create Webhook
POST /api/webhooks
Content-Type: application/json
{
"url": "https://your-server.com/webhook",
"events": ["url.clicked", "url.created"],
"secret": "your_webhook_secret"
}Available Events:
url.createdurl.updatedurl.deletedurl.clicked
List Webhooks
GET /api/webhooksUpdate Webhook
PUT /api/webhooks/{id}Delete Webhook
DELETE /api/webhooks/{id}Test Webhook
POST /api/webhooks/{id}/testGet Webhook Logs
GET /api/webhooks/{id}/logsRetry Failed Delivery
POST /api/webhooks/{webhookId}/logs/{logId}/retryRetries a failed webhook delivery using the original payload and current webhook configuration. Rate limited to 5 requests/minute.
Routing Rules (Smart Routing)
Create Routing Rule
POST /api/urls/{urlId}/routing-rules
Content-Type: application/json
{
"name": "Mobile Users",
"targetUrl": "https://example.com/mobile",
"priority": 1,
"conditions": {
"logicalOperator": "AND",
"items": [
{
"type": "DEVICE",
"operator": "EQUALS",
"value": "MOBILE"
}
]
}
}List Routing Rules
GET /api/urls/{urlId}/routing-rulesUpdate Routing Rule
PUT /api/urls/{urlId}/routing-rules/{ruleId}Delete Routing Rule
DELETE /api/urls/{urlId}/routing-rules/{ruleId}Get Routing Templates
GET /api/routing-templatesCreate Rule from Template
POST /api/urls/{urlId}/routing-rules/from-template
{
"templateId": "template_id",
"targetUrl": "https://example.com/target"
}User Management (Admin)
List Users
GET /api/users?page=1&limit=10&search=emailCreate User
POST /api/users
Content-Type: application/json
{
"email": "newuser@example.com",
"password": "password123",
"role": "USER"
}Update User Role
PATCH /api/users/{id}/role
{ "role": "ADMIN" }Update User Status
PATCH /api/users/{id}/status
{ "status": "ACTIVE" }Reset User Password
POST /api/users/{id}/reset-password
{ "newPassword": "new_password" }Delete User
DELETE /api/users/{id}Audit Logs (Admin)
List Audit Logs
GET /api/audit-logs?page=1&limit=20&action=LOGIN&startDate=2025-01-01Query Parameters:
| Parameter | Type | Description |
|---|---|---|
page | number | Page number |
limit | number | Items per page |
action | string | Filter by action type |
userId | string | Filter by user ID |
startDate | string | Start date |
endDate | string | End date |
SSO / OIDC (Admin)
List OIDC Providers
GET /api/admin/oidc-providersCreate OIDC Provider
POST /api/admin/oidc-providers
Content-Type: application/json
{
"name": "Google",
"slug": "google",
"discoveryUrl": "https://accounts.google.com/.well-known/openid-configuration",
"clientId": "your-client-id",
"clientSecret": "your-client-secret",
"scopes": "openid email profile", // Optional, default: "openid email profile"
"isActive": true // Optional, default: true
}Get OIDC Provider
GET /api/admin/oidc-providers/{slug}Update OIDC Provider
PUT /api/admin/oidc-providers/{slug}
Content-Type: application/json
{
"name": "Google Workspace",
"discoveryUrl": "https://accounts.google.com/.well-known/openid-configuration",
"clientId": "new-client-id",
"clientSecret": "new-client-secret",
"scopes": "openid email profile",
"isActive": true
}Delete OIDC Provider
DELETE /api/admin/oidc-providers/{slug}List Active SSO Providers (Public)
GET /api/auth/ssoReturns active SSO providers available for login (public endpoint, no authentication required).
Initiate SSO Login
GET /api/auth/sso/{slug}/login?redirect=/dashboardRedirects to the OIDC provider's authorization URL. The optional redirect query parameter specifies where to redirect after successful login.
SSO Callback
GET /api/auth/sso/{slug}/callbackHandles the OIDC provider callback after authentication. This endpoint is called automatically by the identity provider.
Redirect Service
Redirect to Original URL
GET /{slug}Get URL Info
GET /{slug}/infoVerify Password
POST /{slug}/verify
Content-Type: application/json
{
"password": "link_password"
}Error Handling
All errors follow this format:
{
"statusCode": 400,
"message": "Validation failed",
"errors": [
{
"field": "originalUrl",
"message": "Invalid URL format"
}
]
}Common Status Codes:
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 429 | Too Many Requests |
| 500 | Internal Server Error |
Rate Limiting
- Default: 100 requests per minute
- Rate limit headers are included in responses:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000