> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tktchurch.com/llms.txt
> Use this file to discover all available pages before exploring further.

# List User Devices

> List all active devices for a specific user

<Note>
  This endpoint requires authentication and the `viewUserDevices` permission.
</Note>

### Path Parameters

<ParamField path="userId" type="string" required>
  The UUID of the user whose devices to list
</ParamField>

### Response

Returns an array of active devices. Each device represents a refresh token and its associated access token.

<ResponseField name="id" type="string">
  Unique identifier for the token (UUID)
</ResponseField>

<ResponseField name="deviceInfo" type="object" required={false}>
  Information about the device

  <Expandable title="Device Info">
    <ResponseField name="deviceId" type="string" required={false}>
      Unique identifier for the device
    </ResponseField>

    <ResponseField name="deviceType" type="string">
      Type of device. One of:

      * `mobile`: Mobile phone
      * `tablet`: Tablet device
      * `desktop`: Desktop computer
      * `other`: Other device type
    </ResponseField>

    <ResponseField name="deviceName" type="string" required={false}>
      Name of the device
    </ResponseField>

    <ResponseField name="deviceModel" type="string" required={false}>
      Model of the device
    </ResponseField>

    <ResponseField name="osName" type="string" required={false}>
      Operating system name
    </ResponseField>

    <ResponseField name="osVersion" type="string" required={false}>
      Operating system version
    </ResponseField>

    <ResponseField name="appVersion" type="string" required={false}>
      Application version
    </ResponseField>

    <ResponseField name="ipAddress" type="string" required={false}>
      IP address of the device
    </ResponseField>

    <ResponseField name="userAgent" type="string" required={false}>
      User agent string
    </ResponseField>

    <ResponseField name="lastLocation" type="string" required={false}>
      Last known location of the device
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="lastUsedAt" type="string">
  Timestamp when the token was last used
</ResponseField>

<ResponseField name="createdAt" type="string" required={false}>
  Timestamp when the token was created
</ResponseField>

<ResponseField name="expiresAt" type="string">
  Timestamp when the token expires
</ResponseField>

### Error Responses

<ResponseField name="error" type="object">
  Error details when the request fails

  <Expandable title="Error Object">
    <ResponseField name="status" type="integer">
      HTTP status code
    </ResponseField>

    <ResponseField name="reason" type="string">
      Error message explaining why the request failed
    </ResponseField>
  </Expandable>
</ResponseField>

<Note>
  Common error cases:

  * 400 Bad Request: Invalid user ID format
  * 401 Unauthorized: Missing or invalid access token
  * 403 Forbidden: Insufficient permissions (missing viewUserDevices)
  * 404 Not Found: User not found
</Note>

<RequestExample>
  ```bash cURL theme={null}
  curl -X GET "https://api.tktchurch.com/v1/auth/devices/users/123e4567-e89b-12d3-a456-426614174000" \
    -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
  ```

  ```javascript JavaScript theme={null}
  const listUserDevices = async (accessToken, userId) => {
    const response = await fetch(
      `https://api.tktchurch.com/v1/devices/users/${userId}`,
      {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${accessToken}`
        }
      }
    );

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error.reason);
    }

    return response.json();
  };

  // Usage
  try {
    const devices = await listUserDevices(
      'eyJhbGciOiJIUzI1NiIs...', // access token
      '123e4567-e89b-12d3-a456-426614174000' // user ID
    );
    console.log('User devices:', devices);
  } catch (error) {
    console.error('Failed to list devices:', error.message);
  }
  ```

  ```swift Swift theme={null}
  func listUserDevices(accessToken: String, userId: UUID) async throws -> [Device] {
      var urlRequest = URLRequest(url: URL(string: "https://api.tktchurch.com/v1/devices/users/\(userId)")!)
      urlRequest.httpMethod = "GET"
      urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
      
      let (data, response) = try await URLSession.shared.data(for: urlRequest)
      guard let httpResponse = response as? HTTPURLResponse else {
          throw URLError(.badServerResponse)
      }
      
      if httpResponse.statusCode != 200 {
          let error = try JSONDecoder().decode(ErrorResponse.self, from: data)
          throw error
      }
      
      return try JSONDecoder().decode([Device].self, from: data)
  }

  // Usage
  do {
      let devices = try await listUserDevices(
          accessToken: "eyJhbGciOiJIUzI1NiIs...",
          userId: UUID(uuidString: "123e4567-e89b-12d3-a456-426614174000")!
      )
      print("User devices:", devices)
  } catch {
      print("Failed to list devices: \(error.localizedDescription)")
  }
  ```

  ```kotlin Kotlin theme={null}
  class DeviceService(private val client: OkHttpClient) {
      suspend fun listUserDevices(accessToken: String, userId: String): List<Device> {
          val request = Request.Builder()
              .url("https://api.tktchurch.com/v1/devices/users/$userId")
              .get()
              .header("Authorization", "Bearer $accessToken")
              .build()

          client.newCall(request).execute().use { response ->
              if (!response.isSuccessful) {
                  val error = response.body?.string()?.fromJson<ErrorResponse>()
                  throw Exception(error?.reason ?: "Failed to list devices")
              }
              
              return response.body?.string()?.fromJson<List<Device>>()
                  ?: throw Exception("Empty response")
          }
      }
  }

  // Usage
  try {
      val devices = deviceService.listUserDevices(
          "eyJhbGciOiJIUzI1NiIs...", // access token
          "123e4567-e89b-12d3-a456-426614174000" // user ID
      )
      println("User devices: $devices")
  } catch (e: Exception) {
      println("Failed to list devices: ${e.message}")
  }
  ```

  ```typescript React Native theme={null}
  import axios from 'axios';

  interface DeviceInfo {
    deviceId?: string;
    deviceType: 'mobile' | 'tablet' | 'desktop' | 'other';
    deviceName?: string;
    deviceModel?: string;
    osName?: string;
    osVersion?: string;
    appVersion?: string;
    ipAddress?: string;
    userAgent?: string;
    lastLocation?: string;
  }

  interface Device {
    id: string;
    deviceInfo?: DeviceInfo;
    lastUsedAt: string;
    createdAt?: string;
    expiresAt: string;
  }

  const listUserDevices = async (accessToken: string, userId: string): Promise<Device[]> => {
    try {
      const response = await axios.get(
        `https://api.tktchurch.com/v1/devices/users/${userId}`,
        {
          headers: {
            'Authorization': `Bearer ${accessToken}`
          },
        }
      );
      
      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        throw new Error(error.response?.data?.error?.reason || 'Failed to list devices');
      }
      throw error;
    }
  };

  // Usage
  try {
    const devices = await listUserDevices(
      'eyJhbGciOiJIUzI1NiIs...', // access token
      '123e4567-e89b-12d3-a456-426614174000' // user ID
    );
    console.log('User devices:', devices);
  } catch (error) {
    console.error('Failed to list devices:', error.message);
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Success theme={null}
  [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "deviceInfo": {
        "deviceId": "device123",
        "deviceType": "mobile",
        "deviceName": "iPhone 13",
        "deviceModel": "iPhone13,2",
        "osName": "iOS",
        "osVersion": "16.0",
        "appVersion": "1.0.0",
        "ipAddress": "192.168.1.1",
        "userAgent": "Mozilla/5.0...",
        "lastLocation": "New York, US"
      },
      "lastUsedAt": "2024-01-20T08:30:00Z",
      "createdAt": "2024-01-01T00:00:00Z",
      "expiresAt": "2024-02-01T00:00:00Z"
    }
  ]
  ```

  ```json 400 Bad Request theme={null}
  {
    "error": {
      "status": 400,
      "reason": "Invalid user ID"
    }
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "error": {
      "status": 401,
      "reason": "Invalid or expired access token"
    }
  }
  ```

  ```json 403 Forbidden theme={null}
  {
    "error": {
      "status": 403,
      "reason": "Insufficient permissions"
    }
  }
  ```

  ```json 404 Not Found theme={null}
  {
    "error": {
      "status": 404,
      "reason": "User not found"
    }
  }
  ```
</ResponseExample>
