Files and browsers
POST /v1/stores/{store}/attachments/url answers where an attachment's file is. A file open to anyone comes back as a public URL. A private one comes back as a URL on this API, /v1/stores/{store}/files/..., which answers the bytes to a GET with the same bearer that asked for it: the capability is bound to that token, so fetch it at once, before the token is refreshed. Asked with a publishable key, a file open to the store's visitors (a free lesson's video, a file a page shows) comes back the same way, and is fetched with the same key, which its URL already carries as publishable_key: the key is public, so the URL can be named as it is in a <video src> or <img src>, with no script, and works for anyone holding it until expires_at. Sending the same key in the header too is fine. A customer's token is never taken in a URL, which logs, history and Referer would keep. A video or audio file asked for with a token (a paid lesson's video) needs none: its URL alone plays it, so it can be named as it is in a <video src> or <audio src>, with no header and no script. It plays for anyone holding it, on any site, until expires_at, and stops sooner once the token is revoked or refreshed, or the customer's access to the file ends; so treat it as you would the token for that time, and never log it. Any other file asked for with a token (an image, a document, a download) is fetched with the token in the Authorization header, so a page fetches it rather than naming it in a src; fetched with no credential, it answers 401 with WWW-Authenticate: Bearer, as does a file asked for with a publishable key whose URL lost its key. A file shown in place (a video, an image, a document) answers a Range header naming one bytes range with 206 and just those bytes, so a player can seek in a long video without loading it whole, as a video element does on its own. A range outside the file, or several ranges, answers 416; If-Range with an earlier ETag answers the whole file once it changed. A download is answered whole. The URL serves until expires_at, each range included: a video's holds longer than other files', and one asked for with a publishable key holds four hours, so a bare <video src> plays a lesson shorter than that through with no script. Past it, or with a token after a refresh (which also stops a token's video URL), ask again, with renew: true for a token's video still playing so it stays one play, and keep its ETag for If-Range; a video element playing past it needs a script to set the new URL and its currentTime back. A download works once, so the route takes no HEAD. A 403 means the file is held back from the customer, or the capability is another token's or key's, or no longer valid: ask for a new URL, and with a token, sign the customer in again if that is refused too.
A page on another origin may call this API: a preflight is answered for any origin, since the bearer or the key, never a cookie, is the credential. A success is readable only from the store's own origins (https://{store}, the store's primary hostname, and the external site set in its storefront settings) and from the origins the merchant allows for the app that issued the token, or for the publishable key sent, set on its API clients page. A video fetched by its URL alone, with no credential, is readable from https://{store} only, and plays in a media element on any page. A refusal is readable from any origin, so a page can sign the customer in again. Both are read again every 5 minutes, so an app's origin added or removed takes effect within that time; while they can't be read, those read in the last 10 minutes still stand. A preflight is kept 10 minutes per URL, so a page reading many products pays one per product URL.