Table of contents

Publishing API: Examples

This documentation aims to explain integration steps for a publishing
application to manage content and links within the Publishing API.
Included are request flow concepts and trivial code examples to aid the
developer with integration steps.

The Publishing API provides a set of endpoints aimed at providing client
publishing applications a way to draft and modify content and links, and
then to publish or unpublish them.

Content and links for a shared content item can be modified separately on
different endpoints, these distinct changes will ultimately modify the
downstream representation in the content store.

Authentication and audit trail

Publishing applications may need to track the changes made to a content item
against an authenticated user. In order to do this
integration with GDS-SSO is required, this usually
means implementing a persisted user model in the publishing application and
applying a filter in the application controller to authenticate the user.
The User#uid value can then be sent as a request parameter when drafting
content to restrict access. eg.

  access_limited : ["8f3173bb-ee2a-48a2-aa86-f2f030830888"]

Draft and Publish endpoint flow

The Publishing API prescribes a draft-to-live workflow where the publishing
application submits new draft content, this content may then be updated via the
same endpoint and finally can be published in a discrete endpoint request.

  Publishing Application                                 Publishing API

  [ Initial draft ] -----------------------------------> [ PUT /v2/content/:content_id ]
                                                                            |
               Responds with updated content including lock version         |
           <----------------------------------------------------------------


  [ Updated draft ] -----------------------------------> [ PUT /v2/content/:content_id ]
          |
          |            Publish request
           --------------------------------------------> [ POST /v2/content/:content_id/publish ]

Configuring the Publishing API for use in a publishing application

HTTP requests to Publishing API endpoints should be made using the
[publishing-api v2 gds-api-adapters client library][publishing-api-gds-adapters].
A trivial example of how to configure a publishing application to use the
Publishing API v2 client adapter would be:

  require "gds_api/publishing_api_v2"

  publishing_api = GdsApi::PublishingApiV2.new(Plek.new("publishing-api"))

Drafting content from a publishing app

Publishing applications should use the PUT /v2/content/:content_id endpoint
to add content.
Please refer to the api documentation and
model documentation for further details.

The publishing application is responsible for the generation of a content_id,
the primary identifier for content in the Publishing API and content store.
The convention is to use SecureRandom.uuid to generate a valid content_id.

Details of how the payload should be constructed including required fields can
be found in govuk-content-schemas where each format is
defined for both publishing and frontend applications. For example the payload
for a case study would need to provide body and
first_public_at attributes along with other mandatory attributes such as
title and base_path.

Using the Publishing API v2 client adapter with a valid content_id and payload,
the following example would make the request.

  publishing_api = GdsApi::PublishingApiV2.new(Plek.new("publishing-api"))
  content_id = SecureRandom.uuid
  guide = GuideContentModel.new(
    content_id: content_id,
    base_path: "/vat-rates",
    title: "VAT rates",
    description: "VAT rates for goods and services",
    details: { body: "Something about VAT" },
    access_limited : ["8f3173bb-ee2a-48a2-aa86-f2f030830888"],
    rendering_app: "frontend",
    publishing_app: "my-shiny-publishing-app",
    public_updated_at: "2014-05-14T13:00:06.000Z",
    document_type: "guide",
    schema_name: "guide",
    routes: [{
      path: "/vat-rates", type: "exact"
    }]
  )
  payload = guide.as_json
  publishing_api.put_content(content_id, payload)

The response body would contain a presentation of the saved edition
including the item lock version eg.

  {"content_id":"940b88db-8f15-4859-b5b2-4761ba62a067",
  "locale":"en",
  "base_path":"/vat-rates",
  "title":"VAT rates",
  "description":"VAT rates for goods and services",
  "document_type":"guide",
  "schema_name":"guide",
  "public_updated_at":"2014-05-14T13:00:06.000Z",
  "details":{"body":"Something about VAT"},
  "access_limited":["8f3173bb-ee2a-48a2-aa86-f2f030830888"],
  "routes":[{"path":"/vat-rates","type":"exact"}],
  "redirects":[],
  "publishing_app":"my-shiny-publishing-app",
  "rendering_app":"frontend",
  "lock version":"1"}

Which could then be used to update the local model instance in the publishing
application:

  parsed_response = JSON.parse(response.body)
  guide.update_attributes(parsed_response)

Error handling

The response will indicate any errors that may have occurred in making the
request. These may be validation errors for required content fields or more
general errors pertaining to lock version locking. A 4xx error code will be
returned along with error messages in the response.body.error object. eg.

  {
    "status": 409,
    "headers": {
      "Content-Type": "application/json; charset=utf-8"
    },
    "body": {
      "error": {
        "code": 409,
        "message": "Conflict",
        "fields": {
          "previous_version": [
            "does not match"
          ]
        }
      }
    }
  }

LockVersioning

The response body contains the current lock version of the document for GET
and PUT /v2/content/:content_id endpoints. This allows the publishing
application to track the updated draft lock version, this is can in turn be
used in an optional previous_version request parameter to prevent conflicting
updates from overwriting content. ie.

  Publishing Application                                 Publishing API

  [ Initial draft ] -----------------------------------> [ PUT /v2/content/:content_id ]
                                                                       |
                    Responds with lock version 1                       |
                    <--------------------------------------------------

  [ Update lock version 1 ] ------ previous_version: 1 -----> [ PUT /v2/content/:content_id ]
                                                                       |
                    Responds with lock version 2                       |
                    <--------------------------------------------------

                    This next request to update will fail since lock version increment.

  [ Update lock version 1 ] ------ previous_version: 1 -----> [ PUT /v2/content/:content_id ]

                    This next request to update will succeed.

  [ Update lock version 2 ] ------ previous_version: 2 -----> [ PUT /v2/content/:content_id ]

GET endpoints also exist for content and links should the publishing
application wish to make a request for the current state of these items in the
Publishing API.

Publishing

Publishing content is handled by a discrete endpoint accepting the
content_id of the item to publish and optionally the locale of the item to
publish. eg.

POST /v2/940b88db-8f15-4859-b5b2-4761ba62a067/publish?locale=fr

When making a request to publish, the current draft item of the same content_id
and locale is used as the basis of the payload sent downstream to the live
content store.

Automatic Redirects

Publishing API automatically creates redirects for documents when
their base paths change between editions.
When the edition is created as a draft, a redirect edition is also created in
draft. When the edition is published, the redirect is also published.


Is there anything wrong with the documentation? If so:

  • Open a pull request
  • Speak to the Publishing Platform team