Redmine-Gitea-Sync/API_DOCUMENTATION.md

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 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:

{
  "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:

  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

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:

{
  "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 identifier
  • subject: 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, or all
  • labels: Comma-separated label names
  • q: Search query
  • limit: Maximum results
  • page: 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, or all

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 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