---
title: "Custom Integrations"
description: "Build custom integrations to connect TraceRTM with any external system"
---

# Custom Integrations

TraceRTM's integration framework allows you to build custom integrations for any external system, API, or data source not covered by built-in integrations.

## Integration Framework

### Integration Structure

```python
from tracertm.integrations import BaseIntegration, IntegrationConfig

class MyCustomIntegration(BaseIntegration):
    """Custom integration with external system."""

    name = "my_integration"
    version = "1.0.0"

    def __init__(self, config: IntegrationConfig):
        super().__init__(config)
        self.api_url = config.get("api_url")
        self.api_key = config.get("api_key")

    def connect(self):
        """Establish connection to external system."""
        # Initialize API client
        self.client = ExternalAPIClient(
            url=self.api_url,
            key=self.api_key
        )

    def disconnect(self):
        """Close connection."""
        self.client.close()

    def sync(self):
        """Synchronize data between systems."""
        # Implement sync logic
        pass
```

### Register Integration

```python
from tracertm import TraceRTMClient

client = TraceRTMClient()

# Register custom integration
integration = MyCustomIntegration({
    "api_url": "https://api.external.com",
    "api_key": "your-api-key"
})

client.register_integration(integration)
```

## Configuration

### Config File Integration

```yaml
# .tracertm/config.yaml
integrations:
  custom:
    my_integration:
      enabled: true
      api_url: "https://api.external.com"
      api_key: ${MY_API_KEY}
      sync_interval: 300

      # Custom settings
      settings:
        import_types: ["task", "issue"]
        sync_direction: bidirectional
```

### CLI Registration

```bash
# Register custom integration
tracertm integrations register my_integration \
  --module "mycompany.integrations.MyCustomIntegration" \
  --config '{"api_url": "https://api.external.com"}'

# Enable
tracertm integrations enable my_integration

# Configure
tracertm integrations configure my_integration \
  --api_url "https://api.external.com" \
  --api_key "${MY_API_KEY}"
```

## Data Import

### Import External Data

```python
class MyCustomIntegration(BaseIntegration):
    def import_items(self, filters=None):
        """Import items from external system."""
        external_items = self.client.get_items(filters)

        imported = []
        for ext_item in external_items:
            # Transform to TraceRTM format
            tracertm_item = self.transform_to_tracertm(ext_item)

            # Create in TraceRTM
            item = self.tracertm.create_item(**tracertm_item)
            imported.append(item)

            # Store mapping
            self.store_mapping(ext_item.id, item.id)

        return imported

    def transform_to_tracertm(self, external_item):
        """Transform external item to TraceRTM format."""
        return {
            "title": external_item.name,
            "description": external_item.description,
            "item_type": self.map_type(external_item.type),
            "status": self.map_status(external_item.status),
            "metadata": {
                "external_id": external_item.id,
                "external_url": external_item.url,
                "custom_field": external_item.custom
            }
        }
```

### CLI Import

```bash
# Import from custom integration
tracertm my_integration import

# With filters
tracertm my_integration import --type task --status open
```

## Data Export

### Export to External System

```python
class MyCustomIntegration(BaseIntegration):
    def export_item(self, item_id):
        """Export TraceRTM item to external system."""
        item = self.tracertm.get_item(item_id)

        # Transform to external format
        external_data = self.transform_to_external(item)

        # Check if already exists
        mapping = self.get_mapping(item_id)
        if mapping:
            # Update existing
            self.client.update_item(mapping.external_id, external_data)
        else:
            # Create new
            external_item = self.client.create_item(external_data)
            self.store_mapping(item_id, external_item.id)

        return external_item

    def transform_to_external(self, tracertm_item):
        """Transform TraceRTM item to external format."""
        return {
            "name": tracertm_item.title,
            "description": tracertm_item.description,
            "type": self.reverse_map_type(tracertm_item.item_type),
            "status": self.reverse_map_status(tracertm_item.status)
        }
```

## Field Mapping

### Define Field Mappings

```python
class MyCustomIntegration(BaseIntegration):
    # Field mappings
    field_map = {
        "title": "name",
        "description": "desc",
        "status": "state",
        "priority": "urgency"
    }

    # Type mappings
    type_map = {
        "feature": "enhancement",
        "bug": "defect",
        "task": "work_item"
    }

    # Status mappings
    status_map = {
        "draft": "new",
        "in_progress": "active",
        "done": "closed"
    }

    def map_field(self, tracertm_field):
        return self.field_map.get(tracertm_field, tracertm_field)

    def map_type(self, external_type):
        reverse = {v: k for k, v in self.type_map.items()}
        return reverse.get(external_type, "task")
```

### Configurable Mappings

```yaml
integrations:
  custom:
    my_integration:
      field_mapping:
        title: name
        description: desc
        status: state

      type_mapping:
        feature: enhancement
        bug: defect

      status_mapping:
        draft: new
        in_progress: active
        done: closed
```

## Webhooks

### Handle Incoming Webhooks

```python
class MyCustomIntegration(BaseIntegration):
    def register_webhook_handlers(self):
        """Register handlers for incoming webhooks."""

        @self.webhook_handler("item_created")
        def handle_created(payload):
            # Import new item
            self.import_item(payload["item_id"])

        @self.webhook_handler("item_updated")
        def handle_updated(payload):
            # Sync changes
            self.sync_item(payload["item_id"])

        @self.webhook_handler("item_deleted")
        def handle_deleted(payload):
            # Archive in TraceRTM
            mapping = self.get_mapping_by_external(payload["item_id"])
            if mapping:
                self.tracertm.archive_item(mapping.tracertm_id)
```

### Webhook Endpoint

```python
from flask import Flask, request
from tracertm.integrations import webhook_router

app = Flask(__name__)

@app.route("/webhooks/my_integration", methods=["POST"])
def handle_webhook():
    payload = request.json
    signature = request.headers.get("X-Signature")

    # Verify signature
    if not integration.verify_signature(payload, signature):
        return {"error": "Invalid signature"}, 401

    # Route to handler
    integration.handle_webhook(payload)
    return {"status": "ok"}
```

## Outgoing Events

### Notify External System

```python
class MyCustomIntegration(BaseIntegration):
    def setup_tracertm_handlers(self):
        """Handle TraceRTM events."""

        @self.tracertm.on("item_created")
        def on_item_created(item):
            if self.should_export(item):
                self.export_item(item.id)

        @self.tracertm.on("item_updated")
        def on_item_updated(item, changes):
            mapping = self.get_mapping(item.id)
            if mapping:
                self.sync_item(item.id, changes)

        @self.tracertm.on("status_changed")
        def on_status_changed(item, old_status, new_status):
            # Update external system
            self.update_external_status(item.id, new_status)
```

## Synchronization

### Bidirectional Sync

```python
class MyCustomIntegration(BaseIntegration):
    def sync_all(self):
        """Full bidirectional sync."""
        # Get all mappings
        mappings = self.get_all_mappings()

        for mapping in mappings:
            try:
                self.sync_item(mapping)
            except SyncError as e:
                self.log_error(f"Sync failed for {mapping.tracertm_id}: {e}")

    def sync_item(self, mapping):
        """Sync a single item."""
        tracertm_item = self.tracertm.get_item(mapping.tracertm_id)
        external_item = self.client.get_item(mapping.external_id)

        # Determine which is newer
        if tracertm_item.updated_at > external_item.updated_at:
            # Push to external
            self.push_to_external(tracertm_item, mapping.external_id)
        else:
            # Pull from external
            self.pull_from_external(external_item, mapping.tracertm_id)
```

### Conflict Resolution

```python
class MyCustomIntegration(BaseIntegration):
    def resolve_conflict(self, mapping, tracertm_item, external_item):
        """Resolve sync conflict."""
        strategy = self.config.get("conflict_resolution", "use_newer")

        if strategy == "use_tracertm":
            self.push_to_external(tracertm_item, mapping.external_id)
        elif strategy == "use_external":
            self.pull_from_external(external_item, mapping.tracertm_id)
        elif strategy == "use_newer":
            if tracertm_item.updated_at > external_item.updated_at:
                self.push_to_external(tracertm_item, mapping.external_id)
            else:
                self.pull_from_external(external_item, mapping.tracertm_id)
        elif strategy == "manual":
            self.create_conflict_record(mapping, tracertm_item, external_item)
```

## Testing

### Test Your Integration

```python
import pytest
from tracertm.testing import IntegrationTestCase

class TestMyIntegration(IntegrationTestCase):
    def setUp(self):
        self.integration = MyCustomIntegration({
            "api_url": "https://mock.api.com",
            "api_key": "test-key"
        })

    def test_import_items(self):
        # Mock external API
        self.mock_external_api([
            {"id": "ext-1", "name": "Item 1", "status": "new"}
        ])

        # Import
        imported = self.integration.import_items()

        assert len(imported) == 1
        assert imported[0].title == "Item 1"

    def test_export_item(self):
        # Create TraceRTM item
        item = self.create_test_item(title="Test Feature")

        # Export
        external = self.integration.export_item(item.id)

        assert external.name == "Test Feature"

    def test_sync_conflict(self):
        # Set up conflict scenario
        mapping = self.create_mapping_with_conflict()

        # Resolve
        self.integration.sync_item(mapping)

        # Verify resolution
        assert self.get_item(mapping.tracertm_id).status == "expected"
```

## Packaging

### Create Integration Package

```
my_integration/
├── __init__.py
├── integration.py
├── config.py
├── transformers.py
├── handlers.py
├── requirements.txt
└── setup.py
```

### setup.py

```python
from setuptools import setup

setup(
    name="tracertm-my-integration",
    version="1.0.0",
    packages=["my_integration"],
    install_requires=[
        "tracertm>=1.0.0",
        "requests>=2.28.0"
    ],
    entry_points={
        "tracertm.integrations": [
            "my_integration = my_integration:MyCustomIntegration"
        ]
    }
)
```

### Install Custom Integration

```bash
# Install from package
pip install tracertm-my-integration

# Or install from source
pip install git+https://github.com/company/tracertm-my-integration.git

# Register
tracertm integrations register my_integration
```

## Examples

### REST API Integration

```python
class RestAPIIntegration(BaseIntegration):
    """Generic REST API integration."""

    def __init__(self, config):
        super().__init__(config)
        self.base_url = config["api_url"]
        self.auth = (config["username"], config["password"])

    def import_items(self, endpoint="/items"):
        response = requests.get(
            f"{self.base_url}{endpoint}",
            auth=self.auth
        )
        return [self.transform(item) for item in response.json()]
```

### Database Integration

```python
class DatabaseIntegration(BaseIntegration):
    """Direct database integration."""

    def __init__(self, config):
        super().__init__(config)
        self.engine = create_engine(config["connection_string"])

    def import_items(self, query=None):
        with self.engine.connect() as conn:
            result = conn.execute(query or "SELECT * FROM items")
            return [self.transform(row) for row in result]
```

## Best Practices

### 1. Handle Errors Gracefully

```python
def sync_item(self, item_id):
    try:
        result = self._do_sync(item_id)
        return result
    except ExternalAPIError as e:
        self.log_error(f"API error: {e}")
        self.queue_retry(item_id)
    except TransformError as e:
        self.log_error(f"Transform error: {e}")
        self.create_conflict_record(item_id, e)
```

### 2. Implement Rate Limiting

```python
from ratelimit import limits, sleep_and_retry

class MyIntegration(BaseIntegration):
    @sleep_and_retry
    @limits(calls=100, period=60)
    def api_call(self, endpoint, data):
        return self.client.request(endpoint, data)
```

### 3. Cache External Data

```python
from functools import lru_cache

class MyIntegration(BaseIntegration):
    @lru_cache(maxsize=1000)
    def get_external_item(self, external_id):
        return self.client.get_item(external_id)
```

## Related Topics

- [Integrations Overview](/docs/wiki/concepts/integrations) - Built-in integrations
- [Webhooks](/docs/wiki/guides/integration-patterns/webhooks) - Webhook handling
- [API Reference](/docs/api-reference) - TraceRTM API

