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

# Android

> Build content-driven Android applications with ButterCMS. Covers setup, fetching pages, collections, components, and blog content using Retrofit.

## Overview

This integration guide shows you how to how to update your existing project to:

1. install the ButterCMS package
2. instantiate ButterCMS
3. create components to fetch and display each of the three ButterCMS content types: [Pages](../../core-concepts/content-types/pages), [Collections](../../core-concepts/content-types/collections), and [Blog Posts](../../core-concepts/content-types/blog-engine).

<Tip>
  In order for the snippets to work, you'll need to [setup your dashboard content schemas inside of ButterCMS](../buttercms-setup) first.
</Tip>

## Installation

<Tabs>
  <Tab title="Groovy (build.gradle)">
    ```groovy theme={null}
    dependencies {
        implementation 'com.squareup.retrofit2:retrofit:2.9.0'
        implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
        implementation 'com.squareup.okhttp3:okhttp:4.11.0'
        implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
    }
    ```
  </Tab>

  <Tab title="Kotlin DSL (build.gradle.kts)">
    ```kotlin theme={null}
    dependencies {
        implementation("com.squareup.retrofit2:retrofit:2.9.0")
        implementation("com.squareup.retrofit2:converter-gson:2.9.0")
        implementation("com.squareup.okhttp3:okhttp:4.11.0")
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    }
    ```
  </Tab>
</Tabs>

Add your API token to `local.properties`:

```properties theme={null}
BUTTERCMS_API_TOKEN=your_api_token
```

## Initialize the client

Create a reusable API client:

<Tabs>
  <Tab title="Kotlin">
    ```kotlin theme={null}
    // ButterCMS.kt
    import retrofit2.Retrofit
    import retrofit2.converter.gson.GsonConverterFactory
    import retrofit2.http.GET
    import retrofit2.http.Path
    import retrofit2.http.Query

    interface ButterCMSApi {
        @GET("pages/{page_type}/{slug}/")
        suspend fun getPage(
            @Path("page_type") pageType: String,
            @Path("slug") slug: String,
            @Query("auth_token") authToken: String
        ): PageResponse

        @GET("content/")
        suspend fun getCollection(
            @Query("keys") keys: String,
            @Query("auth_token") authToken: String
        ): CollectionResponse

        @GET("posts/")
        suspend fun getPosts(
            @Query("auth_token") authToken: String,
            @Query("page") page: Int = 1,
            @Query("page_size") pageSize: Int = 10
        ): PostsResponse

        @GET("posts/{slug}/")
        suspend fun getPost(
            @Path("slug") slug: String,
            @Query("auth_token") authToken: String
        ): PostResponse
    }

    object ButterCMS {
        private const val BASE_URL = "https://api.buttercms.com/v2/"
        val API_TOKEN = BuildConfig.BUTTERCMS_API_TOKEN

        val api: ButterCMSApi by lazy {
            Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(ButterCMSApi::class.java)
        }
    }
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    // ButterCMS.java
    import retrofit2.Retrofit;
    import retrofit2.converter.gson.GsonConverterFactory;
    import retrofit2.Call;
    import retrofit2.http.GET;
    import retrofit2.http.Path;
    import retrofit2.http.Query;

    public interface ButterCMSApi {
        @GET("pages/{page_type}/{slug}/")
        Call<PageResponse> getPage(
            @Path("page_type") String pageType,
            @Path("slug") String slug,
            @Query("auth_token") String authToken
        );

        @GET("content/")
        Call<CollectionResponse> getCollection(
            @Query("keys") String keys,
            @Query("auth_token") String authToken
        );

        @GET("posts/")
        Call<PostsResponse> getPosts(
            @Query("auth_token") String authToken,
            @Query("page") int page,
            @Query("page_size") int pageSize
        );

        @GET("posts/{slug}/")
        Call<PostResponse> getPost(
            @Path("slug") String slug,
            @Query("auth_token") String authToken
        );
    }

    public class ButterCMS {
        private static final String BASE_URL = "https://api.buttercms.com/v2/";
        public static final String API_TOKEN = BuildConfig.BUTTERCMS_API_TOKEN;

        private static ButterCMSApi api;

        public static ButterCMSApi getApi() {
            if (api == null) {
                Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
                api = retrofit.create(ButterCMSApi.class);
            }
            return api;
        }
    }
    ```
  </Tab>
</Tabs>

<Info>
  For complete API documentation including all available endpoints and parameters, see the [REST API Reference](../../api-reference/pages/get-multiple-pages).
</Info>

## Data models

```kotlin theme={null}
// Models.kt
data class PageResponse(val data: PageData)
data class PageData(val slug: String, val fields: Map<String, Any>)

data class CollectionResponse(val data: Map<String, List<Map<String, Any>>>)

data class PostsResponse(val data: List<Post>, val meta: Meta)
data class PostResponse(val data: Post)

data class Post(
    val slug: String,
    val title: String,
    val body: String,
    val summary: String,
    val published: String,
    val featured_image: String?,
    val author: Author,
    val categories: List<Category>,
    val tags: List<Tag>
)

data class Author(val first_name: String, val last_name: String, val email: String?)
data class Category(val name: String, val slug: String)
data class Tag(val name: String, val slug: String)
data class Meta(val next_page: Int?, val previous_page: Int?, val count: Int)
```

## Pages

<Tabs>
  <Tab title="Kotlin (Coroutines)">
    ```kotlin theme={null}
    // LandingPageRepository.kt
    class LandingPageRepository {
        suspend fun getPage(slug: String): PageData? {
            return try {
                val response = ButterCMS.api.getPage("landing_page", slug, ButterCMS.API_TOKEN)
                response.data
            } catch (e: Exception) {
                null
            }
        }
    }

    // LandingPageViewModel.kt
    class LandingPageViewModel : ViewModel() {
        private val repository = LandingPageRepository()

        private val _page = MutableLiveData<PageData?>()
        val page: LiveData<PageData?> = _page

        fun loadPage(slug: String) {
            viewModelScope.launch {
                _page.value = repository.getPage(slug)
            }
        }
    }

    // LandingPageActivity.kt
    class LandingPageActivity : AppCompatActivity() {
        private val viewModel: LandingPageViewModel by viewModels()

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_landing)

            val slug = intent.getStringExtra("slug") ?: "home"
            viewModel.loadPage(slug)

            viewModel.page.observe(this) { page ->
                page?.let {
                    findViewById<TextView>(R.id.headline).text = it.fields["headline"] as? String
                    findViewById<TextView>(R.id.subheadline).text = it.fields["subheadline"] as? String
                    // Load hero_image with Glide/Coil
                    // Render body HTML in WebView
                }
            }
        }
    }
    ```
  </Tab>

  <Tab title="Java (Callbacks)">
    ```java theme={null}
    // LandingPageRepository.java
    public class LandingPageRepository {
        public void getPage(String slug, Callback<PageResponse> callback) {
            ButterCMS.getApi()
                .getPage("landing_page", slug, ButterCMS.API_TOKEN)
                .enqueue(callback);
        }
    }

    // LandingPageActivity.java
    public class LandingPageActivity extends AppCompatActivity {
        private LandingPageRepository repository = new LandingPageRepository();

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_landing);

            String slug = getIntent().getStringExtra("slug");
            if (slug == null) slug = "home";

            repository.getPage(slug, new Callback<PageResponse>() {
                @Override
                public void onResponse(Call<PageResponse> call, Response<PageResponse> response) {
                    if (response.isSuccessful() && response.body() != null) {
                        PageData page = response.body().getData();
                        TextView headline = findViewById(R.id.headline);
                        headline.setText((String) page.getFields().get("headline"));
                    }
                }

                @Override
                public void onFailure(Call<PageResponse> call, Throwable t) {
                    // Handle error
                }
            });
        }
    }
    ```
  </Tab>
</Tabs>

```xml theme={null}
<!-- res/layout/activity_landing.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/headline"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/subheadline"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textColor="@android:color/darker_gray" />

    <ImageView
        android:id="@+id/heroImage"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginTop="16dp"
        android:scaleType="centerCrop" />

    <WebView
        android:id="@+id/bodyContent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp" />
</LinearLayout>
```

## Collections

<Tabs>
  <Tab title="Kotlin (Coroutines)">
    ```kotlin theme={null}
    // BrandsRepository.kt
    class BrandsRepository {
        suspend fun getBrands(): List<Map<String, Any>>? {
            return try {
                val response = ButterCMS.api.getCollection("brands", ButterCMS.API_TOKEN)
                response.data["brands"]
            } catch (e: Exception) {
                null
            }
        }
    }

    // BrandsViewModel.kt
    class BrandsViewModel : ViewModel() {
        private val repository = BrandsRepository()

        private val _brands = MutableLiveData<List<Map<String, Any>>>()
        val brands: LiveData<List<Map<String, Any>>> = _brands

        fun loadBrands() {
            viewModelScope.launch {
                _brands.value = repository.getBrands() ?: emptyList()
            }
        }
    }

    // BrandsActivity.kt
    class BrandsActivity : AppCompatActivity() {
        private val viewModel: BrandsViewModel by viewModels()

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_brands)

            val recyclerView = findViewById<RecyclerView>(R.id.brandsRecycler)
            recyclerView.layoutManager = LinearLayoutManager(this)

            viewModel.loadBrands()

            viewModel.brands.observe(this) { brands ->
                recyclerView.adapter = BrandsAdapter(brands)
            }
        }
    }
    ```
  </Tab>

  <Tab title="Java (Callbacks)">
    ```java theme={null}
    // BrandsRepository.java
    public class BrandsRepository {
        public void getBrands(Callback<CollectionResponse> callback) {
            ButterCMS.getApi()
                .getCollection("brands", ButterCMS.API_TOKEN)
                .enqueue(callback);
        }
    }

    // BrandsActivity.java
    public class BrandsActivity extends AppCompatActivity {
        private BrandsRepository repository = new BrandsRepository();

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_brands);

            RecyclerView recyclerView = findViewById(R.id.brandsRecycler);
            recyclerView.setLayoutManager(new LinearLayoutManager(this));

            repository.getBrands(new Callback<CollectionResponse>() {
                @Override
                public void onResponse(Call<CollectionResponse> call, Response<CollectionResponse> response) {
                    if (response.isSuccessful() && response.body() != null) {
                        List<Map<String, Object>> brands = response.body().getData().get("brands");
                        recyclerView.setAdapter(new BrandsAdapter(brands));
                    }
                }

                @Override
                public void onFailure(Call<CollectionResponse> call, Throwable t) {
                    // Handle error
                }
            });
        }
    }
    ```
  </Tab>
</Tabs>

## Dynamic components

### Component renderer

```kotlin theme={null}
// ComponentRenderer.kt
sealed class ButterComponent {
    data class Hero(
        val headline: String,
        val subheadline: String,
        val image: String?,
        val buttonLabel: String?,
        val buttonUrl: String?
    ) : ButterComponent()

    data class Features(val items: List<Map<String, Any>>) : ButterComponent()
    data class CTA(val title: String, val buttonText: String, val buttonUrl: String) : ButterComponent()
}

object ComponentParser {
    fun parse(components: List<Map<String, Any>>): List<ButterComponent> {
        return components.mapNotNull { component ->
            val type = component["type"] as? String
            val fields = component["fields"] as? Map<String, Any> ?: return@mapNotNull null

            when (type) {
                "hero" -> ButterComponent.Hero(
                    headline = fields["headline"] as? String ?: "",
                    subheadline = fields["subheadline"] as? String ?: "",
                    image = fields["image"] as? String,
                    buttonLabel = fields["button_label"] as? String,
                    buttonUrl = fields["button_url"] as? String
                )
                "cta" -> ButterComponent.CTA(
                    title = fields["title"] as? String ?: "",
                    buttonText = fields["button_text"] as? String ?: "",
                    buttonUrl = fields["button_url"] as? String ?: ""
                )
                else -> null
            }
        }
    }
}
```

### Component views

```kotlin theme={null}
// HeroView.kt
class HeroView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {

    init {
        orientation = VERTICAL
        LayoutInflater.from(context).inflate(R.layout.component_hero, this, true)
    }

    fun bind(hero: ButterComponent.Hero) {
        findViewById<TextView>(R.id.heroHeadline).text = hero.headline
        findViewById<TextView>(R.id.heroSubheadline).text = hero.subheadline
        hero.image?.let { url ->
            Glide.with(this).load(url).into(findViewById(R.id.heroImage))
        }
        hero.buttonLabel?.let { label ->
            findViewById<Button>(R.id.heroButton).apply {
                text = label
                visibility = VISIBLE
                setOnClickListener {
                    // Handle button click
                }
            }
        }
    }
}
```

### Using in activities

```kotlin theme={null}
// ComponentPageActivity.kt
class ComponentPageActivity : AppCompatActivity() {
    private val viewModel: LandingPageViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_component_page)

        val container = findViewById<LinearLayout>(R.id.componentsContainer)
        val slug = intent.getStringExtra("slug") ?: "home"

        viewModel.loadPage(slug)

        viewModel.page.observe(this) { page ->
            page?.let {
                @Suppress("UNCHECKED_CAST")
                val bodyComponents = it.fields["body"] as? List<Map<String, Any>> ?: emptyList()
                val components = ComponentParser.parse(bodyComponents)

                container.removeAllViews()
                components.forEach { component ->
                    when (component) {
                        is ButterComponent.Hero -> {
                            val heroView = HeroView(this)
                            heroView.bind(component)
                            container.addView(heroView)
                        }
                        is ButterComponent.CTA -> {
                            // Add CTA view
                        }
                        else -> {}
                    }
                }
            }
        }
    }
}
```

## Blog

<Tabs>
  <Tab title="Blog Post List">
    <Tabs>
      <Tab title="Kotlin (Coroutines)">
        ```kotlin theme={null}
        // BlogRepository.kt
        class BlogRepository {
            suspend fun getPosts(page: Int = 1): PostsResponse? {
                return try {
                    ButterCMS.api.getPosts(ButterCMS.API_TOKEN, page, 10)
                } catch (e: Exception) {
                    null
                }
            }
        }

        // BlogListViewModel.kt
        class BlogListViewModel : ViewModel() {
            private val repository = BlogRepository()

            private val _posts = MutableLiveData<List<Post>>()
            val posts: LiveData<List<Post>> = _posts

            private val _meta = MutableLiveData<Meta?>()
            val meta: LiveData<Meta?> = _meta

            fun loadPosts(page: Int = 1) {
                viewModelScope.launch {
                    repository.getPosts(page)?.let { response ->
                        _posts.value = response.data
                        _meta.value = response.meta
                    }
                }
            }
        }

        // BlogListActivity.kt
        class BlogListActivity : AppCompatActivity() {
            private val viewModel: BlogListViewModel by viewModels()

            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContentView(R.layout.activity_blog_list)

                val recyclerView = findViewById<RecyclerView>(R.id.postsRecycler)
                recyclerView.layoutManager = LinearLayoutManager(this)

                viewModel.loadPosts()

                viewModel.posts.observe(this) { posts ->
                    recyclerView.adapter = PostsAdapter(posts) { post ->
                        val intent = Intent(this, BlogPostActivity::class.java)
                        intent.putExtra("slug", post.slug)
                        startActivity(intent)
                    }
                }
            }
        }
        ```
      </Tab>

      <Tab title="Java (Callbacks)">
        ```java theme={null}
        // BlogRepository.java
        public class BlogRepository {
            public void getPosts(int page, Callback<PostsResponse> callback) {
                ButterCMS.getApi()
                    .getPosts(ButterCMS.API_TOKEN, page, 10)
                    .enqueue(callback);
            }
        }

        // BlogListActivity.java
        public class BlogListActivity extends AppCompatActivity {
            private BlogRepository repository = new BlogRepository();

            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_blog_list);

                RecyclerView recyclerView = findViewById(R.id.postsRecycler);
                recyclerView.setLayoutManager(new LinearLayoutManager(this));

                repository.getPosts(1, new Callback<PostsResponse>() {
                    @Override
                    public void onResponse(Call<PostsResponse> call, Response<PostsResponse> response) {
                        if (response.isSuccessful() && response.body() != null) {
                            List<Post> posts = response.body().getData();
                            recyclerView.setAdapter(new PostsAdapter(posts, post -> {
                                Intent intent = new Intent(BlogListActivity.this, BlogPostActivity.class);
                                intent.putExtra("slug", post.getSlug());
                                startActivity(intent);
                            }));
                        }
                    }

                    @Override
                    public void onFailure(Call<PostsResponse> call, Throwable t) {
                        // Handle error
                    }
                });
            }
        }
        ```
      </Tab>
    </Tabs>

    ```kotlin theme={null}
    // PostsAdapter.kt
    class PostsAdapter(
        private val posts: List<Post>,
        private val onClick: (Post) -> Unit
    ) : RecyclerView.Adapter<PostsAdapter.ViewHolder>() {

        class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val title: TextView = view.findViewById(R.id.postTitle)
            val summary: TextView = view.findViewById(R.id.postSummary)
            val author: TextView = view.findViewById(R.id.postAuthor)
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_post, parent, false)
            return ViewHolder(view)
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val post = posts[position]
            holder.title.text = post.title
            holder.summary.text = post.summary
            holder.author.text = "By ${post.author.first_name} ${post.author.last_name}"
            holder.itemView.setOnClickListener { onClick(post) }
        }

        override fun getItemCount() = posts.size
    }
    ```
  </Tab>

  <Tab title="Single Blog Post">
    <Tabs>
      <Tab title="Kotlin (Coroutines)">
        ```kotlin theme={null}
        // BlogRepository.kt
        class BlogRepository {
            suspend fun getPost(slug: String): Post? {
                return try {
                    ButterCMS.api.getPost(slug, ButterCMS.API_TOKEN).data
                } catch (e: Exception) {
                    null
                }
            }
        }

        // BlogPostViewModel.kt
        class BlogPostViewModel : ViewModel() {
            private val repository = BlogRepository()

            private val _post = MutableLiveData<Post?>()
            val post: LiveData<Post?> = _post

            fun loadPost(slug: String) {
                viewModelScope.launch {
                    _post.value = repository.getPost(slug)
                }
            }
        }

        // BlogPostActivity.kt
        class BlogPostActivity : AppCompatActivity() {
            private val viewModel: BlogPostViewModel by viewModels()

            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContentView(R.layout.activity_blog_post)

                val slug = intent.getStringExtra("slug") ?: return finish()
                viewModel.loadPost(slug)

                viewModel.post.observe(this) { post ->
                    post?.let {
                        findViewById<TextView>(R.id.postTitle).text = it.title
                        findViewById<TextView>(R.id.postAuthor).text =
                            "By ${it.author.first_name} ${it.author.last_name}"

                        it.featured_image?.let { url ->
                            Glide.with(this)
                                .load(url)
                                .into(findViewById(R.id.featuredImage))
                        }

                        findViewById<WebView>(R.id.postBody).loadDataWithBaseURL(
                            null, it.body, "text/html", "UTF-8", null
                        )
                    }
                }
            }
        }
        ```
      </Tab>

      <Tab title="Java (Callbacks)">
        ```java theme={null}
        // BlogPostActivity.java
        public class BlogPostActivity extends AppCompatActivity {
            private BlogRepository repository = new BlogRepository();

            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_blog_post);

                String slug = getIntent().getStringExtra("slug");
                if (slug == null) {
                    finish();
                    return;
                }

                repository.getPost(slug, new Callback<PostResponse>() {
                    @Override
                    public void onResponse(Call<PostResponse> call, Response<PostResponse> response) {
                        if (response.isSuccessful() && response.body() != null) {
                            Post post = response.body().getData();

                            TextView title = findViewById(R.id.postTitle);
                            title.setText(post.getTitle());

                            TextView author = findViewById(R.id.postAuthor);
                            author.setText("By " + post.getAuthor().getFirstName() +
                                " " + post.getAuthor().getLastName());

                            WebView body = findViewById(R.id.postBody);
                            body.loadDataWithBaseURL(null, post.getBody(),
                                "text/html", "UTF-8", null);
                        }
                    }

                    @Override
                    public void onFailure(Call<PostResponse> call, Throwable t) {
                        // Handle error
                    }
                });
            }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

## Caching

Use OkHttp's caching for better performance:

```kotlin theme={null}
// ButterCMS.kt
import okhttp3.Cache
import okhttp3.OkHttpClient
import java.io.File

object ButterCMS {
    private const val BASE_URL = "https://api.buttercms.com/v2/"
    val API_TOKEN = BuildConfig.BUTTERCMS_API_TOKEN

    fun createApi(context: Context): ButterCMSApi {
        val cacheSize = 10L * 1024 * 1024 // 10 MB
        val cache = Cache(File(context.cacheDir, "butter_cache"), cacheSize)

        val okHttpClient = OkHttpClient.Builder()
            .cache(cache)
            .addInterceptor { chain ->
                val request = chain.request().newBuilder()
                    .header("Cache-Control", "public, max-age=3600") // 1 hour
                    .build()
                chain.proceed(request)
            }
            .build()

        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ButterCMSApi::class.java)
    }
}
```

## Resources

<CardGroup cols={2}>
  <Card title="Kotlin SDK" icon="code" href="../sdks/kotlin-sdk">
    Kotlin SDK reference
  </Card>

  <Card title="Java SDK" icon="java" href="../sdks/java-sdk">
    Official Java SDK reference
  </Card>

  <Card title="GitHub Repository" icon="github" href="https://github.com/ButterCMS/buttercms-java">
    View source code
  </Card>

  <Card title="Content API" icon="database" href="../../api-reference/pages/get-multiple-pages">
    REST API documentation
  </Card>
</CardGroup>
