Redmine-Gitea-Sync/API_DOCUMENTATION.md

933 lines
16 KiB
Markdown

# API Documentation
## Overview
This document describes the APIs utilized by the Redmine-Gitea Sync Server, including both external APIs (Redmine and Gitea) and the server's own endpoints.
## Table of Contents
- [Server Endpoints](#server-endpoints)
- [Redmine API Integration](#redmine-api-integration)
- [Gitea API Integration](#gitea-api-integration)
- [Webhook Payloads](#webhook-payloads)
- [Data Mapping](#data-mapping)
---
## Server Endpoints
### Health Check
**Endpoint:** `GET /health`
**Description:** Returns server health status and configuration information.
**Response:**
```json
{
"status": "ok",
"uptime": 12345.67,
"timestamp": "2025-12-17T01:49:47Z",
"cache": {
"size": 5,
"ttl": 30000
},
"config": {
"redmine": {
"url": "https://redmine.example.com",
"connected": true
},
"gitea": {
"url": "https://gitea.example.com",
"owner": "username",
"connected": true
},
"sync": {
"enableLabels": true,
"enableMilestones": true,
"enableAttachments": false,
"enableCustomFields": true,
"retryAttempts": 3,
"retryDelay": 1000
}
}
}
```
### Status Check
**Endpoint:** `GET /status`
**Description:** Tests connectivity to both Redmine and Gitea platforms.
**Response:**
```json
{
"server": "running",
"uptime": 12345.67,
"cache": {
"size": 5,
"ttl": 30000
},
"connections": {
"redmine": "connected",
"gitea": "connected"
}
}
```
**Possible Connection States:**
- `connected`: Successfully connected to platform
- `disconnected`: Failed to connect to platform
- `unknown`: Connection not yet tested
### Clear Cache
**Endpoint:** `POST /cache/clear`
**Description:** Manually clears the sync cache. Useful for troubleshooting sync issues.
**Response:**
```json
{
"message": "Cache cleared",
"timestamp": "2025-12-17T01:49:47Z"
}
```
### Redmine Webhook
**Endpoint:** `POST /webhook/redmine`
**Description:** Receives webhook notifications from Redmine for issue updates.
**Expected Payload:** See [Redmine Webhook Payload](#redmine-webhook-payload)
**Response:** `200 OK` (always returns immediately)
**Processing:**
1. Validates payload structure
2. Checks sync cache to prevent loops
3. Retrieves or creates corresponding Gitea issue
4. Syncs metadata (status, assignee, labels, milestone)
5. Syncs journals as comments
6. Updates cache
### Gitea Webhook
**Endpoint:** `POST /webhook/gitea`
**Description:** Receives webhook notifications from Gitea for issue updates.
**Expected Payload:** See [Gitea Webhook Payload](#gitea-webhook-payload)
**Response:** `200 OK` (always returns immediately)
**Processing:**
1. Validates payload structure
2. Checks sync cache to prevent loops
3. Retrieves or creates corresponding Redmine issue
4. Syncs metadata (status, assignee, due date)
5. Syncs comments as journals
6. Updates cache
---
## Redmine API Integration
### Base Configuration
**Base URL:** Configured via `REDMINE_API_URL`
**Authentication:** API Key via `X-Redmine-API-Key` header
**Format:** JSON
### Endpoints Used
#### Get Issue
**Method:** `GET /issues/{id}.json`
**Parameters:**
- `include`: `attachments,journals,watchers`
**Response Example:**
```json
{
"issue": {
"id": 31,
"project": {
"id": 2,
"identifier": "ai-smuggling",
"name": "AI Smuggling"
},
"tracker": {
"id": 1,
"name": "Bug"
},
"status": {
"id": 2,
"name": "In Progress"
},
"priority": {
"id": 2,
"name": "Normal"
},
"author": {
"id": 1,
"login": "admin"
},
"assignee": {
"id": 1,
"login": "admin"
},
"subject": "Issue title",
"description": "Issue description",
"start_date": "2025-12-16",
"due_date": "2025-12-19",
"estimated_hours": 5.0,
"custom_fields": [],
"journals": [],
"attachments": []
}
}
```
#### Create Issue
**Method:** `POST /issues.json`
**Payload:**
```json
{
"issue": {
"project_id": "ai-smuggling",
"tracker_id": 1,
"status_id": 2,
"priority_id": 2,
"subject": "Issue title",
"description": "Issue description",
"assigned_to_id": 1,
"start_date": "2025-12-16",
"due_date": "2025-12-19",
"estimated_hours": 5.0
}
}
```
**Response:**
```json
{
"issue": {
"id": 32,
"project": {...},
"subject": "Issue title",
...
}
}
```
#### Update Issue
**Method:** `PUT /issues/{id}.json`
**Payload:**
```json
{
"issue": {
"subject": "Updated title",
"description": "Updated description",
"status_id": 5,
"assigned_to_id": 2,
"due_date": "2025-12-20",
"notes": "Adding a comment"
}
}
```
#### Search Issues
**Method:** `GET /issues.json`
**Parameters:**
- `project_id`: Project identifier
- `subject`: Subject search (supports `~` for partial match)
- `limit`: Maximum results (default: 100)
**Response:**
```json
{
"issues": [
{
"id": 31,
"subject": "Issue title",
...
}
],
"total_count": 1,
"offset": 0,
"limit": 100
}
```
#### Get Project
**Method:** `GET /projects/{id}.json`
**Response:**
```json
{
"project": {
"id": 2,
"identifier": "ai-smuggling",
"name": "AI Smuggling",
"description": "Project description",
"created_on": "2025-12-15T08:37:45.000Z"
}
}
```
#### Get Trackers
**Method:** `GET /trackers.json`
**Response:**
```json
{
"trackers": [
{
"id": 1,
"name": "Bug"
},
{
"id": 2,
"name": "Feature"
}
]
}
```
#### Get Issue Statuses
**Method:** `GET /issue_statuses.json`
**Response:**
```json
{
"issue_statuses": [
{
"id": 1,
"name": "New",
"is_closed": false
},
{
"id": 5,
"name": "Closed",
"is_closed": true
}
]
}
```
#### Get Priorities
**Method:** `GET /enumerations/issue_priorities.json`
**Response:**
```json
{
"issue_priorities": [
{
"id": 1,
"name": "Low"
},
{
"id": 2,
"name": "Normal"
}
]
}
```
#### Get Versions (Milestones)
**Method:** `GET /projects/{id}/versions.json`
**Response:**
```json
{
"versions": [
{
"id": 1,
"name": "Version 1.0",
"description": "First release",
"status": "open",
"due_date": "2025-12-31"
}
]
}
```
---
## Gitea API Integration
### Base Configuration
**Base URL:** Configured via `GITEA_API_URL`
**Authentication:** Token via `Authorization: token {TOKEN}` header
**Format:** JSON
### Endpoints Used
#### Get Issue
**Method:** `GET /api/v1/repos/{owner}/{repo}/issues/{index}`
**Response Example:**
```json
{
"id": 27,
"number": 27,
"title": "Issue title",
"body": "Issue description",
"state": "open",
"user": {
"id": 1,
"login": "tombomb122",
"full_name": "Thomas Scott"
},
"assignee": {
"id": 1,
"login": "tombomb122"
},
"labels": [],
"milestone": null,
"created_at": "2025-12-17T01:49:47Z",
"updated_at": "2025-12-17T01:49:47Z",
"due_date": null,
"comments": 0
}
```
#### Search Issues
**Method:** `GET /api/v1/repos/{owner}/{repo}/issues`
**Parameters:**
- `state`: `open`, `closed`, or `all`
- `labels`: Comma-separated label names
- `q`: Search query
- `limit`: Maximum results
- `page`: Page number
**Response:**
```json
[
{
"id": 27,
"number": 27,
"title": "Issue title",
...
}
]
```
#### Create Issue
**Method:** `POST /api/v1/repos/{owner}/{repo}/issues`
**Payload:**
```json
{
"title": "Issue title",
"body": "Issue description",
"assignee": "username",
"milestone": 1,
"labels": [1, 2, 3],
"due_date": "2025-12-19T00:00:00Z"
}
```
**Response:**
```json
{
"id": 28,
"number": 28,
"title": "Issue title",
...
}
```
#### Update Issue
**Method:** `PATCH /api/v1/repos/{owner}/{repo}/issues/{index}`
**Payload:**
```json
{
"title": "Updated title",
"body": "Updated description",
"state": "closed",
"assignee": "username",
"milestone": 2,
"due_date": "2025-12-20T00:00:00Z"
}
```
#### Get Comments
**Method:** `GET /api/v1/repos/{owner}/{repo}/issues/{index}/comments`
**Response:**
```json
[
{
"id": 1,
"body": "Comment text",
"user": {
"login": "username"
},
"created_at": "2025-12-17T01:49:47Z",
"updated_at": "2025-12-17T01:49:47Z"
}
]
```
#### Add Comment
**Method:** `POST /api/v1/repos/{owner}/{repo}/issues/{index}/comments`
**Payload:**
```json
{
"body": "Comment text"
}
```
**Response:**
```json
{
"id": 2,
"body": "Comment text",
"user": {...},
"created_at": "2025-12-17T02:00:00Z"
}
```
#### Get Labels
**Method:** `GET /api/v1/repos/{owner}/{repo}/labels`
**Response:**
```json
[
{
"id": 1,
"name": "bug",
"color": "#ff0000",
"description": "Bug label"
}
]
```
#### Create Label
**Method:** `POST /api/v1/repos/{owner}/{repo}/labels`
**Payload:**
```json
{
"name": "bug",
"color": "#ff0000",
"description": "Bug label"
}
```
#### Set Issue Labels
**Method:** `PUT /api/v1/repos/{owner}/{repo}/issues/{index}/labels`
**Payload:**
```json
{
"labels": [1, 2, 3]
}
```
#### Get Milestones
**Method:** `GET /api/v1/repos/{owner}/{repo}/milestones`
**Parameters:**
- `state`: `open`, `closed`, or `all`
**Response:**
```json
[
{
"id": 1,
"title": "Version 1.0",
"description": "First release",
"state": "open",
"due_on": "2025-12-31T00:00:00Z"
}
]
```
#### Create Milestone
**Method:** `POST /api/v1/repos/{owner}/{repo}/milestones`
**Payload:**
```json
{
"title": "Version 1.0",
"description": "First release",
"due_on": "2025-12-31T00:00:00Z"
}
```
#### Get Repository
**Method:** `GET /api/v1/repos/{owner}/{repo}`
**Response:**
```json
{
"id": 1,
"name": "AI_Smuggling",
"full_name": "tombomb122/AI_Smuggling",
"description": "Repository description",
"owner": {...},
"created_at": "2025-12-15T08:40:06Z"
}
```
---
## Webhook Payloads
### Redmine Webhook Payload
**Event:** Issue Update
```json
{
"payload": {
"action": "updated",
"issue": {
"id": 31,
"subject": "Issue title",
"description": "Issue description",
"created_on": "2025-12-16T14:06:41.000Z",
"updated_on": "2025-12-16T14:07:37.000Z",
"start_date": "2025-12-16",
"due_date": "2025-12-19",
"estimated_hours": 5.0,
"project": {
"id": 2,
"identifier": "ai-smuggling",
"name": "AI Smuggling"
},
"status": {
"id": 2,
"name": "In Progress"
},
"tracker": {
"id": 1,
"name": "Bug"
},
"priority": {
"id": 2,
"name": "Normal"
},
"author": {
"id": 1,
"login": "admin"
},
"assignee": {
"id": 1,
"login": "admin"
}
},
"journal": {
"id": 14,
"notes": "Comment text",
"created_on": "2025-12-16T14:07:37.000Z",
"author": {
"id": 1,
"login": "admin"
},
"details": [
{
"property": "attr",
"prop_key": "status_id",
"old_value": "1",
"value": "2"
}
]
},
"url": "https://redmine.example.com/issues/31"
}
}
```
### Gitea Webhook Payload
**Event:** Issue Update
```json
{
"action": "opened",
"number": 27,
"issue": {
"id": 27,
"number": 27,
"title": "Issue title",
"body": "Issue description",
"state": "open",
"user": {
"id": 1,
"login": "tombomb122",
"full_name": "Thomas Scott"
},
"assignee": {
"id": 1,
"login": "tombomb122"
},
"labels": [],
"milestone": null,
"created_at": "2025-12-17T01:49:47Z",
"updated_at": "2025-12-17T01:49:47Z",
"due_date": null
},
"repository": {
"id": 1,
"name": "AI_Smuggling",
"full_name": "tombomb122/AI_Smuggling",
"owner": {
"login": "tombomb122"
}
},
"sender": {
"id": 1,
"login": "tombomb122"
}
}
```
**Event:** Issue Comment
```json
{
"action": "created",
"issue": {...},
"comment": {
"id": 1,
"body": "Comment text",
"user": {
"login": "tombomb122"
},
"created_at": "2025-12-17T02:00:00Z",
"updated_at": "2025-12-17T02:00:00Z"
},
"repository": {...},
"sender": {...}
}
```
---
## Data Mapping
### Status Mapping
#### Redmine → Gitea
| Redmine Status ID | Redmine Status Name | Gitea State |
|-------------------|---------------------|-------------|
| 1 | New | open |
| 2 | In Progress | open |
| 3 | Resolved | open |
| 4 | Feedback | open |
| 5 | Closed | closed |
| 6 | Rejected | closed |
#### Gitea → Redmine
| Gitea State | Redmine Status ID | Redmine Status Name |
|-------------|-------------------|---------------------|
| open | 2 | In Progress |
| closed | 5 | Closed |
### Priority Mapping
| Redmine Priority ID | Redmine Priority Name | Gitea Label |
|---------------------|----------------------|-------------|
| 1 | Low | Low Priority |
| 2 | Normal | Normal Priority |
| 3 | High | High Priority |
| 4 | Urgent | Urgent |
| 5 | Immediate | Immediate |
### Tracker Mapping
| Redmine Tracker ID | Redmine Tracker Name | Gitea Label |
|--------------------|---------------------|-------------|
| 1 | Bug | bug |
| 2 | Feature | feature |
| 3 | Support | support |
### Issue Title Format
**Redmine → Gitea:**
```
Redmine #{id}: {subject}
```
Example: `Redmine #31: Fix login bug`
**Gitea → Redmine:**
The original Gitea title is used, but when synced back to Gitea, it gets the Redmine prefix.
### Issue Description Format
**Gitea → Redmine:**
```
[Gitea Issue #{number}]
Author: {username}
Created at: {timestamp}
{original body}
```
**Redmine → Gitea:**
```
{original description}
---
**Custom Fields:**
- Field1: Value1
- Field2: Value2
```
### Comment/Journal Format
**Redmine Journal → Gitea Comment:**
```
[Redmine Journal #{id}]
**{author}** commented:
{notes}
**Changes:**
- field_name: old_value → new_value
```
**Gitea Comment → Redmine Journal:**
```
[Gitea Comment #{id}]
{username} commented:
{body}
```
---
## Error Codes
### HTTP Status Codes
- `200 OK`: Request successful
- `400 Bad Request`: Invalid payload or parameters
- `401 Unauthorized`: Invalid or missing API credentials
- `403 Forbidden`: Insufficient permissions
- `404 Not Found`: Resource not found
- `422 Unprocessable Entity`: Validation errors
- `500 Internal Server Error`: Server error
### Redmine Errors
Common Redmine API errors:
- Invalid API key
- Project not found
- Issue not found
- Insufficient permissions
- Validation errors
### Gitea Errors
Common Gitea API errors:
- Invalid token
- Repository not found
- Issue not found
- Label already exists
- Rate limit exceeded
---
## Rate Limiting
### Redmine
- No explicit rate limit in API
- Server configuration may impose limits
- Consider implementing client-side throttling
### Gitea
- Rate limits depend on instance configuration
- Default: Varies by instance
- The sync server implements retry logic with exponential backoff
---
## Best Practices
1. **Authentication**: Securely store API keys and tokens
2. **Error Handling**: Always check response status codes
3. **Retry Logic**: Implement exponential backoff for transient failures
4. **Webhooks**: Validate webhook signatures in production
5. **Logging**: Log all API interactions for debugging
6. **Testing**: Test sync in development environment first
7. **Monitoring**: Monitor API usage and error rates
---
## Troubleshooting
### Common API Issues
**Issue: 401 Unauthorized**
- Verify API key/token is correct
- Check token has required permissions
- Ensure token hasn't expired
**Issue: 404 Not Found**
- Verify resource exists (project, issue, repository)
- Check project/repository identifiers are correct
- Ensure proper URL formatting
**Issue: 422 Validation Error**
- Review required fields
- Check data types match API expectations
- Validate date formats
**Issue: Rate Limited**
- Implement retry with backoff
- Reduce sync frequency
- Contact administrator for increased limits
### Debug Tips
1. Enable verbose logging: `LOG_VERBOSE=true`
2. Use API testing tools (Postman, curl) to verify credentials
3. Check server logs for detailed error messages
4. Verify network connectivity to both platforms
5. Test individual API endpoints before full sync
---
## References
- [Redmine API Documentation](https://www.redmine.org/projects/redmine/wiki/Rest_api)
- [Gitea API Documentation](https://docs.gitea.com/api/1.20/)
- [Redmine Webhooks](https://www.redmine.org/projects/redmine/wiki/Webhooks)
- [Gitea Webhooks](https://docs.gitea.com/usage/webhooks)