diff --git a/src/services/FyydApi.js b/src/services/FyydApi.js
new file mode 100644
index 0000000000000000000000000000000000000000..7db2eb441e860e1f1dc52e1b09c61ac48c1a9f2c
--- /dev/null
+++ b/src/services/FyydApi.js
@@ -0,0 +1,100 @@
+/*
+ * @copyright Copyright (c) 2020 Jonas Heinrich <onny@project-insanity.org>
+ *
+ * @author Jonas Heinrich <onny@project-insanity.org>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import axios from '@nextcloud/axios'
+
+export class FyydApi {
+
+	url() {
+		const url = 'https://api.fyyd.de/0.2'
+		return url
+	}
+
+	queryPodcast(podcastId) {
+
+		delete axios.defaults.headers.requesttoken
+		return axios.get(this.url() + '/podcast', {
+			params: {
+				podcast_id: podcastId,
+			},
+		})
+			.then(
+				(response) => {
+					return Promise.resolve(response.data)
+				},
+				(err) => {
+					return Promise.reject(err)
+				}
+			)
+			.catch((err) => {
+				return Promise.reject(err)
+			})
+
+	}
+
+	queryEpisodes(podcastId, page) {
+
+		delete axios.defaults.headers.requesttoken
+		return axios.get(this.url() + '/podcast/episodes', {
+			params: {
+				podcast_id: podcastId,
+				count: 20,
+				page,
+			},
+		})
+			.then(
+				(response) => {
+					return Promise.resolve(response.data)
+				},
+				(err) => {
+					return Promise.reject(err)
+				}
+			)
+			.catch((err) => {
+				return Promise.reject(err)
+			})
+
+	}
+
+	queryEpisode(episodeId) {
+
+		delete axios.defaults.headers.requesttoken
+		return axios.get(this.url() + '/episode', {
+			params: {
+				episode_id: episodeId,
+			},
+		})
+			.then(
+				(response) => {
+					return Promise.resolve(response.data)
+				},
+				(err) => {
+					return Promise.reject(err)
+				}
+			)
+			.catch((err) => {
+				return Promise.reject(err)
+			})
+
+	}
+
+}
diff --git a/src/services/ShowApi.js b/src/services/ShowApi.js
new file mode 100644
index 0000000000000000000000000000000000000000..65d5f1b0d609acf38ab4a2729055df761d5c6513
--- /dev/null
+++ b/src/services/ShowApi.js
@@ -0,0 +1,57 @@
+/*
+ * @copyright Copyright (c) 2020 Jonas Heinrich <onny@project-insanity.org>
+ *
+ * @author Jonas Heinrich <onny@project-insanity.org>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import axios from '@nextcloud/axios'
+import { generateUrl } from '@nextcloud/router'
+
+const requesttoken = axios.defaults.headers.requesttoken
+
+export class ShowApi {
+
+	url(url) {
+		url = `/apps/podcast/api${url}`
+		return generateUrl(url)
+	}
+
+	addShow(show) {
+		show = {
+			id: show.id,
+			imgurl: show.smallImageURL,
+			title: show.title,
+			author: show.author,
+		}
+		axios.defaults.headers.requesttoken = requesttoken
+		return axios.post(this.url('/shows'), show)
+			.then(
+				(response) => {
+					return Promise.resolve(response.data)
+				},
+				(err) => {
+					return Promise.reject(err)
+				}
+			)
+			.catch((err) => {
+				return Promise.reject(err)
+			})
+	}
+
+}
diff --git a/src/services/player.js b/src/services/player.js
index 2e405dbe6f534b942e396da6d6d83b0c18c33309..1cb127d4b1ddc308204029c0988784a6972e51ae 100644
--- a/src/services/player.js
+++ b/src/services/player.js
@@ -1,3 +1,25 @@
+/*
+ * @copyright Copyright (c) 2020 Jonas Heinrich <onny@project-insanity.org>
+ *
+ * @author Jonas Heinrich <onny@project-insanity.org>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
 import { Howl, Howler } from 'howler'
 import { showError } from '@nextcloud/dialogs'
 
diff --git a/src/services/podcastApi.js b/src/services/podcastApi.js
index ac0a5c6f45d95359a4e04fd67babe3e4f23d9695..0ad52ba81557b6f06c0b249e631b8d3c54c12da2 100644
--- a/src/services/podcastApi.js
+++ b/src/services/podcastApi.js
@@ -1,3 +1,25 @@
+/*
+ * @copyright Copyright (c) 2020 Jonas Heinrich <onny@project-insanity.org>
+ *
+ * @author Jonas Heinrich <onny@project-insanity.org>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
 const categories = require('../assets/categories.json')
 
 export function getCategoryName(categoryid) {
diff --git a/src/views/Episode.vue b/src/views/Episode.vue
index 2ddec6ac24122b68bfdb477368225fd252d31d6f..925b5fd0c8c74869775fcd53d3b451b297d2d561 100644
--- a/src/views/Episode.vue
+++ b/src/views/Episode.vue
@@ -77,14 +77,15 @@
 
 <script>
 
-import { showError } from '@nextcloud/dialogs'
-import axios from '@nextcloud/axios'
 import EpisodeEmpty from './placeholder/Episode'
 import ContentCollapsable from '../components/ContentCollapsable'
 import MediaHeader from '../components/MediaHeader'
 
 import TimeAgo from 'javascript-time-ago'
 
+import { FyydApi } from './../services/FyydApi'
+const fyydClient = new FyydApi()
+
 const timeAgo = new TimeAgo('en-US')
 
 export default {
@@ -133,14 +134,14 @@ export default {
 					},
 				})
 					.then(function(response) {
-						vm.processPodcast(response.data)
+						vm.processEpisode(response.data)
 					})
 			} catch (error) {
 				showError(t('podcast', 'Could not fetch episode from remote API'))
 			}
 		},
 
-		processPodcast(episode) {
+		processEpisode(episode) {
 			this.episode = episode.data
 			document.title = episode.data.title + ' - Podcast - Nextcloud'
 			this.loading = false
diff --git a/src/views/Show.vue b/src/views/Show.vue
index 629ad33d2046681bc5beab5c4ccf7c8bff953cc6..858bdb990f7debcacfd5e26748fbd2e288744311 100644
--- a/src/views/Show.vue
+++ b/src/views/Show.vue
@@ -37,10 +37,10 @@
 					@doSubscribe="doSubscribe" />
 				<Table
 					v-resize="onResize"
-					:episodes="podcast.episodes"
+					:episodes="episodes"
 					@doPlay="doPlay" />
 				<EmptyContent
-					v-show="nextPage"
+					v-show="page"
 					icon="icon-loading"
 					class="tableLoading" />
 			</div>
@@ -52,17 +52,16 @@
 import Table from '../components/Table'
 import MediaHeader from '../components/MediaHeader'
 
-import { showError } from '@nextcloud/dialogs'
 import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
 
-import axios from '@nextcloud/axios'
-import { generateUrl } from '@nextcloud/router'
-
 import { audioPlayer, doPlay } from '../services/player'
 
 import ShowEmpty from './placeholder/Show'
 
-const requesttoken = axios.defaults.headers.requesttoken
+import { ShowApi } from './../services/ShowApi'
+import { FyydApi } from './../services/FyydApi'
+const fyydClient = new FyydApi()
+const apiClient = new ShowApi()
 
 export default {
 	name: 'Show',
@@ -73,25 +72,24 @@ export default {
 		ShowEmpty,
 	},
 	data: () => ({
-		podcast: {
-			episodes: [],
-		},
-		loading: true,
-		nextPage: null,
+		podcast: {},
+		episodes: [],
+		page: 0,
 		podcastId: null,
 		issubscribed: false,
 	}),
 	computed: {
+		loading() {
+			return this.episodes.length === 0
+		},
 		player() {
 			return this.$store.state.player
 		},
 	},
 	watch: {
 		'$route'(to, from) {
-			this.loading = true
-			this.nextPage = null
 			this.podcastId = to.params.id
-			this.queryPodcast(this.podcastId)
+			this.init()
 		},
 		'player.volume'(newVolume, oldVolume) {
 			if (audioPlayer !== null) {
@@ -107,17 +105,23 @@ export default {
 		},
 	},
 	mounted() {
-		this.podcastId = this.$route.params.id
-		this.queryPodcast(this.podcastId)
-	},
-	created() {
-	  window.addEventListener('scroll', this.handleScroll)
+		this.init()
 	},
 	destroyed() {
 	  window.removeEventListener('scroll', this.handleScroll)
 	},
 	methods: {
 
+		init() {
+			this.podcast = {}
+			this.episodes = []
+			this.page = 0
+			this.podcastId = this.$route.params.id
+			window.addEventListener('scroll', this.handleScroll)
+			this.queryPodcast(this.podcastId)
+			this.queryEpisodes(this.podcastId, this.page)
+		},
+
 		onResize({ width, height }) {
 
 			const rect = document.getElementsByClassName('episodeTable')[0].getBoundingClientRect()
@@ -136,23 +140,8 @@ export default {
 		},
 
 		async doSubscribe() {
-			const podcastShow = {
-				id: this.podcast.id,
-				imgurl: this.podcast.smallImageURL,
-				title: this.podcast.title,
-				author: this.podcast.author,
-			}
-			try {
-				axios.defaults.headers.requesttoken = requesttoken
-				await axios
-					.post(generateUrl('/apps/podcast/api/shows'), podcastShow)
-					.then(response => {
-						this.issubscribed = true
-					})
-			} catch (error) {
-				showError(t('podcast', 'Could not subscribe to podcast'))
-			}
-
+			await apiClient.addShow(this.podcast)
+			this.issubscribed = true
 		},
 
 		preFill() {
@@ -161,42 +150,24 @@ export default {
 		},
 
 		async queryPodcast(podcastId) {
-
-			const vm = this
-
-			const queryURI = this.$apiUrl + '/podcast/episodes'
-			try {
-				delete axios.defaults.headers.requesttoken
-				await axios.get(queryURI, {
-					params: {
-						podcast_id: podcastId,
-						count: 20,
-						page: vm.nextPage,
-					},
-				})
-					.then(function(response) {
-						vm.nextPage = response.data.meta.paging.next_page
-						vm.processPodcast(response.data)
-					})
-			} catch (error) {
-				showError(t('podcast', 'Could not fetch stations from remote API'))
-			}
+			const podcast = await fyydClient.queryPodcast(podcastId)
+			this.processPodcast(podcast.data)
 		},
 
-		processPodcast(podcast) {
-			if (podcast.meta.paging.page === podcast.meta.paging.last_page) {
-				this.nextPage = false
+		async queryEpisodes(podcastId, page) {
+			const episodes = await fyydClient.queryEpisodes(podcastId, page)
+			this.episodes = this.episodes.concat(episodes.data.episodes)
+			if (episodes.meta.paging.next_page === null) {
+				this.page = null
 				window.removeEventListener('scroll', this.handleScroll)
 			} else {
-				this.nextPage = podcast.meta.paging.next_page
+				this.page += 1
 			}
-			if (podcast.meta.paging.page === 0) {
-				this.podcast = podcast.data
-			} else {
-				this.podcast.episodes = this.podcast.episodes.concat(podcast.data.episodes)
-			}
-			document.title = podcast.data.title + ' - Podcast - Nextcloud'
-			this.loading = false
+		},
+
+		processPodcast(podcast) {
+			this.podcast = podcast
+			document.title = podcast.title + ' - Podcast - Nextcloud'
 		},
 
 		/**
@@ -205,7 +176,7 @@ export default {
 		handleScroll() {
 			if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
 				if (!this.episodesLoaded) {
-					this.queryPodcast(this.podcastId)
+					this.queryEpisodes(this.podcastId, this.page)
 				}
 			}
 		},