CodeFactor, Uploading Images via REST, and the Ghost Admin API v2.0

Full article

I finally wrapped up (for now) a little side project of mine, a C# wrapper around v2.0 of the Ghost RESTful API. I call it GhostSharp, and it's available on NuGet. Check out the source code, or learn more about the API and related tools and libraries in the Ghost docs.

I started writing it early last year when the v0.1 API was out, as an exercise in learning more about the API and honing my C# skills, but it was deprecated in favor of the (much better) v2.0 API. So early this year, I tagged the v0.1 stuff and started rewriting everything to work with 2.0.


Ghost splits their API into two areas of concern, depending on what you'd like to accomplish - the Content API and the Admin API. The former deals with read-only access, allowing consumers to display public data, while the latter is concerned with creating and managing (administrating) that same data.

To begin using it, create a new integration and snag the Content API Key and Admin API Key it generates. Be careful who you share them with, especially the Admin API Key... with that, someone could modify (or delete) most of the data on your site!

Accessing the Content API

To access the Content API, grab the API URL and Content API Key from the "Integrations" page. Once you have those pieces of information, you can access any "public" read-only content.

var ghost = new GhostSharp.GhostContentAPI("", "a6d33f1b95ff17adf0f787a70a");
var settings = ghost.GetSettings();

Console.WriteLine($"Welcome to {settings.Title}: {settings.Description}\r\n");
Console.WriteLine($"Navigation: {string.Join(", ", settings.Navigation.Select(x => x.Label))}");


Welcome to Grant Winney: We learn by doing. We've all got something to contribute.

Navigation: Home, APIs, Lambda, Rasp PI, About Me, CV

Accessing the Admin API

To access the Admin API, grab the API URL and Admin API Key, also from the "Integrations" page. Once you have those pieces of information, you can access "private" content, and create, modify, or even delete a lot of "public" content.

var ghost = new GhostSharp.GhostAdminAPI("", 
var site = ghost.GetSite();

Console.WriteLine($"Welcome to <a href='{site.Url}'>{site.Title}</a>\r\n");
Console.WriteLine($"Running Ghost v{site.Version}");


Welcome to <a href=''>Grant Winney</a>

Running Ghost v2.23


Everything went smoothly, even a little repetitive and boring, until I tackled the last stable Admin API endpoint - uploading images. The docs state that

To upload an image, send a multipart formdata request by providing the 'Content-Type': 'multipart/form-data;' header...

They post an example using curl with the -F option. I didn't try it, but it's consistent with what they're asking for.

curl -X POST -F 'file=@/path/to/images/my-image.jpg' -F 'ref=path/to/images/my-image.jpg' -H "Authorization: 'Ghost $token'" https://{admin_domain}/ghost/api/{version}/admin/images/upload/

But when I tried to post to the endpoint using RestSharp with that header added, the only response I could get back was something about "please select an image". I spent hours researching how to upload an image file, and tried a half-dozen different ways.

var request = new RestRequest("images/upload/", Method.POST);

request.AddHeader("Content-Type", "multipart/form-data");

The solution? Remove the header. ๐Ÿคฌ

var request = new RestRequest("images/upload/", Method.POST);

if (image.FilePath != null)
    request.AddFile("file", image.FilePath, GetMimeType(image.ImageType));
    request.AddFile("file", image.File, image.FileName, GetMimeType(image.ImageType));

request.AddParameter("purpose", image.Purpose.ToString().ToLower());
request.AddParameter("ref", image.Reference);

return Execute<ImageResponse>(request).Images[0];  // adds authentication and execute s the query

I have no idea why it was a problem, and I have little desire to dig into the guts of RestSharp to figure it out. Somehow, that header was messing up the request so that Ghost thought there was no image being uploaded. Maybe I was using the header wrong? Adding the file incorrectly to work with that header? Maybe AddFile() does something behind the scenes that made it all just very very wrong?

Oh well!

What'd I learn?



If you need to get/post fields with lowercase or underscores in them, but you'd like your classes to all be pascal case (or some other format), you might need a way to translate between the two. JsonProperty to the rescue. It assists RestSharp in translating between C# property names and JSON field names.

namespace GhostSharp.Entities
    public class Author
        public string Id { get; set; }

        public string Name { get; set; }

        public string ProfileImage { get; set; }

        public string CoverImage { get; set; }

Travis CI

Take a few minutes to import your projects into it, especially if you have tests. Then you can just keep pushing changes, merrily and giddily ignorant of the consequences, and Travis has your back. Or something like that.


CodeFactor is another awesome service. It monitors your repo too, suggesting code fixes. I had a few dozen of what I'd call "cosmetic" changes - multiple classes in a single file, extra blank lines, etc - but it still felt good to clean things up a bit and see that big green bar.

The Ghost team is very responsive

I asked quite a few questions in their forum, and they were helpful...

Have a question?

Well, if you happen to use it, let me know. I'd love to hear what you're doing with it.

Open an issue if you have a problem, request, or even just a comment (or post below). And if you find something wrong (there's bound to be a few somethings at least), feel free to open a PR if you figure out how to fix it too.


Grant Winney

I write when I've got something to share - a personal project, a solution to a difficult problem, or just an idea. We learn by doing and sharing. We've all got something to contribute.

Comments / Reactions

One of the most enjoyable things about blogging is engaging with and learning from others. Leave a comment below with your questions, comments, or ideas. Let's start a conversation!