Ghost Admin API: create members

I’d like to be able to create, udpate, destroy members for Ghost. The current node doesn’t allow for this, Zapier offers this option. I’m doing everything else via n8n and would love to do this as well.

Ideas?

I wonder which endpoints they are using. Just checked the Admin API and the endpoint /users is experimental. There is no endpoint called members, so I would assume /users is used to create members? If that is the case, there seems to be no information about it.

The Content API has the author endpoint. Is it possible that author is what you are looking for?

Uh too bad, not sure I can do that on my own right now. I assumed because Zapier has them, it might be possible or worth an integration.

Thanks!

Should not be an issue to add, but since it’s experimental it’s subject to change. You can use the HTTP node to use such endpoints. Did you try that?

Much love @RicardoE105 - if you guide my mind I will most definitely try that right away.

1 Like

The request URl should be a POST to ${yourbaseurl}/ghost/api/canary/admin/members/?include=labels,email_recipients

The body should follow the structure below. I do not think all parameters are needed, but you can test it. I would only send the name, email, and a note.

{
   "members":[
      {
         "name":"xzxzxz",
         "email":"[email protected]",
         "note":"asasasasasasas",
         "subscriptions":[
            
         ],
         "subscribed":true,
         "comped":false,
         "email_count":0,
         "email_opened_count":0,
         "email_open_rate":null,
         "products":[
            
         ],
         "labels":[
            
         ]
      }
   ]
}

Regarding the authentication, follow the instructions below, and when you have the token, simply include it in the header.

1 Like

This is brilliant. I think I’m still missing a step in my mind - how do I set the structure for the file I’m going to send? Do I have to work myself backwards with a Set node and include expressions for each (labels, products, subscriptions, members)?

Then, in the http node, how do I set that data to send, somehow still trying to figure that out.

Thank you very much

The data goes into the body. You can add them using key-value pairs. However, considering the data is an array of members, I would create the members with a Set or Function node and then reference it in the HTTP node. Check the example below.

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "return [\n  {\n    json: {\n      members: [\n      {\n                 \"name\":\"xzxzxz\",\n         \"email\":\"[email protected]\",\n         \"note\":\"asasasasasasas\",\n         \"subscribed\": true,\n      }\n      ]\n    }\n  },\n]"
      },
      "name": "Sample Data",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        510,
        300
      ]
    },
    {
      "parameters": {
        "requestMethod": "POST",
        "url": "your url",
        "options": {},
        "bodyParametersUi": {
          "parameter": [
            {
              "name": "members",
              "value": "={{ $node[\"Sample Data\"].json[\"members\"] }}"
            }
          ]
        }
      },
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        760,
        300
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Sample Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample Data": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Thank you very much that should get me there. Wonderful.

Ricardo, I apologise for abusing your kindness, but I am indeed failing again at the token generation event for the authentication.

The npm tools I’m finding help generating a token for an hour, for instance. Will I have to generate this token only once, every time the http request runs…or?

I’m absolutely struggling with the guidance provided on the Ghost documentation:


Admin API keys are made up of an id and secret, separated by a colon. These values are used separately to get a signed JWT token, which is used in the Authorization header of the request:

curl -H "Authorization: Ghost $token" https://{admin_domain}/ghost/api/{version}/admin/{resource}/

The Admin API JavaScript client handles all the technical details of generating a JWT from an admin API key, meaning you only have to provide your url, version and key to start making requests.


What I see there is the authorisation flow, correct? It does say however it handles the tehcnical details of generating a JWT. Again, I apologise, maybe I’m not seeing straight, but likely I’ll have to get some help instead and come back to this. Cheers

I’m always happy to help.

Yes, we have to generate it with a function node, everything time before making the request.

Yes, since we are not using either the Javascript SDK or the node (The node does that behind the scenes), you need to create the JWT using a function node. Check the example below. Just be aware that in order to use the jsonwebtoken library in the function node, you need to set the env variable NODE_FUNCTION_ALLOW_EXTERNAL with the value jsonwebtoken. Assuming that you are using either macOS or Linux you can set the env variable like this: export NODE_FUNCTION_ALLOW_EXTERNAL=jsonwebtoken

const jwt = require('jsonwebtoken');

const version = 'v2';
const apiKey = 'your:apikey';
		// Create the token (including decoding secret)
const [id, secret] = apiKey.split(':');

const token = jwt.sign({}, Buffer.from(secret, 'hex'), {
	keyid: id,
	algorithm: 'HS256',
	expiresIn: '5m',
	audience: `/${version}/admin/`,
});

return [
  {
    json: {
      token,
    }
  }
]

Amazing, but in other words I cannot really do it on my n8n cloud version? Last I checked this was a feature that wasn’t really available for the hosted version?

Ahhh, I did not know you were using n8n cloud. There might be a way of creating the token without a dependency on JS. But that is probably a pain. The easiest way of doing it is by adding the functionality to the node. There are other ways, like creating a small external service that makes the JWT and exposes it via API. But, in terms of efforts, I would add it to the node.

Good day,

The easiest way of doing it is by adding the functionality to the node. There are other ways, like creating a small external service that makes the JWT and exposes it via API. But, in terms of efforts, I would add it to the node.

just to make sure I’m not misreading it: = I need that self-hosted version for that.

To be able to import packages in the function node, you need to use the self-hosted version. If we add the functionality to the node (members), you can use it in the n8n cloud.

Ok great. I’ll try the self-hosted version for this then. Thanks for now!

Riccardo, this all works brilliantly.

I’ve installed an additional self-hosted instance and I get all the way to the token authentication without issues: it returns the token.

However, not entirely sure how to pass the token as I keep getting this error here:

"reason": {

"name": "StatusCodeError",

"statusCode": 403,

"message": "403 - {"errors":[{"message":"Authorization failed","context":"Unable to determine the authenticated user or integration. Check that cookies are being passed through if using session authentication.","type":"NoPermissionError","details":null,"property":null,"help":null,"code":null,"id":"c90f8700-4b8e-11ec-a4b0-312aea2e4fa2"}]}",

"error": { (...)

Do we actually need to add an additional steps for the cookies there, or am I failing at another step?

Thank you

You have to send it in the header. The key needs to be set to Authorization and the value to Ghost ${token}. In the value, you use expressions to reference the token. Let me know if that helps.

Yes thank you, exactly what I tried also but it returns an invalid token error, even though it gets wonderfully generated by the function snippet supplied above. Here’s the error:

(...)  
"headers": {
    "Authorization": "Ghost $eyJhbGciOiJI(...)9.eyJpYX(...)uLyJ9.rxO(....)aoeaIo_n2uiy_S7_kN(...)E",
    "accept": "application/json,text/*;q=0.99"
  },
(...)

and

    "error": {
  "errors": [
    {
      "message": "Invalid token",
      "context": null,
      "type": "BadRequestError",
      "details": null,
      "property": null,
      "help": null,
      "code": "INVALID_JWT",
      "id": "3(...)0-4c37-1(...)2"
    }
  ]
},

I think I’m going by the book, but it seems something is still off with the authentication.