API

The API responses adheres to json:api standards. All endpoints are available without throttling.

Get public data

Prepend all requests with https://panoptikum.social/jsonapi.

All instances in “relationships” can be found with a link and a minimal set of attributes in “included”. You can basically explore the whole API world of Panoptikum by starting at /categories and following links from there recursively.

path method purpose included
/categories GET tree of categories children = subcategories
/categories/:id GET single category children, (paginated) podcasts, parent ; podcasts ordered by last episode publishing date descending nulls last
/podcasts GET list of podcast, paginated, ordered by insertion date descending categories, languages, engagements & contributors (= personas)
/podcasts/:id GET single podcast (paginated) episodes + enclosures, subscription_count, engagements, recommendations, categories, contributors (= personas, follower_count, likes_count, languages, feeds ; ETag
/podcasts/most_liked GET 10 most liked podcasts ordered by like count descending -
/podcasts/most_subscribed GET 10 most subscribed podcasts ordered by subscription count descending -
/podcasts/last_updated GET last updated podcasts paginated
/languages GET list of languages -
/languages/:id GET single language -
/engagements/:id GET engagement persona, podcast
/personas/ GET list of personas redirect
/personas/:id GET single persona redirect, engagements & podcasts, (paginated) gigs & episodes, delegates
/users/ GET list of users -
/users/:id GET single user users_i_like, categories_i_like, depending on preferences: podcasts_i_subscribed, podcasts_i_follow
/recommendations GET list of recommendations, paginated, order by insertion date descending episode, podcast, chapter, user
/recommendations/:id GET single recommendation user-name, one of: podcast, episode, chapter
/recommendations/random GET random recommendation episode, podcast, category;
the episode belongs to that podcast, the podcast belongs to that category
/feeds/:id GET single feed podcast, alternate_feed
/alternate_feeds/:id GET single alternate feed feed
/episodes GET list of episodes, paginated, ordered by publishing_date descending podcast, gigs & contributors (= personas)
/episodes/:id GET single episode podcast, chapters, enclosures, recommendations, gigs & contributors (= personas), like_count
/chapters/:id GET single chapter episode, recommendations, like_count
/enclosure/:id GET single enclosure episode
/gigs/:id GET single gig persona, episode
/subscription/:id GET single subscription;
only shown, when user shares
user, podcast
/follow/:id GET single follow;
only shown, when user shares
category, follower, user, podcast, persona
/like/:id GET single like category, enjoyer, user, podcast, chapter, persona, episode

The ETag is is response header, containing the MD5 hash of the JSON to be sent. You can make a HEAD request, compare the ETag response header to the last one you have stored and know, that there isn’t anything new, if the ETag has not changed. If there are changes, go on with the GET request and get the full JSON.

Pagination

Podcasts within a category, gigs for a persona and episodes for a podcast are paginated:

path method purpose included
/search?filter[:type]=:term GET redirects to the appropriate route below type can be either persona,category,podcast or episode
/categories/search?filter=:term GET searches for categories with :term children = subcategories
/podcasts/search?filter=:term GET searches for podcasts with :term categories, languages, engagements & contributors (= personas)
/episodes/search?filter=:term GET searches for episodes with :term podcast, gigs & contributors (= personas)
/personas/search?filter=:term GET searches for personas with :term redirect (= persona) , delegates (= personas), podcasts

User generation

You can use any user generated in the web interface for full API access, but you can also create users via the API itself.

path method params (* … required)
/users POST username* (>3), password* (>6), password_confirmation* , email* (>5, unique), name* (>3),
podcaster (boolean), share_subscriptions (boolean), share_follows (boolean)

Login

To be able to post data and receive your own private data, it’s necessary to post a token along side. To get a token, post username and password to recieve a token back, that’s valid for one hour. The token will also be provided in the header token.

Typically, you would call something a login, though it’s only getting you a new token, we provide both routes as endpoints, take the one with the name you prefer. There is no session or cookie connected to that, so there is also no logout.

path method params (* … required) purpose included
/login or /get_token POST username* , password* get a token token including validity data and user id

One user can access the application via the api from different devices simuntaneously. They are not device specific.

Only users with confirmed email address can aquire tokens. Please make sure, that you click the email confirmation link in the email that is sent to you after login via the web interface. There will be an approriate error message telling you to do so, if you request a token and your email is not confirmed yet.

Example: Get a token

If working with tokens is something new for you, an example might help to set the expectations right.

Request:

curl --data "username=janedoe&password=secret" https://panoptikum.social/jsonapi/login

Response:

{"jsonapi":
  {"version":"1.0"},
  "data":{"type":"session-api",
          "id":"6",
          "attributes":{"valid-until":"2017-09-08T16:46:08.393642Z",
                        "valid-for":"1 hour",
                        "token":"SFMyNTY.g3QAAAACdwRk....yNXj3wvSs9a9Ps5wO6yrY",
                        "created-at":"2017-09-08T15:46:08.393630Z"}}}

A note on security

You get the token in a transport encrypted response via https. If you disclose the token, you basically disclose your password. This is the case because the token owner can change the password within the next hour, no matter if you recreated another token. So don’t store it unencrypted.

Post data and get private data

For all these end points it is necessary to provide a valid token. Unless you do so, you will receive appropriate feedback, if the token is invalid, out of date or has not been provided at all.

The token has to be provided as “Authorization: Bearer :token” header, where you exchange the variable :token with the token, that you aquired before as described above.

Why didn’d we choose to support a token provided as a parameter, you might ask? We don’t want to log tokens, which we would have done for get requests.

Requests are done with or for the user identified by the token.

Post, patch and put requests have all params on top level, as we don’t have nested resources available here, see documentation for the idividual requests below. E.g., instead of json:api style

PATCH /pan/personas/42

data: {type: "personas",
       id: "42",
       attributes: {name: "My new name",
                    uri: "https://uri/to/me"}}

you would simply post in a more REST style:

PATCH /pan/personas/42

name: "My new name",
uri: "https://uri/to/me"
path method params (* … required) purpose included
/pan/likes/toggle POST category_id or podcast_id
or episode_id or chapter_id
or user_id or persona_id
like or unlike a category, podcast, episode, chapter, user or persona deleted: true / false
created: true / false
/pan/follows/toggle POST category_id or podcast_id or user_id or persona_id follow or unfollow a category, podcast, user or persona deleted: true / false
created: true / false
/pan/subscriptions/toggle POST podcast_id* follow or unfollow a podcast deleted: true / false
created: true / false
/pan/gigs/toggle POST episode_id* , persona_id* proclaim a gig (= episode contribution) or retract it;
only possible for owned personas
deleted: true / false
created: true / false
/pan/recommedations/my GET - list of my recommendations podcast
/pan/podcasts/:id/trigger_update GET - triggers immediate metadata update for a single podcast, available once per hour (paginated) episodes, subscription_count, engagements, recommendations, categories, contributors (= personas, follower_count, likes_count, languages, feeds
/pan/podcasts/:id/trigger_episode_update GET - triggers immediate check for new episodes, available every 30 minutes (paginated) episodes, subscription_count, engagements, recommendations, categories, contributors (= personas, follower_count, likes_count, languages, feeds
/pan/podcasts/i_follow GET - list of podcasts i follow categories, engagements, contributors and languages
/pan/podcasts/i_like GET - list of podcasts i like categories, engagements, contributors and languages
/pan/podcasts/i_subscribed GET - list of podcasts i subscribed to categories, engagements, contributors and languages
/pan/podcasts/also_listened_to GET - list of 10 podcasts that listeners of the podcasts you listen to, also listen to categories, engagements, contributors and languages
/pan/podcasts/also_liked GET - list of 10 podcasts that listeners of the podcasts you like, also like categories, engagements, contributors and languages
/pan/categories/my GET - list of 10 categories, the podcasts i subscribed to are assigned to children, parent
/pan/recommendations POST (podcast_id or episode_id or chapter_id), comment* creates a recommendation for a podcast, an episode or a chapter and returns it podcast or episode or chapter ; user
/pan/like_all_subscribed_podcasts POST - creates likes for all subscribed podcasts not liked yet, returns all likes for podcasts podcast, user
/pan/follow_all_subscribed_podcasts POST - creates follows for all subscribed podcasts not followed yet, returns all follows for podcasts podcast, user
/pan/opmls/ GET - list of OPML files you uploaded user
/pan/opmls/:id GET - single OPML file user
/pan/opmls POST upload* (file) upload an OPML file, see curl example below user
/pan/opmls/:id/import GET - imports feeds from OPML file into feed backlog user
/pan/opmls/:id DELETE - delete single OPML file; users can only delete OPML files they uploaded user, deleted
/pan/feed_backlogs/:id GET - single feed in backlog user
/pan/feed_backlogs POST url* post a feed_url to the feed backlog a.k.a. suggest a podcast user
/pan/users/my GET - my user profile personas
/pan/update_password PATCH or PUT password* , password_confirmation* update password; server validates identicality and length > 5 personas
/pan/update_user PATCH or PUT email* (>5 unique), name* (>3) , username* (>3 unique), podcaster (boolean), share_follows (boolean), paper_bill (boolean), share_subscriptions (boolean), billing_address(preformatted) updates account data personas
/pan/update_persona PATCH or PUT name* , uri* update persona with user account redirect, engagements & podcasts, (paginated) gigs & episodes, delegates
/pro/update_persona PATCH or PUT pid* (unique), name* , uri* , email, image_url, image_title, description (gets used as header), long_description (markdown) update persona (pro user account) redirect, engagements & podcasts, (paginated) gigs & episodes, delegates
/pan/delegations/:id GET - single delegation, only returned, if persona and delegate manfestate in user persona, delegate
/pro/delegations/toggle POST persona_id* , delegate_id* toggles and returns delegation; persona and delegate need to manifest in user persona, delegate
/pro/personas/:id/redirect POST target_id* redirects persona to persona with id = target_id redirect, engagements & podcasts, (paginated) gigs & episodes, delegates
/pro/personas/:id/cancel_redirect POST - cancels a persona redirect redirect, engagements & podcasts, (paginated) gigs & episodes, delegates
/pan/personas/:id/claim POST - claims a persona detail: “An Email to the Persona has been sent”

Example: Like a podcast

Here is an example how you would like (or unlike, it you liked it before) a podcast.

Request:

curl --data "username=informatom&password=secret" https://panoptikum.social/jsonapi/login

Pick up the token from the response and provide it in the next request as a header.

Request:

curl --data "podcast_id=104" -H "Authorization: Bearer SFMyNTY.g3QAAAACdwRk....yNXj3wvSs9a9Ps5wO6yrY" https://panoptikum.social/jsonapi/pan/likes/toggle

Example: Upload a file

To send a file from the local file system with the parameter upload, a curl request like this can be used:

curl -v -H "Authorization: Bearer :token" -F "upload=@/full/path/to/opml.xml" https://panoptikum.social/jsonapi/pan/opmls

Example: Delete a file

To delete a file via curl, you have to set the request method via the X flag:

curl -v -X "DELETE" -H "Authorization: Bearer :token" http://localhost:4000/jsonapi/pan/opmls/:id