* Initial plan * Add OpenAPI documentation for privacy, plugins, and admin routes Co-authored-by: onnwee <211922112+onnwee@users.noreply.github.com> * Add comprehensive API documentation guides with code examples Co-authored-by: onnwee <211922112+onnwee@users.noreply.github.com> * Add API documentation index and update main README Co-authored-by: onnwee <211922112+onnwee@users.noreply.github.com> * Add comprehensive API documentation portal implementation summary Co-authored-by: onnwee <211922112+onnwee@users.noreply.github.com> * Fix broken documentation links Co-authored-by: onnwee <211922112+onnwee@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: onnwee <211922112+onnwee@users.noreply.github.com>
16 KiB
Interactive API Documentation Guide
Welcome to the Spywatcher API! This guide will help you get started with using our interactive API documentation portal.
📚 Available Documentation Interfaces
1. Swagger UI - Interactive Testing
URL: http://localhost:3001/api/docs (Development) or https://api.spywatcher.dev/api/docs (Production)
Best for:
- Testing API endpoints directly in your browser
- Experimenting with different request parameters
- Understanding request/response formats
- Quick prototyping and validation
Features:
- ✅ Try-it-out functionality
- ✅ Authentication support (Bearer token)
- ✅ Request/response examples
- ✅ Schema validation
- ✅ Real-time API testing
2. ReDoc - Clean Documentation
URL: http://localhost:3001/api/redoc (Development) or https://api.spywatcher.dev/api/redoc (Production)
Best for:
- Reading and understanding the API structure
- Searching for specific endpoints
- Print-friendly documentation
- Mobile-friendly viewing
Features:
- ✅ Clean, professional design
- ✅ Three-panel layout
- ✅ Deep linking
- ✅ Responsive design
- ✅ Easy navigation
3. OpenAPI JSON Specification
URL: http://localhost:3001/api/openapi.json (Development) or https://api.spywatcher.dev/api/openapi.json (Production)
Best for:
- Generating client SDKs
- Importing into API tools (Postman, Insomnia)
- Automated testing
- CI/CD integration
🚀 Getting Started
Step 1: Authentication
Most endpoints require authentication. Here's how to get started:
-
Direct users to Discord OAuth2:
https://discord.com/oauth2/authorize?client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&response_type=code&scope=identify%20guilds -
Handle the OAuth callback:
GET /api/auth/discord?code={AUTHORIZATION_CODE} -
Save the access token from the response:
{ "accessToken": "eyJhbGciOiJIUzI1NiIs...", "user": { "id": "uuid", "discordId": "123456789", "username": "User#1234" } } -
Use the token in subsequent requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Step 2: Try Your First Request
Using Swagger UI
- Open
http://localhost:3001/api/docsin your browser - Click the "Authorize" button at the top right
- Enter your JWT token:
Bearer {your-token} - Click "Authorize" and "Close"
- Navigate to any endpoint (e.g.,
/api/auth/me) - Click "Try it out"
- Click "Execute"
- See the response!
Using cURL
curl -X GET "http://localhost:3001/api/auth/me" \
-H "Authorization: Bearer {your-token}" \
-H "Content-Type: application/json"
💡 Code Examples
JavaScript/TypeScript (Fetch API)
// Authenticate and get user info
async function getUserInfo() {
const response = await fetch('http://localhost:3001/api/auth/me', {
method: 'GET',
headers: {
'Authorization': 'Bearer your-jwt-token',
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
}
// Get ghost scores
async function getGhostScores(guildId) {
const params = new URLSearchParams({
guildId: guildId,
page: '1',
perPage: '20'
});
const response = await fetch(`http://localhost:3001/api/ghosts?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer your-jwt-token',
'Content-Type': 'application/json'
}
});
return response.json();
}
Python (requests)
import requests
# Base configuration
BASE_URL = "http://localhost:3001/api"
TOKEN = "your-jwt-token"
headers = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
# Get user info
def get_user_info():
response = requests.get(
f"{BASE_URL}/auth/me",
headers=headers
)
response.raise_for_status()
return response.json()
# Get ghost scores
def get_ghost_scores(guild_id, page=1, per_page=20):
params = {
"guildId": guild_id,
"page": page,
"perPage": per_page
}
response = requests.get(
f"{BASE_URL}/ghosts",
headers=headers,
params=params
)
response.raise_for_status()
return response.json()
# Request account deletion
def request_deletion(reason=""):
data = {"reason": reason}
response = requests.post(
f"{BASE_URL}/privacy/delete-request",
headers=headers,
json=data
)
response.raise_for_status()
return response.json()
Node.js (axios)
const axios = require('axios');
const BASE_URL = 'http://localhost:3001/api';
const TOKEN = 'your-jwt-token';
const api = axios.create({
baseURL: BASE_URL,
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
}
});
// Get user info
async function getUserInfo() {
try {
const response = await api.get('/auth/me');
return response.data;
} catch (error) {
console.error('Error:', error.response?.data || error.message);
throw error;
}
}
// Get ghost scores
async function getGhostScores(guildId, page = 1, perPage = 20) {
try {
const response = await api.get('/ghosts', {
params: { guildId, page, perPage }
});
return response.data;
} catch (error) {
console.error('Error:', error.response?.data || error.message);
throw error;
}
}
// Export user data (GDPR)
async function exportUserData() {
try {
const response = await api.get('/privacy/export');
return response.data;
} catch (error) {
console.error('Error:', error.response?.data || error.message);
throw error;
}
}
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const (
BaseURL = "http://localhost:3001/api"
Token = "your-jwt-token"
)
type Client struct {
httpClient *http.Client
token string
}
func NewClient(token string) *Client {
return &Client{
httpClient: &http.Client{},
token: token,
}
}
func (c *Client) doRequest(method, path string, body interface{}) (*http.Response, error) {
var bodyReader io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(jsonBody)
}
req, err := http.NewRequest(method, BaseURL+path, bodyReader)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("Content-Type", "application/json")
return c.httpClient.Do(req)
}
// Get user info
func (c *Client) GetUserInfo() (map[string]interface{}, error) {
resp, err := c.doRequest("GET", "/auth/me", nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
// Get ghost scores
func (c *Client) GetGhostScores(guildID string, page, perPage int) (map[string]interface{}, error) {
path := fmt.Sprintf("/ghosts?guildId=%s&page=%d&perPage=%d", guildID, page, perPage)
resp, err := c.doRequest("GET", path, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
Java (OkHttp)
import okhttp3.*;
import org.json.JSONObject;
import java.io.IOException;
public class SpywatcherClient {
private static final String BASE_URL = "http://localhost:3001/api";
private final String token;
private final OkHttpClient client;
public SpywatcherClient(String token) {
this.token = token;
this.client = new OkHttpClient();
}
// Get user info
public JSONObject getUserInfo() throws IOException {
Request request = new Request.Builder()
.url(BASE_URL + "/auth/me")
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.get()
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return new JSONObject(response.body().string());
}
}
// Get ghost scores
public JSONObject getGhostScores(String guildId, int page, int perPage) throws IOException {
HttpUrl url = HttpUrl.parse(BASE_URL + "/ghosts").newBuilder()
.addQueryParameter("guildId", guildId)
.addQueryParameter("page", String.valueOf(page))
.addQueryParameter("perPage", String.valueOf(perPage))
.build();
Request request = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.get()
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return new JSONObject(response.body().string());
}
}
// Export user data
public JSONObject exportUserData() throws IOException {
Request request = new Request.Builder()
.url(BASE_URL + "/privacy/export")
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.get()
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return new JSONObject(response.body().string());
}
}
}
🔐 Authentication Details
JWT Bearer Token
All authenticated endpoints require a JWT Bearer token in the Authorization header:
Authorization: Bearer {your-jwt-token}
Token Lifespan
- Access Token: Valid for 15 minutes
- Refresh Token: Valid for 7 days
Refreshing Tokens
When your access token expires, use the refresh endpoint:
POST /api/auth/refresh
Content-Type: application/json
{
"refreshToken": "your-refresh-token"
}
Response:
{
"accessToken": "new-access-token",
"refreshToken": "new-refresh-token"
}
⚡ Rate Limiting
The API implements multiple rate limiting tiers to ensure stability and fair usage:
Rate Limit Tiers
| Tier | Endpoints | Limit | Window |
|---|---|---|---|
| Global | All /api/* routes |
100 requests | 15 minutes |
| Analytics | Analytics endpoints | 30 requests | 1 minute |
| Authentication | Auth endpoints | Custom limits | Varies |
| Public API | Public routes | 60 requests | 1 minute |
| Admin | Admin routes | 100 requests | 15 minutes |
Rate Limit Headers
All responses include rate limit information:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1699999999
Handling Rate Limits
When you exceed the rate limit, you'll receive a 429 Too Many Requests response:
{
"error": "Too Many Requests",
"message": "Rate limit exceeded"
}
The response includes a Retry-After header indicating when you can retry:
Retry-After: 60
Best Practices:
- Monitor
X-RateLimit-Remainingheader - Implement exponential backoff
- Cache responses when possible
- Use webhooks instead of polling
- Respect
Retry-Afterheader
📊 Common Endpoints
Authentication
GET /api/auth/discord?code={code}- Discord OAuth callbackGET /api/auth/me- Get current userPOST /api/auth/refresh- Refresh access tokenPOST /api/auth/logout- LogoutGET /api/auth/sessions- List sessionsDELETE /api/auth/sessions/{sessionId}- Revoke session
Analytics
GET /api/ghosts- Ghost scoresGET /api/heatmap- Channel activity heatmapGET /api/lurkers- Lurker detectionGET /api/roles- Role drift analysisGET /api/clients- Client usage patternsGET /api/shifts- Behavior shift detection
Privacy (GDPR Compliance)
GET /api/privacy/export- Export all user dataPOST /api/privacy/delete-request- Request account deletionPOST /api/privacy/cancel-deletion- Cancel deletion requestGET /api/privacy/deletion-status- Check deletion statusPATCH /api/privacy/profile- Update profile
Plugins (Admin Only)
GET /api/plugins- List all pluginsGET /api/plugins/{id}- Get plugin detailsGET /api/plugins/{id}/health- Plugin health statusPOST /api/plugins/{id}/start- Start pluginPOST /api/plugins/{id}/stop- Stop pluginDELETE /api/plugins/{id}- Unload plugin
System Status
GET /api/status- System statusGET /health/live- Liveness probeGET /health/ready- Readiness probe
🛠️ Generating Client SDKs
You can auto-generate client libraries from the OpenAPI specification:
Using OpenAPI Generator
# Install OpenAPI Generator
npm install @openapitools/openapi-generator-cli -g
# Generate TypeScript client
openapi-generator-cli generate \
-i http://localhost:3001/api/openapi.json \
-g typescript-fetch \
-o ./generated-client
# Generate Python client
openapi-generator-cli generate \
-i http://localhost:3001/api/openapi.json \
-g python \
-o ./generated-client
# Generate Java client
openapi-generator-cli generate \
-i http://localhost:3001/api/openapi.json \
-g java \
-o ./generated-client
# Generate Go client
openapi-generator-cli generate \
-i http://localhost:3001/api/openapi.json \
-g go \
-o ./generated-client
Supported Languages
OpenAPI Generator supports 50+ languages including:
- TypeScript/JavaScript
- Python
- Java
- Go
- C#
- Ruby
- PHP
- Swift
- Kotlin
- Rust
- And many more!
📱 Importing to API Tools
Postman
- Open Postman
- Click "Import"
- Select "Link" tab
- Enter:
http://localhost:3001/api/openapi.json - Click "Continue" then "Import"
Insomnia
- Open Insomnia
- Click "Create" → "Import From" → "URL"
- Enter:
http://localhost:3001/api/openapi.json - Click "Fetch and Import"
VS Code REST Client
Create a file with .http extension:
@baseUrl = http://localhost:3001/api
@token = your-jwt-token
### Get user info
GET {{baseUrl}}/auth/me
Authorization: Bearer {{token}}
### Get ghost scores
GET {{baseUrl}}/ghosts?guildId=123456&page=1&perPage=20
Authorization: Bearer {{token}}
### Export user data
GET {{baseUrl}}/privacy/export
Authorization: Bearer {{token}}
❓ Common Issues & Solutions
Issue: 401 Unauthorized
Cause: Missing or invalid authentication token
Solution:
- Ensure you've included the Authorization header
- Check token format:
Bearer {token} - Verify token hasn't expired
- Use refresh endpoint to get a new token
Issue: 429 Too Many Requests
Cause: Rate limit exceeded
Solution:
- Check
Retry-Afterheader - Implement exponential backoff
- Reduce request frequency
- Consider caching responses
Issue: 403 Forbidden
Cause: Insufficient permissions
Solution:
- Verify your user role
- Check if endpoint requires admin access
- Contact support for elevated permissions
Issue: 404 Not Found
Cause: Resource doesn't exist or incorrect URL
Solution:
- Verify the endpoint URL
- Check if resource ID is correct
- Ensure resource hasn't been deleted
📞 Support
- Documentation: GitHub Repository
- Issues: GitHub Issues
- Email: support@spywatcher.dev