Skip to main content
Last updated: 7 Jun 2024

publishing-api: Publishing application 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. 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.find("publishing-api"))

Configuring authentication

Individual applications require bearer tokens to access the publishing API. To create and configure tokens:

  1. Go to the API users config in Signon for each environment (e.g. https://signon.integration.publishing.service.gov.uk/api_users). You must be a superadmin to see this page.
  2. Find your application in the list or create a new API user for it. The app's email address should be name-of-app@digital.cabinet-office.gov.uk.
  3. Add a publishing API application token for that user.
  4. Add the tokens for each environment to govuk-secrets (example).
  5. Configure govuk-puppet to create an environment variable for the token (example).
  6. Use the environment variable in the app:
@publishing_api = GdsApi::PublishingApiV2.new(
  Plek.find("publishing-api"),
  bearer_token: ENV["PUBLISHING_API_BEARER_TOKEN"] || "example",
)

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 the content_schemas folder 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.find("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