---
title: "List Projects"
description: "API endpoint for listing and filtering TraceRTM projects"
---

# List Projects

Retrieve a paginated list of projects accessible to the authenticated user.

## Endpoint

```http
GET /v1/projects
```

## Authentication

Requires a valid API key or access token with `read:projects` scope.

## Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page` | integer | 1 | Page number for pagination |
| `per_page` | integer | 20 | Items per page (1-100) |
| `cursor` | string | - | Cursor for cursor-based pagination |
| `limit` | integer | 20 | Items to return with cursor pagination |
| `status` | string | - | Filter: `active`, `archived`, `all` |
| `visibility` | string | - | Filter: `private`, `public`, `organization` |
| `owner_id` | string | - | Filter by owner user ID |
| `search` | string | - | Search in name and description |
| `sort` | string | `-updated_at` | Sort field with optional `-` prefix for descending |
| `fields` | string | - | Comma-separated list of fields to return |

## Sort Fields

| Field | Description |
|-------|-------------|
| `name` | Project name (alphabetical) |
| `key` | Project key (alphabetical) |
| `created_at` | Creation date |
| `updated_at` | Last update date |
| `item_count` | Number of items |

## Request Examples

### Basic Request

```bash
curl -X GET "https://api.tracertm.dev/v1/projects" \
  -H "Authorization: Bearer $TRACERTM_API_KEY"
```

### With Filters

```bash
# Active projects, sorted by name
curl -X GET "https://api.tracertm.dev/v1/projects?status=active&sort=name" \
  -H "Authorization: Bearer $TRACERTM_API_KEY"

# Search for projects
curl -X GET "https://api.tracertm.dev/v1/projects?search=mobile&status=active" \
  -H "Authorization: Bearer $TRACERTM_API_KEY"

# My projects only
curl -X GET "https://api.tracertm.dev/v1/projects?owner_id=me" \
  -H "Authorization: Bearer $TRACERTM_API_KEY"
```

### With Pagination

```bash
# Page-based pagination
curl -X GET "https://api.tracertm.dev/v1/projects?page=2&per_page=50" \
  -H "Authorization: Bearer $TRACERTM_API_KEY"

# Cursor-based pagination
curl -X GET "https://api.tracertm.dev/v1/projects?cursor=eyJpZCI6InByb2pfYWJjMTIzIn0&limit=50" \
  -H "Authorization: Bearer $TRACERTM_API_KEY"
```

### Sparse Fieldsets

```bash
# Only return specific fields
curl -X GET "https://api.tracertm.dev/v1/projects?fields=id,name,key,item_counts" \
  -H "Authorization: Bearer $TRACERTM_API_KEY"
```

## Response

### Success Response (200 OK)

```json
{
  "data": [
    {
      "id": "proj_abc123xyz",
      "name": "Product Requirements",
      "key": "PRD",
      "description": "Requirements for Q1 2025 release",
      "status": "active",
      "visibility": "private",
      "owner_id": "user_xyz789",
      "item_counts": {
        "total": 150,
        "by_type": {
          "requirement": 80,
          "test_case": 50,
          "bug": 20
        }
      },
      "member_count": 12,
      "created_at": "2025-01-01T10:00:00Z",
      "updated_at": "2025-01-15T14:30:00Z"
    },
    {
      "id": "proj_def456uvw",
      "name": "Test Automation Suite",
      "key": "TAS",
      "description": "Automated test cases",
      "status": "active",
      "visibility": "organization",
      "owner_id": "user_abc123",
      "item_counts": {
        "total": 200,
        "by_type": {
          "test_case": 180,
          "test_suite": 20
        }
      },
      "member_count": 8,
      "created_at": "2025-01-05T09:00:00Z",
      "updated_at": "2025-01-14T10:00:00Z"
    }
  ],
  "meta": {
    "total": 25,
    "page": 1,
    "per_page": 20,
    "total_pages": 2,
    "request_id": "req_xyz789"
  },
  "links": {
    "self": "https://api.tracertm.dev/v1/projects?page=1",
    "first": "https://api.tracertm.dev/v1/projects?page=1",
    "last": "https://api.tracertm.dev/v1/projects?page=2",
    "next": "https://api.tracertm.dev/v1/projects?page=2",
    "prev": null
  }
}
```

### Cursor-Based Response

```json
{
  "data": [
    {"id": "proj_abc123", "name": "Project A"},
    {"id": "proj_def456", "name": "Project B"}
  ],
  "meta": {
    "has_more": true,
    "next_cursor": "eyJpZCI6InByb2pfZGVmNDU2In0",
    "request_id": "req_xyz789"
  }
}
```

### Empty Response

```json
{
  "data": [],
  "meta": {
    "total": 0,
    "page": 1,
    "per_page": 20,
    "total_pages": 0
  }
}
```

## SDK Examples

### Python

```python
from tracertm import TraceRTMClient

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

# Basic list
projects = await client.projects.list()
for project in projects:
    print(f"{project.key}: {project.name}")

# With filters
active_projects = await client.projects.list(
    status="active",
    sort="-updated_at",
    per_page=50
)

# Search
search_results = await client.projects.list(
    search="mobile",
    status="active"
)

# Iterate through all pages automatically
async for project in client.projects.list_all(status="active"):
    print(f"Processing: {project.name}")

# Get only my projects
my_projects = await client.projects.list(owner_id="me")

# With specific fields
minimal_projects = await client.projects.list(
    fields=["id", "name", "key", "item_counts"]
)
```

### TypeScript

```typescript
import { TraceRTMClient } from '@tracertm/sdk'

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

// Basic list
const { data: projects, meta } = await client.projects.list()
console.log(`Found ${meta.total} projects`)

// With filters
const activeProjects = await client.projects.list({
  status: 'active',
  sort: '-updated_at',
  perPage: 50
})

// Search
const searchResults = await client.projects.list({
  search: 'mobile',
  status: 'active'
})

// Async iterator for all pages
for await (const project of client.projects.listAll({ status: 'active' })) {
  console.log(`Processing: ${project.name}`)
}

// Type-safe response
interface ProjectListResponse {
  data: Project[]
  meta: {
    total: number
    page: number
    perPage: number
  }
}
```

### Go

```go
package main

import (
    "context"
    "fmt"
    "github.com/tracertm/tracertm-go"
)

func main() {
    client := tracertm.NewClient("rtm_live_...")
    ctx := context.Background()

    // Basic list
    projects, err := client.Projects.List(ctx, nil)
    if err != nil {
        panic(err)
    }

    for _, project := range projects.Data {
        fmt.Printf("%s: %s\n", project.Key, project.Name)
    }

    // With options
    opts := &tracertm.ProjectListOptions{
        Status:  "active",
        Sort:    "-updated_at",
        PerPage: 50,
    }
    activeProjects, _ := client.Projects.List(ctx, opts)

    // Pagination
    for projects.Meta.HasMore {
        projects, _ = client.Projects.List(ctx, &tracertm.ProjectListOptions{
            Cursor: projects.Meta.NextCursor,
        })
        // Process projects...
    }
}
```

## Error Responses

### Invalid Parameters (400)

```json
{
  "error": {
    "code": "invalid_parameter",
    "message": "Invalid query parameter",
    "details": [
      {
        "field": "per_page",
        "message": "Must be between 1 and 100"
      }
    ]
  }
}
```

### Unauthorized (401)

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing authentication token"
  }
}
```

### Forbidden (403)

```json
{
  "error": {
    "code": "forbidden",
    "message": "Missing required scope: read:projects"
  }
}
```

## Best Practices

### Efficient Pagination

```python
# For large datasets, use cursor-based pagination
cursor = None
all_projects = []

while True:
    response = await client.projects.list(cursor=cursor, limit=100)
    all_projects.extend(response.data)

    if not response.meta.has_more:
        break
    cursor = response.meta.next_cursor
```

### Caching

```python
import hashlib
from functools import lru_cache

@lru_cache(maxsize=100, ttl=300)  # Cache for 5 minutes
async def get_projects(status: str = "active"):
    return await client.projects.list(status=status)
```

### Parallel Fetching

```python
import asyncio

async def fetch_all_projects():
    # Fetch first page to get total
    first_page = await client.projects.list(page=1, per_page=100)
    total_pages = first_page.meta.total_pages

    if total_pages <= 1:
        return first_page.data

    # Fetch remaining pages in parallel
    tasks = [
        client.projects.list(page=p, per_page=100)
        for p in range(2, total_pages + 1)
    ]
    results = await asyncio.gather(*tasks)

    all_projects = first_page.data
    for result in results:
        all_projects.extend(result.data)

    return all_projects
```

## Related Documentation

- [Projects API](/docs/api-reference/rest-api/projects) - All project endpoints
- [Create Project](/docs/api-reference/rest-api/projects-create) - Creating projects
- [Pagination](/docs/api-reference/rest-api/pagination) - Pagination details
- [Filtering](/docs/api-reference/rest-api/filtering) - Filter syntax

