Protected Content Syndication - Use an RSS/API?

Is it possible to syndicate protected content through RSS or API?

Problem - no direct way to syndicate protected content as RSS & content API only picks up public information (aka free tier content)

I’m trying to syndicate protected content together with other private blogs automatically. One way i’ve found is to use serverless function to transmit content but requires and admin api key… but I can’t give out an Admin API key without risking my current subscribers data.

Any ways to create this channel?

You can make an RSS feed of the public portion of the posts, with a custom hbs file. I’m not sure if that fits your needs or not? Users would have to come to the site yo read the full thing.

i have to distribute the content as a whole as its sold so can’t use this unfortunately

Gotcha. Then you’re going to need a cloudflare worker or something similar, that gets the feed content with the admin api or user credentials and returns an RSS feed. How are you limiting access to that feed?

so what i’ve done is placed the apis and jwts on netlify and got an rss feed deployed from github to pull tags/newsletters (protected).

Not sure if it works as intended… anywhere to check if it pulls only /newsletters and not anything else?

If your api request is correctly formatted including a filter parameter to only get whatever would show up at newsletter, you should be good, but it sounds like you need to create some test content and check!

Tested and parameters are pulling the right tags.

But not the full content.

I’ve got the content api key in the github code… and i think thats the problem.

Tried replacing the content api keys with admin keys and it didn’t work.

Any ideas?

The content API is the problem. You’re going to have to use the Admin API to get full access to posts.

I highly recommend that you use the Admin API SDK - it makes things very easy, and yes, it works on Netlify. It looks something like this:

const GhostAdminAPI = require('@tryghost/admin-api');

const handler = async (event) => {
  const api = new GhostAdminAPI({
    url: process.env.GHOST_API_URL,
    version: "v5.0",
    key: process.env.GHOST_ADMIN_API_KEY
  });

And then you just call api.posts.browse and pass in a config object with your filters.

Documentation here: Admin API JavaScript Client

(Obviously, don’t embed an admin API key or credentials in the Github code. Use a Netlify env variable, like above.)

Make sure you exclude draft posts, or it’ll grab those too, I think…

Tested again, with the admin api sdk & admin api keys.

RSS feed made and pulling the right content with the right tags, but its still not pulling the whole content.

not sure why it isn’t pulling. heres my code

  const posts = await api.posts.browse({ limit: 'all', include: 'tags,authors', filter: 'tag:newsletters+status:published' });

    posts.forEach((post) => {
        feed.item({
            title: post.title,
            description: post.html,
            url: post.url,
            guid: post.id,
            categories: post.tags.map((tag) => tag.name),
            author: post.authors.map((author) => author.name).join(', '),
            date: post.published_at,
            custom_elements: [
                {'excerpt': post.excerpt},
                {'image': post.feature_image}
            ]

Are you missing the body of the post? If you want the content in HTML format, you need format: html on that api call.

@Cathy_Sarisky What about this pattern?

  1. Using a reverse proxy like Nginx, Proxy a URL like /secret-rss/59e4b1c4a9b5840559b04b20
  2. The Nginx config would set a header for proxying that allow it access the private content.
  3. In Ghost, use routes.yml to point this URL to a RSS feeds template that also shows private content.

In the simplest design, all your subscribers would be trusted share the same secret URL, which could you could change an redistribute periodically.

Oooh, I like that option, for self-hosters. You could just add a fake cookie with the request…

Solved! all that was missing was the formats: [‘html’] and it worked.

heres my full code for refernce for anyone who wants its or improve on it.

const GhostAdminAPI = require('@tryghost/admin-api');
const RSS = require('rss');

const api = new GhostAdminAPI({
  url: process.env.GHOST_API_URL,
  key: process.env.GHOST_ADMIN_API_KEY,
  version: 'v5',
});

exports.handler = function(event, context, callback) {
  api.posts.browse({
    include: 'authors,tags',
    filter: 'tag:newsletters+status:published',
    formats: ['html'],
  }).then(posts => {
    let feed = new RSS({
      title: 'title',
      description: 'description',
      feed_url: 'YourNetlifyFeed,
      site_url: 'YourSite',
      managingEditor: 'webMaster',
      webMaster: 'webMaster',
      language: 'en',
      pubDate: new Date().toLocaleString(),
    });

    posts.forEach(post => {
      feed.item({
        title:  post.title,
        description: post.html,
        url: post.url,
        author: post.primary_author.name,
        date: post.published_at,
        custom_elements: [
          {
            'content:encoded': {
              _cdata: post.html
            },
      });
    });

    callback(null, {
      headers: {
        'Content-Type': 'application/xml',
      },
      statusCode: 200,
      body: feed.xml(),
    });
  }).catch(err => {
    callback(err);
  });
};
2 Likes

nice idea, I’ll try this out, thanks for sharing this :)