Removing comp subscriptions via API

I’m a Ghost(Pro) user (v5.65.0) having trouble using the API to remove complimentary subscriptions from members. I’m working in a Google Script environment.

Background

I am importing a large (and changing) list of subscribers from an external source. I’m doing this by giving them comp memberships in Ghost. On update, if they are no longer subscribed externally, I want to set their status as free members in hopes that they’ll re-subscribe.

I’m creating the members and their comp subscriptions just fine with the API. I’m also able to delete expired members fine with the API.

Issue

But I can’t remove the comp subscription.

I’ve been trying to PUT an existing member by setting "comped" : false and also doing that along with "status" : "free", but there is no change; when I GET the member to verify, they are still comped, and their status is still that of someone with paid access.

I’ve also tried setting the member.subscriptions to [] to remove the subscription, and setting member.subscriptions[0].status = "canceled". These do not work either.

I can delete the members and re-create them as free members, but then I’d lose the information on their email opens and link clicks, which I’d rather maintain.

Code

function testRemoveCompSubscription() {
  let authorization = getGhostAuthorization();
  let id = MEMBER_ID;

  let member = getGhostMember(id, authorization);

  // I've tried various combinations of the following four lines,
  // to no avail.
  member.status = "free";
  member.comped = false;
  member.subscriptions[0].status = "canceled";
  // member.subscriptions = [];

  delete member.note; // I get a 422 if I don't include this.
  updateGhostMember(member, authorization);
}

function updateGhostMember(member, authorization = false) {
  if (!authorization) {
    authorization = getGhostAuthorization();
  }

  let options = {
    'method': 'PUT',
    'headers': {
      'Authorization': authorization,
    },
    'muteHttpExceptions': true,
    'contentType': 'application/json',
    'payload': JSON.stringify({
      "members": [
         member
      ]
    }),
  }
  var response = UrlFetchApp.fetch(`https://${admin_domain}/ghost/api/admin/members/${member.id}`, options);
  console.log(response.getResponseCode());
  if (response.getResponseCode() != 200) {
    console.log(response.getContentText());
  }
  var jsResponse = JSON.parse(response.getContentText()).members[0];
  console.log('Member: \n ' + JSON.stringify(jsResponse,null,2));
}

Current results

When I do this with a member with a complimentary subscription, I get back the following for the member. See three comments for changes that are not being made. I have tested changing the member’s name, which does work, so I don’t think there’s something generally wrong with my API use.

Member: 
 {
  "id": ——,
  "uuid": ——,
  "email": ——,
  "name": ——,
  "note": null,
  "geolocation": null,
  "subscribed": true,
  "created_at": "2023-09-21T22:41:17.000Z",
  "updated_at": "2023-09-27T14:46:17.000Z",
  "labels": [],
  "subscriptions": [
    {
      "id": "",
      "tier": {
        "id": ——,
        "name": ——,
        "slug": "default-product",
        "active": true,
        "welcome_page_url": null,
        "visibility": "public",
        "trial_days": 7,
        "description": null,
        "type": "paid",
        "currency": "USD",
        "monthly_price": 1500,
        "yearly_price": 15000,
        "created_at": "2023-07-17T21:50:31.000Z",
        "updated_at": "2023-09-21T22:43:06.000Z",
        "monthly_price_id": null,
        "yearly_price_id": null,
        "expiry_at": null
      },
      "customer": {
        "id": "",
        "name": ——,
        "email": ——,
      },
      "plan": {
        "id": "",
        "nickname": "Complimentary",
        "interval": "year",
        "currency": "USD",
        "amount": 0
      },
      "status": "active", // status not changed to "canceled"
      "start_date": "2023-09-21T22:41:23.000Z",
      "default_payment_card_last4": "****",
      "cancel_at_period_end": false,
      "cancellation_reason": null,
      "current_period_end": null,
      "price": {
        "id": "",
        "price_id": "",
        "nickname": "Complimentary",
        "amount": 0,
        "interval": "year",
        "type": "recurring",
        "currency": "USD",
        "tier": {
          "id": "",
          "tier_id": ——.
        }
      },
      "offer": null
    }
  ],
  "avatar_image": ——,
  "comped": true, // comped remains true
  "email_count": 0,
  "email_opened_count": 0,
  "email_open_rate": null,
  "status": "comped", // status not updated to "free"
  "last_seen_at": "2023-09-27T14:25:14.000Z",
  "attribution": {
    "id": null,
    "type": null,
    "url": null,
    "title": null,
    "referrer_source": "Created manually",
    "referrer_medium": "Ghost Admin",
    "referrer_url": null
  },
  "tiers": [
    {
      "id": ——,
      "name": ——,
      "slug": "default-product",
      "active": true,
      "welcome_page_url": null,
      "visibility": "public",
      "trial_days": 7,
      "description": null,
      "type": "paid",
      "currency": "USD",
      "monthly_price": 1500,
      "yearly_price": 15000,
      "created_at": "2023-07-17T21:50:31.000Z",
      "updated_at": "2023-09-21T22:43:06.000Z",
      "monthly_price_id": null,
      "yearly_price_id": null,
      "expiry_at": null
    }
  ],
  "email_suppression": {
    "suppressed": false,
    "info": null
  },
  "newsletters": [
    {
      "id": ——,
      "name": "Daily Briefing",
      "description": null,
      "status": "active"
    },
    {
      "id": ——,
      "name": "Weekly Roundup",
      "description": null,
      "status": "active"
    }
  ]
}

Any advice?

Yep. My perpetual API-wrangling advice. Do the acting in the admin panel and watch the network calls. In this case, here’s what I saw when I chose ‘remove complimentary subscription’ from a user:

endpoint: https://cathys-second-demo-site.ghost.io/ghost/api/admin/members/64bd3bf3f178b200013ea5f4/
PUT
payload: 
{"members":[{"id":"64bd3bf3f178b200013ea5f4","email":"catsarisky+testportal@gmail.com","tiers":[]}]}

So it looks like you need to pass an empty array for tiers to wipe out the comped tier. (If the user is somehow in two tiers, you’d want to remove the comped one from the list. This can happen when a user upgrades/downgrades before a plan period ends.)

1 Like

Thank you so much for this solution — I’ll follow your advice on watching what the admin panel is doing!