invidious icon indicating copy to clipboard operation
invidious copied to clipboard

[Enhancement] API v2 mockup

Open SamantazFox opened this issue 3 years ago • 16 comments

Here is the v2 API mockup I came with. Comments are appreciated!

APIs I got inspiration from:

  • https://docs.github.com/en/rest
  • https://api.ovh.com/console/
api/v2
│
├── channels/<ucid>
│   │
│   ├── community
│   │   └─> GET  ctoken?
│   │
│   ├── channels
│   │   └─> GET  ctoken?
│   │
│   ├── latest
│   │   └─> GET  ctoken?
│   │
│   ├── playlists
│   │   └─> GET  ctoken?
│   │
│   └── videos
│       └─> GET  page?
│
├── feeds
│   │
│   ├── trending
│   │   └─> GET  ctoken?, region?
│   │
│   └── popular
│       └─> GET  ctoken?, region?
│
├── playlists
│   │
│   ├── <plid>
│   │   ├─> GET  page?, per_page?    (Note: 'per_page' only for IV playlists)
│   │   │
│   │  [A]
│   │   │
│   │   ├─> PUT     [ids]
│   │   ├─> DELETE  <no params>
│   │   │
│   │   ├── copy
│   │   │   └─> POST  name, visibility?
│   │   │
│   │   └── edit
│   │       ├── append
│   │       │   └─>  POST [ids]
│   │       │
│   │       ├── clear
│   │       │   └─> POST  <no params>
│   │       │
│   │       ├── insert
│   │       │   └─> POST  index, [ids]
│   │       │
│   │       └── remove
│   │           └─> POST  id
│   │           └─> POST  [ids]
│   │
│   └──[A] new
│       └─> POST  name, visibility?
│
├── search
│   ├─> GET   query, {params}?, page?, region?
│   ├─> POST  query, {params}?, page?, region?
│   │
│   ├── channel
│   │   ├─> GET   query, ucid, page?, region?
│   │   └─> POST  query, ucid, page?, region?
│   │
│   └── suggestions
│       ├─> GET   query, page?, region?
│       └─> POST  query, page?, region?
│
├──[A] user
│   │
│   ├── account
│   │   ├─> GET     <no params>
│   │   ├─> DELETE  <no params>
│   │   │
│   │   └── change_password
│   │       └─> POST  password
│   │
│   ├── feed
│   │   │
│   │   └── TBD !!!
│   │
│   ├── history
│   │   ├─> GET     page?, per_page?, order?
│   │   ├─> POST    [ids]
│   │   │
│   │   ├── append
│   │   │   └─> POST  [ids]
│   │   │
│   │   ├── clear
│   │   │   └─> POST  <no params>
│   │   │
│   │   ├── disable
│   │   │   └─> POST  <no params>
│   │   │
│   │   ├── enable
│   │   │   └─> POST  <no params>
│   │   │
│   │   └── remove
│   │       └─> POST  [ids]
│   │
│   ├── notifications
│   │   │
│   │   └── TBD !!!
│   │
│   ├── playlists
│   │   └─> GET  <no params>
│   │
│   ├── preferences
│   │   ├─> GET   <no params>
│   │   └─> POST  {preferences}
│   │
│   ├── subscriptions
│   │   │
│   │   └── TBD !!!
│   │
│   └── tokens
│       ├─> GET  <no params>
│       │
│       ├── delete
│       │   └─> POST  id
│       │
│       └── new
│           └─> POST  scopes
│
└── videos/<id>
    ├─> GET  <no params>
    │
    ├── annotations
    │   └─> GET  region?
    │
    ├── captions
    │   └─> GET  region?
    │
    ├── comments
    │   ├── reddit
    │   │   └─> GET  thin_mode?
    │   └── youtube
    │       └─> GET  region?, continuation?, sort_by?, thin_mode?
    │
    └── storyboards
        └─> GET  region?


Legend:

── path     indicates a path segment

│─> GET     indicates supported HTTP methods

[A]         this path requires authentication


HTTP methods parameters:
 - name   = mandatory
 - name?  = optional
 - [name] = 'name' is a list
 - {name} = 'name' is a JSON object



General notes:
 - All endpoints must use the adequate HTTP error codes to tell a status
 - All endpoints shall adapt the content's Media Type according to the `Accept` header
 - If the endpoint can't provide the Media Type requested, it must return 415

Notes on authenticated endpoints:
 - Authentication tokens must be passed using the `Authorization: token <access_token>` header
 - All authenticated endpoints must return 403 to an unauthenticated user
 - All authenticated endpoints must return 404 for any resources that aren't
   owned by the currently authenticated user (for privacy reasons)

SamantazFox avatar Aug 14 '22 22:08 SamantazFox

Why is a new API needed - what's wrong with /v1/?

How is pagination planned to work in the new API?

cloudrac3r avatar Aug 18 '22 12:08 cloudrac3r

Why is a new API needed - what's wrong with /v1/?

I'd like to make breaking changes to the API in order to clean things up. This will give the other devs enough time to migrate, without breaking anything for the end-user (especially given that we don't control how long it takes for changes to propagate across the various instances).

For instance, I'd really like to create data objects that can be used in different contexts, instead of having flat structures everywhere (=> easier to maintain on both sides).

E.g, the author details (from the video endpoint):

{
  "author": "<name>",
  "authorId": "<ucid>",
  "authorUrl": "/channel/<ucid>",
  "authorThumbnails": [
    "/path/to/thubmnail1.png",
    "/path/to/thubmnail2.png",
    "/path/to/thubmnail3.png",
    "/path/to/thubmnail4.png"
  ],
  "subCountText": "1.2M"
}

would become a dedicated object, which can be reused for almost all the other endpoints (search, hashtag, feeds, ...) as well as other objects on the same endpoint (e.g: related videos):

{
  "author": {
    "name": "<name>",
    "id": "<ucid>",
    "url": "/channel/<ucid>",
    "thumbnails": [
      "/path/to/thubmnail1.png",
      "/path/to/thubmnail2.png",
      "/path/to/thubmnail3.png",
      "/path/to/thubmnail4.png"
    ],
    "subCount": 1200000,
    "verified": false
  }
}

How is pagination planned to work in the new API?

On that part, it depend on what youtube allows us to do.

I'd like to use page numbers as much as possible, but given how some continuation tokens became impossible to predict, it seems reasonnable to always provide said token in the response, and use that all the time.

Without a more advanced caching system is available, I hardly see how we can circumvent that problem, without having to do hundred of innertube queries.

EDIT: indeed, for invidious playlists, there will be regular paging, as we fully control the content. page and per_page would control what's returned (with per_page being limited to 200 or something)

SamantazFox avatar Aug 21 '22 10:08 SamantazFox

I'm also considering following the OpenAPI specification to provide a proper API documentation (using swagger).

SamantazFox avatar Aug 21 '22 10:08 SamantazFox

From my thoughts developing NewLeaf, I'd prefer to use continuation tokens as much as possible, because it makes caching easier for everybody and they can be bookmarked on the web-side without losing one's place. I talked about this in great detail in CloudTube on Matrix but that's the summary. Even if there's a way to random access seek into pages by page number, I think continuation token is more stable and easier, and should always be supported.

I still think it would be best to paginate playlists by a token even when we control the playlist content, for consistency.

I tried using OpenAPI/Swagger in the past and it was an absolute pain. If you really want to try it (why?) set yourself a time limit and don't work past that time limit, or you'll frustrate yourself and be out of time.

cloudrac3r avatar Aug 21 '22 13:08 cloudrac3r

Agreed on moving author data to an author object, but what if one fetching method provides more information than another? For example, watching a video would provide author icon url, but related videos wouldn't provide that. Are most of the author fields just going to be optional, and provided on a best-effort basis depending on which API endpoint is being called?

cloudrac3r avatar Aug 21 '22 13:08 cloudrac3r

From my thoughts developing NewLeaf, I'd prefer to use continuation tokens as much as possible, because it makes caching easier for everybody and they can be bookmarked on the web-side without losing one's place. I talked about this in great detail in CloudTube on Matrix but that's the summary. Even if there's a way to random access seek into pages by page number, I think continuation token is more stable and easier, and should always be supported.

Ah, thanks for the input :)

Note that in some places (like a channel's playlist or community page) the token represents a fixed point in the content (timestamps, in the case of the community page), but that in most places, the continuation token is relative (e.g in playlists, we provide an offset n, and it returns 100 results starting from there. The returned results will be different if new videos were inserted at the beginning of the playlist).

I still think it would be best to paginate playlists by a token even when we control the playlist content, for consistency.

I'm not sure how to achieve that. What would represent that token, in that case? a specific video ID? How would it change if the playlist order changes?

One sure thing is that we'll have to send a lastModified info (both in headers and body?)

I tried using OpenAPI/Swagger in the past and it was an absolute pain. If you really want to try it (why?) set yourself a time limit and don't work past that time limit, or you'll frustrate yourself and be out of time.

Thanks, noted!

Agreed on moving author data to an author object, but what if one fetching method provides more information than another? For example, watching a video would provide author icon url, but related videos wouldn't provide that. Are most of the author fields just going to be optional, and provided on a best-effort basis depending on which API endpoint is being called?

I'd still send the thumbnail field, but it would be an empty string. Same thing with the "verified" info: it would be false if upstream doesn't provide anything.

In general, I don't like optional fields. I prefer using default values.

SamantazFox avatar Aug 21 '22 14:08 SamantazFox

When you're the author of an API, you need to use either optional fields or null values so that API consumers can tell whether the data is real or is a placeholder. views: 0 and verified: false mean no views and not verified. You can't use those to mean "well, they might be or they might not". Otherwise, how can anyone trust the API? Returning incorrect information is always a bad idea, and here it is completely preventable.

cloudrac3r avatar Aug 22 '22 02:08 cloudrac3r

@cloudrac3r Ah, yeah, makes sense! I'll probably go with nil/null, then.

SamantazFox avatar Aug 22 '22 06:08 SamantazFox

What about the /latest_version endpoint? I can't find any documentation on that, but I can successfully use it to get links to embeddable streams in my project. There seems to also be an /api/manifest endpoint, both of these aren't under /v1/ - should these be included?

panki27 avatar Aug 24 '22 17:08 panki27

What about the /latest_version endpoint? I can't find any documentation on that, but I can successfully use it to get links to embeddable streams in my project.

The /latest_version endpoint is specific to the video proxy. It's used to get a fresh video playback URL, as they expire after 6h (we don't want to serve an outdated URL from the cached entry stored in the DB).

There seems to also be an /api/manifest endpoint, both of these aren't under /v1/ - should these be included?

This is for compatibility with the endpoint of the same name on Youtube. It provides the DASH manifest required to play adaptative videos. Similarly, it's part of the video playback (and proxying) system.

SamantazFox avatar Sep 13 '22 19:09 SamantazFox

I recently started looking into Invidious, as a backend of choice for a Roku TV app (https://github.com/iBicha/roku-youtube)

From initial observation, the default front end is way more capable than what the API can do in most cases. Or at least this is my perception (perhaps the information on https://docs.invidious.io/api/ is incomplete)

On non-web frontends, multi-instance support is a consideration. For example, switch to an instance with the least load, etc. In this case I'm talking about the https://api.invidious.io/, but instances that advertise their load can help. Additionally, I see a lot of people asking about proxying and how it is expensive to them. It would be interesting to see if the instance has proxying enabled or not.

iBicha avatar Nov 06 '22 23:11 iBicha

Mention because it affects all projects. @FireMasterK @cloudrac3r @SamantazFox

Working on my fork/rewrite of Invidious and in terms of interoperability I think creating one API spec that fits all is the wrong approach. Since each project works a bit differently, e.g. Piped uses client-side rendering while Invidious uses server-side rendering, the APIs already have different needs and are therefore different anyway.

In my project for example I want to support multiple backends as well as different services, such as SoundCloud using NPE, I came up with a set of interfaces that allow me to implement any service/API I want. Not saying that my are the way to go but its a good starting point I think.

You might think why not define a standardized API specification since we are all working with the same data anyway, but currently the Piped API provides different data than the Invidious API, e.g. licenses are present in the Invidious API but not in the Piped API. So now if Invidious were to implement the Piped API, what about the license data for a particular video? Return an empty string or make a separate request to YouTube? Well, that depends, but using said interfaces, it wouldn't really be a problem since the Piped API would have to supply the license data or the interface implementation would have to return placeholder data. Now you might say, but isn't that exactly the same as before? No, it's not, because if Piped now decides to supply the license data in the API, the change is just a few lines of standardized code for everyone.

I think the best approach for each project would be to use the same approach as NPE, i.e. modify the APIs so that they can be used to implement a number of the standardized interfaces mentioned. This would provide enough flexibility for each project while allowing interoperability at no additional cost, other than the upfront cost of developing the standardized interfaces.

This is also easy to implement in each language of each project and has the advantage that it would allow the use of basically any service as long as the returned data can be represented via the mentioned interfaces.

What do you all think of this idea?

89Q12 avatar Nov 20 '22 22:11 89Q12

Well, I like the idea of having a unified API, but first we should first define a concrete specification, preferably as an OpenAPI one

FireMasterK avatar Nov 21 '22 00:11 FireMasterK

That would be an option too but I think I haven't expressed myself clearly, what I meant is that we define a concrete adapter design composed of interfaces(like the ones I linked) that can be used with every project API. I don't mean that we all have to speak the same API, because with a standardized adapter there is no need for a unified API as I think this would introduce unnecessary overhead. I hope its a bit clearer what I meant, sorry for not being that clear in the first place.

89Q12 avatar Nov 21 '22 01:11 89Q12

After a while of consuming the v1 api, here are some of my concrete thoughts on the limitations

  • There's definitely a need to separate Invidious playlists and YouTube playlists. They fetch and cache differently, and it's been a headache treating them as the same thing.
  • IV playlists need more granular control: insert a video at specific index, for example. I know this requires to shift all indexes of the videos, but it can be redesigned to achieve this. Doing this in YouTube is a simple drag and drop of the videos to reorder them, and we should aim to do the same in Invidious
  • /api/v1/auth/subscriptions should return more than the ID and the name. At least the thumbnails, and the sub count. We need a way to display the channels like in https://www.youtube.com/feed/channels
  • Watch history should be set with a timestamp. This is important when importing watch history from other platforms, without bringing up really old watched videos to the top.
  • Importing playlists should have the option to update existing playlists with the same id (currently an import means creating a new playlist) - This is useful when playlists has been exported from somewhere, and imported to Invidious, then another export has been made (with some updates) now all we want is to set the diff.
  • This is one of the most important ones: the videos/:id is an all or nothing, which is a problem. We fetch the basic video info, the next endpoint (for related videos and such) and another hit for streaming data. This is often an overkill and slower than it needs to be. Also, when the streaming data has an error (such as the VideoNotAvailableException("The video returned by YouTube isn't the requested one")) we end up getting none of the other info, which usually does not have restrictions.

I know some of these are requiring not only an api change but also a db change, but I just wanted to share some of the areas of improvements from the perspective of a 3rd app.

iBicha avatar Mar 29 '24 16:03 iBicha