---
title: Migration Guide
description: Step-by-step guides for migrating between TraceRTM API versions and deprecated features
---

# Migration Guide

This guide provides detailed instructions for migrating between API versions and from deprecated features to their replacements.

## Current Migrations

### Offset to Cursor Pagination

**Status**: Recommended (not yet deprecated)
**Reason**: Better performance and consistency for large datasets

#### Why Migrate?

| Aspect | Offset Pagination | Cursor Pagination |
|--------|-------------------|-------------------|
| Performance | Degrades with offset | Consistent |
| Data consistency | May skip/duplicate | Guaranteed |
| Real-time changes | Affected | Handled |
| Random access | Supported | Sequential only |

#### When to Migrate

- Datasets > 1,000 items
- Real-time data changes
- Performance-critical applications

#### Migration Steps

**Step 1: Update SDK**

```bash
# Python
pip install --upgrade tracertm>=2.0.0

# JavaScript
npm install @tracertm/sdk@latest

# Go
go get -u github.com/tracertm/tracertm-go@latest
```

**Step 2: Update Code**

##### Python

```python
# Before: Offset pagination
def get_all_items_offset(client, project_id):
    all_items = []
    offset = 0
    limit = 100

    while True:
        response = client.items.list(
            project_id=project_id,
            limit=limit,
            offset=offset
        )
        all_items.extend(response.data)

        if len(response.data) < limit:
            break
        offset += limit

    return all_items

# After: Cursor pagination
def get_all_items_cursor(client, project_id):
    all_items = []

    for item in client.items.iterate(project_id=project_id):
        all_items.append(item)

    return all_items

# Or with explicit cursor handling
def get_all_items_cursor_manual(client, project_id):
    all_items = []
    cursor = None

    while True:
        response = client.items.list(
            project_id=project_id,
            limit=100,
            cursor=cursor
        )
        all_items.extend(response.data)

        if not response.pagination.has_next:
            break
        cursor = response.pagination.next_cursor

    return all_items
```

##### TypeScript

```typescript
// Before: Offset pagination
async function getAllItemsOffset(client: TraceRTMClient, projectId: string) {
  const allItems: Item[] = [];
  let offset = 0;
  const limit = 100;

  while (true) {
    const response = await client.items.list({
      projectId,
      limit,
      offset
    });
    allItems.push(...response.data);

    if (response.data.length < limit) break;
    offset += limit;
  }

  return allItems;
}

// After: Cursor pagination
async function getAllItemsCursor(client: TraceRTMClient, projectId: string) {
  const allItems: Item[] = [];

  for await (const item of client.items.iterate({ projectId })) {
    allItems.push(item);
  }

  return allItems;
}

// Or with explicit cursor handling
async function getAllItemsCursorManual(client: TraceRTMClient, projectId: string) {
  const allItems: Item[] = [];
  let cursor: string | undefined;

  while (true) {
    const response = await client.items.list({
      projectId,
      limit: 100,
      cursor
    });
    allItems.push(...response.data);

    if (!response.pagination.hasNext) break;
    cursor = response.pagination.nextCursor;
  }

  return allItems;
}
```

##### Go

```go
// Before: Offset pagination
func getAllItemsOffset(client *tracertm.Client, projectID string) ([]tracertm.Item, error) {
    var allItems []tracertm.Item
    offset := 0
    limit := 100

    for {
        items, err := client.Items.List(ctx, &tracertm.ListItemsRequest{
            ProjectID: projectID,
            Limit:     limit,
            Offset:    offset,
        })
        if err != nil {
            return nil, err
        }

        allItems = append(allItems, items.Data...)

        if len(items.Data) < limit {
            break
        }
        offset += limit
    }

    return allItems, nil
}

// After: Cursor pagination
func getAllItemsCursor(client *tracertm.Client, projectID string) ([]tracertm.Item, error) {
    var allItems []tracertm.Item

    iter := client.Items.Iterate(ctx, projectID, nil)
    for iter.Next() {
        allItems = append(allItems, iter.Item())
    }

    if err := iter.Err(); err != nil {
        return nil, err
    }

    return allItems, nil
}
```

**Step 3: Handle Edge Cases**

```python
# Handle cursor expiration
from tracertm.exceptions import CursorExpiredError

def resilient_pagination(client, project_id):
    all_items = []
    cursor = None

    while True:
        try:
            response = client.items.list(
                project_id=project_id,
                limit=100,
                cursor=cursor
            )
            all_items.extend(response.data)

            if not response.pagination.has_next:
                break
            cursor = response.pagination.next_cursor

        except CursorExpiredError:
            # Cursor expired, restart from beginning
            # Note: May have duplicates if this happens
            print("Cursor expired, restarting...")
            all_items = []
            cursor = None

    return all_items
```

**Step 4: Test**

```python
# Verify same results with both methods
offset_items = get_all_items_offset(client, project_id)
cursor_items = get_all_items_cursor(client, project_id)

assert len(offset_items) == len(cursor_items)
assert set(i.id for i in offset_items) == set(i.id for i in cursor_items)
```

---

### Single Sort to Multi-Sort

**Status**: Enhancement available
**Reason**: More powerful sorting capabilities

#### Migration Steps

**Step 1: Identify Sort Needs**

Review your current sorting:

```python
# Current: Single sort
items = client.items.list(
    project_id=project_id,
    sort="priority",
    order="desc"
)
```

**Step 2: Add Secondary Sort for Stability**

```python
# Improved: Multi-sort with stable tie-breaker
items = client.items.list(
    project_id=project_id,
    sort=["priority", "created_at", "id"],
    order=["desc", "desc", "asc"]
)
```

**Step 3: Leverage Advanced Sorting**

```python
# Advanced: Different null handling per field
items = client.items.list(
    project_id=project_id,
    sort=[
        {"field": "priority", "order": "desc"},
        {"field": "due_date", "order": "asc", "nulls": "last"},
        {"field": "id", "order": "asc"}
    ]
)
```

---

### Query Parameters to Filter Expressions

**Status**: Enhancement available
**Reason**: Complex AND/OR/NOT logic

#### When to Migrate

- Need OR logic between fields
- Need NOT conditions
- Need nested conditions

#### Migration Steps

**Step 1: Simple Filters (No Change Needed)**

```python
# These still work fine
items = client.items.list(
    project_id=project_id,
    type="requirement",
    status="approved"
)
```

**Step 2: Complex Filters with Filter Expressions**

```python
# Before: Multiple API calls for OR logic
critical_items = client.items.list(project_id=project_id, priority="critical")
urgent_high = client.items.list(
    project_id=project_id,
    priority="high",
    tags__any=["urgent"]
)
combined = list(set(critical_items + urgent_high))  # Dedupe

# After: Single request with filter expression
items = client.items.query(
    project_id=project_id,
    filter={
        "or": [
            {"priority": {"eq": "critical"}},
            {"and": [
                {"priority": {"eq": "high"}},
                {"tags": {"any": ["urgent"]}}
            ]}
        ]
    }
)
```

**Step 3: TypeScript Example**

```typescript
// Before: Workaround for OR
const critical = await client.items.list({
  projectId,
  priority: 'critical'
});
const highUrgent = await client.items.list({
  projectId,
  priority: 'high',
  'tags[any]': 'urgent'
});
const combined = [...new Map(
  [...critical.data, ...highUrgent.data].map(i => [i.id, i])
).values()];

// After: Single query
const items = await client.items.query({
  projectId,
  filter: {
    or: [
      { priority: { eq: 'critical' } },
      { and: [
        { priority: { eq: 'high' } },
        { tags: { any: ['urgent'] } }
      ]}
    ]
  }
});
```

---

## Future Migration: v1 to v2

**Status**: Planned (v2 not yet released)
**Timeline**: v2 preview expected 2025

### Expected Changes

| v1 | v2 | Change Type |
|----|-----|-------------|
| `item.legacy_id` | Removed | Field removal |
| `GET /items/{id}` | `GET /projects/{id}/items/{id}` | Path change |
| Separate link endpoints | Inline links in item | Structure change |

### Preparing for v2

1. **Stop using legacy fields**
   - Use `id` instead of `legacy_id`
   - Use `created_at` instead of `created`

2. **Use project-scoped endpoints**
   - Already the recommended pattern
   - v2 will require project context

3. **Update error handling**
   - v2 may have new error codes
   - SDK will map automatically

4. **Monitor announcements**
   - Subscribe to API updates
   - Review v2 preview when available

---

## SDK Upgrade Guide

### Python: 1.x to 2.x

**Breaking Changes:**

| 1.x | 2.x |
|-----|-----|
| `client.get_items()` | `client.items.list()` |
| `client.get_item(id)` | `client.items.get(project_id, id)` |
| `client.create_item(data)` | `client.items.create(project_id, data)` |
| `TraceRTMException` | Specific exception types |

**Migration:**

```python
# 1.x
from tracertm import TraceRTM, TraceRTMException

client = TraceRTM(api_key="...")

try:
    items = client.get_items(project_id="proj_abc")
    item = client.get_item("item_123")
except TraceRTMException as e:
    print(f"Error: {e}")

# 2.x
from tracertm import TraceRTMClient
from tracertm.exceptions import NotFoundError, ValidationError

client = TraceRTMClient(api_key="...")

try:
    items = client.items.list(project_id="proj_abc")
    item = client.items.get(project_id="proj_abc", item_id="item_123")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except ValidationError as e:
    print(f"Validation: {e.details}")
```

### TypeScript: 1.x to 2.x

**Breaking Changes:**

| 1.x | 2.x |
|-----|-----|
| `new TraceRTM(apiKey)` | `new TraceRTMClient({ apiKey })` |
| Callbacks | Async/await |
| `client.items.get(id)` | `client.items.get({ projectId, itemId })` |

**Migration:**

```typescript
// 1.x
import TraceRTM from 'tracertm';

const client = new TraceRTM('rtm_live_sk_...');

client.items.list('proj_abc', (err, items) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(items);
});

// 2.x
import { TraceRTMClient } from '@tracertm/sdk';

const client = new TraceRTMClient({ apiKey: 'rtm_live_sk_...' });

try {
  const items = await client.items.list({ projectId: 'proj_abc' });
  console.log(items.data);
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error('Not found:', error.message);
  }
}
```

---

## Testing Migrations

### Parallel Testing Pattern

```python
import os
from tracertm import TraceRTMClient

# Test environment with new version
test_client = TraceRTMClient(
    api_key=os.environ["TRACERTM_TEST_KEY"],
    api_version="2024-12-01"  # Latest
)

# Production with stable version
prod_client = TraceRTMClient(
    api_key=os.environ["TRACERTM_PROD_KEY"],
    api_version="2024-06-01"  # Pinned
)

def compare_results():
    """Verify both versions return equivalent results"""
    test_items = list(test_client.items.iterate(project_id="proj_test"))
    prod_items = list(prod_client.items.iterate(project_id="proj_test"))

    assert len(test_items) == len(prod_items)

    test_ids = {i.id for i in test_items}
    prod_ids = {i.id for i in prod_items}

    assert test_ids == prod_ids, "Item mismatch between versions"
    print("Both versions return equivalent results")
```

### Staged Rollout

```python
import random

class MigrationClient:
    def __init__(self, api_key, rollout_percentage=0):
        self.old_client = TraceRTMClient(
            api_key=api_key,
            api_version="2024-06-01"
        )
        self.new_client = TraceRTMClient(
            api_key=api_key,
            api_version="2024-12-01"
        )
        self.rollout_percentage = rollout_percentage

    def should_use_new(self):
        return random.random() * 100 < self.rollout_percentage

    def items(self):
        if self.should_use_new():
            return self.new_client.items
        return self.old_client.items

# Usage
client = MigrationClient(
    api_key="rtm_live_sk_...",
    rollout_percentage=10  # 10% traffic to new version
)

items = client.items().list(project_id="proj_abc")
```

---

## Getting Help

### Migration Support

- **Documentation**: https://docs.tracertm.dev/migration
- **GitHub Issues**: https://github.com/tracertm/sdk/issues
- **Email**: api-support@tracertm.dev
- **Slack**: #tracertm-api (Enterprise customers)

### Common Issues

| Issue | Solution |
|-------|----------|
| Cursor expired during migration | Implement retry with fresh cursor |
| Different result counts | Check filter compatibility |
| Performance regression | Verify using cursor pagination |
| SDK type errors | Update SDK to latest version |

---

## Related Documentation

- [API Versioning](/docs/api-reference/rest-api/versioning) - Versioning strategy
- [API Versions](/docs/api-reference/rest-api/api-versions) - Version changelog
- [Deprecations](/docs/api-reference/rest-api/deprecations) - Current deprecations
