On this page
PHP Code Examples
PHP can call the MeowLoad API with nothing installed: an HTTP stream context passed to file_get_contents is enough to see a first response, and the cURL extension bundled with practically every PHP build is what you want in production. Both examples below POST one URL to /openapi/v1/extract/post, read the JSON result and handle the error body, and both are plain PHP — no framework, no Composer package. Swap <your API key> for the key in your Developer Console first.
🚀 Using file_get_contents
Handy on a box where you cannot install anything: this needs only allow_url_fopen, which most hosts leave enabled. The catch is that PHP treats a 400 as a failure — without ignore_errors => true the call emits a warning, returns false, and discards the very body that tells you what went wrong. The status code is not returned either; it turns up only in the magic $http_response_header variable, which PHP populates in whatever scope made the request, so parse it immediately rather than passing the response somewhere else first. Your only timeout knob is default_socket_timeout (60 seconds unless your php.ini says otherwise), which is the main reason not to ship this version.
<?php
$apiUrl = "https://api.meowload.net/openapi/v1/extract/post";
$apiKey = "<your API key>";
$params = array("url" => "https://www.youtube.com/watch?v=dQw4w9WgXcQ");
$options = array(
"http" => array(
"header" =>
"Content-Type: application/json\r\n" .
"Authorization: Bearer " . $apiKey . "\r\n" .
"Accept-Language: en",
"method" => "POST",
"content" => json_encode($params),
"ignore_errors" => true, // Read the body on non-200 too, otherwise the error message is lost
),
);
$context = stream_context_create($options);
$response = file_get_contents($apiUrl, false, $context);
// Parse HTTP response status code
$statusLine = $http_response_header[0];
preg_match('{HTTP\/\S*\s(\d{3})}', $statusLine, $match);
$statusCode = $match[1];
$data = json_decode($response, true);
if ($statusCode == 200) {
// Both title and text are optional: sites with titles return title, others return text
$caption = $data['title'] ?? $data['text'] ?? '(none)';
echo "✅ Extraction successful! Site: " . ($data['site'] ?? 'unknown') . ", caption: " . $caption . "\n";
foreach ($data['medias'] as $media) {
echo "- " . $media['media_type'] . ": " . $media['resource_url'] . "\n";
// Resolutions / audio tracks live in variants; both video_url and audio_url present means a split stream
foreach ($media['variants'] ?? array() as $variant) {
$split = (!empty($variant['video_url']) && !empty($variant['audio_url'])) ? " (needs merging)" : "";
echo " " . ($variant['quality_label'] ?? 'default') . $split . "\n";
}
}
} else {
echo "❌ Request failed (" . $statusCode . "): " . $data['message'] . "\n";
if (!empty($data['retryable'])) {
echo " Code " . $data['code'] . " is transient; retry later\n";
}
}
?>📦 Using cURL
This is the one to ship. cURL hands you the status code through curl_getinfo, keeps the body out of the output buffer as long as CURLOPT_RETURNTRANSFER is set, and lets you bound the call with CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT. Check curl_errno($ch) before decoding: on a transport failure curl_exec returns false, json_decode turns that into null, and the exception you raise then blames the API for what was really a DNS or TLS problem. Wrapping the failure in an exception that carries code and retryable, as below, keeps the retry decision at the call site — retry only when retryable is true, and back off on 429 instead of hammering, since one key is capped at 1,200 requests per minute. Extracting many links in a loop? Reuse a single $ch handle rather than calling curl_init each time, so the TLS handshake is paid once.
<?php
class ExtractException extends Exception {
public $code;
public $retryable;
public function __construct($message, $statusCode, $code = null, $retryable = false) {
parent::__construct($message, $statusCode);
$this->code = $code;
$this->retryable = $retryable;
}
}
function extractPost($url, $apiKey) {
$apiUrl = "https://api.meowload.net/openapi/v1/extract/post";
$ch = curl_init($apiUrl);
$payload = json_encode(array("url" => $url));
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
'Accept-Language: en'
));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($statusCode == 200) {
return $data;
}
// 400 carries code and retryable; 401/402/422/429 are identified by the status alone
throw new ExtractException(
$data['message'],
$statusCode,
$data['code'] ?? null,
!empty($data['retryable'])
);
}
// Usage
try {
$result = extractPost("https://www.youtube.com/watch?v=dQw4w9WgXcQ", "<your API key>");
$caption = $result['title'] ?? $result['text'] ?? '(none)';
echo "✅ Extraction successful! Caption: " . $caption . ", media count: " . count($result['medias']) . "\n";
} catch (ExtractException $e) {
echo "❌ Error (" . $e->getCode() . "): " . $e->getMessage() . "\n";
if ($e->retryable) {
echo " Code " . $e->code . " is transient; retry later\n";
}
}
?>The quickstart is the shortest path from a fresh key to a working call, and error codes maps every failure to a cause and tells you whether retrying it is pointless. Field-by-field documentation for the endpoint used here lives on the single post API page and in the API reference. Before a batch job, have it read your remaining balance through the credits API — only successful extractions are charged, but a run that hits zero mid-way starts collecting HTTP 402 responses instead of results.