Home/PHP SDK
SDK

PHP SDK

PHP 8.2+PackagistGitHub

PHP client for the ScrapingIsNotACrime API over any PSR-18 HTTP client, with responses as final readonly classes.

PackagistRepository

01Install

If your project has no PSR-18 client or PSR-17 factory yet, Composer's php-http/discovery plugin installs one. Composer asks once whether to allow the plugin; answer yes. If discovery finds some other PSR-18 client (not Symfony HttpClient or Guzzle), the SDK uses it as found, so that client's own timeout and redirect behavior apply; see โ€œBring your own HTTP clientโ€.

bash
composer require scrapingisnotacrime/sdk

Or pick the implementation yourself:

explicit
composer require scrapingisnotacrime/sdk symfony/http-client nyholm/psr7

02Quick start

php
use ScrapingIsNotACrime\Client;
use ScrapingIsNotACrime\Exception\NotFoundException;

$client = new Client(apiKey: 'sinac_...');

try {
    $profile = $client->instagram->profile('nasa');
    echo $profile->username, ' ', $profile->followers, PHP_EOL;
} catch (NotFoundException) {
    echo 'no such profile', PHP_EOL;
}

Get a key at your dashboard. Keys start with sinac_, and new accounts get 100 free credits.

new Client() with no arguments reads SCRAPINGISNOTACRIME_API_KEY from the environment.

03Configuration

php
$client = new Client(
    apiKey: 'sinac_...',
    baseUrl: 'https://api.scrapingisnotacrime.com/v1',
    timeout: 30.0,
    maxRetries: 2,
    httpClient: null,
    requestFactory: null,
);
OptionDefaultDescription
apiKeySCRAPINGISNOTACRIME_API_KEYYour API key. The constructor throws \InvalidArgumentException if none is set. Defaults to the SCRAPINGISNOTACRIME_API_KEY environment variable.
baseUrlhttps://api.scrapingisnotacrime.com/v1API base URL. Redirects are not followed, so use the final HTTPS URL.
timeout30.0Per attempt, in seconds. Applied only when the SDK builds the HTTP client itself.
maxRetries2Extra attempts for 429, 502 and network errors. 0 disables retries.
httpClientโ€”Your own PSR-18 client, used as is. By default, the SDK builds a client itself.
requestFactoryโ€”Your own PSR-17 request factory. By default, discovered via php-http/discovery.

04Methods

05Pagination

Page has items, hasMore, nextCursor or nextPage, data and next(). A foreach over it fetches later pages lazily.

Each page fetched is one billed request, so bound your loops. Breaking out early stops fetching.

php
$page = $client->bluesky->posts('bsky.app', limit: 25);
$fetched = 0;
while ($page !== null && $fetched < 3) {
    foreach ($page->items as $post) {
        echo $post->text, PHP_EOL;
    }
    $page = $page->next();
    $fetched++;
}
foreach
$page = $client->github->followers('torvalds', limit: 100);
$count = 0;
foreach ($page as $user) {
    echo $user->username, PHP_EOL;
    if (++$count >= 250) {
        break; // stop early; no further pages are fetched
    }
}

06Errors

Every failure is an exception extending ScrapingIsNotACrimeException with ->status and ->requestId. Invalid arguments throw \InvalidArgumentException before any request. If no PSR-18 client or PSR-17 factory can be found, the constructor throws \LogicException.

TypeStatusRetried
BadRequestException400no
AuthenticationException401no
QuotaExceededException402no
NotFoundException404no
RateLimitException429yes
UpstreamException502yes
ConnectionExceptionnetwork failure or per-attempt timeoutyes
ApiExceptionany other status, a 2xx without the JSON envelope, a redirect, or data of an unexpected shapeno
php
use ScrapingIsNotACrime\Exception\NotFoundException;
use ScrapingIsNotACrime\Exception\QuotaExceededException;
use ScrapingIsNotACrime\Exception\RateLimitException;
use ScrapingIsNotACrime\Exception\ScrapingIsNotACrimeException;

try {
    $profile = $client->tiktok->profile('this-user-does-not-exist-123');
    echo $profile->username, PHP_EOL;
} catch (NotFoundException) {
    echo 'no such profile', PHP_EOL;
} catch (QuotaExceededException $e) {
    throw $e; // includes the pricing link in the message
} catch (RateLimitException) {
    echo 'TikTok is rate limiting; already retried, try again later', PHP_EOL;
} catch (ScrapingIsNotACrimeException $e) {
    throw $e;
}

07Retries

Rate limits (429), upstream failures (502) and network failures or timeouts are retried automatically, up to 2 extra attempts by default. These failures don't consume credits. The wait honors the Retry-After header; otherwise it is exponential backoff with jitter, starting at 500 ms and capped at 10 seconds.

Pass maxRetries: 0 to disable retries.

08Bring your own HTTP client

Pass httpClient for tests, proxies or connection pooling. The SDK never modifies it, so set its timeout and turn off redirects yourself.

guzzle
use GuzzleHttp\Client;
use ScrapingIsNotACrime\Client as ScrapingIsNotACrimeClient;

$client = new ScrapingIsNotACrimeClient(
    apiKey: 'sinac_...',
    httpClient: new Client([
        'timeout' => 30,
        'allow_redirects' => false,
        // 'proxy' => 'http://localhost:8080',
    ]),
);
symfony
use ScrapingIsNotACrime\Client as ScrapingIsNotACrimeClient;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Psr18Client;

$client = new ScrapingIsNotACrimeClient(
    apiKey: 'sinac_...',
    httpClient: new Psr18Client(HttpClient::create([
        'timeout' => 30,
        'max_duration' => 30,
        'max_redirects' => 0,
        // 'proxy' => 'http://localhost:8080',
    ])),
);

With your consent, we use Google Analytics and non-personalized Google Ads measurement to understand how the site and the dashboard are used and whether our ads work โ€” pages visited and actions like creating an API key or testing a request. We never send Google your email, name, account ID, API keys or request content. You can change your mind anytime in Cookie preferences, in the footer.