Hi, all. My newsletter (Bamboo Weekly), running on Ghost(Pro), has a free tier and a paid tier. On the Ghost(Pro) site, that works like a charm.
But people can also get my newsletter when they subscribe to my online Python+Data course membership. I use Zapier to accomplish this; when someone signs up for a membership at LernerPython.com, Zapier signs the person up for a comped Bamboo Weekly subscription. That is, they don’t have to pay, but they get the benefits of a paid subscription.
That’s all great, until/unless they cancel their membership. I’ve been in touch with the Ghost(Pro) folks, and they say that Zapier only makes it possible to do comped subscriptions for one year. And those subscriptions auto-renew. And there’s no Zapier way to remove someone’s comped status. That’s in part because the subscription is handled by Stripe.
For now, I go to my Ghost dashboard, find the user manually, use the provided link to go to the Stripe subscription, and cancel that. Which is… quite a pain.
I’ve been trying to write a Python program that’ll allow me to enter an e-mail address and then have the person’s subscription changed from comped to free. But it doesn’t seem to work at this point. The API call succeeds, but the comped status remains.
I’m enclosing my code here. I’d like to think that I’m trying to do something relatively simple, but this is really driving me crazy, and eating up a fair amount of time. Any thoughts?
#!/usr/bin/env python3
import json
import requests
import jwt
import time
from datetime import datetime as dt
from io import BytesIO
class GhostAdmin():
def __init__(self, siteName):
self.siteName = siteName
self.site = {'name': 'bambooweekly.com',
'url': 'https://bamboo-weekly.ghost.io/',
'AdminAPIKey': 'ADMIN_KEY_GOES_HERE',
'ContentAPIKey': 'CONTENT_KEY_GOES_HERE'}
self.token = self.create_token()
self.headers = {'Authorization': f'Ghost {self.token}'}
def create_token(self):
key = self.site['AdminAPIKey']
kid, secret = key.split(':')
iat = int(dt.now().timestamp())
header = {'alg': 'HS256', 'typ': 'JWT', 'kid': kid}
payload = {
'iat': iat,
'exp': iat + (5 * 60),
'aud': '/v3/admin/'
}
return jwt.encode(payload, bytes.fromhex(secret), algorithm='HS256', headers=header)
def get_subscriber(self, email):
url = f"{self.site['url']}ghost/api/v3/admin/members/?filter=email:{email}"
result = requests.get(url, headers=self.headers)
if result.ok:
data = json.loads(result.content)
members = data['members']
if members:
return members[0]
return None
def update_subscriber_status(self, member_id, updates):
url = f"{self.site['url']}ghost/api/v3/admin/members/{member_id}/"
data = {"members": [{"id": member_id, **updates}]}
result = requests.put(url, headers=self.headers, json=data)
print(f"API Response Status Code: {result.status_code}")
print(f"API Response Content: {result.content}")
if result.ok:
updated_member = json.loads(result.content)['members'][0]
print(f"Updated member data: {json.dumps(updated_member, indent=2)}")
return updated_member
else:
print(f"Error updating member: {result.content}")
return None
def convert_comped_to_free(self, email):
subscriber = self.get_subscriber(email)
if not subscriber:
print(f"No subscriber found with email: {email}")
return None
if not subscriber['comped']:
print(f"Subscriber {email} is not currently comped. No action needed.")
return subscriber
# Try updating comped status first
updates = {"comped": False}
print(f"Attempting to update subscriber {email} with the following changes: {updates}")
updated_subscriber = self.update_subscriber_status(subscriber['id'], updates)
if updated_subscriber and not updated_subscriber['comped']:
print("Successfully removed comped status.")
else:
print("Failed to remove comped status.")
# Now try updating the status
updates = {"status": "free"}
print(f"Attempting to update subscriber {email} with the following changes: {updates}")
updated_subscriber = self.update_subscriber_status(subscriber['id'], updates)
if updated_subscriber:
if not updated_subscriber['comped'] and updated_subscriber['status'] == 'free':
print(f"Successfully converted {email} from comped to free status.")
else:
print(f"Update call succeeded, but subscriber status did not change as expected.")
print(f"Current status: comped={updated_subscriber['comped']}, status={updated_subscriber['status']}")
return updated_subscriber
else:
print(f"Failed to convert {email} from comped to free status.")
return None
if __name__ == '__main__':
ga = GhostAdmin('bambooweekly.com')
email = input("Enter the email address of the subscriber: ")
subscriber = ga.get_subscriber(email)
if subscriber:
print(f"\nSubscriber information for {email}:")
for field in ['id', 'name', 'email', 'status', 'subscribed', 'comped']:
print(f'{field}: {subscriber.get(field, "N/A")}')
if subscriber['comped']:
convert = input("This subscriber is comped. Do you want to convert them to a free subscription? (y/n): ")
if convert.lower() == 'y':
updated_subscriber = ga.convert_comped_to_free(email)
if updated_subscriber:
print("\nUpdated subscriber information:")
for field in ['id', 'name', 'email', 'status', 'subscribed', 'comped']:
print(f'{field}: {updated_subscriber.get(field, "N/A")}')
else:
print(f"No subscriber found with email: {email}")