openapi: '3.0.0'

info:
  title: Prenly Remote authority API (stable)
  version: 1.5.0
  description: |

    ## Introduction

    The Prenly remote authority API introduces the possibility to implement your logic handling authentication (i.e. user login) and/or authorisation (i.e. subscription-based read-access). All you need to do is set up API endpoints according to this specification. The endpoints will be queried as the users interact with the Prenly application.

    The API specification will let you implement your version of the Prenly Remote authority API enabling you to let Prenly authenticate (log in the user) and authorise (determine what the user might see and read) based on your requirements. Both authentication and authorisation requests are initiated by the user. Authorization requests are triggered when the user tries to open restricted-access publications. Authentication requests are triggered each time the user needs to verify their identity to access the restricted-access publications.

    The Prenly reader application (Android, iOS or web) communicates with Prenly's core backend system, which in its turn communicates as needed with your API endpoints behind the scenes. Since all technical communication is mediated by Prenly, your API endpoints are never exposed to the end user.

    This API specification determines the common-ground communication between Prenly and your system. i.e. what questions Prenly can ask your implementation on your own API-hosting web server (which is called __the remote server__) and what answers your system is allowed to give. The API specification guarantees the expected user experience within the Prenly application.

    Any deviation from the API specification will be seen as an implementation error you will be responsible to correct.

    ### API variants

    The API specification has three technically different variants depending on your needs.

    #### 1. External OAuth2 login form hosted on your web page

    * You provide a server supporting the OAuth2 industry standard "authorization code flow"
    * You provide an API endpoint that Prenly will query to fetch subscriptions given a unique user id
    * Allows you to offer users a single sign-on (SSO) service

    This option is suitable when you already have an OAuth2 authorisation server, or if you want a SSO environment.

    Prenly has built-in support to login users via the OAuth2 standard "authorization code flow". Your Prenly application will be configured to work with your Oauth2 server, and handles standard components such as redirect_uri, scope and state.

    Note that for security reasons, your token endpoint must return refresh tokens and thereby support the `refresh_token` grant type.

    For authorising users via subscriptions, you will also need to implement an endpoint that let's Prenly fetch product codes (subscriptions) by providing a unique user id.

    You must support one of the following ways to provide Prenly with the user id:

    - Return an ID token in the access token response, according to the [OpenID Connect standard](https://openid.net/specs/openid-connect-core-1_0.html#IDToken). The ID token should contain the user id in the claim called `sub` (for "subject").
    - Implement an OpenID Connect [UserInfo endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo). The endpoint must return JSON containing a property/claim `sub` (for "subject"), containing the user id.
    - Return the user id in a custom property in the access token response, you can choose the name of the property yourself (for example `user_id`).

    To enable this, you need to provide a working Oauth2 authorization server, and implement the getUser endpoint under __1. OAuth2 login form__.


    #### 2. Prenly built-in login form - login credentials are validated by your system

    * Usernames and passwords are stored and validated in your technical environment
    * You provide an API endpoint to verify username/password combinations entered in the form
    * You provide an API endpoint that returns user information including subscriptions

    This option is suitable when you have a system to handle login accounts that does not support OAuth2.

    To enable this you are required to implement the API-specification parts under __2. Built-in login form, remote accounts__.

    Prenly will request user information by the unique user ID (`uid`) your system provided upon successful authentication. If you also provided a token then Prenly will request user information from your system with both the unique user ID and the provided token.

    Currently, there are two methods to log in users hosted in your login system:

    - A username/password combination that the user enters in the built-in login form in the Prenly application.

    - A unique short-lived token yielded previously by your remote server and used to automatically authenticate a user into the Prenly application if the user is seen as already authenticated, by the remote server, in the remote system (e.g. on a separate site). This is an optional feature that is not so commonly used.

      In practice, this allows the remote site (e.g. a news site with login) to provide a special link that redirects the logged-in user to the Prenly application. The Prenly application will consume the link and initiate the authentication via a short-lived token process in the background and, if the remote server authenticates the short-lived token, sign in the user.

      Currently, short-lived authentication is only supported by the web platform.

      The Prenly Reader web consumes a short-lived token on the path `/authority/auth-token` which takes three query parameters:

      * `auth_token` – Mandatory
        Specify the short-lived token to consume and verify by the remote server
      * `success_redirect_url` – Optional. Defaults to the start page.
        Specify a redirect URL upon successful (200) authentication.
      * `failure_redirect_url` – Optional. Defaults to the start page.
        Specify a redirect URL upon failed authentication. Any non-200 remote server response is treated as a failed authentication attempt.

      Let's assume the short-lived token to verify by the remote server is `eta-beta` and your Prenly Reader web is accessible at `https://example.com/`. The short-lived token authentication path is `https://example.com/authority/auth-token?auth_token=eta-beta`.

      Let's assume you would like to redirect the reader to `https://success.example.com/` upon authentication and to `https://failure.example.com/` on failed authentication. The short-lived token authentication is `https://example.com/authority/auth-token?auth_token=eta-beta&success_redirect_url=https://success.example.com/&failure_redirect_url=https://failure.example.com/`.

      You can combine `success_redirect_url` with the special Prenly latest published publication link enabling your reader to be immediately taken to the reading mode of the latest published publication upon success authentication via the short-lived token.


    #### 3. Prenly built-in login form - login credentials are validated by Prenly

    * Usernames and passwords are stored and validated in Prenly's technical environment
    * You provide an API endpoint that returns info about subscribers having a specific e-mail address
    * You provide an API endpoint that returns info about a subscriber with a given customer number

    This option is suitable when you have users with subscription information in your own system, but lack a system to handle login accounts.

    Usernames and passwords are securely stored and verified within Prenly's technical environment, while your system provides Prenly with subscription information to ensure that only subscribers can access protected content.

    To enable this you are required to implement both endpoints under __3. Built-in login form, Prenly accounts__.

    Prenly will request user information by the validated e-mail address that the user logged in with. If a user is found, the response must contain a customer number that will be associated with the Prenly account, and used for further requests to fetch user details including subscriptions (in the form product codes).

    If the remote server could not find such a user or if multiple users that share the e-mail address are returned, no remote user association can be made. The account will still be logged-in, but without imported subscriptions.


    ### Product codes and limited product codes

    A product code added to the `productCodes` property grants access to all publications within a title, provided that the product code has been linked to the title in Prenly Workspace by an administrator. If you wish to restrict access based on publication dates, use the `limitedProductCodes` property to define specific date intervals for the product code. Both `productCodes` and `limitedProductCodes` can be included in the same response, but they serve different purposes. The `productCodes` property provides unrestricted access to any linked publications, while `limitedProductCodes` enforces date-based restrictions.


    ## Endpoint URLs

    You choose the URLs, including the paths, for each endpoint implemented. The only requirements are to follow the API specification which includes the query parameters.

    For example, the URL to your password login endpoint may be `https://foo.bar/myapi/prenly-auth`.

    When the endpoints are implemented and tested communicate the chosen URLs to Prenly to set up the integration for your Prenly application.

    ## Validating your API endpoints

    Visit <a href="https://remoteapi.prenly.com" target="_blank">https://remoteapi.prenly.com</a> to test that your endpoints validate according to this specification.
    You find the test cases to validate by <a href="https://docs.google.com/document/d/1UxmvDs7_Z0GwGbwCHXr4Ojpb9HaZif1nJ8YRMmztzeo/" target="_blank">Testing your Prenly remote API implementation</a>.

    ## Terminology

    ### Authentication

    Authentication is the process to validate log-in data in the login process to identify the visitor as a user, i.e. a subscriber.

    ### Authorisation

    Authorisation is the process to grant permission/access to a specific resource. In Prenly this is viewed as the granting of read-access to subscription-required restricted publications.

    Prenly will query the remote server for user information and subscription status (in the form of a user summary) which is used to grant or reject read-access. In addition, if you were to opt-in to the feature, Prenly will also query the user's favorite titles.

    ### Remote server

    Your API-hosting web server running your implementation of the Prenly remote authority API specification.

    ## User meta-data

    From specification version 1.3.0, the user summary schema supports a user's meta-data. The goal is to allow a bi-directional synchronization between Prenly and the remote server. This allows Prenly to export users' meta-data to store on the remote server and import it from the remote server, allowing the remote server to act as the master source of the meta-data.

    The following meta-data is currently supported:

    * Favorite titles

    ### Favorite titles

    Applicable when the Prenly application is set up to use the favorite-titles-functionality, allowing a user to choose his or her favorite titles in a multi-title application.

    Favorites can be exported from Prenly to the remote server when the user updates his or her favorites,
    as well as be imported to Prenly in the normal flow when Prenly fetches the user's meta-data from the remote server.

    ## Contact

  contact:
    name: Textalk Media
    email: hello@prenly.com
    url: https://prenly.com/

tags:
  - name: 1. OAuth2 login form
    description: External OAuth2 authorisation server and login form
  - name: 2. Built-in login form, remote accounts
    description: Prenly built-in login form - login credentials are validated by your system
  - name: 3. Built-in login form, Prenly accounts
    description: Prenly built-in login form - login credentials are validated by Prenly
  - name: Miscellaneous
    description: Additional endpoints for specific use cases

servers:
  - url: /remote-api/server/v1

paths:
  '/oauth2/getUser':
    post:
      operationId: "getUserWithBearerToken"
      summary: Get user information given the unique user id that was retrieved when the user logged in.
      description: |
        Get user information, including product codes representing subscriptions, when the setup where
        users login via an OAuth2 form.

        The logged-in user's access token will be passed as a bearer header, according to the OAuth2 standard: `Authorization: bearer <access token>`. This allows the remote server to verify that it belongs to the user with the provided uid, as well as logging out the user with a 401 response if the provided token is invalid for some reason.
      tags:
        - 1. OAuth2 login form
      security:
        - oAuth2BearerAuth: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/OAuth2GetUserByIdParams'
      responses:
        '200':
          description: |
            User is found - process the user's information.

            Authorise read access. Prenly will use the listed product codes to
            determine which publications can be read by the user.
            Leave `productCodes` as an empty list if the user currently does not
            have access to any products, i.e. does not have any subscriptions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSummary'
        '401':
          description: |
            Logout user - the user is found but should be logged out. This could be the result if the
            provided bearer authorisation header is invalid, doesn't match the provided uid, or if the
            server for other reasons determines that this user should be logged out.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The client is not authorised to do this request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No user with the given uid was found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '412':
          description: Invalid or missing client request parameter(s).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  '/authenticateWithUsernameAndPassword':
    post:
      operationId: "authenticateWithUsernameAndPassword"
      summary: Authenticate the user with username and password.
      tags:
        - 2. Built-in login form, remote accounts
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/AuthenticateWithUsernameAndPasswordParams'
      responses:
        '200':
          description: Authenticated - the provided username and password are valid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserIdentification'
        '401':
          description: Not authenticated - the provided username and password are invalid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The client is not authorised to do this request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '412':
          description: Invalid or missing client request parameter(s).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  '/authenticateWithShortLivedToken':
    post:
      operationId: "authenticateWithShortLivedToken"
      summary: Authenticate the user with a temporary token issued by the remote server.
      tags:
        - 2. Built-in login form, remote accounts
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/AuthenticateWithShortLivedTokenParams'
      responses:
        '200':
          description: Authenticated - the provided token is valid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserIdentification'
        '401':
          description: Not authenticated - the provided token is invalid and/or expired.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The client is not authorised to do this request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '412':
          description: Invalid or missing client request parameter(s).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  '/getUser':
    post:
      operationId: "getUser"
      summary: Get user information given a unique id.
      tags:
        - 2. Built-in login form, remote accounts
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/GetUserByIdParams'
      responses:
        '200':
          description: |
            User is found - process the user's information.

            Authorise read access. Prenly will use the listed product codes to
            determine which publications can be read by the user.
            Leave `productCodes` as an empty list if the user currently does not
            have access to any products, i.e. does not have any subscriptions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSummary'
        '401':
          description: Logout user - the user is found but should be logged out.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The client is not authorised to do this request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No user with the given uid was found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '412':
          description: Invalid or missing client request parameter(s).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  '/getUserByCustomerNumber':
    post:
      operationId: "getUserByCustomerNumber"
      summary: Get user information given a unique customer number.
      tags:
        - 3. Built-in login form, Prenly accounts
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/GetUserByCustomerNumberParams'
      responses:
        '200':
          description: |
            User is found - process the user's information.

            Authorise read access. Prenly will use the listed product codes to
            determine which publications can be read by the user.
            Leave `productCodes` as an empty list if the user currently does not
            have access to any products, i.e. does not have any subscriptions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSummary'
        '403':
          description: The client is not authorised to do this request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No user with the given customer number was found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '412':
          description: Invalid or missing client request parameter(s).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  '/getUsersByEmail':
    post:
      operationId: "getUsersByEmail"
      summary: Get a list of users that have the given e-mail address.
      tags:
        - 3. Built-in login form, Prenly accounts
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/GetUsersByEmailParams'
      responses:
        '200':
          description: Object representing a list of users (possibly empty) with matching e-mail address.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSummaryCollection'
        '403':
          description: The client is not authorised to do this request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '412':
          description: Invalid or missing client request parameter(s).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  '/updateFavoriteTitles':
    post:
      operationId: "updateFavoriteTitles"
      summary: Activate or deactive favorite titles for a specific user.
      description: |
        If user favorite titles functionality is enabled in your Prenly integration, this endpoint will
        be called when the user modifies its favorite titles within Prenly. The request submits a set of
        titles to add and/or remove to the user's list of favorite titles, as currently stored by the
        remote server.

        ## Requirements

        If enabled, the remote server is expected to return the full list of the user's favorite
        articles in all Remote api requests returning a user summary (as specified by the *UserSummary*
        data model).

        * The remote server is expected to add all titles in **titleSlugsToAdd** to the set of favorite
          titles, as stored on the remote server.

        * The remote server is expected to remove all titles in **titleSlugsToRemove** from the set of
          favorite titles, as stored on the remote server.

        * The remote server should support both adding and removing favorites in one single request. Prenly
          should never send the same slug in both **titleSlugsToAdd** and **titleSlugsToRemove** in one single
          request, but feel free to respond with a 400 http error.

        * **titleSlugsToAdd** and **titleSlugsToRemove** should be an array of unique title identifiers
          ("Prenly slugs") as defined in Prenly. Each such slug represents a Prenly title. Slugs will only
          contain either lower-case ascii characters (a-z), numbers (0-9) or dash (-).

        Note that **titleSlugsToRemove** is not an exhaustive list of titles that should be set as favorites
        on the remote server. This means, the remote server should **never** remove currently stored titles
        from favorites if they are not explicitly submitted in **titleSlugsToRemove** list.

      tags:
        - Miscellaneous
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateFavoriteTitlesParams'
      responses:
        '204':
          description: Updates were applied on the remote server.
        '403':
          description: You are not authorized to do this request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '412':
          description: Invalid or missing parameter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

components:
  securitySchemes:
    oAuth2BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: whatever
      description: |
        Enter the user's access token, it will be sent according to the standard on this form:
        `Authorization: bearer <access token>`.
  schemas:
    SharedKey:
      type: string
      description: A pre-shared key used to authenticate the client using the API.

    AuthenticateWithUsernameAndPasswordParams:
      type: object
      properties:
        sharedKey:
          $ref: '#/components/schemas/SharedKey'
        username:
          type: string
          description: A unique username.
        password:
          type: string
          description: A password.
      required:
        - username
        - password
        - sharedKey

    AuthenticateWithShortLivedTokenParams:
      type: object
      properties:
        sharedKey:
          $ref: '#/components/schemas/SharedKey'
        shortLivedToken:
          type: string
          description: A unique short-lived token issue by the remote server.
      required:
        - shortLivedToken
        - sharedKey

    OAuth2GetUserByIdParams:
      type: object
      properties:
        sharedKey:
          $ref: '#/components/schemas/SharedKey'
        uid:
          type: string
          description: A unique id for the user to fetch.
      required:
        - sharedKey
        - uid

    GetUserByIdParams:
      type: object
      properties:
        sharedKey:
          $ref: '#/components/schemas/SharedKey'
        uid:
          type: string
          description: A unique id for the user to fetch.
        token:
          type: string
          description: |
            A unique token for this login session, if such was returned in the login response.
      required:
        - sharedKey
        - uid

    GetUserByCustomerNumberParams:
      type: object
      properties:
        sharedKey:
          $ref: '#/components/schemas/SharedKey'
        customerNumber:
          type: string
          description: A unique customer number for the user to fetch.
      required:
        - customerNumber
        - sharedKey

    GetUsersByEmailParams:
      type: object
      properties:
        sharedKey:
          $ref: '#/components/schemas/SharedKey'
        email:
          type: string
          description: The email address for the user to fetch.
      required:
        - email
        - sharedKey

    UpdateFavoriteTitlesParams:
      type: object
      properties:
        sharedKey:
          $ref: '#/components/schemas/SharedKey'
        uid:
          type: string
          description: The unique id for the user to update favorite titles for.
        titleSlugsToAdd:
          description: A list of title slugs (possibly empty) to add to the user's favorites.
          type: array
          items:
            type: string
        titleSlugsToRemove:
          description: A list of title slugs (possibly empty) to remove from the user's favorites.
          type: array
          items:
            type: string
      required:
        - uid

    UserIdentification:
      type: object
      properties:
        uid:
          type: string
          description: The unique id of the user.
        token:
          type: string
          nullable: true
          description: Optionally, a token that is specific for this login session.
      required:
        - uid

    PrenlySlug:
      type: string
      description: |
        A Prenly slug is a normalized string, representing an entity in a human- and machine readable format.
        It may only contain lower-case ascii characters (a-z), numbers (0-9) or dash (-).

        #### Valid slugs

        * "lowercasecharacters"
        * "chars-separarted-with-dash"
        * "3rd-generation-911"

        #### Invalid slugs

        * "spaces are not allowed"
        * "Capital-letters-are-not-allowed"
        * "non-a-z-characters-like-åäö-are-not-allowed"

    UserSummary:
      type: object
      description: A summary of profile and permission properties for the user.
      properties:
        uid:
          type: string
          description: |
            A unique identification of this user, used to identify the user in other requests.
            It may be a numeric id, a customer number, a username or any other string that is unique for this user.
        customerNumber:
          type: string
          nullable: true
          description: |
              A customer number that may be shown to the end user. If customer number is used as a unique
              user identifier, this property should have the same value as the uid property.
        email:
          type: string
          nullable: true
          description: |
              The e-mail address of this user. If e-mail address is used as a unique user identifier,
              this property should have the same value as the uid property.
        givenName:
          type: string
          nullable: true
          description: The given name (first name) of the user.
        familyName:
          type: string
          nullable: true
          description: The family name (surname) of the user.
        productCodes:
          type: array
          items:
            type: string
          description: |
            A list of product (or service) codes/slugs that this user is entitled to, normally representing subscriptions.
            Each such defines a product that may grant read permission to one or more Prenly titles. The codes are mapped
            to Prenly packages by a direct matching between the product code and the package slug. A logged-in user that 
            is entitled to a certain product, will be granted full read access according to the mapped packages.
        limitedProductCodes:
          type: array
          items:
            type: object
            properties:
              productCode:
                type: string
                example: extra
                description: The product code which is mapped to a Prenly package.
              publicationStart:
                type: string
                format: date
                nullable: true
                example: "2024-08-01"
                description: |
                  The publication date for the start date of the period in the format YYYY-MM-DD.
                  Leave out or set to null for no start date.
              publicationEnd:
                type: string
                format: date
                nullable: true
                example: "2024-08-31"
                description: |
                  The publication date for the end date of the period in the format YYYY-MM-DD.
                  Leave out or set to null for no end date.
            required:
              - productCode
          description: |
            A list of product (or service) codes/slugs and for which publication periods they are valid.
            The user is granted access according to the mapped package only for the publications with a publication date
            within that period. This can be used to let a user continue having access to the publications that were
            published during the period they had a subcription, or even for single publication purchases.
            The list should be as short as possible, if there are multiple consecutive periods (with no gaps) they should
            be sent as one period. For example, a subscription for two months instead of sending two periods
            2024-08-01 - 2024-08-31 and 2024-09-01 - 2024-09-30, they should be sent as one period 2024-08-01 - 2024-09-30.
        metaData:
          type: object
          description: |
            Various data for a user, depending on what Prenly functionality that is being enabled.
            This object may be extended with new properties that can be used for different features
            in Prenly.
          properties:
            favoriteTitleSlugs:
              description: |
                An exhaustive list of Prenly slugs, each one representing a Prenly title, that Prenly
                should import as the user's favorite titles.

                #### Examples

                * ["some-magazine-name", "some-insert-name-2"]
                * []

              type: array
              items:
                $ref: '#/components/schemas/PrenlySlug'
      required:
        - uid

    UserSummaryCollection:
      type: object
      description: A collection of user summaries.
      properties:
        users:
          type: array
          items:
            $ref: '#/components/schemas/UserSummary'
      required:
        - users

    Error:
      type: object
      properties:
        message:
          title: An error message that may be logged in Prenly, but not visible to end users.
          type: string
        code:
          title: An error code that may be logged in Prenly, but not visible to end users.
          type: string
