openapi: 3.0.0
info:
  version: 1.0.0
  title: Shift72 Content API
servers:
  - url: https://{siteUrl}
    variables:
      siteUrl:
        default: vod.shift72.com
        description: Url to your Shift72 site
tags:
  - name: Playback
    description: |
      The main API for playing content is the [Streams
      API](#tag/Playback/operation/getStreamsForSlug). This gives you the URLs of the manifest (DASH
      .mpd, or HLS .m3u8), as well as granting a playtoken needed for the license check.

      The APIs for issuing playtokens are used when you need to use an API key to authorize a
      playback. The playtoken can be used by players allowing the bearer to watch content without
      needing an authtoken.
  - name: User Library
    description: |
      A user's library is the TVOD content they've bought (e.g. rentals or EST
      purchases). 

      The library can also contain plan content if the plan has been flagged as
      a "library plan". This is useful for hybrid models, such as festival
      passes where there may be a small number of films that should appear
      similar to other rentals.

      For SVOD sites with large content libraries it's more appropriate for
      users to discover plan content other ways -- including the full catalogue
      in their library view would be overwhelming!
paths:
  /services/playback/streams/{slug}:
    get:
      operationId: getStreamsForSlug
      summary: Get playback streams for a slug
      description: |
        Returns the details you need to play some content.

        Content is encoded with different formats (HLS/DASH) and DRM, so this will
        return multiple Streams objects and you need to pick the right one for your
        player.

        This will return a playtoken. This is a JWT authorizing you to watch this
        specific content. You'll need to provide the playtoken to the license server
        in order to prove you're allowed to watch.

        You can either provide an authtoken for a user, or a valid playtoken for the
        content.

        **Note**: this API can also be called as a POST with an empty body.
      tags:
        - Playback
      security:
        - AuthToken: []
        - PlayToken: []
      parameters:
        - name: slug
          in: path
          description: The slug of content to play, i.e. a film, film bonus, or TV episode.
          example: /film/123
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Streams API response
          content:
            application/json:
              schema:
                type: object
                properties:
                  play_token:
                    description: a JWT used to authorize this playback session with the license server
                    type: string
                  streams:
                    type: array
                    items:
                      type: object
                      properties:
                        url:
                          description: |
                            Stream URL (e.g. the .m3u8 or .mpd manifests, or .mp4 depending on the content type). Usually
                            this is going to point to mrs.shift72.com with the playtoken as a query param.
                          type: string
                        encoding_type:
                          description: the type of stream. Common values are 'hd_dash' (.mpd manifest used with Widevine and PlayReady DRM), 'hls' (used with Fairplay), and 'hd_mp4' for raw files (no DRM, e.g. trailers).
                          type: string
                          enum:
                            - hd_dash
                            - hd_mp4
                            - hls
                        drm_type:
                          description: the DRMs used for this content encoding. DASH manifests contain both PlayReady and Widevine.
                          type: array
                          nullable: true
                          items:
                            type: string
                            enum:
                              - widevine
                              - playready
                              - fairplay
                  playback_progress:
                    description: The last known playback position, if available.
                    nullable: true
                    type: object
                    properties:
                      play_position:
                        type: number
                        description: Last reported play position, in seconds
                      length:
                        type: number
                        description: Reported content length
                      last_played_at:
                        type: string
                        format: date-time
                        description: When was the content last played?
                  ad_tag:
                    description: a VMAP url to use for this content if ads are configured.
                    nullable: true
                    type: string
                  will_trigger_watch_window:
                    description: If true, the watch window will be triggered when a license request is made
                    type: boolean
                  willTriggerRentalWindow:
                    description: Use `will_trigger_watch_window` instead. This field is retained for compatibility.
                    deprecated: true
                    type: boolean
                  watch_window_duration:
                    description: The duration of the watch window, in minutes. This will be null if there's no watch window or it's not a rental.
                    type: number
                    nullable: true
                  default_subtitle_language:
                    description: A hint to the player for picking default subtitles. Currently configured globally for a site.
                    nullable: true
                    type: string
                  config:
                    description: Playback and content related configurations and feature toggles
                    type: object
                    additionalProperties: true
              example:
                play_token: eyJhbGciOiJIUz...
                streams:
                  - url: https://mrs.shift72.com/[uuid].mpd?pt=eyJhbGciOiJIUz...
                    encoding_type: hd_dash
                    drm_type:
                      - widevine
                      - playready
                  - url: https://mrs.shift72.com/[uuid].m3u8?pt=eyJhbGciOiJIUz...
                    encoding_type: hls
                    drm_type:
                      - fairplay
                ad_tag: ''
                will_trigger_watch_window: true
                watch_window_duration: 1800
                default_subtitle_language: en
                config: {}
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          description: Permission denied. This could indicate the user doesn't have access to the content, the content is unpublished or availability conditions are not met.
        '404':
          description: Content not found
  /services/playback/playlist/{slug}:
    get:
      operationId: getPlaylistForSlug
      summary: Get playlist for a slug
      description: |
        Playable slugs have a playlist associated with them. Currently, this is used
        for preroll and postroll, but it can theoretically support any arbitrary
        content on your platform.

        A playlist is a series of items to play (e.g. different slugs).

        The default playlist for any playable slug (e.g film, bonus or episode) is
        just a playlist with a single item in it.

        Auth is not required, but is recommended so that your playback progress can 
        be taken into account.

        **Note**: this API can also be called as a POST with an empty body.
      tags:
        - Playback
      security:
        - AuthToken: []
        - PlayToken: []
      parameters:
        - name: slug
          in: path
          description: The slug to get playlist for, i.e. a film, film bonus, or TV episode.
          example: /film/123
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Playlist response
          content:
            application/json:
              schema:
                type: object
                properties:
                  title:
                    description: Title of the content being watched
                    type: string
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          description: a unique id of this item within the playlist
                          type: string
                        title:
                          description: The title of the content
                          type: string
                        has_watched:
                          description: |
                            Whether the user has already watched this content. This isn't very
                            accurate because our playback progress isn't per-playlist yet
                          type: boolean
                        skip_policy:
                          description: |
                            How should the user be able to skip this content? This is used by
                            the playlist system in Shift72's player, for example to require a
                            sponsorship ad is shown at least once, but is then skippable in
                            future.
                          type: string
                          enum:
                            - allowed
                            - must-view-once
                            - must-view-once-per-session
                            - never
                        action:
                          type: object
                          properties:
                            type:
                              description: The action type. "play" is the only supported type
                              type: string
                              enum:
                                - play
                            slug:
                              description: The item to play
                              type: string
              example:
                title: Citizen Kane
                items:
                  - id: preroll-1
                    title: Shift72 Presents
                    skip_policy: must-view-once
                    has_watched: true
                    action:
                      type: play
                      slug: /film/123/bonus/1
                  - id: feature
                    title: Citizen Kane
                    skip_policy: allowed
                    has_watched: false
                    action:
                      type: play
                      slug: /film/281
        '403':
          description: Permission denied. This could indicate the user doesn't have access to the content.
        '404':
          description: Content not found
  /services/playback/play_token/{slug}:
    post:
      operationId: postPlaytokenForCurrentUser
      summary: Request playtoken for current user
      description: |
        Request a playtoken for the current user.

        This usually isn't necessary, as the Streams API will issue a playtoken for 
        the user if they use an authtoken.

        For regular users, no request parameters are accepted: you get what you get
        and you don't get upset.

        For admin users, the request body can tailor the playtoken a little bit,
        such as requesting a different expiry, changing the delivery policy (e.g. to
        reduce DRM strictness to allow all quality levels to be viewed on browsers
        that would otherwise be limited to SD-ish), and changing whether telemetry
        should be sent.
      tags:
        - Playback
      security:
        - AuthToken: []
        - AdminAuthToken: []
      parameters:
        - name: slug
          in: path
          description: The slug to get token for, i.e. a film, film bonus, or TV episode.
          example: /film/123
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              oneOf:
                - type: object
                  description: '**Admin users only**: these parameters can influence the playtoken.'
                  properties:
                    exp:
                      description: Expiry timestamp for this playtoken. **NOTE** this is an ISO8601 timestamp, NOT UNIX TIME. At most 24 hours in future
                      type: string
                      format: date-time
                    uid:
                      description: '**Optional**: text to show as a watermark over the video. This is applied by players to help discourage or track down screen recording'
                      type: string
                      nullable: true
                    olv:
                      description: '**Optional**: the style of overlay to use. `og` (default) shows the watermark on the left edge of the video. `apple` displays text in a different corner for 20 seconds at a time'
                      nullable: true
                      default: og
                      type: string
                      enum:
                        - og
                        - apple
                    delpol:
                      description: |
                        The delivery policy to use. If not specified, the content or site default will be used.

                         - `urn:shift72`: "Standard". Allows HD playback on all devices.
                         - `urn:shift72.studio.1`: "Studio 1". Default for most clients. HDCP is required, and HD content is limited on less secure devices (e.g. Widevine browsers)
                         - `urn:shift72.studio.2`: "Studio 2". Really restrictive. Will restrict playback on a lot of devices. 
                      nullable: true
                      type: string
                      enum:
                        - urn:shift72
                        - urn:shift72.studio.1
                        - urn:shift72.studio.2
                - type: object
                  description: Empty object. No parameters are supported for non-admin users.
                  example: {}
      responses:
        '200':
          $ref: '#/components/responses/PlaytokenResponse'
        '403':
          description: Permission denied. This could indicate the user doesn't have access to the content.
        '404':
          description: Content not found
  /services/playback/user/{userId}/play_token/{slug}:
    post:
      operationId: postPlaytokenForUser
      summary: Issue playtoken for a Shift72 user
      description: |
        Request a playtoken on a user's behalf.

        This is used for scenarios where you want to generate a playtoken using an
        API key for a particular user.
      tags:
        - Playback
      security:
        - ApiKey: []
      parameters:
        - name: userId
          in: path
          description: The Shift72 user ID to issue this play token for
          example: 123456
          required: true
          schema:
            type: number
        - name: slug
          in: path
          description: The slug to get token for, i.e. a film, film bonus, or TV episode.
          example: /film/123
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                exp:
                  description: Expiry timestamp for this playtoken. Defaults to 24 hours from now. **NOTE** this is an ISO8601 timestamp, NOT UNIX TIME. At most 24 hours in future.
                  type: string
                  format: date-time
                uid:
                  description: Text to show as a watermark over the video. This is applied by players to help discourage or track down screen recording"
                  type: string
                  nullable: true
                olv:
                  description: The style of overlay to use. `og` (default) shows the watermark on the left edge of the video. `apple` displays text in a different corner for 20 seconds at a time
                  nullable: true
                  default: og
                  type: string
                  enum:
                    - og
                    - apple
                delpol:
                  description: |
                    The delivery policy to use. If not specified, the content or site default will be used.

                      - `urn:shift72`: "Standard". Allows HD playback on all devices.
                      - `urn:shift72.studio.1`: "Studio 1". Default for most clients. HDCP is required, and HD content is limited on less secure devices (e.g. Widevine browsers)
                      - `urn:shift72.studio.2`: "Studio 2". Really restrictive. Will restrict playback on a lot of devices. 
                  nullable: true
                  type: string
                  enum:
                    - urn:shift72
                    - urn:shift72.studio.1
                    - urn:shift72.studio.2
      responses:
        '200':
          $ref: '#/components/responses/PlaytokenResponse'
        '403':
          description: Permission denied. This could indicate the user doesn't have access to the content.
        '404':
          description: Content not found
  /services/playback/user/external/play_token/{slug}:
    post:
      operationId: postPlaytokenForExternalUser
      summary: Issue playtoken for an external user
      description: |
        Get a playtoken to allow an external user to watch content.

        As these tokens are for external users, no ownership, device limits, or
        region checks will be done. You need to ensure that the recipient is allowed
        to play this content before giving them a token.

        You must provide an expiry time for the token, no more than 24 hours in the
        future. After a token has expired, attempts to start playback or perform
        license checks will fail and a new token will be required.
      tags:
        - Playback
      security:
        - ApiKey: []
      parameters:
        - name: slug
          in: path
          description: The slug to get token for, i.e. a film, film bonus, or TV episode.
          example: /film/123
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - usr
                - exp
              properties:
                usr:
                  description: The user ID to associate with this playtoken. This can be a user ID in an external system.
                  type: string
                exp:
                  description: Expiry timestamp for this playtoken. **NOTE** this is an ISO8601 timestamp, NOT UNIX TIME
                  type: string
                  format: date-time
                uid:
                  description: '**Optional**: text to show as a watermark over the video. This is applied by players to help discourage or track down screen recording'
                  type: string
                  nullable: true
                olv:
                  description: '**Optional**: the style of overlay to use. `og` (default) shows the watermark on the left edge of the video. `apple` displays text in a different corner for 20 seconds at a time'
                  nullable: true
                  default: og
                  type: string
                  enum:
                    - og
                    - apple
                delpol:
                  description: |
                    **Optional**: The delivery policy to use. If not specified, the content or site default will be used.

                      - `urn:shift72`: "Standard". Allows HD playback on all devices.
                      - `urn:shift72.studio.1`: "Studio 1". Default for most clients. HDCP is required, and HD content is limited on less secure devices (e.g. Widevine browsers)
                      - `urn:shift72.studio.2`: "Studio 2". Really restrictive. Will restrict playback on a lot of devices. 
                  nullable: true
                  type: string
                  enum:
                    - urn:shift72
                    - urn:shift72.studio.1
                    - urn:shift72.studio.2
              additionalProperties:
                description: |
                  Additional claims to include in the playtoken. These can be JSON strings, booleans or
                  numbers. It's strongly recommended that you add a vendor prefix to any custom claims
                  so that they don't conflict with any Shift72 issued claims.
                anyOf:
                  - type: string
                  - type: boolean
                  - type: number
      responses:
        '200':
          $ref: '#/components/responses/PlaytokenResponse'
        '403':
          description: Permission denied. This could indicate the user doesn't have access to the content.
        '404':
          description: Content not found
  /services/content/v5/user_library:
    get:
      operationId: getUserLibraryV5
      summary: Get my user library
      description: Gets the library content and plans for the current user
      tags:
        - User Library
      security:
        - AuthToken: []
      parameters:
        - name: location
          in: query
          description: override the users location, used for plan-as-library content
          required: false
          schema:
            type: string
      responses:
        '200':
          description: user library response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserLibrary'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
  /services/content/v5/user_library/{user_id}:
    get:
      operationId: getUserLibraryV5ByUserId
      summary: Get a user library by User ID
      description: |
        A user can request to see their own library, or a privileged user can 
        view others libraries.
      tags:
        - User Library
      security:
        - AuthToken: []
        - AdminAuthToken: []
        - ApiKey:
            - content:user_library
      parameters:
        - name: user_id
          in: path
          description: User ID
          required: true
          schema:
            type: integer
            format: int32
        - name: location
          in: query
          description: override the users location for plan-as-library content
          required: false
          schema:
            type: string
      responses:
        '200':
          description: User library response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserLibrary'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          description: Permission denied
components:
  securitySchemes:
    AuthToken:
      type: apiKey
      in: header
      name: X-Auth-Token
      description: A user auth token or API key.
    PlayToken:
      type: apiKey
      in: header
      name: X-Auth-Token
      description: A JWT playtoken. This is an authorization to watch specific content and is used with streams, license and telemetry APIs.
    AdminAuthToken:
      type: apiKey
      in: header
      name: X-Auth-Token
      description: A user with the admin role
    ApiKey:
      type: apiKey
      in: header
      name: X-Auth-Token
      description: An API key. This may need specific permissions, documented in the endpoint
  responses:
    UnauthorizedError:
      description: Auth token or API key is missing or invalid
    PlaytokenResponse:
      description: |
        Playtoken JWT. This may contain more claims than documented here, but expect the following claims:

        - `exp`: standard JWT expiry claim, Unix timestamp
        - `iat`: standard JWT issued at claim, Unix timestamp
        - `slug`: the slug of the content this token is for
        - `usr`: number or string. The user id this token is for.
        - `playid`: string (uuid) unique id for this playtoken. Useful for correlating telemetry and diagnostics.

        And some optional claims:
        - `uid`: (optional) the player overlay text
        - `olv`: the style of overlay requested for this token
      content:
        text/plain:
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2x1ZyI6Ii9maWxtLzI4MSIsInVzciI6Ilt1c2VyaWRdIiwiZXhwIjoxNTE2MjM5MDIyLCJpYXQiOjE1MTYyMzkwMjJ9.l9J2jy0LROLrDTcpqsjUDnOMRhGRs0jVLORxlBWjJbk
          schema:
            type: string
  schemas:
    LibraryItem:
      type: object
      properties:
        ownership:
          type: string
          description: How you own the content. Plan content only appears here if the plan has been flagged as a library plan
          enum:
            - rent
            - buy
            - plan
        type:
          type: string
          enum:
            - film
            - film-bonus
            - tv-season
            - tv-episode
            - tv-season-bonus
        slug:
          type: string
        title:
          type: string
        available_from:
          type: string
          description: ISO8601 timestamp The start of the content's availability window. The content cannot be watched before this time.
        available_to:
          type: string
          description: ISO8601 timestamp The end of the content's availability window. The content cannot be watched after this time. This is a hard cutoff enforced by the license server.
        expiry:
          type: string
          description: ISO8601 timestamp. The rental expiry. Once a watch window is triggered the expiry will be set to `watch_window_end`, shortening or lengthening the original rental period.
        is_expired:
          type: boolean
          description: Is it past the expiry date? This is calculated from the `expiry` field based on server time.
        watch_window_duration:
          type: number
          description: Duration of the watch window for a rental, in minutes. Once you start playing the content, you will have this long to finish.
        watch_window_start:
          type: string
          description: If rental watch window has been triggered, this is when it started. ISO8601 UTC timestamp, or null
        watch_window_end:
          type: string
          description: |
            If rental watch window has been triggered, this is when it will end. ISO8601 UTC timestamp or null.
            You must finish watching the content by this time.
        plan_id:
          type: number
          description: Plan content only. This is the plan ID that granted it
        playback_progress:
          description: Where were you up to? This is based off playback telemetry and only accurate to about 30 seconds.
          type: object
          properties:
            play_position:
              type: number
              description: The play position, in seconds
            last_played_at:
              type: string
              description: When was telemetry last received?
            length:
              type: number
              description: 'How long is the content in seconds. Note: this is based on player-reported duration and can be inaccurate in some situations!'
            completed:
              type: boolean
              description: Have you watched most of it? (currently, >80%). This is used to determine if a player should just start over
    UserLibrary:
      type: object
      properties:
        items:
          description: |
            The media items a user owns. This is usually stuff they've rented or bought. 
            Plan content can appear here too, if the plan has been set up as a *Library Plan*.

            Recent expired rentals appear in the library too. These can be identified
            by `is_expired: true` or checking the expiry date is in the past.

            The same item can appear multiple times -- e.g. if the user rents an item,
            and it later is added via a Library Plan. If the same content is in
            multiple Library Plans, then there will be one library item for each of
            the plans.

            **Sorting**: the library items are sorted so that watchable items will
            always appear before expired content. There are some heuristics applied so
            that the most relevant items are near the top of the list (e.g. expiring
            soon)
          type: array
          items:
            $ref: '#/components/schemas/LibraryItem'
        plans:
          type: array
          description: The plans this user has access to. These could be subscriptions, one-off plans (e.g. passes) or plans that were automatically assigned on sign-up or via an integration.
          items:
            type: object
            properties:
              plan_id:
                type: number
              expiry:
                description: 'When does the user''s access to this plan expire? **Note**: if this plan is a subscription, this date will be extended when the next invoice is paid.'
                type: string
