Search by

aporat / scrapecreators-php

aporat

PHP client for the Scrape Creators API: typed methods for every documented endpoint across TikTok, Instagram, YouTube, X, Reddit, LinkedIn, Facebook and 20 more platforms, provider-side cache control, typed exceptions for missing, restricted and age-gated profiles, and an optional Laravel service pr

Package info

github.com/aporat/scrapecreators-php

pkg:composer/aporat/scrapecreators-php

Statistics

Installs: 7

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

0.1.0 2026-10-20 15:44 UTC

This package is auto-updated.

Last update: 2026-10-20 15:50:30 UTC


README

A PHP client for the Scrape Creators API: a typed method for every documented endpoint across TikTok, Instagram, YouTube, X, Reddit, LinkedIn, Facebook, GitHub, Threads, Bluesky, Spotify and twenty more platforms, control of the provider-side cache that turns repeat lookups into zero-credit calls, and exceptions that tell a missing profile from a restricted one from an outage, including the HTTP 200 placeholders the API uses for some of them. Works in any PHP project; a service provider is included for Laravel.

Latest Stable Version Downloads codecov GitHub Actions License

Not affiliated with Scrape Creators.

✨ Features

  • Every endpoint, typed. 251 methods on 27 platform resources, generated from the API's OpenAPI document: required parameters are positional, optional ones are named, each carries the API's own description and allowed values. $client->instagram()->profile('nasa', trim: true). Anything newer than this package is reachable through get() / post() or the $extra parameter every method takes
  • Provider-side cache, handled. Set a default cache_max_age once and the client sends it to every endpoint that supports it; Scrape Creators then answers from a recent scrape for 0 credits. Pass Client::LIVE on a call to force a fresh scrape
  • Errors you can branch on. NotFoundException for a missing account, RestrictedException for a private, age-gated or region-blocked one (with isAgeRestricted() and isPrivate()), UnavailableException for their outage or a timeout, InsufficientCreditsException, AuthenticationException, InvalidRequestException, RateLimitException. The API reports several of these states as HTTP 200 placeholders; the client reads the envelope so you do not have to
  • Billing in view. Every Response exposes creditsCharged(), creditsRemaining(), isCached() and cachedAt() alongside data() and dot-path get()
  • Laravel ready. Auto-discovered service provider, config/scrapecreators.php, Client bound as a singleton

🛠 Installation

composer require aporat/scrapecreators-php

Requires PHP 8.4+ and Guzzle 7.9 or 8. Get an API key at app.scrapecreators.com.

use Aporat\ScrapeCreators\Client;

$client = new Client($apiKey, [
    'cache_max_age' => '1d',   // optional: accept scrapes up to a day old for 0 credits
    'timeout' => 20,           // seconds, the default
]);

$profile = $client->instagram()->profile('nasa');

$profile->get('data.user.follower_count');
$profile->creditsCharged();   // 1 for a live scrape, 0 from the cache
$profile->isCached();

📚 Usage

Platforms

One accessor per platform, one method per endpoint. Method names follow the path: /v1/tiktok/video/transcript is $client->tiktok()->videoTranscript($url), /v3/tiktok/profile/videos is profileVideos(), /v1/account/get-api-usage is $client->account()->apiUsage().

$client->tiktok()->profile(handle: 'nasa');
$client->tiktok()->profileVideos(handle: 'nasa', sortBy: 'popular');
$client->youtube()->channel(handle: '@nasa');
$client->youtube()->videoTranscript('https://www.youtube.com/watch?v=...');
$client->twitter()->userTweets(handle: 'nasa');
$client->reddit()->subreddit(subreddit: 'php', sort: 'top', timeframe: 'week');
$client->linkedin()->company(url: 'https://www.linkedin.com/company/nasa');
$client->threads()->profile(handle: 'nasa');
$client->bluesky()->profile(handle: 'nasa.bsky.social');
$client->spotify()->artist(url: 'https://open.spotify.com/artist/...');
$client->github()->user(handle: 'aporat');
$client->creatorTools()->findSocialProfiles('instagram', 'nasa');
$client->linkInBio()->linktree(url: 'https://linktr.ee/nasa');
$client->account()->creditBalance();
Accessor Platform Accessor Platform
tiktok() TikTok, TikTok Shop, TikTok Ad Library twitch() Twitch
instagram() Instagram appleMusic() Apple Music
youtube() YouTube spotify() Spotify
facebook() Facebook, Marketplace, Events, Ad Library soundCloud() SoundCloud
twitter() X / Twitter kwai() Kwai
reddit() Reddit kick() Kick
linkedin() LinkedIn, LinkedIn Ad Library snapchat() Snapchat
github() GitHub telegram() Telegram
google() Google search, Google Ad Library cosmos() Cosmos
threads() Threads rumble() Rumble
bluesky() Bluesky creatorTools() find social profiles, find creator emails, detect age and gender
truthSocial() Truth Social linkInBio() Linktree, Komi, Pillar, Linkbio, Linkme
pinterest() Pinterest amazon() Amazon shop
account() credit balance, usage

The three endpoints that also accept a JSON body have a ...Post() twin taking the body as an array: facebook()->adLibrarySearchAdsPost([...]), facebook()->adLibraryCompanyAdsPost([...]), reddit()->postCommentsPost([...]).

Any endpoint

$client->get('v1/instagram/profile', ['handle' => 'nasa', 'trim' => true], cacheable: true);
$client->post('v1/reddit/post/comments', ['url' => $url]);

Booleans are sent as true / false, nulls are dropped, enums are sent by value. cacheable says whether the endpoint takes cache_max_age, so the client's default applies.

Responses

$response = $client->instagram()->profile('nasa');

$response->data();                        // the `data` member, or the whole envelope when there is none
$response->get('data.user.biography');    // dot path, with an optional default
$response['data']['user'];                // array access to the envelope
$response->toArray();
$response->creditsCharged();              // int|null
$response->creditsRemaining();            // int|null
$response->isCached();                    // served from Scrape Creators' cache (0 credits)
$response->cachedAt();                    // DateTimeImmutable|null
$response->raw();                         // the PSR-7 response

The provider-side cache

Thirty-six endpoints take cache_max_age (1d, 3d, 7d, 14d, 30d): when Scrape Creators holds a scrape at least that fresh it returns it for 0 credits, marked cached: true with cached_at. Set a default once and override per call:

$client = new Client($apiKey, ['cache_max_age' => '1d']);

$client->instagram()->profile('nasa');                            // cache_max_age=1d
$client->instagram()->profile('nasa', cacheMaxAge: '7d');         // cache_max_age=7d
$client->instagram()->profile('nasa', cacheMaxAge: Client::LIVE); // no cache_max_age: a live scrape

A cached profile carries whatever signed CDN image URLs the scrape saw, and networks expire those within days. Keep the default short if you download pictures.

Errors

use Aporat\ScrapeCreators\Exceptions\InsufficientCreditsException;
use Aporat\ScrapeCreators\Exceptions\NotFoundException;
use Aporat\ScrapeCreators\Exceptions\RestrictedException;
use Aporat\ScrapeCreators\Exceptions\ScrapeCreatorsException;
use Aporat\ScrapeCreators\Exceptions\UnavailableException;

try {
    $profile = $client->instagram()->profile($handle);
} catch (NotFoundException) {
    // No such account. Safe to cache as a miss.
} catch (RestrictedException $e) {
    // The account exists but the network will not show it: $e->isAgeRestricted(), $e->isPrivate().
    // Do not reject the handle and do not cache it as missing; restrictions lift.
} catch (UnavailableException $e) {
    // Their outage, or a timeout. Says nothing about the account; retry later.
} catch (InsufficientCreditsException) {
    // Top up.
} catch (ScrapeCreatorsException $e) {
    $e->getStatusCode();   // int|null
    $e->getApiMessage();   // the envelope's `message`, when there was one
    $e->getBody();         // the decoded envelope
    $e->getResponse();     // the PSR-7 response
}

How responses map:

What Scrape Creators sends Exception
400 InvalidRequestException
401 AuthenticationException (also thrown before any request when the key is empty)
402 InsufficientCreditsException
403, or any envelope with age_restricted: true or a message saying "restricted" or "private" RestrictedException
404, any other 4xx, or a 200 with error: "not_found", account_deactivated: true, userId: null or a 4xx errorStatus NotFoundException
429 RateLimitException
5xx, a 200 with a 5xx errorStatus, or no response at all UnavailableException
a body that is not a JSON object InvalidResponseException
success: false with anything else ScrapeCreatorsException

🧱 Laravel

The service provider is auto-discovered and binds Aporat\ScrapeCreators\Client as a singleton. Publish the config and set the key:

php artisan vendor:publish --tag=scrapecreators-config
SCRAPECREATORS_API_KEY=sc_...
SCRAPECREATORS_CACHE_MAX_AGE=1d
SCRAPECREATORS_TIMEOUT=20
public function __construct(private readonly \Aporat\ScrapeCreators\Client $scrapeCreators) {}

In tests, bind a client built on a Guzzle MockHandler, or $this->app->instance(Client::class, ...).

🔁 Keeping up with the API

spec/openapi.json is a snapshot of docs.scrapecreators.com/openapi.json. src/Resources is generated from it:

curl -sSL -o spec/openapi.json https://docs.scrapecreators.com/openapi.json
composer generate

CI fails when the committed resources differ from what the snapshot produces. Until a new endpoint lands here, call it with get() / post().

📄 License

MIT. See LICENSE.