On this page
Migrating from the legacy API
v1 uses the same API key and the same billing as the legacy API (the unversioned /openapi/… endpoints), and the request parameters are unchanged. What changes is the response: the legacy API returns a shape frozen years ago, while v1 returns the extraction engine's current shape as-is. This page lists every difference. Once you have worked through them, your integration is ready for v1.
The legacy API stays available with no shutdown date, but it will not receive new fields.
What changed
| Item | Legacy API | v1 |
|---|---|---|
| URL prefix | /openapi | /openapi/v1 |
| Authentication | X-API-Key header | Authorization: Bearer <your API key> header |
| Post caption | A single text | title + text |
| Resolution list | formats | variants, with renamed inner fields |
| Split-stream flag | separate | Removed; check whether video_url and audio_url both exist |
| Author info | user | author on posts, profile on playlists |
| Platform id | None | New site field |
| Error body | { message, code } with PascalCase code | { message, code, retryable } with snake_case code |
| Credits endpoint | GET /available-credits | GET /credits, same response |
| Rate limit | 20 requests per second | 1,200 requests per minute per key (the same 20 per second); 429 beyond that |
Endpoint URLs
| Legacy API | v1 |
|---|---|
POST https://api.meowload.net/openapi/extract/post | POST https://api.meowload.net/openapi/v1/extract/post |
POST https://api.meowload.net/openapi/extract/playlist | POST https://api.meowload.net/openapi/v1/extract/playlist |
POST https://api.meowload.net/openapi/extract/subtitles | POST https://api.meowload.net/openapi/v1/extract/subtitles |
GET https://api.meowload.net/openapi/available-credits | GET https://api.meowload.net/openapi/v1/credits |
Request bodies are identical to the legacy API: url in the body (plus an optional cursor for playlists), and the optional Accept-Language header works the same way. Authentication changes: the legacy API uses X-API-Key, v1 uses Authorization: Bearer <your API key>. The key itself stays the same.
Post text is split into title and text
The legacy API had a single text with mixed semantics: sites with titles put the title there, sites without titles put the body there. v1 splits it into two fields:
| Field | Meaning | When present |
|---|---|---|
title | The title | Sites that have titles (YouTube, Bilibili, Xiaohongshu, Reddit, and so on) |
text | Body, caption or description | Most sites |
Both are optional. To get roughly the same display caption as the legacy API, use title || text.
This applies to the /extract/post response and to every item in posts[] of the /extract/playlist response.
Media field renames
The resolution list inside medias[] is renamed from formats to variants, because for audio media it represents audio tracks, not formats. The fields inside the array are renamed alongside it:
| Legacy API | v1 | Description |
|---|---|---|
formats | variants | The array itself |
quality_note | quality_label | Display label for the quality, such as 1080p, 4K, Original |
video_size | video_filesize | Video file size in bytes |
audio_size | audio_filesize | Audio file size in bytes |
default | is_default | Whether this is the recommended variant / default audio track |
separate | Removed | See the next section |
quality, fps, video_url, video_ext, video_codec, audio_url, audio_ext, audio_codec, language_tag and language_name keep their names. A quality of 9999 means original quality (a source file whose real resolution is unknown), and its quality_label is always Original.
separate was removed
The legacy API flagged split audio and video streams with separate: 1 / 0. v1 no longer sends that field. The rule is:
A variant that has both video_url and audio_url is a split stream: download both and merge them. A variant with only one of them is a single file: download it directly.
def is_split(variant):
return bool(variant.get("video_url")) and bool(variant.get("audio_url"))New site field
All three v1 extraction responses gain an optional top-level site: the platform id the link belongs to, such as youtube, tiktok or instagram. Use it to route your own logic instead of maintaining a domain table. The key is omitted when the link cannot be classified.
Error response
The legacy error docs only listed message, but the body also carried a PascalCase code. The v1 error body is:
{
"message": "The content has been deleted or does not exist",
"code": "content_deleted",
"retryable": false
}Three changes: code is now snake_case; retryable is new and tells you whether retrying the same URL later might succeed; every 400 is guaranteed to carry a code. The 18 codes map as follows:
| Legacy API (PascalCase) | v1 (snake_case) |
|---|---|
Unknown | unknown |
Timeout | timeout |
InvalidURL | invalid_url |
ExtractFailed | extract_failed |
UnsupportedURL | unsupported_url |
UnsupportedSite | unsupported_site |
PrivateContent | private_content |
LiveStreamNotSupported | live_stream_not_supported |
PlaylistNotSupported | playlist_not_supported |
UserNotFound | user_not_found |
NoStory | no_story |
ContentDeleted | content_deleted |
InvalidPlaylistURL | invalid_playlist_url |
MembersOnlyContent | members_only_content |
NotPremiered | not_premiered |
AgeRestricted | age_restricted |
RegionRestricted | region_restricted |
Retryable | retryable |
One status code was also unified: on the legacy API, a malformed link returned 422 from the single-post endpoint but 400 from the playlist and subtitle endpoints. v1 always returns 400 with code: "invalid_url". On v1, 422 is reserved for an invalid request body (for example a missing url). See Errors for what each code means and how to handle it.
Credits endpoint renamed
GET /openapi/available-credits becomes GET /openapi/v1/credits. The response is unchanged:
{ "availableCredits": 282539 }Migration checklist
- Change the URL prefix to
/openapi/v1, andavailable-creditstocredits. - Switch the auth header to
Authorization: Bearer <your API key>. - Read post captions from
title/text. - Rename
formatstovariants, andquality_note/video_size/audio_size/defaultto their new names. - Stop reading
separate; check whethervideo_urlandaudio_urlboth exist instead. - Branch error handling on the snake_case
code, and allow a delayed retry whenretryableistrue. - Add back-off handling for
429. - Compare against a real response using the try-it console in the API reference.
Updated examples for each language are in Python, JavaScript, PHP and Golang.
Legacy API documentation
The legacy API is documented in the GitHub repository MeowLoad/Media-Downloader-API (Chinese only):