MeowLoad Docs
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

ItemLegacy APIv1
URL prefix/openapi/openapi/v1
AuthenticationX-API-Key headerAuthorization: Bearer <your API key> header
Post captionA single texttitle + text
Resolution listformatsvariants, with renamed inner fields
Split-stream flagseparateRemoved; check whether video_url and audio_url both exist
Author infouserauthor on posts, profile on playlists
Platform idNoneNew site field
Error body{ message, code } with PascalCase code{ message, code, retryable } with snake_case code
Credits endpointGET /available-creditsGET /credits, same response
Rate limit20 requests per second1,200 requests per minute per key (the same 20 per second); 429 beyond that

Endpoint URLs

Legacy APIv1
POST https://api.meowload.net/openapi/extract/postPOST https://api.meowload.net/openapi/v1/extract/post
POST https://api.meowload.net/openapi/extract/playlistPOST https://api.meowload.net/openapi/v1/extract/playlist
POST https://api.meowload.net/openapi/extract/subtitlesPOST https://api.meowload.net/openapi/v1/extract/subtitles
GET https://api.meowload.net/openapi/available-creditsGET 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:

FieldMeaningWhen present
titleThe titleSites that have titles (YouTube, Bilibili, Xiaohongshu, Reddit, and so on)
textBody, caption or descriptionMost 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 APIv1Description
formatsvariantsThe array itself
quality_notequality_labelDisplay label for the quality, such as 1080p, 4K, Original
video_sizevideo_filesizeVideo file size in bytes
audio_sizeaudio_filesizeAudio file size in bytes
defaultis_defaultWhether this is the recommended variant / default audio track
separateRemovedSee 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)
Unknownunknown
Timeouttimeout
InvalidURLinvalid_url
ExtractFailedextract_failed
UnsupportedURLunsupported_url
UnsupportedSiteunsupported_site
PrivateContentprivate_content
LiveStreamNotSupportedlive_stream_not_supported
PlaylistNotSupportedplaylist_not_supported
UserNotFounduser_not_found
NoStoryno_story
ContentDeletedcontent_deleted
InvalidPlaylistURLinvalid_playlist_url
MembersOnlyContentmembers_only_content
NotPremierednot_premiered
AgeRestrictedage_restricted
RegionRestrictedregion_restricted
Retryableretryable

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

  1. Change the URL prefix to /openapi/v1, and available-credits to credits.
  2. Switch the auth header to Authorization: Bearer <your API key>.
  3. Read post captions from title / text.
  4. Rename formats to variants, and quality_note / video_size / audio_size / default to their new names.
  5. Stop reading separate; check whether video_url and audio_url both exist instead.
  6. Branch error handling on the snake_case code, and allow a delayed retry when retryable is true.
  7. Add back-off handling for 429.
  8. 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):