Skip to main content

Getting Started with Streams API

This guide will walk you through setting up and using the Streams API to fetch and display content from your Streams Platform organization.

Prerequisites

  • Node.js 16+ and npm/yarn
  • TypeScript knowledge (recommended)
  • A Streams Platform organization with published content
  • Access to Web Studio for client generation

Step 1: Generate Your API Client

The Streams API client is automatically generated through Web Studio based on your organization's content:

  1. Navigate to Web Studio in your Streams Platform dashboard
  2. Your API client will be automatically generated with:
    • Organization ID and Site ID pre-configured
    • GraphQL queries for your content types
    • TypeScript type definitions
    • Complete client implementation

Step 2: Client Structure

Your generated client will include several key files:

streams-api-client/
├── streams-content-api-client.ts # Main client class
├── types.ts # TypeScript definitions
├── postsquery.graphql # Posts query
├── articlequery.graphql # Articles query
├── contentlistsquery.graphql # Content lists query
└── adcampaignsquery.graphql # Ad campaigns query

Step 3: Use in Your Application

SvelteKit Example

// src/routes/+page.ts
import { mapApiArticleToArticlePreview } from "$lib/posts/mapper";
import { StreamsContentApiClient } from "../../../streams-api-client/streams-content-api-client";
import type { Post } from "../../../streams-api-client/types";
import type { PageLoad } from "./$types";

export const load: PageLoad = async ({ params }) => {
const apiContentClient = new StreamsContentApiClient();

// Get all content lists
const contentLists = (await apiContentClient.getContentLists()) ?? [];
const contentListsMap = new Map(contentLists.map(({ name, id }) => [name, id]));

// Helper function to fetch and map content
const fetchAndMapContent = async <T>(
listName: string,
mapper: (post: Post) => T
): Promise<T[]> => {
if (!contentListsMap.has(listName)) {
return [];
}
const listId = contentListsMap.get(listName);
const apiContent = (await apiContentClient.getPosts(null, [], listId)) ?? [];
return apiContent.map(mapper);
};

// Fetch content from specific lists
const [news, featuredPosts, videos] = await Promise.all([
fetchAndMapContent('Latest News', mapApiArticleToArticlePreview),
fetchAndMapContent('Featured Content', mapApiArticleToArticlePreview),
apiContentClient.getPosts(null, ['SHORT_VIDEO'], null)
]);

return {
news,
featuredPosts,
videos: videos ?? []
};
};
<!-- src/routes/+page.svelte -->
<script lang="ts">
import Articles3_1 from '../components/article/articles3_1.svelte';
import Articles3_2 from '../components/article/articles3_2.svelte';
import VideoGrid from '../components/video/video_grid.svelte';
import type { PageData } from './$types';

export let data: PageData;

let { news, featuredPosts, videos } = data;
</script>

<div class="container">
<!-- Featured News Section -->
<section class="news-section">
<h2>Latest News</h2>
<Articles3_1 articles={news.slice(0, 3)} />
<Articles3_2 articles={news.slice(3, 6)} />
</section>

<!-- Featured Content -->
<section class="featured-section">
<h2>Featured Content</h2>
{#each featuredPosts as post}
<article class="featured-post">
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
{#if post.media}
<img src={post.media.thumbnailUrl} alt={post.title} />
{/if}
</article>
{/each}
</section>

<!-- Video Content -->
{#if videos.length > 0}
<section class="videos-section">
<h2>Latest Videos</h2>
<VideoGrid {videos} />
</section>
{/if}
</div>

<style>
.container {
max-width: 1221px;
margin: 0 auto;
padding: 2rem;
}

.news-section, .featured-section, .videos-section {
margin-bottom: 3rem;
}

h2 {
font-size: 2rem;
margin-bottom: 1.5rem;
color: #2563eb;
}

.featured-post {
margin-bottom: 2rem;
padding: 1.5rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
}

.featured-post img {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 4px;
margin-top: 1rem;
}
</style>

React/Next.js Example

// pages/index.tsx
import { GetStaticProps } from 'next';
import { StreamsContentApiClient } from '../lib/streams-api-client/streams-content-api-client';
import type { Post } from '../lib/streams-api-client/types';

interface HomeProps {
posts: Post[];
articles: any[];
}

export default function Home({ posts, articles }: HomeProps) {
return (
<div className="container">
<section>
<h1>Latest Posts</h1>
{posts.map((post) => (
<article key={post.id} className="post-card">
<h2>{post.postData.articleData?.title || 'Post'}</h2>
{post.postData.media && (
<img
src={post.postData.media.thumbnailUrl}
alt={post.postData.articleData?.title || 'Post image'}
/>
)}
<p>{post.postData.textData?.text}</p>
</article>
))}
</section>
</div>
);
}

export const getStaticProps: GetStaticProps = async () => {
const client = new StreamsContentApiClient();

try {
const posts = await client.getPosts(null, ['REGULAR_POST', 'SHORT_VIDEO']);
const contentLists = await client.getContentLists();

return {
props: {
posts: posts || [],
contentLists: contentLists || []
},
revalidate: 60 // Revalidate every minute
};
} catch (error) {
console.error('Failed to fetch content:', error);
return {
props: {
posts: [],
contentLists: []
}
};
}
};

Step 4: Advanced Features

Content Type Filtering

// Get only short videos
const shortVideos = await apiClient.getPosts(null, ['SHORT_VIDEO']);

// Get only articles
const articles = await apiClient.getPosts(null, ['ARTICLE']);

// Get mixed content types
const mixedContent = await apiClient.getPosts(null, ['REGULAR_POST', 'SHORT_VIDEO', 'ARTICLE']);

Search Functionality

// Search across all content
const searchResults = await apiClient.getPosts(null, null, null, 'sustainability');

// Search within a specific content list
const contentListId = 'your-content-list-id';
const searchInList = await apiClient.getPosts(null, null, contentListId, 'climate change');

Working with Rich Media

// Access media with tags and metadata
posts.forEach(post => {
if (post.postData.media) {
const media = post.postData.media;

// Display image with proper scaling
console.log('Image URL:', media.url);
console.log('Thumbnail:', media.thumbnailUrl);
console.log('Dimensions:', media.metadata.width, 'x', media.metadata.height);

// Handle product tags for e-commerce
media.productTags?.forEach(tag => {
console.log('Product:', tag.product.name, tag.product.price);
console.log('Position:', tag.leftPCT, tag.topPCT);
});

// Handle user tags for social features
media.userTags?.forEach(tag => {
console.log('Tagged user:', tag.user.name);
console.log('Position:', tag.leftPCT, tag.topPCT);
});
}
});

Article Content Structure

// Working with structured articles
const article = await apiClient.getArticle('my-article-slug');

if (article) {
console.log('Title:', article.id);
console.log('Published:', article.publishedAt);
console.log('Main image:', article.mainImage);

// Process article sections
article.sections.forEach(section => {
section.layouts.forEach(layout => {
layout.fields.forEach(field => {
switch (field.fieldData.type) {
case 'RICH_TEXT':
console.log('Rich text:', field.fieldData.richTextData?.text);
break;
case 'IMAGE':
console.log('Image:', field.fieldData.imageData?.image.url);
break;
case 'VIDEO':
console.log('Video:', field.fieldData.videoData?.video.url);
break;
case 'MEDIA_GALLERY':
console.log('Gallery items:', field.fieldData.mediaGalleryData?.media.length);
break;
}
});
});
});
}

Error Handling

try {
const posts = await apiClient.getPosts(null, ['REGULAR_POST']);

if (!posts || posts.length === 0) {
console.log('No posts found');
return;
}

// Process posts
posts.forEach(post => {
console.log('Post:', post.id);
});

} catch (error) {
console.error('Failed to fetch posts:', error);

// Handle specific GraphQL errors
if (error instanceof Error) {
try {
const graphqlErrors = JSON.parse(error.message);
console.error('GraphQL errors:', graphqlErrors);
} catch (parseError) {
console.error('Network or other error:', error.message);
}
}
}

Best Practices

  1. Use Content Lists: Organize content using content lists for better performance and organization
  2. Implement Caching: Use the X-Use-Cache header and implement client-side caching
  3. Handle Loading States: Always show loading indicators while fetching content
  4. Optimize Images: Use appropriate image scales based on your layout requirements
  5. Error Boundaries: Implement proper error handling in your UI components
  6. Type Safety: Leverage the generated TypeScript types for better development experience

Performance Tips

  • Batch Requests: Fetch related content in parallel using Promise.all()
  • Image Optimization: Use thumbnail URLs for previews and full URLs for detail views
  • Pagination: Implement pagination for large content sets
  • Selective Fetching: Only fetch the content types you need