16 KiB
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
Health Check
Endpoint: GET /health
Description: Returns server health status and configuration information.
Response:
{
"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:
{
"server": "running",
"uptime": 12345.67,
"cache": {
"size": 5,
"ttl": 30000
},
"connections": {
"redmine": "connected",
"gitea": "connected"
}
}
Possible Connection States:
connected: Successfully connected to platformdisconnected: Failed to connect to platformunknown: Connection not yet tested
Clear Cache
Endpoint: POST /cache/clear
Description: Manually clears the sync cache. Useful for troubleshooting sync issues.
Response:
{
"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
Response: 200 OK (always returns immediately)
Processing:
- Validates payload structure
- Checks sync cache to prevent loops
- Retrieves or creates corresponding Gitea issue
- Syncs metadata (status, assignee, labels, milestone)
- Syncs journals as comments
- Updates cache
Gitea Webhook
Endpoint: POST /webhook/gitea
Description: Receives webhook notifications from Gitea for issue updates.
Expected Payload: See Gitea Webhook Payload
Response: 200 OK (always returns immediately)
Processing:
- Validates payload structure
- Checks sync cache to prevent loops
- Retrieves or creates corresponding Redmine issue
- Syncs metadata (status, assignee, due date)
- Syncs comments as journals
- 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:
{
"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:
{
"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:
{
"issue": {
"id": 32,
"project": {...},
"subject": "Issue title",
...
}
}
Update Issue
Method: PUT /issues/{id}.json
Payload:
{
"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 identifiersubject: Subject search (supports~for partial match)limit: Maximum results (default: 100)
Response:
{
"issues": [
{
"id": 31,
"subject": "Issue title",
...
}
],
"total_count": 1,
"offset": 0,
"limit": 100
}
Get Project
Method: GET /projects/{id}.json
Response:
{
"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:
{
"trackers": [
{
"id": 1,
"name": "Bug"
},
{
"id": 2,
"name": "Feature"
}
]
}
Get Issue Statuses
Method: GET /issue_statuses.json
Response:
{
"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:
{
"issue_priorities": [
{
"id": 1,
"name": "Low"
},
{
"id": 2,
"name": "Normal"
}
]
}
Get Versions (Milestones)
Method: GET /projects/{id}/versions.json
Response:
{
"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:
{
"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, oralllabels: Comma-separated label namesq: Search querylimit: Maximum resultspage: Page number
Response:
[
{
"id": 27,
"number": 27,
"title": "Issue title",
...
}
]
Create Issue
Method: POST /api/v1/repos/{owner}/{repo}/issues
Payload:
{
"title": "Issue title",
"body": "Issue description",
"assignee": "username",
"milestone": 1,
"labels": [1, 2, 3],
"due_date": "2025-12-19T00:00:00Z"
}
Response:
{
"id": 28,
"number": 28,
"title": "Issue title",
...
}
Update Issue
Method: PATCH /api/v1/repos/{owner}/{repo}/issues/{index}
Payload:
{
"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:
[
{
"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:
{
"body": "Comment text"
}
Response:
{
"id": 2,
"body": "Comment text",
"user": {...},
"created_at": "2025-12-17T02:00:00Z"
}
Get Labels
Method: GET /api/v1/repos/{owner}/{repo}/labels
Response:
[
{
"id": 1,
"name": "bug",
"color": "#ff0000",
"description": "Bug label"
}
]
Create Label
Method: POST /api/v1/repos/{owner}/{repo}/labels
Payload:
{
"name": "bug",
"color": "#ff0000",
"description": "Bug label"
}
Set Issue Labels
Method: PUT /api/v1/repos/{owner}/{repo}/issues/{index}/labels
Payload:
{
"labels": [1, 2, 3]
}
Get Milestones
Method: GET /api/v1/repos/{owner}/{repo}/milestones
Parameters:
state:open,closed, orall
Response:
[
{
"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:
{
"title": "Version 1.0",
"description": "First release",
"due_on": "2025-12-31T00:00:00Z"
}
Get Repository
Method: GET /api/v1/repos/{owner}/{repo}
Response:
{
"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
{
"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
{
"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
{
"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 successful400 Bad Request: Invalid payload or parameters401 Unauthorized: Invalid or missing API credentials403 Forbidden: Insufficient permissions404 Not Found: Resource not found422 Unprocessable Entity: Validation errors500 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
- Authentication: Securely store API keys and tokens
- Error Handling: Always check response status codes
- Retry Logic: Implement exponential backoff for transient failures
- Webhooks: Validate webhook signatures in production
- Logging: Log all API interactions for debugging
- Testing: Test sync in development environment first
- 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
- Enable verbose logging:
LOG_VERBOSE=true - Use API testing tools (Postman, curl) to verify credentials
- Check server logs for detailed error messages
- Verify network connectivity to both platforms
- Test individual API endpoints before full sync