rem
This commit is contained in:
@@ -1,493 +0,0 @@
|
||||
# Authentication Guide
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Last Updated**: 2025-11-16
|
||||
|
||||
Complete guide for authenticating with the IGNY8 API v1.0.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The IGNY8 API uses **JWT (JSON Web Token) Bearer Token** authentication. All endpoints require authentication except:
|
||||
- `POST /api/v1/auth/login/` - User login
|
||||
- `POST /api/v1/auth/register/` - User registration
|
||||
|
||||
---
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
### 1. Register or Login
|
||||
|
||||
**Register** (if new user):
|
||||
```http
|
||||
POST /api/v1/auth/register/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"username": "user",
|
||||
"password": "secure_password123",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}
|
||||
```
|
||||
|
||||
**Login** (existing user):
|
||||
```http
|
||||
POST /api/v1/auth/login/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "secure_password123"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Receive Tokens
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"user": {
|
||||
"id": 1,
|
||||
"email": "user@example.com",
|
||||
"username": "user",
|
||||
"role": "owner",
|
||||
"account": {
|
||||
"id": 1,
|
||||
"name": "My Account",
|
||||
"slug": "my-account"
|
||||
}
|
||||
},
|
||||
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE3MDAxMjM0NTZ9...",
|
||||
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE3MDAxODk0NTZ9..."
|
||||
},
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use Access Token
|
||||
|
||||
Include the `access` token in all subsequent requests:
|
||||
|
||||
```http
|
||||
GET /api/v1/planner/keywords/
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
### 4. Refresh Token (when expired)
|
||||
|
||||
When the access token expires (15 minutes), use the refresh token:
|
||||
|
||||
```http
|
||||
POST /api/v1/auth/refresh/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
},
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Token Expiration
|
||||
|
||||
- **Access Token**: 15 minutes
|
||||
- **Refresh Token**: 7 days
|
||||
|
||||
### Handling Token Expiration
|
||||
|
||||
**Option 1: Automatic Refresh**
|
||||
```python
|
||||
def get_access_token():
|
||||
# Check if token is expired
|
||||
if is_token_expired(current_token):
|
||||
# Refresh token
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/auth/refresh/",
|
||||
json={"refresh": refresh_token}
|
||||
)
|
||||
data = response.json()
|
||||
if data['success']:
|
||||
return data['data']['access']
|
||||
return current_token
|
||||
```
|
||||
|
||||
**Option 2: Re-login**
|
||||
```python
|
||||
def login():
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/auth/login/",
|
||||
json={"email": email, "password": password}
|
||||
)
|
||||
data = response.json()
|
||||
if data['success']:
|
||||
return data['data']['access']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import requests
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
class Igny8API:
|
||||
def __init__(self, base_url="https://api.igny8.com/api/v1"):
|
||||
self.base_url = base_url
|
||||
self.access_token = None
|
||||
self.refresh_token = None
|
||||
self.token_expires_at = None
|
||||
|
||||
def login(self, email, password):
|
||||
"""Login and store tokens"""
|
||||
response = requests.post(
|
||||
f"{self.base_url}/auth/login/",
|
||||
json={"email": email, "password": password}
|
||||
)
|
||||
data = response.json()
|
||||
|
||||
if data['success']:
|
||||
self.access_token = data['data']['access']
|
||||
self.refresh_token = data['data']['refresh']
|
||||
# Token expires in 15 minutes
|
||||
self.token_expires_at = datetime.now() + timedelta(minutes=14)
|
||||
return True
|
||||
else:
|
||||
print(f"Login failed: {data['error']}")
|
||||
return False
|
||||
|
||||
def refresh_access_token(self):
|
||||
"""Refresh access token using refresh token"""
|
||||
if not self.refresh_token:
|
||||
return False
|
||||
|
||||
response = requests.post(
|
||||
f"{self.base_url}/auth/refresh/",
|
||||
json={"refresh": self.refresh_token}
|
||||
)
|
||||
data = response.json()
|
||||
|
||||
if data['success']:
|
||||
self.access_token = data['data']['access']
|
||||
self.refresh_token = data['data']['refresh']
|
||||
self.token_expires_at = datetime.now() + timedelta(minutes=14)
|
||||
return True
|
||||
else:
|
||||
print(f"Token refresh failed: {data['error']}")
|
||||
return False
|
||||
|
||||
def get_headers(self):
|
||||
"""Get headers with valid access token"""
|
||||
# Check if token is expired or about to expire
|
||||
if not self.token_expires_at or datetime.now() >= self.token_expires_at:
|
||||
if not self.refresh_access_token():
|
||||
raise Exception("Token expired and refresh failed")
|
||||
|
||||
return {
|
||||
'Authorization': f'Bearer {self.access_token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
def get(self, endpoint):
|
||||
"""Make authenticated GET request"""
|
||||
response = requests.get(
|
||||
f"{self.base_url}{endpoint}",
|
||||
headers=self.get_headers()
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def post(self, endpoint, data):
|
||||
"""Make authenticated POST request"""
|
||||
response = requests.post(
|
||||
f"{self.base_url}{endpoint}",
|
||||
headers=self.get_headers(),
|
||||
json=data
|
||||
)
|
||||
return response.json()
|
||||
|
||||
# Usage
|
||||
api = Igny8API()
|
||||
api.login("user@example.com", "password")
|
||||
|
||||
# Make authenticated requests
|
||||
keywords = api.get("/planner/keywords/")
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
class Igny8API {
|
||||
constructor(baseUrl = 'https://api.igny8.com/api/v1') {
|
||||
this.baseUrl = baseUrl;
|
||||
this.accessToken = null;
|
||||
this.refreshToken = null;
|
||||
this.tokenExpiresAt = null;
|
||||
}
|
||||
|
||||
async login(email, password) {
|
||||
const response = await fetch(`${this.baseUrl}/auth/login/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.accessToken = data.data.access;
|
||||
this.refreshToken = data.data.refresh;
|
||||
// Token expires in 15 minutes
|
||||
this.tokenExpiresAt = new Date(Date.now() + 14 * 60 * 1000);
|
||||
return true;
|
||||
} else {
|
||||
console.error('Login failed:', data.error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async refreshAccessToken() {
|
||||
if (!this.refreshToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/auth/refresh/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ refresh: this.refreshToken })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.accessToken = data.data.access;
|
||||
this.refreshToken = data.data.refresh;
|
||||
this.tokenExpiresAt = new Date(Date.now() + 14 * 60 * 1000);
|
||||
return true;
|
||||
} else {
|
||||
console.error('Token refresh failed:', data.error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getHeaders() {
|
||||
// Check if token is expired or about to expire
|
||||
if (!this.tokenExpiresAt || new Date() >= this.tokenExpiresAt) {
|
||||
if (!await this.refreshAccessToken()) {
|
||||
throw new Error('Token expired and refresh failed');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'Authorization': `Bearer ${this.accessToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
}
|
||||
|
||||
async get(endpoint) {
|
||||
const response = await fetch(
|
||||
`${this.baseUrl}${endpoint}`,
|
||||
{ headers: await this.getHeaders() }
|
||||
);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async post(endpoint, data) {
|
||||
const response = await fetch(
|
||||
`${this.baseUrl}${endpoint}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: await this.getHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
);
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const api = new Igny8API();
|
||||
await api.login('user@example.com', 'password');
|
||||
|
||||
// Make authenticated requests
|
||||
const keywords = await api.get('/planner/keywords/');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### 1. Store Tokens Securely
|
||||
|
||||
**❌ Don't:**
|
||||
- Store tokens in localStorage (XSS risk)
|
||||
- Commit tokens to version control
|
||||
- Log tokens in console/logs
|
||||
- Send tokens in URL parameters
|
||||
|
||||
**✅ Do:**
|
||||
- Store tokens in httpOnly cookies (server-side)
|
||||
- Use secure storage (encrypted) for client-side
|
||||
- Rotate tokens regularly
|
||||
- Implement token revocation
|
||||
|
||||
### 2. Handle Token Expiration
|
||||
|
||||
Always check token expiration and refresh before making requests:
|
||||
|
||||
```python
|
||||
def is_token_valid(token_expires_at):
|
||||
# Refresh 1 minute before expiration
|
||||
return datetime.now() < (token_expires_at - timedelta(minutes=1))
|
||||
```
|
||||
|
||||
### 3. Implement Retry Logic
|
||||
|
||||
```python
|
||||
def make_request_with_retry(url, headers, max_retries=3):
|
||||
for attempt in range(max_retries):
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
if response.status_code == 401:
|
||||
# Token expired, refresh and retry
|
||||
refresh_token()
|
||||
headers = get_headers()
|
||||
continue
|
||||
|
||||
return response.json()
|
||||
|
||||
raise Exception("Max retries exceeded")
|
||||
```
|
||||
|
||||
### 4. Validate Token Before Use
|
||||
|
||||
```python
|
||||
def validate_token(token):
|
||||
try:
|
||||
# Decode token (without verification for structure check)
|
||||
import jwt
|
||||
decoded = jwt.decode(token, options={"verify_signature": False})
|
||||
exp = decoded.get('exp')
|
||||
|
||||
if exp and datetime.fromtimestamp(exp) < datetime.now():
|
||||
return False
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Authentication Errors
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Authentication required",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Include valid `Authorization: Bearer <token>` header.
|
||||
|
||||
**403 Forbidden**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Permission denied",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: User lacks required permissions. Check user role and resource access.
|
||||
|
||||
---
|
||||
|
||||
## Testing Authentication
|
||||
|
||||
### Using Swagger UI
|
||||
|
||||
1. Navigate to `https://api.igny8.com/api/docs/`
|
||||
2. Click "Authorize" button
|
||||
3. Enter: `Bearer <your_token>`
|
||||
4. Click "Authorize"
|
||||
5. All requests will include the token
|
||||
|
||||
### Using cURL
|
||||
|
||||
```bash
|
||||
# Login
|
||||
curl -X POST https://api.igny8.com/api/v1/auth/login/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"user@example.com","password":"password"}'
|
||||
|
||||
# Use token
|
||||
curl -X GET https://api.igny8.com/api/v1/planner/keywords/ \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: "Authentication required" (401)
|
||||
|
||||
**Causes**:
|
||||
- Missing Authorization header
|
||||
- Invalid token format
|
||||
- Expired token
|
||||
|
||||
**Solutions**:
|
||||
1. Verify `Authorization: Bearer <token>` header is included
|
||||
2. Check token is not expired
|
||||
3. Refresh token or re-login
|
||||
|
||||
### Issue: "Permission denied" (403)
|
||||
|
||||
**Causes**:
|
||||
- User lacks required role
|
||||
- Resource belongs to different account
|
||||
- Site/sector access denied
|
||||
|
||||
**Solutions**:
|
||||
1. Check user role has required permissions
|
||||
2. Verify resource belongs to user's account
|
||||
3. Check site/sector access permissions
|
||||
|
||||
### Issue: Token expires frequently
|
||||
|
||||
**Solution**: Implement automatic token refresh before expiration.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-16
|
||||
**API Version**: 1.0.0
|
||||
|
||||
@@ -1,407 +0,0 @@
|
||||
# API Error Codes Reference
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Last Updated**: 2025-11-16
|
||||
|
||||
This document provides a comprehensive reference for all error codes and error scenarios in the IGNY8 API v1.0.
|
||||
|
||||
---
|
||||
|
||||
## Error Response Format
|
||||
|
||||
All errors follow this unified format:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Error message",
|
||||
"errors": {
|
||||
"field_name": ["Field-specific errors"]
|
||||
},
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP Status Codes
|
||||
|
||||
### 200 OK
|
||||
**Meaning**: Request successful
|
||||
**Response**: Success response with data
|
||||
|
||||
### 201 Created
|
||||
**Meaning**: Resource created successfully
|
||||
**Response**: Success response with created resource data
|
||||
|
||||
### 204 No Content
|
||||
**Meaning**: Resource deleted successfully
|
||||
**Response**: Empty response body
|
||||
|
||||
### 400 Bad Request
|
||||
**Meaning**: Validation error or invalid request
|
||||
**Common Causes**:
|
||||
- Missing required fields
|
||||
- Invalid field values
|
||||
- Invalid data format
|
||||
- Business logic validation failures
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Validation failed",
|
||||
"errors": {
|
||||
"email": ["This field is required"],
|
||||
"password": ["Password must be at least 8 characters"]
|
||||
},
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
### 401 Unauthorized
|
||||
**Meaning**: Authentication required
|
||||
**Common Causes**:
|
||||
- Missing Authorization header
|
||||
- Invalid or expired token
|
||||
- Token not provided
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Authentication required",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
### 403 Forbidden
|
||||
**Meaning**: Permission denied
|
||||
**Common Causes**:
|
||||
- User lacks required role
|
||||
- User doesn't have access to resource
|
||||
- Account/site/sector access denied
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Permission denied",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
### 404 Not Found
|
||||
**Meaning**: Resource not found
|
||||
**Common Causes**:
|
||||
- Invalid resource ID
|
||||
- Resource doesn't exist
|
||||
- Resource belongs to different account
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Resource not found",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
### 409 Conflict
|
||||
**Meaning**: Resource conflict
|
||||
**Common Causes**:
|
||||
- Duplicate resource (e.g., email already exists)
|
||||
- Resource state conflict
|
||||
- Concurrent modification
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Conflict",
|
||||
"errors": {
|
||||
"email": ["User with this email already exists"]
|
||||
},
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
### 422 Unprocessable Entity
|
||||
**Meaning**: Validation failed
|
||||
**Common Causes**:
|
||||
- Complex validation rules failed
|
||||
- Business logic validation failed
|
||||
- Data integrity constraints violated
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Validation failed",
|
||||
"errors": {
|
||||
"site": ["Site must belong to your account"],
|
||||
"sector": ["Sector must belong to the selected site"]
|
||||
},
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
### 429 Too Many Requests
|
||||
**Meaning**: Rate limit exceeded
|
||||
**Common Causes**:
|
||||
- Too many requests in time window
|
||||
- AI function rate limit exceeded
|
||||
- Authentication rate limit exceeded
|
||||
|
||||
**Response Headers**:
|
||||
- `X-Throttle-Limit`: Maximum requests allowed
|
||||
- `X-Throttle-Remaining`: Remaining requests (0)
|
||||
- `X-Throttle-Reset`: Unix timestamp when limit resets
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Rate limit exceeded",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Wait until `X-Throttle-Reset` timestamp before retrying.
|
||||
|
||||
### 500 Internal Server Error
|
||||
**Meaning**: Server error
|
||||
**Common Causes**:
|
||||
- Unexpected server error
|
||||
- Database error
|
||||
- External service failure
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Internal server error",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Retry request. If persistent, contact support with `request_id`.
|
||||
|
||||
---
|
||||
|
||||
## Field-Specific Error Messages
|
||||
|
||||
### Authentication Errors
|
||||
|
||||
| Field | Error Message | Description |
|
||||
|-------|---------------|-------------|
|
||||
| `email` | "This field is required" | Email not provided |
|
||||
| `email` | "Invalid email format" | Email format invalid |
|
||||
| `email` | "User with this email already exists" | Email already registered |
|
||||
| `password` | "This field is required" | Password not provided |
|
||||
| `password` | "Password must be at least 8 characters" | Password too short |
|
||||
| `password` | "Invalid credentials" | Wrong password |
|
||||
|
||||
### Planner Module Errors
|
||||
|
||||
| Field | Error Message | Description |
|
||||
|-------|---------------|-------------|
|
||||
| `seed_keyword_id` | "This field is required" | Seed keyword not provided |
|
||||
| `seed_keyword_id` | "Invalid seed keyword" | Seed keyword doesn't exist |
|
||||
| `site_id` | "This field is required" | Site not provided |
|
||||
| `site_id` | "Site must belong to your account" | Site access denied |
|
||||
| `sector_id` | "This field is required" | Sector not provided |
|
||||
| `sector_id` | "Sector must belong to the selected site" | Sector-site mismatch |
|
||||
| `status` | "Invalid status value" | Status value not allowed |
|
||||
|
||||
### Writer Module Errors
|
||||
|
||||
| Field | Error Message | Description |
|
||||
|-------|---------------|-------------|
|
||||
| `title` | "This field is required" | Title not provided |
|
||||
| `site_id` | "This field is required" | Site not provided |
|
||||
| `sector_id` | "This field is required" | Sector not provided |
|
||||
| `image_type` | "Invalid image type" | Image type not allowed |
|
||||
|
||||
### System Module Errors
|
||||
|
||||
| Field | Error Message | Description |
|
||||
|-------|---------------|-------------|
|
||||
| `api_key` | "This field is required" | API key not provided |
|
||||
| `api_key` | "Invalid API key format" | API key format invalid |
|
||||
| `integration_type` | "Invalid integration type" | Integration type not allowed |
|
||||
|
||||
### Billing Module Errors
|
||||
|
||||
| Field | Error Message | Description |
|
||||
|-------|---------------|-------------|
|
||||
| `amount` | "This field is required" | Amount not provided |
|
||||
| `amount` | "Amount must be positive" | Invalid amount value |
|
||||
| `credits` | "Insufficient credits" | Not enough credits available |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Best Practices
|
||||
|
||||
### 1. Always Check `success` Field
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
if data['success']:
|
||||
# Handle success
|
||||
result = data['data'] or data['results']
|
||||
else:
|
||||
# Handle error
|
||||
error_message = data['error']
|
||||
field_errors = data.get('errors', {})
|
||||
```
|
||||
|
||||
### 2. Handle Field-Specific Errors
|
||||
|
||||
```python
|
||||
if not data['success']:
|
||||
if 'errors' in data:
|
||||
for field, errors in data['errors'].items():
|
||||
print(f"{field}: {', '.join(errors)}")
|
||||
else:
|
||||
print(f"Error: {data['error']}")
|
||||
```
|
||||
|
||||
### 3. Use Request ID for Support
|
||||
|
||||
```python
|
||||
if not data['success']:
|
||||
request_id = data.get('request_id')
|
||||
print(f"Error occurred. Request ID: {request_id}")
|
||||
# Include request_id when contacting support
|
||||
```
|
||||
|
||||
### 4. Handle Rate Limiting
|
||||
|
||||
```python
|
||||
if response.status_code == 429:
|
||||
reset_time = response.headers.get('X-Throttle-Reset')
|
||||
wait_seconds = int(reset_time) - int(time.time())
|
||||
print(f"Rate limited. Wait {wait_seconds} seconds.")
|
||||
time.sleep(wait_seconds)
|
||||
# Retry request
|
||||
```
|
||||
|
||||
### 5. Retry on Server Errors
|
||||
|
||||
```python
|
||||
if response.status_code >= 500:
|
||||
# Retry with exponential backoff
|
||||
time.sleep(2 ** retry_count)
|
||||
# Retry request
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Error Scenarios
|
||||
|
||||
### Scenario 1: Missing Authentication
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /api/v1/planner/keywords/
|
||||
(No Authorization header)
|
||||
```
|
||||
|
||||
**Response** (401):
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Authentication required",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Include `Authorization: Bearer <token>` header.
|
||||
|
||||
### Scenario 2: Invalid Resource ID
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /api/v1/planner/keywords/99999/
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response** (404):
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Resource not found",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Verify resource ID exists and belongs to your account.
|
||||
|
||||
### Scenario 3: Validation Error
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
POST /api/v1/planner/keywords/
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"seed_keyword_id": null,
|
||||
"site_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (400):
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Validation failed",
|
||||
"errors": {
|
||||
"seed_keyword_id": ["This field is required"],
|
||||
"sector_id": ["This field is required"]
|
||||
},
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Provide all required fields with valid values.
|
||||
|
||||
### Scenario 4: Rate Limit Exceeded
|
||||
|
||||
**Request**: Multiple rapid requests
|
||||
|
||||
**Response** (429):
|
||||
```http
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
X-Throttle-Limit: 60
|
||||
X-Throttle-Remaining: 0
|
||||
X-Throttle-Reset: 1700123456
|
||||
|
||||
{
|
||||
"success": false,
|
||||
"error": "Rate limit exceeded",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Wait until `X-Throttle-Reset` timestamp, then retry.
|
||||
|
||||
---
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
1. **Always include `request_id`** when reporting errors
|
||||
2. **Check response headers** for rate limit information
|
||||
3. **Verify authentication token** is valid and not expired
|
||||
4. **Check field-specific errors** in `errors` object
|
||||
5. **Review request payload** matches API specification
|
||||
6. **Use Swagger UI** to test endpoints interactively
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-16
|
||||
**API Version**: 1.0.0
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
# API Migration Guide
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Last Updated**: 2025-11-16
|
||||
|
||||
Guide for migrating existing API consumers to IGNY8 API Standard v1.0.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The IGNY8 API v1.0 introduces a unified response format that standardizes all API responses. This guide helps you migrate existing code to work with the new format.
|
||||
|
||||
---
|
||||
|
||||
## What Changed
|
||||
|
||||
### Before (Legacy Format)
|
||||
|
||||
**Success Response**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Keyword",
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"detail": "Not found."
|
||||
}
|
||||
```
|
||||
|
||||
### After (Unified Format v1.0)
|
||||
|
||||
**Success Response**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "Keyword",
|
||||
"status": "active"
|
||||
},
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Resource not found",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### Step 1: Update Response Parsing
|
||||
|
||||
#### Before
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
# Direct access
|
||||
keyword_id = data['id']
|
||||
keyword_name = data['name']
|
||||
```
|
||||
|
||||
#### After
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
# Check success first
|
||||
if data['success']:
|
||||
# Extract data from unified format
|
||||
keyword_data = data['data'] # or data['results'] for lists
|
||||
keyword_id = keyword_data['id']
|
||||
keyword_name = keyword_data['name']
|
||||
else:
|
||||
# Handle error
|
||||
error_message = data['error']
|
||||
raise Exception(error_message)
|
||||
```
|
||||
|
||||
### Step 2: Update Error Handling
|
||||
|
||||
#### Before
|
||||
|
||||
```python
|
||||
try:
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
except requests.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
print("Not found")
|
||||
elif e.response.status_code == 400:
|
||||
print("Bad request")
|
||||
```
|
||||
|
||||
#### After
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
if not data['success']:
|
||||
# Unified error format
|
||||
error_message = data['error']
|
||||
field_errors = data.get('errors', {})
|
||||
|
||||
if response.status_code == 404:
|
||||
print(f"Not found: {error_message}")
|
||||
elif response.status_code == 400:
|
||||
print(f"Validation error: {error_message}")
|
||||
for field, errors in field_errors.items():
|
||||
print(f" {field}: {', '.join(errors)}")
|
||||
```
|
||||
|
||||
### Step 3: Update Pagination Handling
|
||||
|
||||
#### Before
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
results = data['results']
|
||||
next_page = data['next']
|
||||
count = data['count']
|
||||
```
|
||||
|
||||
#### After
|
||||
|
||||
```python
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
if data['success']:
|
||||
# Paginated response format
|
||||
results = data['results'] # Same field name
|
||||
next_page = data['next'] # Same field name
|
||||
count = data['count'] # Same field name
|
||||
else:
|
||||
# Handle error
|
||||
raise Exception(data['error'])
|
||||
```
|
||||
|
||||
### Step 4: Update Frontend Code
|
||||
|
||||
#### Before (JavaScript)
|
||||
|
||||
```javascript
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
|
||||
// Direct access
|
||||
const keywordId = data.id;
|
||||
const keywordName = data.name;
|
||||
```
|
||||
|
||||
#### After (JavaScript)
|
||||
|
||||
```javascript
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
|
||||
// Check success first
|
||||
if (data.success) {
|
||||
// Extract data from unified format
|
||||
const keywordData = data.data || data.results;
|
||||
const keywordId = keywordData.id;
|
||||
const keywordName = keywordData.name;
|
||||
} else {
|
||||
// Handle error
|
||||
console.error('Error:', data.error);
|
||||
if (data.errors) {
|
||||
// Handle field-specific errors
|
||||
Object.entries(data.errors).forEach(([field, errors]) => {
|
||||
console.error(`${field}: ${errors.join(', ')}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Helper Functions
|
||||
|
||||
### Python Helper
|
||||
|
||||
```python
|
||||
def parse_api_response(response):
|
||||
"""Parse unified API response format"""
|
||||
data = response.json()
|
||||
|
||||
if data.get('success'):
|
||||
# Return data or results
|
||||
return data.get('data') or data.get('results')
|
||||
else:
|
||||
# Raise exception with error details
|
||||
error_msg = data.get('error', 'Unknown error')
|
||||
errors = data.get('errors', {})
|
||||
|
||||
if errors:
|
||||
error_msg += f": {errors}"
|
||||
|
||||
raise Exception(error_msg)
|
||||
|
||||
# Usage
|
||||
response = requests.get(url, headers=headers)
|
||||
keyword_data = parse_api_response(response)
|
||||
```
|
||||
|
||||
### JavaScript Helper
|
||||
|
||||
```javascript
|
||||
function parseApiResponse(data) {
|
||||
if (data.success) {
|
||||
return data.data || data.results;
|
||||
} else {
|
||||
const error = new Error(data.error);
|
||||
error.errors = data.errors || {};
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
try {
|
||||
const keywordData = parseApiResponse(data);
|
||||
} catch (error) {
|
||||
console.error('API Error:', error.message);
|
||||
if (error.errors) {
|
||||
// Handle field-specific errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### 1. Response Structure
|
||||
|
||||
**Breaking**: All responses now include `success` field and wrap data in `data` or `results`.
|
||||
|
||||
**Migration**: Update all response parsing code to check `success` and extract `data`/`results`.
|
||||
|
||||
### 2. Error Format
|
||||
|
||||
**Breaking**: Error responses now use unified format with `error` and `errors` fields.
|
||||
|
||||
**Migration**: Update error handling to use new format.
|
||||
|
||||
### 3. Request ID
|
||||
|
||||
**New**: All responses include `request_id` for debugging.
|
||||
|
||||
**Migration**: Optional - can be used for support requests.
|
||||
|
||||
---
|
||||
|
||||
## Non-Breaking Changes
|
||||
|
||||
### 1. Pagination
|
||||
|
||||
**Status**: Compatible - same field names (`count`, `next`, `previous`, `results`)
|
||||
|
||||
**Migration**: No changes needed, but wrap in success check.
|
||||
|
||||
### 2. Authentication
|
||||
|
||||
**Status**: Compatible - same JWT Bearer token format
|
||||
|
||||
**Migration**: No changes needed.
|
||||
|
||||
### 3. Endpoint URLs
|
||||
|
||||
**Status**: Compatible - same endpoint paths
|
||||
|
||||
**Migration**: No changes needed.
|
||||
|
||||
---
|
||||
|
||||
## Testing Migration
|
||||
|
||||
### 1. Update Test Code
|
||||
|
||||
```python
|
||||
# Before
|
||||
def test_get_keyword():
|
||||
response = client.get('/api/v1/planner/keywords/1/')
|
||||
assert response.status_code == 200
|
||||
assert response.json()['id'] == 1
|
||||
|
||||
# After
|
||||
def test_get_keyword():
|
||||
response = client.get('/api/v1/planner/keywords/1/')
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data['success'] == True
|
||||
assert data['data']['id'] == 1
|
||||
```
|
||||
|
||||
### 2. Test Error Handling
|
||||
|
||||
```python
|
||||
def test_not_found():
|
||||
response = client.get('/api/v1/planner/keywords/99999/')
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data['success'] == False
|
||||
assert data['error'] == "Resource not found"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
- [ ] Update response parsing to check `success` field
|
||||
- [ ] Extract data from `data` or `results` field
|
||||
- [ ] Update error handling to use unified format
|
||||
- [ ] Update pagination handling (wrap in success check)
|
||||
- [ ] Update frontend code (if applicable)
|
||||
- [ ] Update test code
|
||||
- [ ] Test all endpoints
|
||||
- [ ] Update documentation
|
||||
- [ ] Deploy and monitor
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise during migration:
|
||||
|
||||
1. **Temporary Compatibility Layer**: Add wrapper to convert unified format back to legacy format
|
||||
2. **Feature Flag**: Use feature flag to toggle between formats
|
||||
3. **Gradual Migration**: Migrate endpoints one module at a time
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For migration support:
|
||||
- Review [API Documentation](API-DOCUMENTATION.md)
|
||||
- Check [Error Codes Reference](ERROR-CODES.md)
|
||||
- Contact support with `request_id` from failed requests
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-16
|
||||
**API Version**: 1.0.0
|
||||
|
||||
@@ -1,439 +0,0 @@
|
||||
# Rate Limiting Guide
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Last Updated**: 2025-11-16
|
||||
|
||||
Complete guide for understanding and handling rate limits in the IGNY8 API v1.0.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Rate limiting protects the API from abuse and ensures fair resource usage. Different operation types have different rate limits based on their resource intensity.
|
||||
|
||||
---
|
||||
|
||||
## Rate Limit Headers
|
||||
|
||||
Every API response includes rate limit information in headers:
|
||||
|
||||
- `X-Throttle-Limit`: Maximum requests allowed in the time window
|
||||
- `X-Throttle-Remaining`: Remaining requests in current window
|
||||
- `X-Throttle-Reset`: Unix timestamp when the limit resets
|
||||
|
||||
### Example Response Headers
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
X-Throttle-Limit: 60
|
||||
X-Throttle-Remaining: 45
|
||||
X-Throttle-Reset: 1700123456
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limit Scopes
|
||||
|
||||
Rate limits are scoped by operation type:
|
||||
|
||||
### AI Functions (Expensive Operations)
|
||||
|
||||
| Scope | Limit | Endpoints |
|
||||
|-------|-------|-----------|
|
||||
| `ai_function` | 10/min | Auto-cluster, content generation |
|
||||
| `image_gen` | 15/min | Image generation (DALL-E, Runware) |
|
||||
| `planner_ai` | 10/min | AI-powered planner operations |
|
||||
| `writer_ai` | 10/min | AI-powered writer operations |
|
||||
|
||||
### Content Operations
|
||||
|
||||
| Scope | Limit | Endpoints |
|
||||
|-------|-------|-----------|
|
||||
| `content_write` | 30/min | Content creation, updates |
|
||||
| `content_read` | 100/min | Content listing, retrieval |
|
||||
|
||||
### Authentication
|
||||
|
||||
| Scope | Limit | Endpoints |
|
||||
|-------|-------|-----------|
|
||||
| `auth` | 20/min | Login, register, password reset |
|
||||
| `auth_strict` | 5/min | Sensitive auth operations |
|
||||
|
||||
### Planner Operations
|
||||
|
||||
| Scope | Limit | Endpoints |
|
||||
|-------|-------|-----------|
|
||||
| `planner` | 60/min | Keywords, clusters, ideas CRUD |
|
||||
|
||||
### Writer Operations
|
||||
|
||||
| Scope | Limit | Endpoints |
|
||||
|-------|-------|-----------|
|
||||
| `writer` | 60/min | Tasks, content, images CRUD |
|
||||
|
||||
### System Operations
|
||||
|
||||
| Scope | Limit | Endpoints |
|
||||
|-------|-------|-----------|
|
||||
| `system` | 100/min | Settings, prompts, profiles |
|
||||
| `system_admin` | 30/min | Admin-only system operations |
|
||||
|
||||
### Billing Operations
|
||||
|
||||
| Scope | Limit | Endpoints |
|
||||
|-------|-------|-----------|
|
||||
| `billing` | 30/min | Credit queries, usage logs |
|
||||
| `billing_admin` | 10/min | Credit management (admin) |
|
||||
|
||||
### Default
|
||||
|
||||
| Scope | Limit | Endpoints |
|
||||
|-------|-------|-----------|
|
||||
| `default` | 100/min | Endpoints without explicit scope |
|
||||
|
||||
---
|
||||
|
||||
## Rate Limit Exceeded (429)
|
||||
|
||||
When rate limit is exceeded, you receive:
|
||||
|
||||
**Status Code**: `429 Too Many Requests`
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Rate limit exceeded",
|
||||
"request_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Headers**:
|
||||
```http
|
||||
X-Throttle-Limit: 60
|
||||
X-Throttle-Remaining: 0
|
||||
X-Throttle-Reset: 1700123456
|
||||
```
|
||||
|
||||
### Handling Rate Limits
|
||||
|
||||
**1. Check Headers Before Request**
|
||||
|
||||
```python
|
||||
def make_request(url, headers):
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
# Check remaining requests
|
||||
remaining = int(response.headers.get('X-Throttle-Remaining', 0))
|
||||
|
||||
if remaining < 5:
|
||||
# Approaching limit, slow down
|
||||
time.sleep(1)
|
||||
|
||||
return response.json()
|
||||
```
|
||||
|
||||
**2. Handle 429 Response**
|
||||
|
||||
```python
|
||||
def make_request_with_backoff(url, headers, max_retries=3):
|
||||
for attempt in range(max_retries):
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
if response.status_code == 429:
|
||||
# Get reset time
|
||||
reset_time = int(response.headers.get('X-Throttle-Reset', 0))
|
||||
current_time = int(time.time())
|
||||
wait_seconds = max(1, reset_time - current_time)
|
||||
|
||||
print(f"Rate limited. Waiting {wait_seconds} seconds...")
|
||||
time.sleep(wait_seconds)
|
||||
continue
|
||||
|
||||
return response.json()
|
||||
|
||||
raise Exception("Max retries exceeded")
|
||||
```
|
||||
|
||||
**3. Implement Exponential Backoff**
|
||||
|
||||
```python
|
||||
import time
|
||||
import random
|
||||
|
||||
def make_request_with_exponential_backoff(url, headers):
|
||||
max_wait = 60 # Maximum wait time in seconds
|
||||
base_wait = 1 # Base wait time in seconds
|
||||
|
||||
for attempt in range(5):
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
if response.status_code != 429:
|
||||
return response.json()
|
||||
|
||||
# Exponential backoff with jitter
|
||||
wait_time = min(
|
||||
base_wait * (2 ** attempt) + random.uniform(0, 1),
|
||||
max_wait
|
||||
)
|
||||
|
||||
print(f"Rate limited. Waiting {wait_time:.2f} seconds...")
|
||||
time.sleep(wait_time)
|
||||
|
||||
raise Exception("Rate limit exceeded after retries")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Monitor Rate Limit Headers
|
||||
|
||||
Always check `X-Throttle-Remaining` to avoid hitting limits:
|
||||
|
||||
```python
|
||||
def check_rate_limit(response):
|
||||
remaining = int(response.headers.get('X-Throttle-Remaining', 0))
|
||||
|
||||
if remaining < 10:
|
||||
print(f"Warning: Only {remaining} requests remaining")
|
||||
|
||||
return remaining
|
||||
```
|
||||
|
||||
### 2. Implement Request Queuing
|
||||
|
||||
For bulk operations, queue requests to stay within limits:
|
||||
|
||||
```python
|
||||
import queue
|
||||
import threading
|
||||
|
||||
class RateLimitedAPI:
|
||||
def __init__(self, requests_per_minute=60):
|
||||
self.queue = queue.Queue()
|
||||
self.requests_per_minute = requests_per_minute
|
||||
self.min_interval = 60 / requests_per_minute
|
||||
self.last_request_time = 0
|
||||
|
||||
def make_request(self, url, headers):
|
||||
# Ensure minimum interval between requests
|
||||
elapsed = time.time() - self.last_request_time
|
||||
if elapsed < self.min_interval:
|
||||
time.sleep(self.min_interval - elapsed)
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
self.last_request_time = time.time()
|
||||
|
||||
return response.json()
|
||||
```
|
||||
|
||||
### 3. Cache Responses
|
||||
|
||||
Cache frequently accessed data to reduce API calls:
|
||||
|
||||
```python
|
||||
from functools import lru_cache
|
||||
import time
|
||||
|
||||
class CachedAPI:
|
||||
def __init__(self, cache_ttl=300): # 5 minutes
|
||||
self.cache = {}
|
||||
self.cache_ttl = cache_ttl
|
||||
|
||||
def get_cached(self, url, headers, cache_key):
|
||||
# Check cache
|
||||
if cache_key in self.cache:
|
||||
data, timestamp = self.cache[cache_key]
|
||||
if time.time() - timestamp < self.cache_ttl:
|
||||
return data
|
||||
|
||||
# Fetch from API
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
# Store in cache
|
||||
self.cache[cache_key] = (data, time.time())
|
||||
|
||||
return data
|
||||
```
|
||||
|
||||
### 4. Batch Requests When Possible
|
||||
|
||||
Use bulk endpoints instead of multiple individual requests:
|
||||
|
||||
```python
|
||||
# ❌ Don't: Multiple individual requests
|
||||
for keyword_id in keyword_ids:
|
||||
response = requests.get(f"/api/v1/planner/keywords/{keyword_id}/", headers=headers)
|
||||
|
||||
# ✅ Do: Use bulk endpoint if available
|
||||
response = requests.post(
|
||||
"/api/v1/planner/keywords/bulk/",
|
||||
json={"ids": keyword_ids},
|
||||
headers=headers
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limit Bypass
|
||||
|
||||
### Development/Debug Mode
|
||||
|
||||
Rate limiting is automatically bypassed when:
|
||||
- `DEBUG=True` in Django settings
|
||||
- `IGNY8_DEBUG_THROTTLE=True` environment variable
|
||||
- User belongs to `aws-admin` account
|
||||
- User has `admin` or `developer` role
|
||||
|
||||
**Note**: Headers are still set for debugging, but requests are not blocked.
|
||||
|
||||
---
|
||||
|
||||
## Monitoring Rate Limits
|
||||
|
||||
### Track Usage
|
||||
|
||||
```python
|
||||
class RateLimitMonitor:
|
||||
def __init__(self):
|
||||
self.usage_by_scope = {}
|
||||
|
||||
def track_request(self, response, scope):
|
||||
if scope not in self.usage_by_scope:
|
||||
self.usage_by_scope[scope] = {
|
||||
'total': 0,
|
||||
'limited': 0
|
||||
}
|
||||
|
||||
self.usage_by_scope[scope]['total'] += 1
|
||||
|
||||
if response.status_code == 429:
|
||||
self.usage_by_scope[scope]['limited'] += 1
|
||||
|
||||
remaining = int(response.headers.get('X-Throttle-Remaining', 0))
|
||||
limit = int(response.headers.get('X-Throttle-Limit', 0))
|
||||
|
||||
usage_percent = ((limit - remaining) / limit) * 100
|
||||
|
||||
if usage_percent > 80:
|
||||
print(f"Warning: {scope} at {usage_percent:.1f}% capacity")
|
||||
|
||||
def get_report(self):
|
||||
return self.usage_by_scope
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Frequent 429 Errors
|
||||
|
||||
**Causes**:
|
||||
- Too many requests in short time
|
||||
- Not checking rate limit headers
|
||||
- No request throttling implemented
|
||||
|
||||
**Solutions**:
|
||||
1. Implement request throttling
|
||||
2. Monitor `X-Throttle-Remaining` header
|
||||
3. Add delays between requests
|
||||
4. Use bulk endpoints when available
|
||||
|
||||
### Issue: Rate Limits Too Restrictive
|
||||
|
||||
**Solutions**:
|
||||
1. Contact support for higher limits (if justified)
|
||||
2. Optimize requests (cache, batch, reduce frequency)
|
||||
3. Use development account for testing (bypass enabled)
|
||||
|
||||
---
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Python - Complete Rate Limit Handler
|
||||
|
||||
```python
|
||||
import requests
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
class RateLimitedClient:
|
||||
def __init__(self, base_url, token):
|
||||
self.base_url = base_url
|
||||
self.headers = {
|
||||
'Authorization': f'Bearer {token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
self.rate_limits = {}
|
||||
|
||||
def _wait_for_rate_limit(self, scope='default'):
|
||||
"""Wait if approaching rate limit"""
|
||||
if scope in self.rate_limits:
|
||||
limit_info = self.rate_limits[scope]
|
||||
remaining = limit_info.get('remaining', 0)
|
||||
reset_time = limit_info.get('reset_time', 0)
|
||||
|
||||
if remaining < 5:
|
||||
wait_time = max(0, reset_time - time.time())
|
||||
if wait_time > 0:
|
||||
print(f"Rate limit low. Waiting {wait_time:.1f}s...")
|
||||
time.sleep(wait_time)
|
||||
|
||||
def _update_rate_limit_info(self, response, scope='default'):
|
||||
"""Update rate limit information from response headers"""
|
||||
limit = response.headers.get('X-Throttle-Limit')
|
||||
remaining = response.headers.get('X-Throttle-Remaining')
|
||||
reset = response.headers.get('X-Throttle-Reset')
|
||||
|
||||
if limit and remaining and reset:
|
||||
self.rate_limits[scope] = {
|
||||
'limit': int(limit),
|
||||
'remaining': int(remaining),
|
||||
'reset_time': int(reset)
|
||||
}
|
||||
|
||||
def request(self, method, endpoint, scope='default', **kwargs):
|
||||
"""Make rate-limited request"""
|
||||
# Wait if approaching limit
|
||||
self._wait_for_rate_limit(scope)
|
||||
|
||||
# Make request
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
response = requests.request(method, url, headers=self.headers, **kwargs)
|
||||
|
||||
# Update rate limit info
|
||||
self._update_rate_limit_info(response, scope)
|
||||
|
||||
# Handle rate limit error
|
||||
if response.status_code == 429:
|
||||
reset_time = int(response.headers.get('X-Throttle-Reset', 0))
|
||||
wait_time = max(1, reset_time - time.time())
|
||||
print(f"Rate limited. Waiting {wait_time:.1f}s...")
|
||||
time.sleep(wait_time)
|
||||
# Retry once
|
||||
response = requests.request(method, url, headers=self.headers, **kwargs)
|
||||
self._update_rate_limit_info(response, scope)
|
||||
|
||||
return response.json()
|
||||
|
||||
def get(self, endpoint, scope='default'):
|
||||
return self.request('GET', endpoint, scope)
|
||||
|
||||
def post(self, endpoint, data, scope='default'):
|
||||
return self.request('POST', endpoint, scope, json=data)
|
||||
|
||||
# Usage
|
||||
client = RateLimitedClient("https://api.igny8.com/api/v1", "your_token")
|
||||
|
||||
# Make requests with automatic rate limit handling
|
||||
keywords = client.get("/planner/keywords/", scope="planner")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-16
|
||||
**API Version**: 1.0.0
|
||||
|
||||
@@ -1,495 +0,0 @@
|
||||
# Section 1 & 2 Implementation Summary
|
||||
|
||||
**API Standard v1.0 Implementation**
|
||||
**Sections Completed**: Section 1 (Testing) & Section 2 (Documentation)
|
||||
**Date**: 2025-11-16
|
||||
**Status**: ✅ Complete
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document summarizes the implementation of **Section 1: Testing** and **Section 2: Documentation** from the Unified API Standard v1.0 implementation plan.
|
||||
|
||||
---
|
||||
|
||||
## Section 1: Testing ✅
|
||||
|
||||
### Implementation Summary
|
||||
|
||||
Comprehensive test suite created to verify the Unified API Standard v1.0 implementation across all modules and components.
|
||||
|
||||
### Test Suite Structure
|
||||
|
||||
#### Unit Tests (4 files, ~61 test methods)
|
||||
|
||||
1. **test_response.py** (153 lines)
|
||||
- Tests for `success_response()`, `error_response()`, `paginated_response()`
|
||||
- Tests for `get_request_id()`
|
||||
- Verifies unified response format with `success`, `data`/`results`, `message`, `error`, `errors`, `request_id`
|
||||
- **18 test methods**
|
||||
|
||||
2. **test_exception_handler.py** (177 lines)
|
||||
- Tests for `custom_exception_handler()`
|
||||
- Tests all exception types:
|
||||
- `ValidationError` (400)
|
||||
- `AuthenticationFailed` (401)
|
||||
- `PermissionDenied` (403)
|
||||
- `NotFound` (404)
|
||||
- `Throttled` (429)
|
||||
- Generic exceptions (500)
|
||||
- Tests debug mode behavior (traceback, view, path, method)
|
||||
- **12 test methods**
|
||||
|
||||
3. **test_permissions.py** (245 lines)
|
||||
- Tests for all permission classes:
|
||||
- `IsAuthenticatedAndActive`
|
||||
- `HasTenantAccess`
|
||||
- `IsViewerOrAbove`
|
||||
- `IsEditorOrAbove`
|
||||
- `IsAdminOrOwner`
|
||||
- Tests role-based access control (viewer, editor, admin, owner, developer)
|
||||
- Tests tenant isolation
|
||||
- Tests admin/system account bypass logic
|
||||
- **20 test methods**
|
||||
|
||||
4. **test_throttles.py** (145 lines)
|
||||
- Tests for `DebugScopedRateThrottle`
|
||||
- Tests bypass logic:
|
||||
- DEBUG mode bypass
|
||||
- Environment flag bypass (`IGNY8_DEBUG_THROTTLE`)
|
||||
- Admin/developer/system account bypass
|
||||
- Tests rate parsing and throttle headers
|
||||
- **11 test methods**
|
||||
|
||||
#### Integration Tests (9 files, ~54 test methods)
|
||||
|
||||
1. **test_integration_base.py** (107 lines)
|
||||
- Base test class with common fixtures
|
||||
- Helper methods:
|
||||
- `assert_unified_response_format()` - Verifies unified response structure
|
||||
- `assert_paginated_response()` - Verifies pagination format
|
||||
- Sets up: User, Account, Plan, Site, Sector, Industry, SeedKeyword
|
||||
|
||||
2. **test_integration_planner.py** (120 lines)
|
||||
- Tests Planner module endpoints:
|
||||
- `KeywordViewSet` (CRUD operations)
|
||||
- `ClusterViewSet` (CRUD operations)
|
||||
- `ContentIdeasViewSet` (CRUD operations)
|
||||
- Tests AI actions:
|
||||
- `auto_cluster` - Automatic keyword clustering
|
||||
- `auto_generate_ideas` - AI content idea generation
|
||||
- `bulk_queue_to_writer` - Bulk task creation
|
||||
- Tests unified response format and permissions
|
||||
- **12 test methods**
|
||||
|
||||
3. **test_integration_writer.py** (65 lines)
|
||||
- Tests Writer module endpoints:
|
||||
- `TasksViewSet` (CRUD operations)
|
||||
- `ContentViewSet` (CRUD operations)
|
||||
- `ImagesViewSet` (CRUD operations)
|
||||
- Tests AI actions:
|
||||
- `auto_generate_content` - AI content generation
|
||||
- `generate_image_prompts` - Image prompt generation
|
||||
- `generate_images` - AI image generation
|
||||
- Tests unified response format and permissions
|
||||
- **6 test methods**
|
||||
|
||||
4. **test_integration_system.py** (50 lines)
|
||||
- Tests System module endpoints:
|
||||
- `AIPromptViewSet` (CRUD operations)
|
||||
- `SystemSettingsViewSet` (CRUD operations)
|
||||
- `IntegrationSettingsViewSet` (CRUD operations)
|
||||
- Tests actions:
|
||||
- `save_prompt` - Save AI prompt
|
||||
- `test` - Test integration connection
|
||||
- `task_progress` - Get task progress
|
||||
- **5 test methods**
|
||||
|
||||
5. **test_integration_billing.py** (50 lines)
|
||||
- Tests Billing module endpoints:
|
||||
- `CreditBalanceViewSet` (balance, summary, limits actions)
|
||||
- `CreditUsageViewSet` (usage summary)
|
||||
- `CreditTransactionViewSet` (CRUD operations)
|
||||
- Tests unified response format and permissions
|
||||
- **5 test methods**
|
||||
|
||||
6. **test_integration_auth.py** (100 lines)
|
||||
- Tests Auth module endpoints:
|
||||
- `AuthViewSet` (register, login, me, change_password, refresh_token, reset_password)
|
||||
- `UsersViewSet` (CRUD operations)
|
||||
- `GroupsViewSet` (CRUD operations)
|
||||
- `AccountsViewSet` (CRUD operations)
|
||||
- `SiteViewSet` (CRUD operations)
|
||||
- `SectorViewSet` (CRUD operations)
|
||||
- `IndustryViewSet` (CRUD operations)
|
||||
- `SeedKeywordViewSet` (CRUD operations)
|
||||
- Tests authentication flows and unified response format
|
||||
- **8 test methods**
|
||||
|
||||
7. **test_integration_errors.py** (95 lines)
|
||||
- Tests error scenarios:
|
||||
- 400 Bad Request (validation errors)
|
||||
- 401 Unauthorized (authentication errors)
|
||||
- 403 Forbidden (permission errors)
|
||||
- 404 Not Found (resource not found)
|
||||
- 429 Too Many Requests (rate limiting)
|
||||
- 500 Internal Server Error (generic errors)
|
||||
- Tests unified error format for all scenarios
|
||||
- **6 test methods**
|
||||
|
||||
8. **test_integration_pagination.py** (100 lines)
|
||||
- Tests pagination across all modules:
|
||||
- Default pagination (page size 10)
|
||||
- Custom page size (1-100)
|
||||
- Page parameter
|
||||
- Empty results
|
||||
- Count, next, previous fields
|
||||
- Tests pagination on: Keywords, Clusters, Tasks, Content, Users, Accounts
|
||||
- **10 test methods**
|
||||
|
||||
9. **test_integration_rate_limiting.py** (120 lines)
|
||||
- Tests rate limiting:
|
||||
- Throttle headers (`X-Throttle-Limit`, `X-Throttle-Remaining`, `X-Throttle-Reset`)
|
||||
- Bypass logic (admin/system accounts, DEBUG mode)
|
||||
- Different throttle scopes (read, write, ai)
|
||||
- 429 response handling
|
||||
- **7 test methods**
|
||||
|
||||
### Test Statistics
|
||||
|
||||
- **Total Test Files**: 13
|
||||
- **Total Test Methods**: ~115
|
||||
- **Total Lines of Code**: ~1,500
|
||||
- **Coverage**: 100% of API Standard components
|
||||
|
||||
### What Tests Verify
|
||||
|
||||
1. **Unified Response Format**
|
||||
- All responses include `success` field (true/false)
|
||||
- Success responses include `data` (single object) or `results` (list)
|
||||
- Error responses include `error` (message) and `errors` (field-specific)
|
||||
- All responses include `request_id` (UUID)
|
||||
|
||||
2. **Status Codes**
|
||||
- Correct HTTP status codes (200, 201, 400, 401, 403, 404, 429, 500)
|
||||
- Proper error messages for each status code
|
||||
- Field-specific errors for validation failures
|
||||
|
||||
3. **Pagination**
|
||||
- Paginated responses include `count`, `next`, `previous`, `results`
|
||||
- Page size limits enforced (max 100)
|
||||
- Empty results handled correctly
|
||||
- Default page size (10) works correctly
|
||||
|
||||
4. **Error Handling**
|
||||
- All exceptions wrapped in unified format
|
||||
- Field-specific errors included in `errors` object
|
||||
- Debug info (traceback, view, path, method) in DEBUG mode
|
||||
- Request ID included in all error responses
|
||||
|
||||
5. **Permissions**
|
||||
- Role-based access control (viewer, editor, admin, owner, developer)
|
||||
- Tenant isolation (users can only access their account's data)
|
||||
- Site/sector scoping (users can only access their assigned sites/sectors)
|
||||
- Admin/system account bypass (full access)
|
||||
|
||||
6. **Rate Limiting**
|
||||
- Throttle headers present in all responses
|
||||
- Bypass logic for admin/developer/system account users
|
||||
- Bypass in DEBUG mode (for development)
|
||||
- Different throttle scopes (read, write, ai)
|
||||
|
||||
### Test Execution
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
python manage.py test igny8_core.api.tests --verbosity=2
|
||||
|
||||
# Run specific test file
|
||||
python manage.py test igny8_core.api.tests.test_response
|
||||
|
||||
# Run specific test class
|
||||
python manage.py test igny8_core.api.tests.test_response.ResponseHelpersTestCase
|
||||
|
||||
# Run with coverage
|
||||
coverage run --source='igny8_core.api' manage.py test igny8_core.api.tests
|
||||
coverage report
|
||||
```
|
||||
|
||||
### Test Results
|
||||
|
||||
All tests pass successfully:
|
||||
- ✅ Unit tests: 61/61 passing
|
||||
- ✅ Integration tests: 54/54 passing
|
||||
- ✅ Total: 115/115 passing
|
||||
|
||||
### Files Created
|
||||
|
||||
- `backend/igny8_core/api/tests/__init__.py`
|
||||
- `backend/igny8_core/api/tests/test_response.py`
|
||||
- `backend/igny8_core/api/tests/test_exception_handler.py`
|
||||
- `backend/igny8_core/api/tests/test_permissions.py`
|
||||
- `backend/igny8_core/api/tests/test_throttles.py`
|
||||
- `backend/igny8_core/api/tests/test_integration_base.py`
|
||||
- `backend/igny8_core/api/tests/test_integration_planner.py`
|
||||
- `backend/igny8_core/api/tests/test_integration_writer.py`
|
||||
- `backend/igny8_core/api/tests/test_integration_system.py`
|
||||
- `backend/igny8_core/api/tests/test_integration_billing.py`
|
||||
- `backend/igny8_core/api/tests/test_integration_auth.py`
|
||||
- `backend/igny8_core/api/tests/test_integration_errors.py`
|
||||
- `backend/igny8_core/api/tests/test_integration_pagination.py`
|
||||
- `backend/igny8_core/api/tests/test_integration_rate_limiting.py`
|
||||
- `backend/igny8_core/api/tests/README.md`
|
||||
- `backend/igny8_core/api/tests/TEST_SUMMARY.md`
|
||||
- `backend/igny8_core/api/tests/run_tests.py`
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Documentation ✅
|
||||
|
||||
### Implementation Summary
|
||||
|
||||
Complete documentation system for IGNY8 API v1.0 including OpenAPI 3.0 schema generation, interactive Swagger UI, and comprehensive documentation files.
|
||||
|
||||
### OpenAPI/Swagger Integration
|
||||
|
||||
#### Package Installation
|
||||
- ✅ Installed `drf-spectacular>=0.27.0`
|
||||
- ✅ Added to `INSTALLED_APPS` in `settings.py`
|
||||
- ✅ Configured `REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS']`
|
||||
|
||||
#### Configuration (`backend/igny8_core/settings.py`)
|
||||
|
||||
```python
|
||||
SPECTACULAR_SETTINGS = {
|
||||
'TITLE': 'IGNY8 API v1.0',
|
||||
'DESCRIPTION': 'Comprehensive REST API for content planning, creation, and management...',
|
||||
'VERSION': '1.0.0',
|
||||
'SCHEMA_PATH_PREFIX': '/api/v1',
|
||||
'COMPONENT_SPLIT_REQUEST': True,
|
||||
'TAGS': [
|
||||
{'name': 'Authentication', 'description': 'User authentication and registration'},
|
||||
{'name': 'Planner', 'description': 'Keywords, clusters, and content ideas'},
|
||||
{'name': 'Writer', 'description': 'Tasks, content, and images'},
|
||||
{'name': 'System', 'description': 'Settings, prompts, and integrations'},
|
||||
{'name': 'Billing', 'description': 'Credits, usage, and transactions'},
|
||||
],
|
||||
'EXTENSIONS_INFO': {
|
||||
'x-code-samples': [
|
||||
{'lang': 'Python', 'source': '...'},
|
||||
{'lang': 'JavaScript', 'source': '...'}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Endpoints Created
|
||||
|
||||
- ✅ `/api/schema/` - OpenAPI 3.0 schema (JSON/YAML)
|
||||
- ✅ `/api/docs/` - Swagger UI (interactive documentation)
|
||||
- ✅ `/api/redoc/` - ReDoc (alternative documentation UI)
|
||||
|
||||
#### Schema Extensions
|
||||
|
||||
Created `backend/igny8_core/api/schema_extensions.py`:
|
||||
- ✅ `JWTAuthenticationExtension` - JWT Bearer token authentication
|
||||
- ✅ `CSRFExemptSessionAuthenticationExtension` - Session authentication
|
||||
- ✅ Proper OpenAPI security scheme definitions
|
||||
|
||||
#### URL Configuration (`backend/igny8_core/urls.py`)
|
||||
|
||||
```python
|
||||
from drf_spectacular.views import (
|
||||
SpectacularAPIView,
|
||||
SpectacularRedocView,
|
||||
SpectacularSwaggerView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
# ... other URLs ...
|
||||
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
|
||||
]
|
||||
```
|
||||
|
||||
### Documentation Files Created
|
||||
|
||||
#### 1. API-DOCUMENTATION.md
|
||||
**Purpose**: Complete API reference
|
||||
**Contents**:
|
||||
- Quick start guide
|
||||
- Authentication guide
|
||||
- Response format details
|
||||
- Error handling
|
||||
- Rate limiting
|
||||
- Pagination
|
||||
- Endpoint reference
|
||||
- Code examples (Python, JavaScript, cURL)
|
||||
|
||||
#### 2. AUTHENTICATION-GUIDE.md
|
||||
**Purpose**: Authentication and authorization
|
||||
**Contents**:
|
||||
- JWT Bearer token authentication
|
||||
- Token management and refresh
|
||||
- Code examples (Python, JavaScript)
|
||||
- Security best practices
|
||||
- Token expiration handling
|
||||
- Troubleshooting
|
||||
|
||||
#### 3. ERROR-CODES.md
|
||||
**Purpose**: Complete error code reference
|
||||
**Contents**:
|
||||
- HTTP status codes (200, 201, 400, 401, 403, 404, 409, 422, 429, 500)
|
||||
- Field-specific error messages
|
||||
- Error handling best practices
|
||||
- Common error scenarios
|
||||
- Debugging tips
|
||||
|
||||
#### 4. RATE-LIMITING.md
|
||||
**Purpose**: Rate limiting and throttling
|
||||
**Contents**:
|
||||
- Rate limit scopes and limits
|
||||
- Handling rate limits (429 responses)
|
||||
- Best practices
|
||||
- Code examples with backoff strategies
|
||||
- Request queuing and caching
|
||||
|
||||
#### 5. MIGRATION-GUIDE.md
|
||||
**Purpose**: Migration guide for API consumers
|
||||
**Contents**:
|
||||
- What changed in v1.0
|
||||
- Step-by-step migration instructions
|
||||
- Code examples (before/after)
|
||||
- Breaking and non-breaking changes
|
||||
- Migration checklist
|
||||
|
||||
#### 6. WORDPRESS-PLUGIN-INTEGRATION.md
|
||||
**Purpose**: WordPress plugin integration
|
||||
**Contents**:
|
||||
- Complete PHP API client class
|
||||
- Authentication implementation
|
||||
- Error handling
|
||||
- WordPress admin integration
|
||||
- Two-way sync (WordPress → IGNY8)
|
||||
- Site data fetching (posts, taxonomies, products, attributes)
|
||||
- Semantic mapping and content restructuring
|
||||
- Best practices
|
||||
- Testing examples
|
||||
|
||||
#### 7. README.md
|
||||
**Purpose**: Documentation index
|
||||
**Contents**:
|
||||
- Documentation index
|
||||
- Quick start guide
|
||||
- Links to all documentation files
|
||||
- Support information
|
||||
|
||||
### Documentation Statistics
|
||||
|
||||
- **Total Documentation Files**: 7
|
||||
- **Total Pages**: ~100+ pages of documentation
|
||||
- **Code Examples**: Python, JavaScript, PHP, cURL
|
||||
- **Coverage**: 100% of API features documented
|
||||
|
||||
### Access Points
|
||||
|
||||
#### Interactive Documentation
|
||||
- **Swagger UI**: `https://api.igny8.com/api/docs/`
|
||||
- **ReDoc**: `https://api.igny8.com/api/redoc/`
|
||||
- **OpenAPI Schema**: `https://api.igny8.com/api/schema/`
|
||||
|
||||
#### Documentation Files
|
||||
- All files in `docs/` directory
|
||||
- Index: `docs/README.md`
|
||||
|
||||
### Files Created/Modified
|
||||
|
||||
#### Backend Files
|
||||
- `backend/igny8_core/settings.py` - Added drf-spectacular configuration
|
||||
- `backend/igny8_core/urls.py` - Added schema/documentation endpoints
|
||||
- `backend/igny8_core/api/schema_extensions.py` - Custom authentication extensions
|
||||
- `backend/requirements.txt` - Added drf-spectacular>=0.27.0
|
||||
|
||||
#### Documentation Files
|
||||
- `docs/API-DOCUMENTATION.md`
|
||||
- `docs/AUTHENTICATION-GUIDE.md`
|
||||
- `docs/ERROR-CODES.md`
|
||||
- `docs/RATE-LIMITING.md`
|
||||
- `docs/MIGRATION-GUIDE.md`
|
||||
- `docs/WORDPRESS-PLUGIN-INTEGRATION.md`
|
||||
- `docs/README.md`
|
||||
- `docs/DOCUMENTATION-SUMMARY.md`
|
||||
- `docs/SECTION-2-COMPLETE.md`
|
||||
|
||||
---
|
||||
|
||||
## Verification & Status
|
||||
|
||||
### Section 1: Testing ✅
|
||||
- ✅ All test files created
|
||||
- ✅ All tests passing (115/115)
|
||||
- ✅ 100% coverage of API Standard components
|
||||
- ✅ Unit tests: 61/61 passing
|
||||
- ✅ Integration tests: 54/54 passing
|
||||
- ✅ Test documentation created
|
||||
|
||||
### Section 2: Documentation ✅
|
||||
- ✅ drf-spectacular installed and configured
|
||||
- ✅ Schema generation working (OpenAPI 3.0)
|
||||
- ✅ Schema endpoint accessible (`/api/schema/`)
|
||||
- ✅ Swagger UI accessible (`/api/docs/`)
|
||||
- ✅ ReDoc accessible (`/api/redoc/`)
|
||||
- ✅ 7 comprehensive documentation files created
|
||||
- ✅ Code examples included (Python, JavaScript, PHP, cURL)
|
||||
- ✅ Changelog updated
|
||||
|
||||
---
|
||||
|
||||
## Deliverables
|
||||
|
||||
### Section 1 Deliverables
|
||||
1. ✅ Complete test suite (13 test files, 115 test methods)
|
||||
2. ✅ Test documentation (README.md, TEST_SUMMARY.md)
|
||||
3. ✅ Test runner script (run_tests.py)
|
||||
4. ✅ All tests passing
|
||||
|
||||
### Section 2 Deliverables
|
||||
1. ✅ OpenAPI 3.0 schema generation
|
||||
2. ✅ Interactive Swagger UI
|
||||
3. ✅ ReDoc documentation
|
||||
4. ✅ 7 comprehensive documentation files
|
||||
5. ✅ Code examples in multiple languages
|
||||
6. ✅ Integration guides
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Completed ✅
|
||||
- ✅ Section 1: Testing - Complete
|
||||
- ✅ Section 2: Documentation - Complete
|
||||
|
||||
### Remaining
|
||||
- Section 3: Frontend Refactoring (if applicable)
|
||||
- Section 4: Additional Features (if applicable)
|
||||
- Section 5: Performance Optimization (if applicable)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Both **Section 1: Testing** and **Section 2: Documentation** have been successfully implemented and verified:
|
||||
|
||||
- **Testing**: Comprehensive test suite with 115 test methods covering all API Standard components
|
||||
- **Documentation**: Complete documentation system with OpenAPI schema, Swagger UI, and 7 comprehensive guides
|
||||
|
||||
All deliverables are complete, tested, and ready for use.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-16
|
||||
**API Version**: 1.0.0
|
||||
**Status**: ✅ Complete
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# Section 2: Documentation - COMPLETE ✅
|
||||
|
||||
**Date Completed**: 2025-11-16
|
||||
**Status**: All Documentation Implemented, Verified, and Fully Functional
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Section 2: Documentation has been successfully implemented with:
|
||||
- ✅ OpenAPI 3.0 schema generation (drf-spectacular v0.29.0)
|
||||
- ✅ Interactive Swagger UI and ReDoc
|
||||
- ✅ 7 comprehensive documentation files
|
||||
- ✅ Code examples in multiple languages
|
||||
- ✅ Integration guides for all platforms
|
||||
|
||||
---
|
||||
|
||||
## Deliverables
|
||||
|
||||
### 1. OpenAPI/Swagger Integration ✅
|
||||
- **Package**: drf-spectacular v0.29.0 installed
|
||||
- **Endpoints**:
|
||||
- `/api/schema/` - OpenAPI 3.0 schema
|
||||
- `/api/docs/` - Swagger UI
|
||||
- `/api/redoc/` - ReDoc
|
||||
- **Configuration**: Comprehensive settings with API description, tags, code samples
|
||||
|
||||
### 2. Documentation Files ✅
|
||||
- **API-DOCUMENTATION.md** - Complete API reference
|
||||
- **AUTHENTICATION-GUIDE.md** - Auth guide with examples
|
||||
- **ERROR-CODES.md** - Error code reference
|
||||
- **RATE-LIMITING.md** - Rate limiting guide
|
||||
- **MIGRATION-GUIDE.md** - Migration instructions
|
||||
- **WORDPRESS-PLUGIN-INTEGRATION.md** - WordPress integration
|
||||
- **README.md** - Documentation index
|
||||
|
||||
### 3. Schema Extensions ✅
|
||||
- Custom JWT authentication extension
|
||||
- Session authentication extension
|
||||
- Proper OpenAPI security schemes
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
✅ **drf-spectacular**: Installed and configured
|
||||
✅ **Schema Generation**: Working (database created and migrations applied)
|
||||
✅ **Schema Endpoint**: `/api/schema/` returns 200 OK with OpenAPI 3.0 schema
|
||||
✅ **Swagger UI**: `/api/docs/` displays full API documentation
|
||||
✅ **ReDoc**: `/api/redoc/` displays full API documentation
|
||||
✅ **Documentation Files**: 7 files created
|
||||
✅ **Changelog**: Updated with documentation section
|
||||
✅ **Code Examples**: Python, JavaScript, PHP, cURL included
|
||||
|
||||
---
|
||||
|
||||
## Access
|
||||
|
||||
- **Swagger UI**: `https://api.igny8.com/api/docs/`
|
||||
- **ReDoc**: `https://api.igny8.com/api/redoc/`
|
||||
- **OpenAPI Schema**: `https://api.igny8.com/api/schema/`
|
||||
- **Documentation Files**: `docs/` directory
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
**Section 2: Documentation - COMPLETE** ✅
|
||||
|
||||
All documentation is implemented, verified, and fully functional:
|
||||
- Database created and migrations applied
|
||||
- Schema generation working (OpenAPI 3.0)
|
||||
- Swagger UI displaying full API documentation
|
||||
- ReDoc displaying full API documentation
|
||||
- All endpoints accessible and working
|
||||
|
||||
---
|
||||
|
||||
**Completed**: 2025-11-16
|
||||
|
||||
Reference in New Issue
Block a user