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

> Returns a paginated list of newsletters

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

## Query Parameters

<ParamField query="page" type="integer" default="1">
  Page number for pagination
</ParamField>

<ParamField query="per" type="integer" default="10">
  Number of newsletters per page
</ParamField>

## Response

<ResponseField name="items" type="array">
  Array of newsletter objects

  <Expandable title="Newsletter Object">
    <ResponseField name="id" type="string">
      Unique identifier for the newsletter (UUID)
    </ResponseField>

    <ResponseField name="title" type="string">
      Newsletter title
    </ResponseField>

    <ResponseField name="content" type="string">
      Newsletter content
    </ResponseField>

    <ResponseField name="image" type="object" required={false}>
      Uploaded image file information

      <Expandable title="Image Object">
        <ResponseField name="id" type="string">
          File ID
        </ResponseField>

        <ResponseField name="url" type="string">
          File URL
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="imageUrl" type="string" required={false}>
      External image URL if not using uploaded file
    </ResponseField>

    <ResponseField name="pdf" type="object" required={false}>
      Uploaded PDF file information

      <Expandable title="PDF Object">
        <ResponseField name="id" type="string">
          File ID
        </ResponseField>

        <ResponseField name="url" type="string">
          File URL
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="pdfUrl" type="string" required={false}>
      External PDF URL if not using uploaded file
    </ResponseField>

    <ResponseField name="hasPdf" type="boolean">
      Whether the newsletter has an associated PDF
    </ResponseField>

    <ResponseField name="status" type="string">
      Newsletter status. One of: `draft`, `scheduled`, `published`, `archived`
    </ResponseField>

    <ResponseField name="publishDate" type="string" required={false}>
      Scheduled or actual publication date (ISO 8601)
    </ResponseField>

    <ResponseField name="recipientCount" type="integer">
      Number of times the newsletter has been viewed/received
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      Creation timestamp (ISO 8601)
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      Last update timestamp (ISO 8601)
    </ResponseField>

    <ResponseField name="createdBy" type="string">
      UUID of the user who created the newsletter
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="metadata" type="object">
  Pagination metadata

  <Expandable title="Metadata Object">
    <ResponseField name="page" type="integer">
      Current page number
    </ResponseField>

    <ResponseField name="per" type="integer">
      Items per page
    </ResponseField>

    <ResponseField name="total" type="integer">
      Total number of items
    </ResponseField>
  </Expandable>
</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:

  * 401 Unauthorized: Missing or invalid access token
  * 403 Forbidden: Missing required permission
</Note>

<RequestExample>
  ```bash cURL theme={null}
  curl -X GET "https://api.tktchurch.com/v1/newsletters?page=1&per=10" \
    -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
  ```

  ```javascript JavaScript theme={null}
  const listNewsletters = async (accessToken, page = 1, per = 10) => {
    const response = await fetch(
      `https://api.tktchurch.com/v1/newsletters?page=${page}&per=${per}`,
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`
        }
      }
    );

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

    return response.json();
  };

  // Usage
  try {
    const newsletters = await listNewsletters('eyJhbGciOiJIUzI1NiIs...');
    console.log('Newsletters:', newsletters.items);
    console.log('Pagination:', newsletters.metadata);
  } catch (error) {
    console.error('Failed to list newsletters:', error.message);
  }
  ```

  ```swift Swift theme={null}
  struct NewsletterResponse: Codable {
      let items: [Newsletter]
      let metadata: PaginationMetadata
  }

  func listNewsletters(accessToken: String, page: Int = 1, per: Int = 10) async throws -> NewsletterResponse {
      var urlComponents = URLComponents(string: "https://api.tktchurch.com/v1/newsletters")!
      urlComponents.queryItems = [
          URLQueryItem(name: "page", value: String(page)),
          URLQueryItem(name: "per", value: String(per))
      ]
      
      var request = URLRequest(url: urlComponents.url!)
      request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
      
      let (data, response) = try await URLSession.shared.data(for: request)
      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(NewsletterResponse.self, from: data)
  }

  // Usage
  do {
      let response = try await listNewsletters(accessToken: "eyJhbGciOiJIUzI1NiIs...")
      print("Newsletters:", response.items)
      print("Pagination:", response.metadata)
  } catch {
      print("Failed to list newsletters:", error.localizedDescription)
  }
  ```

  ```kotlin Kotlin theme={null}
  data class NewsletterResponse(
      val items: List<Newsletter>,
      val metadata: PaginationMetadata
  )

  class NewsletterService(private val client: OkHttpClient) {
      suspend fun listNewsletters(
          accessToken: String,
          page: Int = 1,
          per: Int = 10
      ): NewsletterResponse {
          val url = HttpUrl.Builder()
              .scheme("https")
              .host("api.tktchurch.com")
              .addPathSegments("v1/newsletters")
              .addQueryParameter("page", page.toString())
              .addQueryParameter("per", per.toString())
              .build()

          val request = Request.Builder()
              .url(url)
              .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 newsletters")
              }
              
              return response.body?.string()?.fromJson<NewsletterResponse>()
                  ?: throw Exception("Empty response")
          }
      }
  }

  // Usage
  try {
      val response = newsletterService.listNewsletters("eyJhbGciOiJIUzI1NiIs...")
      println("Newsletters: ${response.items}")
      println("Pagination: ${response.metadata}")
  } catch (e: Exception) {
      println("Failed to list newsletters: ${e.message}")
  }
  ```

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

  interface Newsletter {
    id: string;
    title: string;
    content: string;
    image?: {
      id: string;
      url: string;
    };
    imageUrl?: string;
    pdf?: {
      id: string;
      url: string;
    };
    pdfUrl?: string;
    hasPdf: boolean;
    status: 'draft' | 'scheduled' | 'published' | 'archived';
    publishDate?: string;
    recipientCount: number;
    createdAt: string;
    updatedAt: string;
    createdBy: string;
  }

  interface NewsletterResponse {
    items: Newsletter[];
    metadata: {
      page: number;
      per: number;
      total: number;
    };
  }

  const listNewsletters = async (
    accessToken: string,
    page: number = 1,
    per: number = 10
  ): Promise<NewsletterResponse> => {
    try {
      const response = await axios.get(
        `https://api.tktchurch.com/v1/newsletters?page=${page}&per=${per}`,
        {
          headers: {
            'Authorization': `Bearer ${accessToken}`
          },
        }
      );
      
      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        throw new Error(error.response?.data?.error?.reason || 'Failed to list newsletters');
      }
      throw error;
    }
  };

  // Usage
  try {
    const response = await listNewsletters('eyJhbGciOiJIUzI1NiIs...');
    console.log('Newsletters:', response.items);
    console.log('Pagination:', response.metadata);
  } catch (error) {
    console.error('Failed to list newsletters:', error.message);
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "items": [
      {
        "id": "123e4567-e89b-12d3-a456-426614174000",
        "title": "January Church Updates",
        "content": "Welcome to our first newsletter of 2024...",
        "image": {
          "id": "file-123",
          "url": "https://cdn.tktchurch.com/files/newsletter-image.jpg"
        },
        "imageUrl": null,
        "pdf": {
          "id": "file-456",
          "url": "https://cdn.tktchurch.com/files/newsletter.pdf"
        },
        "pdfUrl": null,
        "hasPdf": true,
        "status": "published",
        "publishDate": "2024-01-01T12:00:00Z",
        "recipientCount": 150,
        "createdAt": "2023-12-30T15:00:00Z",
        "updatedAt": "2024-01-01T12:00:00Z",
        "createdBy": "user-123e4567-e89b-12d3-a456-426614174000"
      }
    ],
    "metadata": {
      "page": 1,
      "per": 10,
      "total": 45
    }
  }
  ```

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

  ```json 403 Forbidden theme={null}
  {
    "error": {
      "status": 403,
      "reason": "Missing required permission: readNewsletter"
    }
  }
  ```
</ResponseExample>
