Ghost's RESTful Content API delivers published content to the world and can be accessed in a read-only manner by any client to render in a website, app or other embedded media.

Access control is managed via an API key, and even the most complex filters are made simple with our SDK. The Content API is designed to be fully cachable, meaning you can fetch data as often as you like without limitation.


API Clients

JavaScript Client Library

We've developed an API client for JavaScript, that will allow you to quickly and easily interact with the Content API. The client is an advanced wrapper on top of our REST API - everything that can be done with the Content API can be done using the client, with no need to deal with the details of authentication or the request & response format.

Handlebars

You can upgrade your Ghost theme to use the v2 Content API by specifying the ghost-api version in the engines field of your package.json. See the handlebars reference for an example.


Authentication

Url

https://{admin_domain}

Your admin domain can be different to your main domain. Using the correct domain and protocol are critical to getting consistent behaviour, particularly when dealing with CORS in the browser. All Ghost(Pro) blogs have a *.ghost.io domain as their admin domain and require https.

Path & Version

/ghost/api/{version}/content/

Each API is prefixed with the same path, followed by a specific version. Version strings are required and always start with v. The api versioning guide explains the current available versions and stability index.

Key

?key={key}

Content API keys are provided via a query parameter in the url. These keys are safe for use in browsers and other insecure environments, as they only ever provide access to public data. Sites in private mode should consider where they share any keys they create.

The Content API URL and key can be obtained by creating a new Custom Integration under the Integrations screen in Ghost Admin.

Get a Ghost Content API key

Working Example

cURL example
# Real endpoint - copy and paste to see!
curl "https://demo.ghost.io/ghost/api/v2/content/posts/?key=22444f78447824223cefc48062"

Endpoints

The Content API provides access to Posts, Pages, Tags, Authors and Settings. All endpoints return JSON.

Verb Path Method
GET /posts/ Browse posts
GET /posts/{id}/ Read a post by ID
GET /posts/slug/{slug}/ Read a post by slug
GET /authors/ Browse authors
GET /authors/{id}/ Read an author by ID
GET /authors/slug/{slug}/ Read a author by slug
GET /tags/ Browse tags
GET /tags/{id}/ Read a tag by ID
GET /tags/slug/{slug}/ Read a tag by slug
GET /pages/ Browse pages
GET /pages/{id}/ Read a page by ID
GET /pages/slug/{slug}/ Read a page by slug
GET /settings/ Browse settings

The Content API supports two types of request: Browse and Read. Browse endpoints allow you to fetch lists of resources, whereas Read endpoints allow you to fetch a single resource.


Resources

The API will always return valid JSON in the same structure:

{
    "resource_type": [{
        ...
    }],
    "meta": {}
}
  • resource_type: will always match the resource name in the URL. All resources are returned wrapped in an array, with the exception of /site/ and /settings/.
  • meta: contains pagination information for browse requests.

Posts

Posts are the primary resource in a Ghost site. Using the posts endpoint it is possible to get lists of posts filtered by various criteria.

GET /content/posts/
GET /content/posts/{id}/
GET /content/posts/slug/{slug}/

By default, posts are returned in reverse chronological order by published date when fetching more than one.

The most common gotcha when fetching posts from the Content API is not using the include parameter to request related data such as tags and authors. By default, the response for a post will not include these:

{
  "posts": [
    {
      "slug": "welcome-short",
      "id": "5c7ece47da174000c0c5c6d7",
      "uuid": "3a033ce7-9e2d-4b3b-a9ef-76887efacc7f",
      "title": "Welcome",
      "html": "<p>👋 Welcome, it's great to have you here.</p>",
      "comment_id": "5c7ece47da174000c0c5c6d7",
      "feature_image": "https://casper.ghost.org/v2.0.0/images/welcome-to-ghost.jpg",
      "featured": false,
      "page": false,
      "meta_title": null,
      "meta_description": null,
      "created_at": "2019-03-05T19:30:15.000+00:00",
      "updated_at": "2019-03-26T19:45:31.000+00:00",
      "published_at": "2012-11-27T15:30:00.000+00:00",
      "custom_excerpt": "Welcome, it's great to have you here.",
      "codeinjection_head": null,
      "codeinjection_foot": null,
      "og_image": null,
      "og_title": null,
      "og_description": null,
      "twitter_image": null,
      "twitter_title": null,
      "twitter_description": null,
      "custom_template": null,
      "canonical_url": null,
      "primary_author": null,
      "primary_tag": null,
      "url": "https://demo.ghost.io/welcome-short/",
      "excerpt": "Welcome, it's great to have you here."
    }
  ]
}

Posts allow you to include authors and tags using ?include=authors,tags, which will add an authors and tags array to the response, as well as both a primary_author and primary_tag object.

Working Example

cURL example
# Real endpoint - copy and paste to see!
curl "https://demo.ghost.io/ghost/api/v2/content/posts/?key=22444f78447824223cefc48062&include=tags,authors"

Returns:

{
  "posts": [
    {
      "slug": "welcome-short",
      "id": "5c7ece47da174000c0c5c6d7",
      "uuid": "3a033ce7-9e2d-4b3b-a9ef-76887efacc7f",
      "title": "Welcome",
      "html": "<p>👋 Welcome, it's great to have you here.</p>",
      "comment_id": "5c7ece47da174000c0c5c6d7",
      "feature_image": "https://casper.ghost.org/v2.0.0/images/welcome-to-ghost.jpg",
      "featured": false,
      "page": false,
      "meta_title": null,
      "meta_description": null,
      "created_at": "2019-03-05T19:30:15.000+00:00",
      "updated_at": "2019-03-26T19:45:31.000+00:00",
      "published_at": "2012-11-27T15:30:00.000+00:00",
      "custom_excerpt": "Welcome, it's great to have you here.",
      "codeinjection_head": null,
      "codeinjection_foot": null,
      "og_image": null,
      "og_title": null,
      "og_description": null,
      "twitter_image": null,
      "twitter_title": null,
      "twitter_description": null,
      "custom_template": null,
      "canonical_url": null,
      "tags": [
        {
          "id": "59799bbd6ebb2f00243a33db",
          "name": "Getting Started",
          "slug": "getting-started",
          "description": null,
          "feature_image": null,
          "visibility": "public",
          "meta_title": null,
          "meta_description": null,
          "url": "https://demo.ghost.io/tag/getting-started/"
        }
      ],
      "authors": [
        {
          "id": "5951f5fca366002ebd5dbef7",
          "name": "Ghost",
          "slug": "ghost",
          "profile_image": "https://demo.ghost.io/content/images/2017/07/ghost-icon.png",
          "cover_image": null,
          "bio": "The professional publishing platform",
          "website": "https://ghost.org",
          "location": null,
          "facebook": "ghost",
          "twitter": "@tryghost",
          "meta_title": null,
          "meta_description": null,
          "url": "https://demo.ghost.io/author/ghost/"
        }
      ],
      "primary_author": {
        "id": "5951f5fca366002ebd5dbef7",
        "name": "Ghost",
        "slug": "ghost",
        "profile_image": "https://demo.ghost.io/content/images/2017/07/ghost-icon.png",
        "cover_image": null,
        "bio": "The professional publishing platform",
        "website": "https://ghost.org",
        "location": null,
        "facebook": "ghost",
        "twitter": "@tryghost",
        "meta_title": null,
        "meta_description": null,
        "url": "https://demo.ghost.io/author/ghost/"
      },
      "primary_tag": {
        "id": "59799bbd6ebb2f00243a33db",
        "name": "Getting Started",
        "slug": "getting-started",
        "description": null,
        "feature_image": null,
        "visibility": "public",
        "meta_title": null,
        "meta_description": null,
        "url": "https://demo.ghost.io/tag/getting-started/"
      },
      "url": "https://demo.ghost.io/welcome-short/",
      "excerpt": "Welcome, it's great to have you here."
    }
  ]
}

Pages

Pages are static resources that are not included in channels or collections on the Ghost front-end. The API will only return pages that were created as resources and will not contain routes created with dynamic routing.

GET /content/pages/
GET /content/pages/{id}/
GET /content/pages/slug/{slug}/

Pages are structured identically to posts. The response object will look the same, only the resource key will be pages. By default, pages are ordered by title when fetching more than one.

Tags

Tags are the primary taxonomy within a Ghost site.

GET /content/tags/
GET /content/tags/{id}/
GET /content/tags/slug/{slug}/

By default, internal tags are always included, use filter=visibility:"public" to limit the response directly or use the tags helper to handle filtering and outputting the response.

Tags that are not associated with a post are not returned. You can supply include=count.posts to retrieve the number of posts associated with a tag.

{
  "tags": [
    {
      "slug": "getting-started",
      "id": "59799bbd6ebb2f00243a33db",
      "name": "Getting Started",
      "description": null,
      "feature_image": null,
      "visibility": "public",
      "meta_title": null,
      "meta_description": null,
      "url": "https://demo.ghost.io/tag/getting-started/"
    }
  ]
}

By default, tags are ordered by name when fetching more than one.

Authors

Authors are a subset of users who have published posts asscoiated with them.

GET /content/authors/
GET /content/authors/{id}/
GET /content/authors/slug/{slug}/

Authors that are not associated with a post are not returned. You can supply include=count.posts to retrieve the number of posts associated with an author.

{
  "authors": [
    {
      "slug": "cameron",
      "id": "5c9a4da453c79000bf19a6f5",
      "name": "Cameron Almeida",
      "profile_image": "https://demo.ghost.io/content/images/2019/03/1c2f492a-a5d0-4d2d-b350-cdcdebc7e413.jpg",
      "cover_image": null,
      "bio": "Editor at large.",
      "website": "https://example.com",
      "location": "Cape Town",
      "facebook": "example",
      "twitter": "@example",
      "meta_title": null,
      "meta_description": null,
      "count": {
        "posts": 1
      },
      "url": "https://demo.ghost.io/author/cameron/"
    }
  ]
}

Settings

Settings contains the global settings for a site.

GET /content/settings/

The settings endpoint is a special case. You will receive a single object, rather than an array. This endpoint doesn't accept any query parameters.

{
  "settings": {
    "title": "Ghost",
    "description": "The professional publishing platform",
    "logo": "https://demo.ghost.io/content/images/2014/09/Ghost-Transparent-for-DARK-BG.png",
    "icon": "https://demo.ghost.io/content/images/2017/07/favicon.png",
    "cover_image": "https://demo.ghost.io/content/images/2017/07/blog-cover.jpg",
    "facebook": "ghost",
    "twitter": "@tryghost",
    "lang": "en",
    "timezone": "Etc/UTC",
    "navigation": [
      {
        "label": "Home",
        "url": "/"
      },
      {
        "label": "About",
        "url": "/about/"
      },
      {
        "label": "Getting Started",
        "url": "/tag/getting-started/"
      },
      {
        "label": "Try Ghost",
        "url": "https://ghost.org"
      }
    ],
    "codeinjection_head": null,
    "codeinjection_foot": null
  }
}


Parameters

Query parameters provide fine-grained control over responses. All endpoints accept include and fields. Browse endpoints additionally accept filter, limit, page and order.

The values provided as query parameters MUST be url encoded when used directly. The client libraries will handle this for you.

Include

Tells the API to return additional data related to the resource you have requested. The following includes are available:

  • Posts & Pages: authors, tags
  • Authors: count.posts
  • Tags: count.posts

Includes can be combined with a comma, e.g. &include=authors,tags.

For posts and pages:

  • &include=authors will add "authors": [{...},] and "primary_author": {...}
  • &include=tags will add "tags": [{...},] and "primary_tag": {...}

For authors and tags:

  • &include=count.posts will add "count": {"posts": 7} to the response.

Fields

Limit the fields returned in the response object. Useful for optimising queries, but does not play well with include.

E.g. for posts &fields=title,url would return:

{"posts": [{
    "id": "5b7ada404f87d200b5b1f9c8",
    "title": "Welcome to Ghost",
    "url": "https://demo.ghost.io/welcome/"
}]}

Formats

(Posts and Pages only)

By default, only html is returned, however each post and page in Ghost has 2 available formats: html and plaintext.

  • &formats=html,plaintext will additionally return the plaintext format.

Filter

(Browse requests only)

Apply fine-grained filters to target specific data.

  • &filter=featured:true on posts, would return only those marked featured.
  • &filter=tag:getting-started on posts, would return those with the tag slug that matches getting-started.

The possibilities are extensive! Query strings are explained in detail in the filtering section.

Limit

(Browse requests only)

By default, only 15 records are returned at once.

  • &limit=5 would return only 5 records.
  • &limit=all will return all records - use carefully!

Page

(Browse requests only)

By default, the first 15 records are returned.

  • &page=2 will return the second set of 15 records.

Order

(Browse requests only)

Different resources have a different default sort order:

  • Posts: published_at DESC (newest post first)
  • Pages: title ASC (alphabetically by title)
  • Tags: name ASC (alphabetically by name)
  • Authors: name ASC (alphabetically by name)

The syntax for modifying this follows SQL order by syntax:

  • &order=published_at%20asc would return posts with the newest post last

Filtering

Ghost uses a query language called NQL to allow filtering API results. You can filter any field or included field using matches, greater/less than or negation, as well as combining with and/or. NQL doesn't yet support 'like' or partial matches.

Filter strings must be URL encoded. The {{get}} helper and client libraries handle this for you.

At it's most simple, filtering works the same as in GMail, GitHub or Slack - you provide a field and a value, separated by a colon.

Syntax Reference

Filter Expressions

A filter expression is a string which provides the property, operator and value in the form property:operatorvalue:

  • property - a path representing the field to filter on
  • : - separator between property and an operator-value expression
  • operator (optional) - how to compare values (: on its own is roughly =)
  • value - the value to match against

Property

Matches: [a-zA-Z_][a-zA-Z0-9_.]

  • can contain only alpha-numeric characters and _
  • cannot contain whitespace
  • must start with a letter
  • supports . separated paths, E.g. authors.slug or posts.count
  • is always lowercase, but accepts and converts uppercase

Value

Can be one of the following

  • null
  • true
  • false
  • a number (integer)
  • a literal

    • Any character string which follows these rules:
    • Cannot start with - but may contain it
    • Cannot contain any of these symbols: '"+,()><=[] unless they are escaped
    • Cannot contain whitespace
  • a string

    • ' string here ' Any character except a single or double quote surrounded by single quotes
    • Single or Double quote MUST *be escaped
    • Can contain whitespace
    • A string can contain a date any format that can be understood by new Date()

Operators

  • - - not
  • > - greater than
  • >= - greater than or equals
  • < - less than
  • <= - less than or equals
  • [ value, value, ... ] - "in" group, can be negated with -

Combinations

  • + - represents and
  • , - represents or
  • ( filter expression ) - overrides operator precedence

Strings vs Literals

Most of the time, there's no need to put quotes around strings when building filters in Ghost. If you filter based on slugs, slugs are always compatible with literals. However, in some cases you may need to use a string that contains one of the other characters used in the filter syntax, e.g. dates & times contain:. Use single-quotes for these.


Pagination

All browse endpoints are paginated, returning 15 records by default. You can use the page and limit parameters to move through the pages of records. The response object contains a meta.pagination key with information on the current location within the records:

"meta":{
    "pagination":{
      "page":1,
      "limit":2,
      "pages":1,
      "total":1,
      "next":null,
      "prev":null
    }
  }

Errors

The Content API will generate errors for the following cases:

  • Status 400: Badly formed queries e.g. filter parameters that are not correctly encoded
  • Status 401: Authentication failures e.g. unrecognised keys
  • Status 404: Unknown resources e.g. data which is not public
  • Status 500: Server errors e.g. where something has gone

Errors are also formatted in JSON, as an array of error objects. The HTTP status code of the response along with the errorType property indicate the type of error.

The message field is designed to provide clarity on what exactly has gone wrong.

{"errors": [{
    "message": "Unknown Content API Key",
    "errorType": "UnauthorizedError"
}]}

Versioning

The v2 Content API is stable as of Ghost 2.10.0. See the stability index for full details of the API versions. You can disable the v0.1 Public API in the labs section of your admin panel.